base branch for ui v2
This commit is contained in:
parent
3110978d03
commit
e3bb30a591
@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
name: About Modal
|
|
||||||
menu: Components
|
|
||||||
route: /components/about-modal
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Playground, Props } from 'docz'
|
|
||||||
import { State } from 'react-powerplug'
|
|
||||||
import { AboutContent } from './../index.js'
|
|
||||||
|
|
||||||
# About Modal
|
|
||||||
|
|
||||||
## Basic usage
|
|
||||||
|
|
||||||
<Playground>
|
|
||||||
<State initial={{
|
|
||||||
isOpen: false
|
|
||||||
}}>
|
|
||||||
|
|
||||||
{({ state, setState }) => (
|
|
||||||
<React.Fragment>
|
|
||||||
<button
|
|
||||||
className="btn btn-primary"
|
|
||||||
type="button"
|
|
||||||
onClick={ () => setState({ isOpen: true }) }
|
|
||||||
>
|
|
||||||
Open about modal
|
|
||||||
</button>
|
|
||||||
<AboutContent
|
|
||||||
{...state}
|
|
||||||
onCancel={() => setState({ isOpen: false })}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</State>
|
|
||||||
|
|
||||||
</Playground>
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
<Props of={AboutContent} />
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { hotkeys } from '@ohif/core';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take the pressed key array and return the readable string for the keys
|
|
||||||
*
|
|
||||||
* @param {Array} [keys=[]]
|
|
||||||
* @returns {string} string representation of an array of keys
|
|
||||||
*/
|
|
||||||
const formatKeysForInput = (keys = []) => keys.join('+');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* formats given keys sequence to insert the modifier keys in the first index of the array
|
|
||||||
* @param {string} sequence keys sequence from MouseTrap Record -> "shift+left"
|
|
||||||
* @returns {Array} keys in array-format -> ['shift','left']
|
|
||||||
*/
|
|
||||||
const getKeys = ({ sequence, modifier_keys }) => {
|
|
||||||
const keysArray = sequence.join(' ').split('+');
|
|
||||||
let keys = [];
|
|
||||||
let modifiers = [];
|
|
||||||
keysArray.forEach(key => {
|
|
||||||
if (modifier_keys && modifier_keys.includes(key)) {
|
|
||||||
modifiers.push(key);
|
|
||||||
} else {
|
|
||||||
keys.push(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [...modifiers, ...keys];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HotkeyField
|
|
||||||
* Renders a hotkey input
|
|
||||||
*
|
|
||||||
* @param {object} props component props
|
|
||||||
* @param {Array[]} props.keys array of keys to be controlled by this field
|
|
||||||
* @param {function} props.handleChange Callback function to communicate parent once value is changed
|
|
||||||
* @param {string} props.classNames string caontaining classes to be added in the input field
|
|
||||||
* @param {Array[]} props.modifier_keys
|
|
||||||
*/
|
|
||||||
function HotkeyField({ keys, handleChange, classNames, modifier_keys }) {
|
|
||||||
const inputValue = formatKeysForInput(keys);
|
|
||||||
|
|
||||||
const onInputKeyDown = event => {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
hotkeys.record(sequence => {
|
|
||||||
const keys = getKeys({ sequence, modifier_keys });
|
|
||||||
hotkeys.unpause();
|
|
||||||
handleChange(keys);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFocus = () => {
|
|
||||||
hotkeys.pause();
|
|
||||||
hotkeys.startRecording();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
readOnly={true}
|
|
||||||
type="text"
|
|
||||||
value={inputValue}
|
|
||||||
className={classNames}
|
|
||||||
onKeyDown={onInputKeyDown}
|
|
||||||
onFocus={onFocus}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
HotkeyField.propTypes = {
|
|
||||||
keys: PropTypes.array.isRequired,
|
|
||||||
handleChange: PropTypes.func.isRequired,
|
|
||||||
classNames: PropTypes.string,
|
|
||||||
modifier_keys: PropTypes.array,
|
|
||||||
allowed_keys: PropTypes.array,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { HotkeyField };
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { HotkeyField } from './HotkeyField';
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import { StudyBrowser, Thumbnail } from './studyBrowser';
|
|
||||||
import { LayoutButton, LayoutChooser } from './layoutButton';
|
|
||||||
import { MeasurementTable, MeasurementTableItem } from './measurementTable';
|
|
||||||
import { Overlay, OverlayTrigger } from './overlayTrigger';
|
|
||||||
import { TableList, TableListItem } from './tableList';
|
|
||||||
import { AboutContent } from './content/aboutContent/AboutContent';
|
|
||||||
import { TabComponents, TabFooter } from './tabComponents';
|
|
||||||
import { HotkeyField } from './customForm';
|
|
||||||
import { LanguageSwitcher } from './languageSwitcher';
|
|
||||||
|
|
||||||
import { Checkbox } from './checkbox';
|
|
||||||
import { CineDialog } from './cineDialog';
|
|
||||||
import { ViewportDownloadForm } from './content/viewportDownloadForm';
|
|
||||||
import { QuickSwitch } from './quickSwitch';
|
|
||||||
import { RoundedButtonGroup } from './roundedButtonGroup';
|
|
||||||
import { SelectTree } from './selectTree';
|
|
||||||
import { SimpleDialog } from './simpleDialog';
|
|
||||||
import { OHIFModal } from './ohifModal';
|
|
||||||
import { ContextMenu } from './contextMenu';
|
|
||||||
import {
|
|
||||||
PageToolbar,
|
|
||||||
StudyList,
|
|
||||||
TableSearchFilter,
|
|
||||||
TablePagination,
|
|
||||||
} from './studyList';
|
|
||||||
import { ToolbarSection } from './toolbarSection';
|
|
||||||
import { Tooltip } from './tooltip';
|
|
||||||
|
|
||||||
export {
|
|
||||||
ContextMenu,
|
|
||||||
Checkbox,
|
|
||||||
CineDialog,
|
|
||||||
ViewportDownloadForm,
|
|
||||||
LayoutButton,
|
|
||||||
LayoutChooser,
|
|
||||||
MeasurementTable,
|
|
||||||
MeasurementTableItem,
|
|
||||||
Overlay,
|
|
||||||
OverlayTrigger,
|
|
||||||
QuickSwitch,
|
|
||||||
RoundedButtonGroup,
|
|
||||||
PageToolbar,
|
|
||||||
SelectTree,
|
|
||||||
SimpleDialog,
|
|
||||||
StudyBrowser,
|
|
||||||
StudyList,
|
|
||||||
TableList,
|
|
||||||
TableListItem,
|
|
||||||
Thumbnail,
|
|
||||||
TabComponents,
|
|
||||||
TabFooter,
|
|
||||||
HotkeyField,
|
|
||||||
LanguageSwitcher,
|
|
||||||
TableSearchFilter,
|
|
||||||
TablePagination,
|
|
||||||
ToolbarSection,
|
|
||||||
Tooltip,
|
|
||||||
AboutContent,
|
|
||||||
OHIFModal,
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import './LanguageSwitcher.styl';
|
|
||||||
|
|
||||||
const LanguageSwitcher = ({ language, onLanguageChange, languages }) => {
|
|
||||||
const onChange = event => {
|
|
||||||
const { value } = event.target;
|
|
||||||
onLanguageChange(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<select
|
|
||||||
name="language-select"
|
|
||||||
id="language-select"
|
|
||||||
className="language-select"
|
|
||||||
value={language}
|
|
||||||
onChange={onChange}
|
|
||||||
>
|
|
||||||
{languages.map(lng => (
|
|
||||||
<option key={lng.value} value={lng.value}>
|
|
||||||
{lng.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LanguageSwitcher.propTypes = {
|
|
||||||
language: PropTypes.string.isRequired,
|
|
||||||
languages: PropTypes.array.isRequired,
|
|
||||||
onLanguageChange: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { LanguageSwitcher };
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { LanguageSwitcher } from './LanguageSwitcher';
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
// Style
|
|
||||||
import './TabComponents.styl';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take name of the tab and create the data-cy value for it
|
|
||||||
*
|
|
||||||
* @param {string} [name=''] tab name
|
|
||||||
* @returns {string} data-cy value
|
|
||||||
*/
|
|
||||||
const getDataCy = (name = '') => {
|
|
||||||
return name
|
|
||||||
.split(' ')
|
|
||||||
.join('-')
|
|
||||||
.toLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Single tab data information
|
|
||||||
*
|
|
||||||
* @typedef {Object} tabData
|
|
||||||
* @property {string} name - name of the tab
|
|
||||||
* @property {Object} Component - tab component to be rendered
|
|
||||||
* @property {Object} customProps - tab custom properties
|
|
||||||
* @property {bool} hidden - bool to define if tab is hidden of not
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a list of components data and render then into tabs
|
|
||||||
*
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {[tabData]} props.tabs array of tab data
|
|
||||||
* @param {Object} props.customProps common custom properties
|
|
||||||
*/
|
|
||||||
function TabComponents({ tabs, customProps = {} }) {
|
|
||||||
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
tabs.length > 0 && (
|
|
||||||
<div className="TabComponents">
|
|
||||||
<div className="TabComponents_tabHeader">
|
|
||||||
<div className="TabComponents_tabHeader_selector">
|
|
||||||
<div className="dialog-separator-after">
|
|
||||||
<ul className="nav nav-tabs">
|
|
||||||
{tabs.map((tab, index) => {
|
|
||||||
const { name, hidden = false } = tab;
|
|
||||||
return (
|
|
||||||
!hidden && (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentTabIndex(index);
|
|
||||||
}}
|
|
||||||
className={classnames(
|
|
||||||
'nav-link',
|
|
||||||
index === currentTabIndex && 'active'
|
|
||||||
)}
|
|
||||||
data-cy={getDataCy(name)}
|
|
||||||
>
|
|
||||||
<button>{name}</button>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{tabs.map((tab, index) => {
|
|
||||||
const {
|
|
||||||
Component,
|
|
||||||
customProps: tabCustomProps,
|
|
||||||
hidden = false,
|
|
||||||
} = tab;
|
|
||||||
return (
|
|
||||||
!hidden && (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={classnames(
|
|
||||||
'TabComponents_content',
|
|
||||||
index === currentTabIndex && 'active'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Component {...customProps} {...tabCustomProps} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabComponents.propTypes = {
|
|
||||||
tabs: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
name: PropTypes.string,
|
|
||||||
Component: PropTypes.any,
|
|
||||||
customProps: PropTypes.object,
|
|
||||||
hidden: PropTypes.bool,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
customProps: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { TabComponents };
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
@import './../../design/styles/common/navbar.styl'
|
|
||||||
|
|
||||||
.TabComponents
|
|
||||||
.TabComponents_tabHeader
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
margin-left: -20px
|
|
||||||
margin-right: -20px
|
|
||||||
|
|
||||||
.TabComponents_tabHeader_selector
|
|
||||||
border-bottom: 3px solid black
|
|
||||||
padding: 0 25px
|
|
||||||
|
|
||||||
.TabComponents_content
|
|
||||||
min-height: 450px
|
|
||||||
display: none
|
|
||||||
|
|
||||||
&.active
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
justify-content: space-between
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import './TabFooter.styl';
|
|
||||||
|
|
||||||
// In case translate is not passed
|
|
||||||
const translate = word => word;
|
|
||||||
|
|
||||||
function TabFooter({
|
|
||||||
onResetPreferences,
|
|
||||||
onSave,
|
|
||||||
onCancel,
|
|
||||||
hasErrors,
|
|
||||||
t = translate,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="footer">
|
|
||||||
<button
|
|
||||||
className="btn btn-danger pull-left"
|
|
||||||
data-cy="reset-default-btn"
|
|
||||||
onClick={onResetPreferences}
|
|
||||||
>
|
|
||||||
{t('Reset to Defaults')}
|
|
||||||
</button>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
onClick={onCancel}
|
|
||||||
data-cy="cancel-btn"
|
|
||||||
className="btn btn-default"
|
|
||||||
>
|
|
||||||
{t('Cancel')}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="btn btn-primary"
|
|
||||||
data-cy="save-btn"
|
|
||||||
disabled={hasErrors}
|
|
||||||
onClick={onSave}
|
|
||||||
>
|
|
||||||
{t('Save')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabFooter.propTypes = {
|
|
||||||
onResetPreferences: PropTypes.func,
|
|
||||||
onSave: PropTypes.func,
|
|
||||||
onCancel: PropTypes.func,
|
|
||||||
hasErrors: PropTypes.bool,
|
|
||||||
t: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { TabFooter };
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.footer
|
|
||||||
display: flex
|
|
||||||
flex-direction: row
|
|
||||||
justify-content: space-between
|
|
||||||
padding: 20px 20px 0 20px
|
|
||||||
margin: 0 -20px
|
|
||||||
border-top: 3px solid var(--primary-background-color)
|
|
||||||
|
|
||||||
div
|
|
||||||
button:last-child
|
|
||||||
margin-left: 10px
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: Tab Components
|
|
||||||
menu: Components
|
|
||||||
route: /components/tab-components
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Playground, Props } from 'docz'
|
|
||||||
import { State } from 'react-powerplug'
|
|
||||||
import { TabComponents } from './../index.js'
|
|
||||||
|
|
||||||
// Data
|
|
||||||
import tabs from './tabs.js'
|
|
||||||
|
|
||||||
# Tab Components
|
|
||||||
|
|
||||||
## Basic usage
|
|
||||||
|
|
||||||
<Playground>
|
|
||||||
<TabComponents
|
|
||||||
tabs={tabs}
|
|
||||||
customProps={}
|
|
||||||
/>
|
|
||||||
</Playground>
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
<Props of={TabComponents} />
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
export default tabs = [
|
|
||||||
{
|
|
||||||
name: 'tabName1',
|
|
||||||
Component: () => {
|
|
||||||
return <div>tab 1 Content</div>;
|
|
||||||
},
|
|
||||||
customProps: {},
|
|
||||||
hidden: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tabName2',
|
|
||||||
Component: () => {
|
|
||||||
return <div>tab 2 Content</div>;
|
|
||||||
},
|
|
||||||
customProps: {},
|
|
||||||
hidden: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tabName3',
|
|
||||||
Component: () => {
|
|
||||||
return <div>tab 3 Content</div>;
|
|
||||||
},
|
|
||||||
customProps: {},
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export { TabComponents } from './TabComponents';
|
|
||||||
export { TabFooter } from './TabFooter';
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import {
|
|
||||||
ContextMenu,
|
|
||||||
Checkbox,
|
|
||||||
CineDialog,
|
|
||||||
ViewportDownloadForm,
|
|
||||||
LayoutButton,
|
|
||||||
LayoutChooser,
|
|
||||||
MeasurementTable,
|
|
||||||
MeasurementTableItem,
|
|
||||||
Overlay,
|
|
||||||
OverlayTrigger,
|
|
||||||
PageToolbar,
|
|
||||||
QuickSwitch,
|
|
||||||
RoundedButtonGroup,
|
|
||||||
SelectTree,
|
|
||||||
SimpleDialog,
|
|
||||||
StudyBrowser,
|
|
||||||
StudyList,
|
|
||||||
TableList,
|
|
||||||
TableListItem,
|
|
||||||
Thumbnail,
|
|
||||||
TabComponents,
|
|
||||||
TabFooter,
|
|
||||||
HotkeyField,
|
|
||||||
LanguageSwitcher,
|
|
||||||
TableSearchFilter,
|
|
||||||
TablePagination,
|
|
||||||
ToolbarSection,
|
|
||||||
Tooltip,
|
|
||||||
AboutContent,
|
|
||||||
OHIFModal,
|
|
||||||
} from './components';
|
|
||||||
import { useDebounce, useMedia } from './hooks';
|
|
||||||
|
|
||||||
// Elements
|
|
||||||
import {
|
|
||||||
ICONS,
|
|
||||||
Icon,
|
|
||||||
DropdownMenu as Dropdown,
|
|
||||||
Select,
|
|
||||||
OldSelect,
|
|
||||||
Label,
|
|
||||||
Range,
|
|
||||||
TextArea,
|
|
||||||
TextInput,
|
|
||||||
} from './elements';
|
|
||||||
|
|
||||||
// Alias this for now as not all dependents are using strict versioning
|
|
||||||
import ExpandableToolMenu from './viewer/ExpandableToolMenu.js';
|
|
||||||
import PlayClipButton from './viewer/PlayClipButton.js';
|
|
||||||
import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
|
|
||||||
import Toolbar from './viewer/Toolbar.js';
|
|
||||||
import ToolbarButton from './viewer/ToolbarButton.js';
|
|
||||||
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
|
|
||||||
import {
|
|
||||||
SnackbarProvider,
|
|
||||||
useSnackbarContext,
|
|
||||||
withSnackbar,
|
|
||||||
DialogProvider,
|
|
||||||
useDialog,
|
|
||||||
withDialog,
|
|
||||||
ModalProvider,
|
|
||||||
ModalConsumer,
|
|
||||||
useModal,
|
|
||||||
withModal,
|
|
||||||
} from './contextProviders';
|
|
||||||
|
|
||||||
export {
|
|
||||||
// Elements
|
|
||||||
ICONS,
|
|
||||||
//
|
|
||||||
Checkbox,
|
|
||||||
Dropdown,
|
|
||||||
Label,
|
|
||||||
TextArea,
|
|
||||||
TextInput,
|
|
||||||
CineDialog,
|
|
||||||
ContextMenu,
|
|
||||||
ViewportDownloadForm,
|
|
||||||
ExpandableToolMenu,
|
|
||||||
Icon,
|
|
||||||
LayoutButton,
|
|
||||||
LayoutChooser,
|
|
||||||
MeasurementTable,
|
|
||||||
MeasurementTableItem,
|
|
||||||
Overlay,
|
|
||||||
OverlayTrigger,
|
|
||||||
PlayClipButton,
|
|
||||||
PageToolbar,
|
|
||||||
QuickSwitch,
|
|
||||||
Range,
|
|
||||||
RoundedButtonGroup,
|
|
||||||
ScrollableArea,
|
|
||||||
Select,
|
|
||||||
OldSelect,
|
|
||||||
SelectTree,
|
|
||||||
SimpleDialog,
|
|
||||||
StudyBrowser,
|
|
||||||
StudyList,
|
|
||||||
TableList,
|
|
||||||
TableListItem,
|
|
||||||
Thumbnail,
|
|
||||||
TabComponents,
|
|
||||||
TabFooter,
|
|
||||||
HotkeyField,
|
|
||||||
LanguageSwitcher,
|
|
||||||
TableSearchFilter,
|
|
||||||
TablePagination,
|
|
||||||
Toolbar,
|
|
||||||
ToolbarButton,
|
|
||||||
ToolbarSection,
|
|
||||||
Tooltip,
|
|
||||||
AboutContent,
|
|
||||||
ViewerbaseDragDropContext,
|
|
||||||
SnackbarProvider,
|
|
||||||
useSnackbarContext,
|
|
||||||
withSnackbar,
|
|
||||||
ModalProvider,
|
|
||||||
useModal,
|
|
||||||
ModalConsumer,
|
|
||||||
withModal,
|
|
||||||
OHIFModal,
|
|
||||||
DialogProvider,
|
|
||||||
withDialog,
|
|
||||||
useDialog,
|
|
||||||
// Hooks
|
|
||||||
useDebounce,
|
|
||||||
useMedia,
|
|
||||||
};
|
|
||||||
Loading…
Reference in New Issue
Block a user