diff --git a/src/components/Stories/Stories.tsx b/src/components/Stories/Stories.tsx index 51160f85..9df8ce4b 100644 --- a/src/components/Stories/Stories.tsx +++ b/src/components/Stories/Stories.tsx @@ -1,12 +1,13 @@ import React from 'react'; -import {Modal} from '@gravity-ui/uikit'; import type {ModalCloseReason} from '@gravity-ui/uikit'; +import {Modal} from '@gravity-ui/uikit'; import {block} from '../utils/cn'; -import {IndexType, StoriesLayout} from './components/StoriesLayout/StoriesLayout'; import type {StoriesLayoutProps} from './components/StoriesLayout/StoriesLayout'; +import {IndexType, StoriesLayout} from './components/StoriesLayout/StoriesLayout'; +import {useSyncWithLS} from './hooks'; import type {StoriesItem} from './types'; import './Stories.scss'; @@ -26,6 +27,7 @@ export interface StoriesProps { disableOutsideClick?: boolean; className?: string; action?: StoriesLayoutProps['action']; + syncInTabsKey?: string; } export function Stories({ @@ -38,6 +40,7 @@ export function Stories({ disableOutsideClick = true, className, action, + syncInTabsKey, }: StoriesProps) { const [storyIndex, setStoryIndex] = React.useState(initialStoryIndex); @@ -48,13 +51,21 @@ export function Stories({ [onClose], ); + const {callback: closeWithLS} = useSyncWithLS>({ + callback: (event, reason) => { + onClose?.(event, reason); + }, + uniqueKey: `close-story-${syncInTabsKey}`, + }); + const handleButtonClose = React.useCallback< (event: MouseEvent | KeyboardEvent | React.MouseEvent) => void >( (event) => { handleClose(event, 'closeButtonClick'); + if (syncInTabsKey) closeWithLS(event, 'closeButtonClick'); }, - [handleClose], + [handleClose, syncInTabsKey, closeWithLS], ); const handleGotoPrevious = React.useCallback(() => { diff --git a/src/components/Stories/__stories__/Stories.stories.tsx b/src/components/Stories/__stories__/Stories.stories.tsx index 9e5c1c3c..bb0ca091 100644 --- a/src/components/Stories/__stories__/Stories.stories.tsx +++ b/src/components/Stories/__stories__/Stories.stories.tsx @@ -3,8 +3,8 @@ import React from 'react'; import {Button} from '@gravity-ui/uikit'; import type {Meta, StoryFn} from '@storybook/react'; -import {Stories} from '../Stories'; import type {StoriesProps} from '../Stories'; +import {Stories} from '../Stories'; import type {StoriesItem} from '../types'; export default { @@ -94,3 +94,10 @@ WithCustomAction.args = { children: 'View examples', }, }; + +export const WithSyncInTabs = DefaultTemplate.bind({}); +WithSyncInTabs.args = { + open: true, + syncInTabsKey: 'test-story', + items: [items[0]], +}; diff --git a/src/components/Stories/hooks/index.ts b/src/components/Stories/hooks/index.ts new file mode 100644 index 00000000..b4312444 --- /dev/null +++ b/src/components/Stories/hooks/index.ts @@ -0,0 +1 @@ +export * from './useSyncWithLS'; diff --git a/src/components/Stories/hooks/useSyncWithLS/README.md b/src/components/Stories/hooks/useSyncWithLS/README.md new file mode 100644 index 00000000..db2c5788 --- /dev/null +++ b/src/components/Stories/hooks/useSyncWithLS/README.md @@ -0,0 +1,19 @@ + + +# useSyncWithLS + + + +```tsx +import {useSyncWithLS} from '@gravity-ui/components'; +``` + +The `useSyncWithLS` hook executes callback when value changed in Local Storage + +## Properties + +| Name | Description | Type | Default | +| :------------- | :----------------------------------------------------------- | :------------: | :---------: | +| callback | Callback function called when key in local storage triggered | `VoidFunction` | | +| dataSourceName | Name for data source of keys | `string` | 'sync-tabs' | +| uniqueKey | Key in local storage for handle | `string` | | diff --git a/src/components/Stories/hooks/useSyncWithLS/index.ts b/src/components/Stories/hooks/useSyncWithLS/index.ts new file mode 100644 index 00000000..d8481236 --- /dev/null +++ b/src/components/Stories/hooks/useSyncWithLS/index.ts @@ -0,0 +1,2 @@ +export {useSyncWithLS} from './useSyncWithLS'; +export type {UseSyncWithLSInputProps, UseSyncWithLSOutputProps} from './useSyncWithLS'; diff --git a/src/components/Stories/hooks/useSyncWithLS/useSyncWithLS.ts b/src/components/Stories/hooks/useSyncWithLS/useSyncWithLS.ts new file mode 100644 index 00000000..f1cf39d5 --- /dev/null +++ b/src/components/Stories/hooks/useSyncWithLS/useSyncWithLS.ts @@ -0,0 +1,40 @@ +import React from 'react'; + +export type UseSyncWithLSInputProps = { + callback: T; + uniqueKey: string; + dataSourceName?: string; +}; +export type UseSyncWithLSOutputProps = {callback: (...args: unknown[]) => void}; + +export const useSyncWithLS = ({ + dataSourceName = 'sync-tabs', + callback, + uniqueKey, +}: UseSyncWithLSInputProps): UseSyncWithLSOutputProps => { + const key = `${dataSourceName}.${uniqueKey}`; + + const handler = (event: StorageEvent) => { + if (event.key === key && event.newValue) { + return callback(); + } + return undefined; + }; + + React.useEffect(() => { + // Action in non-active tab + window.addEventListener('storage', handler); + + return () => { + window.removeEventListener('storage', handler); + localStorage.removeItem(key); + }; + }); + + return { + callback: React.useCallback(() => { + localStorage.setItem(key, String(Number(localStorage.getItem(key) || '0') + 1)); + return callback(); + }, [key, callback]), + }; +}; diff --git a/src/components/Stories/index.ts b/src/components/Stories/index.ts index 3e9e9f8c..066d3171 100644 --- a/src/components/Stories/index.ts +++ b/src/components/Stories/index.ts @@ -1,2 +1,3 @@ export * from './Stories'; +export * from './hooks'; export * from './types';