diff --git a/packages/smarthr-ui/src/components/Browser/Browser.tsx b/packages/smarthr-ui/src/components/Browser/Browser.tsx index 1ff68cae0c5..c0ff3154c16 100644 --- a/packages/smarthr-ui/src/components/Browser/Browser.tsx +++ b/packages/smarthr-ui/src/components/Browser/Browser.tsx @@ -9,13 +9,18 @@ import { ItemNode, ItemNodeLike, RootNode } from './models' import { getElementIdFromNode } from './utils' const optionsListWrapper = tv({ - base: [ - 'smarthr-ui-Browser shr-flex shr-flex-row shr-flex-nowrap shr-min-h-[355px]', - '[&[data-column-length="0"]]:shr-justify-center [&[data-column-length="0"]]:shr-items-center', - '[&[data-column-length="1"]]:[&>div]:shr-flex-1', - '[&[data-column-length="2"]]:[&>div:nth-child(1)]:shr-flex-1 [&[data-column-length="2"]]:[&>div:nth-child(2)]:shr-flex-[2]', - '[&[data-column-length="3"]]:[&>div]:shr-flex-1', - ], + base: 'smarthr-ui-Browser shr-flex shr-flex-row shr-flex-nowrap shr-min-h-[355px]', + variants: { + columnCount: { + 0: 'shr-justify-center shr-items-center', + 1: '[&>div]:shr-flex-1', + 2: '[&>div:nth-child(1)]:shr-flex-1 [&>div:nth-child(2)]:shr-flex-[2]', + 3: '[&>div]:shr-flex-1', + }, + }, + defaultVariants: { + columnCount: 0, + }, }) type Props = { @@ -36,9 +41,11 @@ const DECORATOR_DEFAULT_TEXTS = { type DecoratorKeyTypes = keyof typeof DECORATOR_DEFAULT_TEXTS export const Browser: FC = ({ value, items, decorators, onSelectItem }) => { - const style = useMemo(() => optionsListWrapper(), []) - const decorated = useDecorators(DECORATOR_DEFAULT_TEXTS, decorators) const rootNode = useMemo(() => RootNode.from({ children: items }), [items]) + const columns = useMemo(() => rootNode.toViewData(value), [rootNode, value]) + + const style = useMemo(() => optionsListWrapper({ columnCount: columns.length as 0 | 1 | 2 | 3 }), [columns.length]) + const decorated = useDecorators(DECORATOR_DEFAULT_TEXTS, decorators) const selectedNode = useMemo(() => { if (value) { @@ -47,39 +54,43 @@ export const Browser: FC = ({ value, items, decorators, onSelectItem }) = return }, [rootNode, value]) - const columns = useMemo(() => rootNode.toViewData(value), [rootNode, value]) - // FIXME: focusメソッドのfocusVisibleが主要ブラウザでサポートされたら使うようにしたい(現状ではマウスクリックでもfocusのoutlineが出てしまう) // https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/focus const handleKeyDown: KeyboardEventHandler = useCallback( (e) => { - let target: ItemNodeLike | null = null + if (!selectedNode) { + return + } + + let target: ItemNode | undefined = undefined switch (e.key) { case 'ArrowUp': { - if (selectedNode) { - target = selectedNode.getPrev() ?? selectedNode.parent?.getLastChild() - } + target = selectedNode.getPrev() ?? selectedNode.parent?.getLastChild() break } case 'ArrowDown': { - if (selectedNode) { - target = selectedNode.getNext() ?? selectedNode.parent?.getFirstChild() - } + target = selectedNode.getNext() ?? selectedNode.parent?.getFirstChild() break } - case 'ArrowLeft': - target = selectedNode?.parent + case 'ArrowLeft': { + const node = selectedNode.parent + + if (node instanceof ItemNode) { + target = node + } break + } case 'ArrowRight': case 'Enter': - case ' ': - target = selectedNode?.getFirstChild() + case ' ': { + target = selectedNode.getFirstChild() break + } } if (target) { @@ -96,14 +107,13 @@ export const Browser: FC = ({ value, items, decorators, onSelectItem }) =
{columns.length > 0 ? ( - columns.map((items, index) => ( + columns.map((colItems, index) => ( = ({ items, index: columnIndex, value, onS
    {items.map((item, rowIndex) => ( 0} @@ -31,10 +32,11 @@ export const BrowserColumn: FC = ({ items, index: columnIndex, value, onS ) const ListItem = React.memo< - Pick & { + Pick & { itemValue: ItemNode['value'] itemLabel: ItemNode['label'] itemHasChildren: boolean + columnIndex: Props['index'] rowIndex: number } >(({ itemValue, itemLabel, itemHasChildren, value, columnIndex, rowIndex, onSelectItem }) => { diff --git a/packages/smarthr-ui/src/components/Browser/BrowserItem.tsx b/packages/smarthr-ui/src/components/Browser/BrowserItem.tsx index 18e3d3944b6..a7f85d5965c 100644 --- a/packages/smarthr-ui/src/components/Browser/BrowserItem.tsx +++ b/packages/smarthr-ui/src/components/Browser/BrowserItem.tsx @@ -1,4 +1,4 @@ -import React, { FC, KeyboardEventHandler, useCallback, useMemo } from 'react' +import React, { KeyboardEventHandler, useMemo } from 'react' import { tv } from 'tailwind-variants' import { FaAngleRightIcon } from '../Icon' diff --git a/packages/smarthr-ui/src/components/Browser/models/ItemNode.ts b/packages/smarthr-ui/src/components/Browser/models/ItemNode.ts index d0ae5bded4d..ddd3641ae62 100644 --- a/packages/smarthr-ui/src/components/Browser/models/ItemNode.ts +++ b/packages/smarthr-ui/src/components/Browser/models/ItemNode.ts @@ -18,6 +18,7 @@ export class ItemNode { this.value = value this.label = label this.context = new NodeContext(this) + for (const child of children) { this.append(child) } @@ -43,6 +44,7 @@ export class ItemNode { if (this.#getDepth() >= NODE_MAX_DEPTH) { throw new Error(`Cannot append to a node deeper than ${NODE_MAX_DEPTH}`) } + this.context.append(that.context) } @@ -50,10 +52,13 @@ export class ItemNode { if (!this.parent) { return } + const index = this.#getIndex() + if (index === -1) { return } + return this.parent.children[index + 1] } @@ -61,10 +66,13 @@ export class ItemNode { if (!this.parent) { return } + const index = this.#getIndex() + if (index === -1) { return } + return this.parent.children[index - 1] } @@ -80,6 +88,7 @@ export class ItemNode { const ancestors: ItemNode[] = [] let current: Node | undefined = this.parent + while (current instanceof ItemNode) { ancestors.unshift(current) current = current.parent @@ -92,6 +101,7 @@ export class ItemNode { if (!this.parent) { return [] } + return this.parent.children } @@ -99,8 +109,10 @@ export class ItemNode { if (this.value === value) { return this } + for (const child of this.children) { const found = child.findByValue(value) + if (found) { return found } @@ -112,6 +124,7 @@ export class ItemNode { if (!this.parent) { return -1 } + return this.parent.children.indexOf(this) } diff --git a/packages/smarthr-ui/src/components/Browser/models/RootNode.ts b/packages/smarthr-ui/src/components/Browser/models/RootNode.ts index f5eb60c186f..f79f5752c44 100644 --- a/packages/smarthr-ui/src/components/Browser/models/RootNode.ts +++ b/packages/smarthr-ui/src/components/Browser/models/RootNode.ts @@ -10,6 +10,7 @@ export class RootNode { constructor(children: ItemNode[]) { this.context = new NodeContext(this) + for (const child of children) { this.append(child) } @@ -38,10 +39,12 @@ export class RootNode { findByValue(value: string): ItemNode | undefined { for (const child of this.children) { const found = child.findByValue(value) + if (found) { return found } } + return } @@ -55,6 +58,7 @@ export class RootNode { } const pivot = this.findByValue(value) + if (!pivot) { return [this.children] } diff --git a/packages/smarthr-ui/src/components/Browser/utils.ts b/packages/smarthr-ui/src/components/Browser/utils.ts index b19208193f3..dfb10ea430b 100644 --- a/packages/smarthr-ui/src/components/Browser/utils.ts +++ b/packages/smarthr-ui/src/components/Browser/utils.ts @@ -1,3 +1 @@ -import { ItemNode } from './models' - -export const getElementIdFromNode = (node: ItemNode) => `radio-${node.value}` +export const getElementIdFromNode = (value: string) => `radio-${value}`