diff --git a/CODEOWNERS b/CODEOWNERS index 2235f6b328..8637d2fa5f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -47,7 +47,11 @@ /src/components/Tooltip @amje /src/components/theme @resure +/src/hooks/useActionHandlers @ogonkov +/src/hooks/useFileInput @korvin89 +/src/hooks/useForkRef @ValeraS /src/hooks/useOutsideClick @NikitaCG +/src/hooks/useUniqId @ValeraS # Allow everyone to update dependencies /package.json diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 9028b18e15..cc26ad7e8c 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import {useActionHandlers} from '../../hooks'; import type {QAProps} from '../types'; import {block} from '../utils/cn'; -import {useActionHandlers} from '../utils/useActionHandlers'; import './Card.scss'; diff --git a/src/components/Disclosure/DisclosureContext.tsx b/src/components/Disclosure/DisclosureContext.tsx index 59cc50d44d..b048ac548a 100644 --- a/src/components/Disclosure/DisclosureContext.tsx +++ b/src/components/Disclosure/DisclosureContext.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {useUniqId} from '../utils/useUniqId'; +import {useUniqId} from '../../hooks'; import type {DisclosureProps} from './Disclosure'; diff --git a/src/components/List/constants.ts b/src/components/List/constants.ts index bfe07a199a..554c3cc3d8 100644 --- a/src/components/List/constants.ts +++ b/src/components/List/constants.ts @@ -1,4 +1,4 @@ -import {KeyCode} from '../constants'; +import {KeyCode} from '../../constants'; export const ListQa = { ACTIVE_ITEM: 'list-active-item', diff --git a/src/components/Menu/MenuGroup.tsx b/src/components/Menu/MenuGroup.tsx index 2471438034..fd7a8a46cb 100644 --- a/src/components/Menu/MenuGroup.tsx +++ b/src/components/Menu/MenuGroup.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import {useUniqId} from '../../hooks'; import type {DOMProps, QAProps} from '../types'; import {block} from '../utils/cn'; -import {useUniqId} from '../utils/useUniqId'; const b = block('menu'); diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index baee84fafa..5daa17e27c 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import {useActionHandlers} from '../../hooks'; import type {DOMProps, QAProps} from '../types'; import {block} from '../utils/cn'; import {eventBroker} from '../utils/event-broker'; -import {useActionHandlers} from '../utils/useActionHandlers'; const b = block('menu'); diff --git a/src/components/Pagination/components/PaginationInput/PaginationInput.tsx b/src/components/Pagination/components/PaginationInput/PaginationInput.tsx index 2768c928e8..76ec8eac6b 100644 --- a/src/components/Pagination/components/PaginationInput/PaginationInput.tsx +++ b/src/components/Pagination/components/PaginationInput/PaginationInput.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {KeyCode} from '../../../constants'; +import {KeyCode} from '../../../../constants'; import {TextInput, TextInputProps} from '../../../controls'; import {blockNew} from '../../../utils/cn'; import i18n from '../../i18n'; diff --git a/src/components/Popover/components/Trigger/Trigger.tsx b/src/components/Popover/components/Trigger/Trigger.tsx index f66f5607ef..4a185e594a 100644 --- a/src/components/Popover/components/Trigger/Trigger.tsx +++ b/src/components/Popover/components/Trigger/Trigger.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {useActionHandlers} from '../../../utils/useActionHandlers'; +import {useActionHandlers} from '../../../../hooks'; interface TriggerArgs { onClick: React.MouseEventHandler; diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx index cac5a944df..c3c4edbd10 100644 --- a/src/components/Popup/Popup.tsx +++ b/src/components/Popup/Popup.tsx @@ -2,12 +2,12 @@ import React from 'react'; import {CSSTransition} from 'react-transition-group'; +import {useForkRef} from '../../hooks'; import {Portal} from '../Portal'; import type {DOMProps, QAProps} from '../types'; import {FocusTrap, useParentFocusTrap} from '../utils/FocusTrap'; import {block} from '../utils/cn'; import {getCSSTransitionClassNames} from '../utils/transition'; -import {useForkRef} from '../utils/useForkRef'; import {useLayer} from '../utils/useLayer'; import type {LayerExtendableProps} from '../utils/useLayer'; import {usePopper} from '../utils/usePopper'; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index c852f89c5b..9b5c75f970 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -1,13 +1,12 @@ import React from 'react'; +import {KeyCode} from '../../constants'; +import {useForkRef, useUniqId} from '../../hooks'; import type {List} from '../List'; -import {KeyCode} from '../constants'; import {useMobile} from '../mobile'; import type {CnMods} from '../utils/cn'; import {useFocusWithin} from '../utils/interactions'; -import {useForkRef} from '../utils/useForkRef'; import {useSelect} from '../utils/useSelect'; -import {useUniqId} from '../utils/useUniqId'; import {EmptyOptions, SelectControl, SelectFilter, SelectList, SelectPopup} from './components'; import {DEFAULT_VIRTUALIZATION_THRESHOLD, selectBlock} from './constants'; diff --git a/src/components/Select/utils.tsx b/src/components/Select/utils.tsx index dcf4f7350d..8e613ea1ea 100644 --- a/src/components/Select/utils.tsx +++ b/src/components/Select/utils.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import {KeyCode} from '../../constants'; import type {List, ListItemData} from '../List'; -import {KeyCode} from '../constants'; import {GROUP_ITEM_MARGIN_TOP, MOBILE_ITEM_HEIGHT, SIZE_TO_ITEM_HEIGHT} from './constants'; import type {Option, OptionGroup} from './tech-components'; diff --git a/src/components/Toc/TocItem/TocItem.tsx b/src/components/Toc/TocItem/TocItem.tsx index f6b8406d0f..d4765075ed 100644 --- a/src/components/Toc/TocItem/TocItem.tsx +++ b/src/components/Toc/TocItem/TocItem.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import {useActionHandlers} from '../../../hooks'; import {blockNew} from '../../utils/cn'; -import {useActionHandlers} from '../../utils/useActionHandlers'; import type {TocItem as TocItemType} from '../types'; import './TocItem.scss'; diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index eb1cf45054..f65c68557f 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -1,12 +1,12 @@ import React from 'react'; +import {KeyCode} from '../../constants'; +import {useForkRef} from '../../hooks'; import {Popup} from '../Popup'; import type {PopupPlacement} from '../Popup'; -import {KeyCode} from '../constants'; import type {DOMProps} from '../types'; import {block} from '../utils/cn'; import {useBoolean} from '../utils/useBoolean'; -import {useForkRef} from '../utils/useForkRef'; import './Tooltip.scss'; diff --git a/src/components/controls/TextArea/TextArea.tsx b/src/components/controls/TextArea/TextArea.tsx index ecc623d742..b94ec7635f 100644 --- a/src/components/controls/TextArea/TextArea.tsx +++ b/src/components/controls/TextArea/TextArea.tsx @@ -1,8 +1,7 @@ import React from 'react'; +import {useForkRef, useUniqId} from '../../../hooks'; import {blockNew} from '../../utils/cn'; -import {useForkRef} from '../../utils/useForkRef'; -import {useUniqId} from '../../utils/useUniqId'; import {ClearButton, mapTextInputSizeToButtonSize} from '../common'; import {OuterAdditionalContent} from '../common/OuterAdditionalContent/OuterAdditionalContent'; import type { diff --git a/src/components/controls/TextArea/TextAreaControl.tsx b/src/components/controls/TextArea/TextAreaControl.tsx index 603e82135d..f0094049eb 100644 --- a/src/components/controls/TextArea/TextAreaControl.tsx +++ b/src/components/controls/TextArea/TextAreaControl.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import {useForkRef} from '../../../hooks'; import {blockNew} from '../../utils/cn'; -import {useForkRef} from '../../utils/useForkRef'; import type {TextAreaProps} from './TextArea'; diff --git a/src/components/controls/TextInput/TextInput.tsx b/src/components/controls/TextInput/TextInput.tsx index 2b6d2cbdf1..1ff63392d4 100644 --- a/src/components/controls/TextInput/TextInput.tsx +++ b/src/components/controls/TextInput/TextInput.tsx @@ -2,12 +2,11 @@ import React from 'react'; import {TriangleExclamation} from '@gravity-ui/icons'; +import {useForkRef, useUniqId} from '../../../hooks'; import {Icon} from '../../Icon'; import {Popover} from '../../Popover'; import {block} from '../../utils/cn'; import {useElementSize} from '../../utils/useElementSize'; -import {useForkRef} from '../../utils/useForkRef'; -import {useUniqId} from '../../utils/useUniqId'; import {ClearButton, mapTextInputSizeToButtonSize} from '../common'; import {OuterAdditionalContent} from '../common/OuterAdditionalContent/OuterAdditionalContent'; import type { diff --git a/src/components/index.ts b/src/components/index.ts index af7bcfca2e..ff58450f0c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -56,12 +56,7 @@ export * from './utils/useLayer'; export {Lang, configure} from './utils/configure'; export * from './utils/useSelect'; export * from './utils/useListNavigation'; -export * from './utils/useForkRef'; -export * from './utils/setRef'; export {useOnFocusOutside} from './utils/useOnFocusOutside'; export * from './utils/interactions'; export * from './utils/xpath'; -export * from './utils/useFileInput/useFileInput'; -export {useActionHandlers} from './utils/useActionHandlers'; -export {useUniqId} from './utils/useUniqId'; export {getLayersCount} from './utils/LayerManager'; diff --git a/src/components/utils/FocusTrap.tsx b/src/components/utils/FocusTrap.tsx index 96d95bb971..e4b177825f 100644 --- a/src/components/utils/FocusTrap.tsx +++ b/src/components/utils/FocusTrap.tsx @@ -3,8 +3,7 @@ import React from 'react'; import {createFocusTrap} from 'focus-trap'; import type {FocusTrap as FocusTrapInstance} from 'focus-trap'; -import {useForkRef} from './useForkRef'; -import {useUniqId} from './useUniqId'; +import {useForkRef, useUniqId} from '../../hooks'; interface FocusTrapContext { addNode: (id: string, node: HTMLElement) => void; diff --git a/src/components/utils/LayerManager.ts b/src/components/utils/LayerManager.ts index 2e0d276c67..047fb0c439 100644 --- a/src/components/utils/LayerManager.ts +++ b/src/components/utils/LayerManager.ts @@ -1,6 +1,6 @@ import type {VirtualElement} from '@popperjs/core'; -import {KeyCode} from '../constants'; +import {KeyCode} from '../../constants'; import {eventBroker} from './event-broker'; diff --git a/src/components/utils/useCheckbox.ts b/src/components/utils/useCheckbox.ts index 8f308c5bb8..7fcac924df 100644 --- a/src/components/utils/useCheckbox.ts +++ b/src/components/utils/useCheckbox.ts @@ -1,9 +1,9 @@ import React from 'react'; +import {useForkRef} from '../../hooks'; import type {ControlProps} from '../types'; import {eventBroker} from './event-broker'; -import {useForkRef} from './useForkRef'; export function useCheckbox({ name, diff --git a/src/components/utils/useRadio.ts b/src/components/utils/useRadio.ts index 2891a17020..a6876946f7 100644 --- a/src/components/utils/useRadio.ts +++ b/src/components/utils/useRadio.ts @@ -1,10 +1,9 @@ import React from 'react'; +import {useForkRef, useUniqId} from '../../hooks'; import type {ControlProps} from '../types'; import {eventBroker} from './event-broker'; -import {useForkRef} from './useForkRef'; -import {useUniqId} from './useUniqId'; export function useRadio({ name, diff --git a/src/components/utils/useRadioGroup.ts b/src/components/utils/useRadioGroup.ts index c211b8ac13..ce56525f13 100644 --- a/src/components/utils/useRadioGroup.ts +++ b/src/components/utils/useRadioGroup.ts @@ -1,9 +1,8 @@ import React from 'react'; +import {useUniqId} from '../../hooks'; import type {ControlGroupProps} from '../types'; -import {useUniqId} from './useUniqId'; - export function useRadioGroup(props: ControlGroupProps) { const { name, diff --git a/src/components/constants.ts b/src/constants.ts similarity index 100% rename from src/components/constants.ts rename to src/constants.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 8ce4679d64..3e266ab629 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,8 @@ +export * from './useActionHandlers'; export * from './useBodyScrollLock'; +export * from './useFileInput'; +export * from './useForkRef'; export * from './useOutsideClick'; export * from './usePortalContainer'; export * from './useVirtualElementRef'; +export * from './useUniqId'; diff --git a/src/hooks/useActionHandlers/README.md b/src/hooks/useActionHandlers/README.md new file mode 100644 index 0000000000..b15f215212 --- /dev/null +++ b/src/hooks/useActionHandlers/README.md @@ -0,0 +1,21 @@ + + +# useActionHandlers + + + +```tsx +import {useActionHandlers} from '@gravity-ui/uikit'; +``` + +The `useActionHandlers` hook emulates behaviour of system controls, that respond to Enter and Spacebar + +## Properties + +| Name | Description | Type | Default | +| :------- | :------------------------- | :------------------------: | :-----: | +| callback | Callback for keydown event | `(...args: any[]) => any;` | | + +## Result + +Keyboard event handler. `React.KeyboardEventHandler` diff --git a/src/hooks/useActionHandlers/index.ts b/src/hooks/useActionHandlers/index.ts new file mode 100644 index 0000000000..cae5130d72 --- /dev/null +++ b/src/hooks/useActionHandlers/index.ts @@ -0,0 +1,2 @@ +export {useActionHandlers} from './useActionHandlers'; +export type {UseActionHandlersProps, UseActionHandlersResult} from './useActionHandlers'; diff --git a/src/components/utils/useActionHandlers.ts b/src/hooks/useActionHandlers/useActionHandlers.ts similarity index 68% rename from src/components/utils/useActionHandlers.ts rename to src/hooks/useActionHandlers/useActionHandlers.ts index a3a126f9dd..73309bd540 100644 --- a/src/components/utils/useActionHandlers.ts +++ b/src/hooks/useActionHandlers/useActionHandlers.ts @@ -1,11 +1,13 @@ import React from 'react'; -import {KeyCode} from '../constants'; +import {KeyCode} from '../../constants'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyFunction = (...args: any[]) => any; -interface UseActionHandlersResult { +export type UseActionHandlersProps = AnyFunction; + +export interface UseActionHandlersResult { onKeyDown: React.KeyboardEventHandler; } @@ -14,13 +16,16 @@ interface UseActionHandlersResult { * @param callback * @return {onKeyDown} */ -export function useActionHandlers(callback?: AnyFunction): UseActionHandlersResult { +export function useActionHandlers( + callback?: UseActionHandlersProps, +): UseActionHandlersResult { const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { if ( callback && [KeyCode.ENTER, KeyCode.SPACEBAR, KeyCode.SPACEBAR_OLD].includes(event.key) ) { + // eslint-disable-next-line callback-return callback(event); } }, diff --git a/src/hooks/useFileInput/README.md b/src/hooks/useFileInput/README.md new file mode 100644 index 0000000000..1929538a6e --- /dev/null +++ b/src/hooks/useFileInput/README.md @@ -0,0 +1,23 @@ + + +# useFileInput + + + +```tsx +import {useFileInput} from '@gravity-ui/uikit'; +``` + +The `useFileInput` hook used to shape props for input with type "file" + +## Properties + +| Name | Description | Type | Default | +| :------- | :------------------- | :----------------------------------: | :-----: | +| onUpdate | Update file callback | `(files: File[]) => void` | | +| onChange | On change callback | `(event: React.ChangeEvent) => void` | | + +## Result + +- controlProps - props for the input with type 'file' `React.DetailedHTMLProps`. +- triggerProps - props for the interactive element that, when interacted with, should open a dialog window for file selection `{onClick: () => void;}`. diff --git a/src/components/utils/useFileInput/__stories__/UseFileInput.stories.tsx b/src/hooks/useFileInput/__stories__/UseFileInput.stories.tsx similarity index 92% rename from src/components/utils/useFileInput/__stories__/UseFileInput.stories.tsx rename to src/hooks/useFileInput/__stories__/UseFileInput.stories.tsx index 20bdf736dd..12e83ed6e5 100644 --- a/src/components/utils/useFileInput/__stories__/UseFileInput.stories.tsx +++ b/src/hooks/useFileInput/__stories__/UseFileInput.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {Meta, StoryFn} from '@storybook/react'; -import {Button} from '../../../Button'; +import {Button} from '../../../components/Button'; import {useFileInput} from '../useFileInput'; export default {title: 'Hooks/useFileInput'} as Meta; diff --git a/src/hooks/useFileInput/index.ts b/src/hooks/useFileInput/index.ts new file mode 100644 index 0000000000..a327ebb520 --- /dev/null +++ b/src/hooks/useFileInput/index.ts @@ -0,0 +1,2 @@ +export {useFileInput} from './useFileInput'; +export type {UseFileInputProps, UseFileInputOutput, UseFileInputResult} from './useFileInput'; diff --git a/src/components/utils/useFileInput/useFileInput.ts b/src/hooks/useFileInput/useFileInput.ts similarity index 94% rename from src/components/utils/useFileInput/useFileInput.ts rename to src/hooks/useFileInput/useFileInput.ts index 642eba2905..d777b920f8 100644 --- a/src/components/utils/useFileInput/useFileInput.ts +++ b/src/hooks/useFileInput/useFileInput.ts @@ -6,6 +6,9 @@ export type UseFileInputProps = { onChange?: (event: React.ChangeEvent) => void; }; +/** + * @deprecated use UseFileInputResult instead + */ export type UseFileInputOutput = { controlProps: React.DetailedHTMLProps< React.InputHTMLAttributes, @@ -16,18 +19,20 @@ export type UseFileInputOutput = { }; }; +export type UseFileInputResult = UseFileInputOutput; + /** * Used to shape props for input with type "file". - * + * * Usage example: ```tsx import React from 'react'; import {Button, useFileInput} from '@gravity-ui/uikit'; - + const Component = () => { const onUpdate = React.useCallback((files: File[]) => console.log(files), []); const {controlProps, triggerProps} = useFileInput({onUpdate}); - + return ( diff --git a/src/hooks/useForkRef/README.md b/src/hooks/useForkRef/README.md new file mode 100644 index 0000000000..463dadcf8c --- /dev/null +++ b/src/hooks/useForkRef/README.md @@ -0,0 +1,21 @@ + + +# useForkRef + + + +```tsx +import {useForkRef} from '@gravity-ui/uikit'; +``` + +The `useForkRef` hook that can combine refs into a single ref + +## Properties + +| Name | Description | Type | Default | +| :--- | :-------------- | :-----------------: | :-----: | +| refs | ref-links array | `React.RefObject[]` | | + +## Result + +Combined ref. `React.RefCallback` diff --git a/src/hooks/useForkRef/index.ts b/src/hooks/useForkRef/index.ts new file mode 100644 index 0000000000..73bbe3a2dd --- /dev/null +++ b/src/hooks/useForkRef/index.ts @@ -0,0 +1,3 @@ +export {useForkRef} from './useForkRef'; +export {setRef} from './setRef'; +export type {UseForkRefProps, UseForkRefResult} from './useForkRef'; diff --git a/src/components/utils/setRef.ts b/src/hooks/useForkRef/setRef.ts similarity index 100% rename from src/components/utils/setRef.ts rename to src/hooks/useForkRef/setRef.ts diff --git a/src/components/utils/useForkRef.ts b/src/hooks/useForkRef/useForkRef.ts similarity index 66% rename from src/components/utils/useForkRef.ts rename to src/hooks/useForkRef/useForkRef.ts index 570d43c0ab..9c45cb1fbb 100644 --- a/src/components/utils/useForkRef.ts +++ b/src/hooks/useForkRef/useForkRef.ts @@ -2,9 +2,10 @@ import React from 'react'; import {setRef} from './setRef'; -export function useForkRef( - ...refs: Array | undefined> -): React.RefCallback | null { +export type UseForkRefProps = Array | undefined>; +export type UseForkRefResult = React.RefCallback | null; + +export function useForkRef(...refs: UseForkRefProps): UseForkRefResult { return React.useMemo(() => { if (refs.every((ref) => ref === null || ref === undefined)) { return null; diff --git a/src/hooks/useUniqId/README.md b/src/hooks/useUniqId/README.md new file mode 100644 index 0000000000..70a0919bb8 --- /dev/null +++ b/src/hooks/useUniqId/README.md @@ -0,0 +1,15 @@ + + +# useUniqId + + + +```tsx +import {useUniqId} from '@gravity-ui/uikit'; +``` + +The `useUniqId` hook creates unique ID. + +## Result + +ID. `string` diff --git a/src/hooks/useUniqId/index.ts b/src/hooks/useUniqId/index.ts new file mode 100644 index 0000000000..25f2b3ba47 --- /dev/null +++ b/src/hooks/useUniqId/index.ts @@ -0,0 +1,2 @@ +export {useUniqId} from './useUniqId'; +export type {UseUniqIdResult} from './useUniqId'; diff --git a/src/components/utils/useUniqId.ts b/src/hooks/useUniqId/useUniqId.ts similarity index 64% rename from src/components/utils/useUniqId.ts rename to src/hooks/useUniqId/useUniqId.ts index cf660620f3..6bffc9efb9 100644 --- a/src/components/utils/useUniqId.ts +++ b/src/hooks/useUniqId/useUniqId.ts @@ -1,7 +1,9 @@ import React from 'react'; -import {NAMESPACE_NEW} from './cn'; -import {getUniqId} from './common'; +import {NAMESPACE_NEW} from '../../components/utils/cn'; +import {getUniqId} from '../../components/utils/common'; + +export type UseUniqIdResult = string; function useUniqIdFallback() { const idRef = React.useRef(); @@ -15,5 +17,5 @@ function useIdNative() { return `${NAMESPACE_NEW}${React.useId()}`; } -export const useUniqId: () => string = +export const useUniqId: () => UseUniqIdResult = typeof React.useId === 'function' ? useIdNative : useUniqIdFallback;