diff --git a/design-system/pkg/package.json b/design-system/pkg/package.json index 1f59f1c74..52b6c7656 100644 --- a/design-system/pkg/package.json +++ b/design-system/pkg/package.json @@ -1479,14 +1479,15 @@ "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@floating-ui/react": "^0.24.0", - "@internationalized/date": "^3.12.2", + "@internationalized/date": "^3.12.3", "@internationalized/number": "^3.6.7", - "@internationalized/string": "^3.2.9", - "@react-types/shared": "^3.36.0", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", "@types/react": "^19.0.8", "emery": "^1.4.1", "facepaint": "^1.2.1", "react-aria": "catalog:", + "react-aria-components": "catalog:", "react-stately": "catalog:" }, "devDependencies": { @@ -1513,6 +1514,7 @@ "next": ">=14", "react": "^18.2.0 || ^19.0.0", "react-aria": "catalog:", + "react-aria-components": "catalog:", "react-dom": "^18.2.0 || ^19.0.0", "react-stately": "catalog:" }, diff --git a/design-system/pkg/src/combobox/useComboboxMultiState.ts b/design-system/pkg/src/combobox/useComboboxMultiState.ts index bbfa3f2a0..a35a651d6 100644 --- a/design-system/pkg/src/combobox/useComboboxMultiState.ts +++ b/design-system/pkg/src/combobox/useComboboxMultiState.ts @@ -220,6 +220,8 @@ export function useComboboxMultiState( ...validation, focusStrategy: triggerState.focusStrategy, isOpen: triggerState.isOpen, + point: triggerState.point, + setPoint: triggerState.setPoint, setOpen: triggerState.setOpen, toggle, open, diff --git a/design-system/pkg/src/drag-and-drop/types.ts b/design-system/pkg/src/drag-and-drop/types.ts index fdb445f65..33d3ebd74 100644 --- a/design-system/pkg/src/drag-and-drop/types.ts +++ b/design-system/pkg/src/drag-and-drop/types.ts @@ -1,91 +1,11 @@ -import { - DraggableCollectionOptions, - DraggableItemProps, - DraggableItemResult, -} from 'react-aria/useDraggableCollection'; -import { DragPreview } from 'react-aria/useDrag'; -import { - DropIndicatorAria, - DropIndicatorProps, - DroppableCollectionOptions, - DroppableCollectionResult, - DroppableItemOptions, - DroppableItemResult, -} from 'react-aria/useDroppableCollection'; -import { - DraggableCollectionState, - DraggableCollectionStateOptions, -} from 'react-stately/useDraggableCollectionState'; -import { - DroppableCollectionState, - DroppableCollectionStateOptions, -} from 'react-stately/useDroppableCollectionState'; -import { - DraggableCollectionProps, - DroppableCollectionProps, - Key, - DragItem, -} from '@react-types/shared'; - -import { RefObject, type JSX } from 'react'; - -interface DraggableCollectionStateOpts - extends Omit {} - -export interface DragHooks { - useDraggableCollectionState?: ( - props: DraggableCollectionStateOpts - ) => DraggableCollectionState; - useDraggableCollection?: ( - props: DraggableCollectionOptions, - state: DraggableCollectionState, - ref: RefObject - ) => void; - useDraggableItem?: ( - props: DraggableItemProps, - state: DraggableCollectionState - ) => DraggableItemResult; - DragPreview?: typeof DragPreview; -} - -export interface DropHooks { - useDroppableCollectionState?: ( - props: DroppableCollectionStateOptions - ) => DroppableCollectionState; - useDroppableCollection?: ( - props: DroppableCollectionOptions, - state: DroppableCollectionState, - ref: RefObject - ) => DroppableCollectionResult; - useDroppableItem?: ( - options: DroppableItemOptions, - state: DroppableCollectionState, - ref: RefObject - ) => DroppableItemResult; - useDropIndicator?: ( - props: DropIndicatorProps, - state: DroppableCollectionState, - ref: RefObject - ) => DropIndicatorAria; -} +import type { + DragAndDropHooks as ReactAriaDragAndDropHooks, + DragAndDropOptions as ReactAriaDragAndDropOptions, +} from 'react-aria-components/useDragAndDrop'; export interface DragAndDropHooks { - /** Drag and drop hooks for the collection element. */ - dragAndDropHooks: DragHooks & - DropHooks & { - isVirtualDragging?: () => boolean; - renderPreview?: (keys: Set, draggedKey: Key | null) => JSX.Element; - }; + /** Drag and drop hooks for the collection element. */ + dragAndDropHooks: ReactAriaDragAndDropHooks; } -export interface DragAndDropOptions - extends Omit, - DroppableCollectionProps { - /** - * A function that returns the items being dragged. If not specified, we assume that the collection is not draggable. - * @default () => [] - */ - getItems?: (keys: Set) => DragItem[]; - /** Provide a custom drag preview. `draggedKey` represents the key of the item the user actually dragged. */ - renderPreview?: (keys: Set, draggedKey: Key | null) => JSX.Element; -} +export interface DragAndDropOptions extends ReactAriaDragAndDropOptions {} diff --git a/design-system/pkg/src/drag-and-drop/useDragAndDrop.ts b/design-system/pkg/src/drag-and-drop/useDragAndDrop.ts index fbbd6d2fa..32006d746 100644 --- a/design-system/pkg/src/drag-and-drop/useDragAndDrop.ts +++ b/design-system/pkg/src/drag-and-drop/useDragAndDrop.ts @@ -1,102 +1,13 @@ -/* eslint-disable react-compiler/react-compiler */ -import { DragPreview } from 'react-aria/useDrag'; -import { - DroppableCollectionOptions, - useDropIndicator, - useDroppableCollection, - useDroppableItem, -} from 'react-aria/useDroppableCollection'; -import { isVirtualDragging } from 'react-aria/private/dnd/DragManager'; -import { - useDraggableCollection, - useDraggableItem, -} from 'react-aria/useDraggableCollection'; -import { - DraggableCollectionStateOptions, - useDraggableCollectionState, -} from 'react-stately/useDraggableCollectionState'; -import { - DroppableCollectionState, - DroppableCollectionStateOptions, - useDroppableCollectionState, -} from 'react-stately/useDroppableCollectionState'; -import type { Key } from '@react-types/shared'; -import { RefObject, useMemo, type JSX } from 'react'; +import { useDragAndDrop as useReactAriaDragAndDrop } from 'react-aria-components/useDragAndDrop'; -import { - DragAndDropHooks, - DragAndDropOptions, - DragHooks, - DropHooks, -} from './types'; +import type { DragAndDropHooks, DragAndDropOptions } from './types'; /** - * Provides the hooks required to enable drag and drop behavior for a drag and drop compatible React Spectrum component. + * Provides the hooks required to enable drag and drop behavior for a drag and + * drop compatible collection component. + * + * This exposes the public React Aria Components drag-and-drop API. */ export function useDragAndDrop(options: DragAndDropOptions): DragAndDropHooks { - let dragAndDropHooks = useMemo(() => { - let { - onDrop, - onInsert, - onItemDrop, - onReorder, - onRootDrop, - getItems, - renderPreview, - } = options; - - let isDraggable = !!getItems; - let isDroppable = !!( - onDrop || - onInsert || - onItemDrop || - onReorder || - onRootDrop - ); - - let hooks = {} as DragHooks & - DropHooks & { - isVirtualDragging?: () => boolean; - renderPreview?: (keys: Set, draggedKey: Key | null) => JSX.Element; - }; - if (isDraggable) { - // @ts-expect-error - hooks.useDraggableCollectionState = - function useDraggableCollectionStateOverride( - props: DraggableCollectionStateOptions - ) { - return useDraggableCollectionState({ ...props, ...options }); - }; - hooks.useDraggableCollection = useDraggableCollection; - hooks.useDraggableItem = useDraggableItem; - hooks.DragPreview = DragPreview; - hooks.renderPreview = renderPreview; - } - - if (isDroppable) { - hooks.useDroppableCollectionState = - function useDroppableCollectionStateOverride( - props: DroppableCollectionStateOptions - ) { - return useDroppableCollectionState({ ...props, ...options }); - }; - hooks.useDroppableItem = useDroppableItem; - hooks.useDroppableCollection = function useDroppableCollectionOverride( - props: DroppableCollectionOptions, - state: DroppableCollectionState, - ref: RefObject - ) { - return useDroppableCollection({ ...props, ...options }, state, ref); - }; - hooks.useDropIndicator = useDropIndicator; - } - - if (isDraggable || isDroppable) { - hooks.isVirtualDragging = isVirtualDragging; - } - - return hooks; - }, [options]); - - return { dragAndDropHooks }; + return useReactAriaDragAndDrop(options); } diff --git a/design-system/pkg/src/list-view/DragPreview.tsx b/design-system/pkg/src/list-view/DragPreview.tsx deleted file mode 100644 index 7cf6c669a..000000000 --- a/design-system/pkg/src/list-view/DragPreview.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { GridNode } from 'react-stately/private/grid/GridCollection'; - -import { Flex, Grid } from '@keystar/ui/layout'; -import { SlotProvider } from '@keystar/ui/slots'; -import { - classNames, - css, - toDataAttributes, - tokenSchema, -} from '@keystar/ui/style'; -import { Text } from '@keystar/ui/typography'; -import { isReactText } from '@keystar/ui/utils'; - -import { listViewItemClassList } from './class-list'; -import type { ListViewProps } from './types'; - -interface DragPreviewProps { - item: GridNode; - itemCount: number; - itemHeight: number; - density: ListViewProps['density']; -} - -export function DragPreview(props: DragPreviewProps) { - let { item, itemCount, itemHeight, density } = props; - - let isDraggingMultiple = itemCount > 1; - - return ( -
- - - {isReactText(item.rendered) ? ( - {item.rendered} - ) : ( - item.rendered - )} - - {isDraggingMultiple && ( - - - {itemCount} - - - )} - - -
- ); -} diff --git a/design-system/pkg/src/list-view/InsertionIndicator.tsx b/design-system/pkg/src/list-view/InsertionIndicator.tsx deleted file mode 100644 index b98e4d60b..000000000 --- a/design-system/pkg/src/list-view/InsertionIndicator.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useVisuallyHidden } from 'react-aria/VisuallyHidden'; -import { ItemDropTarget } from '@react-types/shared'; -import { assert } from 'emery'; -import { useRef } from 'react'; - -import { InsertionIndicatorPrimitive } from '@keystar/ui/drag-and-drop'; - -import { useListViewContext } from './context'; - -interface InsertionIndicatorProps { - target: ItemDropTarget; - isPresentationOnly?: boolean; -} - -export function InsertionIndicator(props: InsertionIndicatorProps) { - let { dropState, dragAndDropHooks } = useListViewContext(); - const { target, isPresentationOnly } = props; - - assert( - !!dragAndDropHooks.useDropIndicator, - 'dragAndDropHooks.useDropIndicator is not defined.' - ); - - let ref = useRef(null); - // eslint-disable-next-line react-compiler/react-compiler - let { dropIndicatorProps } = dragAndDropHooks.useDropIndicator( - props, - dropState, - ref - ); - let { visuallyHiddenProps } = useVisuallyHidden(); - - let isDropTarget = dropState.isDropTarget(target); - - if (!isDropTarget && dropIndicatorProps['aria-hidden']) { - return null; - } - - return ( -
- - {!isPresentationOnly && ( -
- )} - -
- ); -} diff --git a/design-system/pkg/src/list-view/ListView.tsx b/design-system/pkg/src/list-view/ListView.tsx index 789609e8d..4365671f3 100644 --- a/design-system/pkg/src/list-view/ListView.tsx +++ b/design-system/pkg/src/list-view/ListView.tsx @@ -1,29 +1,28 @@ -import { useGridList } from 'react-aria/useGridList'; -import type { DroppableCollectionResult } from 'react-aria/useDroppableCollection'; -import { FocusScope } from 'react-aria/FocusScope'; -import { useLocalizedStringFormatter } from 'react-aria/useLocalizedStringFormatter'; -import { Virtualizer } from 'react-aria/private/virtualizer/Virtualizer'; -import { filterDOMProps } from 'react-aria/filterDOMProps'; -import { mergeProps } from 'react-aria/mergeProps'; -import { useObjectRef } from 'react-aria/useObjectRef'; -import type { DraggableCollectionState } from 'react-stately/useDraggableCollectionState'; -import type { DroppableCollectionState } from 'react-stately/useDroppableCollectionState'; -import { ListState, useListState } from 'react-stately/useListState'; -import { assert } from 'emery'; +import { Node } from '@react-types/shared'; +import { + Collection, + GridList, + GridListItem, + GridListLoadMoreItem, +} from 'react-aria-components/GridList'; +import { + ListLayout, + type ListLayoutOptions, + Virtualizer, +} from 'react-aria-components/Virtualizer'; import React, { - Key, PropsWithChildren, ReactElement, RefObject, - useEffect, useMemo, - useRef, } from 'react'; +import { useListState } from 'react-stately/useListState'; +import { useObjectRef } from 'react-aria/useObjectRef'; +import { useLocalizedStringFormatter } from 'react-aria/useLocalizedStringFormatter'; import { useProvider } from '@keystar/ui/core'; import { ProgressCircle } from '@keystar/ui/progress'; import { - FocusRing, classNames, css, toDataAttributes, @@ -32,47 +31,32 @@ import { } from '@keystar/ui/style'; import { listViewClassList } from './class-list'; -import { ListViewProvider, useListViewContext } from './context'; import localizedMessages from './l10n'; -import { DragPreview as DragPreviewElement } from './DragPreview'; -import { InsertionIndicator } from './InsertionIndicator'; import { ListViewItem } from './ListViewItem'; -import { ListViewLayout } from './ListViewLayout'; -import RootDropIndicator from './RootDropIndicator'; import { ListViewProps } from './types'; -import { ListKeyboardDelegate } from 'react-aria/ListKeyboardDelegate'; const ROW_HEIGHTS = { - compact: { - medium: 32, - large: 40, - }, - regular: { - medium: 40, - large: 50, - }, - spacious: { - medium: 48, - large: 60, - }, + compact: { medium: 32, large: 40 }, + regular: { medium: 40, large: 50 }, + spacious: { medium: 48, large: 60 }, } as const; +const LOAD_MORE_ITEM = { type: 'keystar-load-more' } as const; function useListLayout( - state: ListState, density: NonNullable['density']>, overflowMode: ListViewProps['overflowMode'] ) { let { scale } = useProvider(); - let layout = useMemo( - () => - new ListViewLayout({ - estimatedRowHeight: - overflowMode === 'wrap' ? undefined : ROW_HEIGHTS[density][scale], - }), - [scale, density, overflowMode] - ); - - return layout; + return useMemo(() => { + let layoutOptions: ListLayoutOptions = { + estimatedRowSize: + overflowMode === 'wrap' ? undefined : ROW_HEIGHTS[density][scale], + }; + return { + layout: new ListLayout(layoutOptions), + layoutOptions, + }; + }, [scale, density, overflowMode]); } function ListView( @@ -83,334 +67,149 @@ function ListView( density = 'regular', loadingState, onLoadMore, - isQuiet, overflowMode = 'truncate', onAction, dragAndDropHooks, renderEmptyState, ...otherProps } = props; - - let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState; - let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState; - - let dragHooksProvided = useRef(isListDraggable); - let dropHooksProvided = useRef(isListDroppable); - - useEffect(() => { - if (dragHooksProvided.current !== isListDraggable) { - console.warn( - 'Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.' - ); - } - if (dropHooksProvided.current !== isListDroppable) { - console.warn( - 'Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.' - ); - } - }, [isListDraggable, isListDroppable]); - let domRef = useObjectRef(ref); - let state = useListState({ - ...props, - selectionBehavior: - props.selectionStyle === 'highlight' ? 'replace' : 'toggle', - }); - let { collection, selectionManager } = state; + let collectionState = useListState(props); + let collection = collectionState.collection; + let { layout, layoutOptions } = useListLayout(density, overflowMode); + let styleProps = useStyleProps(props); let stringFormatter = useLocalizedStringFormatter(localizedMessages); let isLoading = loadingState === 'loading' || loadingState === 'loadingMore'; - - let styleProps = useStyleProps(props); - let preview = useRef(null); - - // DraggableCollectionState; - let dragState!: DraggableCollectionState; - if ( - isListDraggable && - dragAndDropHooks?.useDraggableCollectionState && - dragAndDropHooks?.useDraggableCollection - ) { - // consumers are warned when hooks change between renders - // eslint-disable-next-line react-compiler/react-compiler - dragState = dragAndDropHooks.useDraggableCollectionState({ - collection, - selectionManager, - preview, - }); - // eslint-disable-next-line react-compiler/react-compiler - dragAndDropHooks.useDraggableCollection({}, dragState, domRef); - } - let layout = useListLayout(state, props.density || 'regular', overflowMode); - - let DragPreview = dragAndDropHooks?.DragPreview; - let dropState!: DroppableCollectionState; - let droppableCollection: DroppableCollectionResult; - let isRootDropTarget: boolean; - if ( - isListDroppable && - dragAndDropHooks?.useDroppableCollectionState && - dragAndDropHooks?.useDroppableCollection - ) { - // consumers are warned when hooks change between renders - // eslint-disable-next-line react-compiler/react-compiler - dropState = dragAndDropHooks.useDroppableCollectionState({ - collection, - selectionManager, - }); - // eslint-disable-next-line react-compiler/react-compiler - droppableCollection = dragAndDropHooks.useDroppableCollection( - { - keyboardDelegate: new ListKeyboardDelegate({ - collection, - disabledKeys: dragState?.draggingKeys.size - ? undefined - : selectionManager.disabledKeys, - ref: domRef, - layoutDelegate: layout, - }), - dropTargetDelegate: layout, - }, - dropState, - domRef - ); - - isRootDropTarget = dropState.isDropTarget({ type: 'root' }); - } - - let { gridProps } = useGridList( - { - ...props, - isVirtualized: true, - layoutDelegate: layout, - onAction, - }, - state, - domRef - ); - - let focusedKey = selectionManager.focusedKey; - let dropTargetKey: Key | null = null; - if (dropState?.target?.type === 'item') { - dropTargetKey = dropState.target.key; - if (dropState.target.dropPosition === 'after') { - // Normalize to the "before" drop position since we only render those in the DOM. - dropTargetKey = - state.collection.getKeyAfter(dropTargetKey) ?? dropTargetKey; - } - } - - let persistedKeys = useMemo(() => { - return new Set([focusedKey, dropTargetKey].filter(k => k !== null)); - }, [focusedKey, dropTargetKey]); - + let loadingLabel = + collection.size > 0 + ? stringFormatter.format('loadingMore') + : stringFormatter.format('loading'); + let collectionItems = + isLoading || onLoadMore ? [...collection, LOAD_MORE_ITEM] : collection; let hasAnyChildren = useMemo( () => [...collection].some(item => item.hasChildNodes), [collection] ); - let { onScroll: _onScroll, ...virtualizerGridProps } = gridProps; return ( - - - - ({ isLoading }), [isLoading])} - collection={collection} - className={classNames( - listViewClassList.element('root'), - css({ - backgroundColor: tokenSchema.color.background.canvas, - border: `${tokenSchema.size.border.regular} solid ${tokenSchema.color.border.neutral}`, - borderRadius: tokenSchema.size.radius.medium, - boxSizing: 'content-box', // resolves measurement/scroll issues related to border - outline: 0, - overflow: 'auto', - position: 'relative', - transform: 'translate3d(0, 0, 0)', - userSelect: 'none', - - '&[data-drop-target=true]': { - borderColor: tokenSchema.color.alias.focusRing, - backgroundColor: tokenSchema.color.alias.backgroundSelected, - boxShadow: `inset 0 0 0 1px ${tokenSchema.color.alias.focusRing}`, - }, - '&[data-focus=visible]': { - borderColor: tokenSchema.color.alias.focusRing, - boxShadow: `inset 0 0 0 1px ${tokenSchema.color.alias.focusRing}`, - }, - }), - styleProps.className - )} - > - {(type, item) => { - if (type === 'item') { - return ( - <> - {isListDroppable && - collection.getKeyBefore(item.key) == null && ( - - )} - {isListDroppable && ( - - )} - - {isListDroppable && ( - - )} - - ); - } else if (type === 'loader') { - return ( - - 0 - ? stringFormatter.format('loadingMore') - : stringFormatter.format('loading') - } - /> - - ); - } else if (type === 'placeholder') { - let emptyState = props.renderEmptyState - ? props.renderEmptyState() - : null; - if (emptyState == null) { - return null; - } - - return {emptyState}; - } - }} - - - - {DragPreview && isListDraggable && ( - - {() => { - // @ts-expect-error FIXME - let item = state.collection.getItem(dragState.draggedKey); - - assert(item != null, 'Dragged item must exist in collection.'); - - let itemCount = dragState.draggingKeys.size; - // @ts-expect-error - let itemHeight = layout.getLayoutInfo(dragState.draggedKey).rect - .height; + {renderEmptyState()} + : undefined + } + > + + {(item: Node | typeof LOAD_MORE_ITEM) => { + if (isLoadMoreItem(item)) { + let progress = isLoading ? ( + + + + ) : null; + + return onLoadMore ? ( + + {progress} + + ) : ( + + {progress} + + ); + } return ( - ); }} - - )} - + + + ); } function CenteredWrapper({ children }: PropsWithChildren) { - let { state } = useListViewContext(); return (
0} - className={classNames( - listViewClassList.element('centered-wrapper'), - css({ - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%', - width: '100%', - - // if there's items it must be loading—add a gutter between the items - // and the loading indicator - '&[data-has-items=true]': { - paddingTop: tokenSchema.size.space.regular, - }, - }) - )} + className={css({ + alignItems: 'center', + display: 'flex', + height: '100%', + justifyContent: 'center', + width: '100%', + })} > -
{children}
+ {children}
); } -/** - * Displays a list of interactive items, and allows a user to navigate, select, - * or perform an action. - */ const _ListView = React.forwardRef(ListView as any) as ( props: ListViewProps & { ref?: RefObject } ) => ReactElement; + export { _ListView as ListView }; + +function isLoadMoreItem( + item: Node | typeof LOAD_MORE_ITEM +): item is typeof LOAD_MORE_ITEM { + return item === LOAD_MORE_ITEM; +} diff --git a/design-system/pkg/src/list-view/ListViewItem.tsx b/design-system/pkg/src/list-view/ListViewItem.tsx index 3e249fed5..a9f1c3bff 100644 --- a/design-system/pkg/src/list-view/ListViewItem.tsx +++ b/design-system/pkg/src/list-view/ListViewItem.tsx @@ -1,478 +1,298 @@ -import type { DraggableItemResult } from 'react-aria/useDraggableCollection'; -import type { - DropIndicatorAria, - DroppableItemResult, -} from 'react-aria/useDroppableCollection'; - -import { useButton } from 'react-aria/useButton'; -import { useFocusRing } from 'react-aria/useFocusRing'; -import { useHover } from 'react-aria/useHover'; +import { Node } from '@react-types/shared'; import { - useGridListItem, - useGridListSelectionCheckbox, -} from 'react-aria/useGridList'; + Button as AriaButton, + Checkbox as AriaCheckbox, + GridListItem, + Text as AriaText, +} from 'react-aria-components/GridList'; import { useLocale } from 'react-aria/I18nProvider'; -import { mergeProps } from 'react-aria/mergeProps'; -import { useVisuallyHidden } from 'react-aria/VisuallyHidden'; -import { DropTarget, Node } from '@react-types/shared'; - -import { assert } from 'emery'; -import React, { HTMLAttributes, useRef } from 'react'; +import { Children, isValidElement, type ReactNode } from 'react'; -import { Checkbox } from '@keystar/ui/checkbox'; import { KeystarProvider } from '@keystar/ui/core'; import { Icon } from '@keystar/ui/icon'; import { chevronLeftIcon } from '@keystar/ui/icon/icons/chevronLeftIcon'; import { chevronRightIcon } from '@keystar/ui/icon/icons/chevronRightIcon'; +import { checkIcon } from '@keystar/ui/icon/icons/checkIcon'; import { gripVerticalIcon } from '@keystar/ui/icon/icons/gripVerticalIcon'; -import { Flex, Grid } from '@keystar/ui/layout'; +import { Grid } from '@keystar/ui/layout'; import { ClearSlots, SlotProvider } from '@keystar/ui/slots'; import { - FocusRing, classNames, css, toDataAttributes, tokenSchema, - transition, } from '@keystar/ui/style'; import { Text } from '@keystar/ui/typography'; import { isReactText } from '@keystar/ui/utils'; import { listViewClassList, listViewItemClassList } from './class-list'; -import { useListViewContext } from './context'; +import type { ListViewProps } from './types'; interface ListViewItemProps { item: Node; - isEmphasized: boolean; + density: NonNullable['density']>; + overflowMode: NonNullable['overflowMode']>; hasActions: boolean; } -export function ListViewItem(props: ListViewItemProps) { - let { item } = props; - let { - density, - dragAndDropHooks, - dragState, - dropState, - isListDraggable, - isListDroppable, - layout, - loadingState, - overflowMode, - state, - } = useListViewContext(); +export function ListViewItem({ + item, + density, + overflowMode, + hasActions, +}: ListViewItemProps) { let { direction } = useLocale(); - let rowRef = useRef(null); - let { isFocusVisible: isFocusVisibleWithin, focusProps: focusWithinProps } = - useFocusRing({ within: true }); - let { isFocusVisible, focusProps } = useFocusRing(); - let { - rowProps, - gridCellProps, - isPressed, - descriptionProps, - isDisabled, - allowsSelection, - hasAction, - } = useGridListItem( - { - node: item, - isVirtualized: true, - shouldSelectOnPressUp: isListDraggable, - }, - state, - rowRef - ); - let isDroppable = isListDroppable && !isDisabled; - let { hoverProps, isHovered } = useHover({ - isDisabled: !allowsSelection && !hasAction, - }); - - let { checkboxProps } = useGridListSelectionCheckbox( - { key: item.key }, - state - ); - - let draggableItem: DraggableItemResult | null; - if (isListDraggable) { - // FIXME: improve implementation/types such that these guards aren't necessary - assert( - !!(dragAndDropHooks && dragAndDropHooks.useDraggableItem), - 'useDraggableItem is missing from dragAndDropHooks' - ); - draggableItem = dragAndDropHooks.useDraggableItem( - { key: item.key, hasDragButton: true }, - dragState - ); - if (isDisabled) { - draggableItem = null; - } - } - let droppableItem: DroppableItemResult; - let isDropTarget: boolean; - let dropIndicator: DropIndicatorAria; - let dropIndicatorRef = useRef(null); - if (isListDroppable) { - let target = { - type: 'item', - key: item.key, - dropPosition: 'on', - } as DropTarget; - isDropTarget = dropState.isDropTarget(target); - // FIXME: improve implementation/types such that these guards aren't necessary - assert( - !!(dragAndDropHooks && dragAndDropHooks.useDropIndicator), - 'useDropIndicator is missing from dragAndDropHooks' - ); - dropIndicator = dragAndDropHooks.useDropIndicator( - { target }, - dropState, - dropIndicatorRef - ); - } - - let dragButtonRef = useRef(null); - let { buttonProps } = useButton( - { - // @ts-expect-error - ...draggableItem?.dragButtonProps, - elementType: 'div', - }, - dragButtonRef - ); - let chevron = ( -