// // Copyright 2025 DXOS.org // import { useArrowNavigationGroup, useFocusableGroup, useFocusFinders, useMergedTabsterAttributes_unstable, } from '@fluentui/react-tabster'; import { type Decorator, type Meta, type StoryObj } from '@storybook/react-vite'; import React, { forwardRef, useEffect, useMemo, useRef } from 'react'; import { createTabster, disposeTabster } from 'tabster'; import { Input, ScrollArea } from '@dxos/react-ui'; import { withLayout, withTheme } from '@dxos/react-ui/testing'; import { mx } from '@dxos/ui-theme'; // TODO(burdon): Factor out styles (incl. tabster debugging). // TODO(burdon): Implement horizontal movement between columns when column is selected. // TODO(burdon): Prevent tab out of app. const border = 'rounded-xs outline-hidden border border-subdued-separator focus:border-primary-500 focus-within:border-rose-500'; const Board = forwardRef(({ columns }, ref) => { const arrowNavigationAttrs = useArrowNavigationGroup({ axis: 'horizontal', memorizeCurrent: true, tabbable: true }); return (
{columns.map((column) => ( ))}
); }); const Column = forwardRef(({ items }, ref) => { const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' }); const arrowNavigationAttrs = useArrowNavigationGroup({ axis: 'vertical', memorizeCurrent: true }); const tabsterAttrs = useMergedTabsterAttributes_unstable(focusableGroupAttrs, arrowNavigationAttrs); return ( {items.map((item) => ( ))} ); }); const Item = forwardRef(({ value }, ref) => { const focusableGroupAttrs = useFocusableGroup(); return (
); }); const DefaultStory = () => { const columns = useMemo(() => { return [['A1', 'A2', 'A3'], ['B1'], ['C1', 'C2', 'C3', 'C4', 'C5', 'C6'], ['D1', 'D2']]; }, []); const ref = useRef(null); const { findFirstFocusable } = useFocusFinders(); useEffect(() => { if (ref.current) { findFirstFocusable(ref.current)?.focus(); } }, []); return ; }; // TODO(burdon): This doesn't seem to be necessary or recongized; memoization doesn't work. const withTabster: Decorator = (Story) => { useEffect(() => { const tabster = createTabster(window, { autoRoot: {}, // TODO(burdon): Not called. // checkUncontrolledCompletely: (el) => { // console.log(el); // return true; // }, }); return () => { disposeTabster(tabster); }; }, []); return ; }; const meta: Meta = { title: 'ui/react-ui-core/exemplars/tabster', component: DefaultStory, decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withTabster], parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj; export const Default: Story = { args: {}, };