feat(ui): add column visibility and drag-and-drop reordering support (#13198)

This commit is contained in:
Sebastian Rindom
2025-09-01 14:03:56 +02:00
committed by GitHub
parent 5a46372afd
commit 2a94dbd243
19 changed files with 1846 additions and 310 deletions

View File

@@ -19,7 +19,6 @@ export class Views {
})
}
// View configurations
async listConfigurations(
entity: string,
query?: HttpTypes.AdminGetViewConfigurationsParams,

View File

@@ -81,6 +81,9 @@
"vitest": "^3.0.5"
},
"dependencies": {
"@dnd-kit/core": "^6.0.0",
"@dnd-kit/sortable": "^7.0.0",
"@dnd-kit/utilities": "^3.2.0",
"@medusajs/icons": "2.10.1",
"@tanstack/react-table": "8.20.5",
"clsx": "^1.2.1",

View File

@@ -0,0 +1,102 @@
import React from "react"
import { Column } from "@tanstack/react-table"
import { Checkbox } from "@/components/checkbox"
import { DropdownMenu } from "@/components/dropdown-menu"
import { IconButton } from "@/components/icon-button"
import { Tooltip } from "@/components/tooltip"
import { Adjustments } from "@medusajs/icons"
import { clx } from "@/utils/clx"
import { useDataTableContext } from "../context/use-data-table-context"
interface DataTableColumnVisibilityMenuProps {
className?: string
tooltip?: string
}
const DataTableColumnVisibilityMenu = ({
className,
tooltip,
}: DataTableColumnVisibilityMenuProps) => {
const { instance, enableColumnVisibility } = useDataTableContext()
if (!enableColumnVisibility) {
return null
}
const columns = instance
.getAllColumns()
.filter((column) => column.getCanHide())
const handleToggleColumn = (column: Column<any, any>) => {
column.toggleVisibility()
}
const handleToggleAll = (value: boolean) => {
instance.setColumnVisibility(
Object.fromEntries(
columns.map((column: Column<any, any>) => [column.id, value])
)
)
}
const allColumnsVisible = columns.every((column: Column<any, any>) => column.getIsVisible())
const someColumnsVisible = columns.some((column: Column<any, any>) => column.getIsVisible())
const Wrapper = tooltip ? Tooltip : React.Fragment
const wrapperProps = tooltip ? { content: tooltip } : ({} as any)
return (
<DropdownMenu>
<Wrapper {...wrapperProps}>
<DropdownMenu.Trigger asChild>
<IconButton size="small" className={className}>
<Adjustments />
</IconButton>
</DropdownMenu.Trigger>
</Wrapper>
<DropdownMenu.Content align="end" className="min-w-[200px] max-h-[400px] overflow-hidden">
<DropdownMenu.Label>Toggle columns</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item
onSelect={(e: Event) => {
e.preventDefault()
handleToggleAll(!allColumnsVisible)
}}
>
<div className="flex items-center gap-x-2">
<Checkbox
checked={allColumnsVisible ? true : (someColumnsVisible && !allColumnsVisible) ? "indeterminate" : false}
/>
<span>Toggle all</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<div className="max-h-[250px] overflow-y-auto">
{columns.map((column: Column<any, any>) => {
return (
<DropdownMenu.Item
key={column.id}
onSelect={(e: Event) => {
e.preventDefault()
handleToggleColumn(column)
}}
>
<div className="flex items-center gap-x-2">
<Checkbox checked={column.getIsVisible()} />
<span className="truncate">
{(column.columnDef.meta as any)?.name || column.id}
</span>
</div>
</DropdownMenu.Item>
)
})}
</div>
</DropdownMenu.Content>
</DropdownMenu>
)
}
export { DataTableColumnVisibilityMenu }
export type { DataTableColumnVisibilityMenuProps }

View File

@@ -3,28 +3,114 @@
import * as React from "react"
import { DataTableFilter } from "@/blocks/data-table/components/data-table-filter"
import { DataTableFilterMenu } from "@/blocks/data-table/components/data-table-filter-menu"
import { DataTableSortingMenu } from "@/blocks/data-table/components/data-table-sorting-menu"
import { DataTableColumnVisibilityMenu } from "@/blocks/data-table/components/data-table-column-visibility-menu"
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
import { Button } from "@/components/button"
import { Skeleton } from "@/components/skeleton"
interface DataTableFilterBarProps {
clearAllFiltersLabel?: string
alwaysShow?: boolean
sortingTooltip?: string
columnsTooltip?: string
children?: React.ReactNode
}
interface LocalFilter {
id: string
value: unknown
isNew: boolean
}
const DataTableFilterBar = ({
clearAllFiltersLabel = "Clear all",
alwaysShow = false,
sortingTooltip,
columnsTooltip,
children,
}: DataTableFilterBarProps) => {
const { instance } = useDataTableContext()
const filterState = instance.getFiltering()
const { instance, enableColumnVisibility } = useDataTableContext()
// Local state for managing intermediate filters
const [localFilters, setLocalFilters] = React.useState<LocalFilter[]>([])
const parentFilterState = instance.getFiltering()
const availableFilters = instance.getFilters()
// Sync parent filters with local state
React.useEffect(() => {
setLocalFilters(prevLocalFilters => {
const parentIds = Object.keys(parentFilterState)
const localIds = prevLocalFilters.map(f => f.id)
// Remove local filters that have been removed from parent
const updatedLocalFilters = prevLocalFilters.filter(f =>
parentIds.includes(f.id) || f.isNew
)
// Add parent filters that don't exist locally
parentIds.forEach(id => {
if (!localIds.includes(id)) {
updatedLocalFilters.push({
id,
value: parentFilterState[id],
isNew: false
})
}
})
// Only update if there's an actual change
if (updatedLocalFilters.length !== prevLocalFilters.length ||
updatedLocalFilters.some((f, i) => f.id !== prevLocalFilters[i]?.id)) {
return updatedLocalFilters
}
return prevLocalFilters
})
}, [parentFilterState])
// Add a new filter locally
const addLocalFilter = React.useCallback((id: string, value: unknown) => {
setLocalFilters(prev => [...prev, { id, value, isNew: true }])
}, [])
// Update a local filter's value
const updateLocalFilter = React.useCallback((id: string, value: unknown) => {
setLocalFilters(prev => prev.map(f =>
f.id === id ? { ...f, value, isNew: false } : f
))
// If the filter has a meaningful value, propagate to parent
if (value !== undefined && value !== null && value !== '' &&
!(Array.isArray(value) && value.length === 0)) {
instance.updateFilter({ id, value })
}
}, [instance])
// Remove a local filter
const removeLocalFilter = React.useCallback((id: string) => {
setLocalFilters(prev => prev.filter(f => f.id !== id))
// Also remove from parent if it exists there
if (parentFilterState[id] !== undefined) {
instance.removeFilter(id)
}
}, [instance, parentFilterState])
const clearFilters = React.useCallback(() => {
setLocalFilters([])
instance.clearFilters()
}, [instance])
const filterCount = Object.keys(filterState).length
const filterCount = localFilters.length
const hasAvailableFilters = availableFilters.length > 0
// Check if sorting is enabled
const sortableColumns = instance.getAllColumns().filter((column) => column.getCanSort())
const hasSorting = instance.enableSorting && sortableColumns.length > 0
if (filterCount === 0) {
// Always show the filter bar when there are available filters, sorting, column visibility, or when forced
if (filterCount === 0 && !hasAvailableFilters && !hasSorting && !enableColumnVisibility && !alwaysShow && !children) {
return null
}
@@ -33,21 +119,27 @@ const DataTableFilterBar = ({
}
return (
<div className="bg-ui-bg-subtle flex w-full flex-nowrap items-center gap-2 overflow-x-auto border-t px-6 py-2 md:flex-wrap">
{Object.entries(filterState).map(([id, filter]) => (
<DataTableFilter key={id} id={id} filter={filter} />
))}
{filterCount > 0 ? (
<Button
variant="transparent"
size="small"
className="text-ui-fg-muted hover:text-ui-fg-subtle flex-shrink-0 whitespace-nowrap"
type="button"
onClick={clearFilters}
>
{clearAllFiltersLabel}
</Button>
) : null}
<div className="bg-ui-bg-subtle flex w-full flex-nowrap items-center justify-between gap-2 overflow-x-auto border-t px-6 py-2">
<div className="flex flex-nowrap items-center gap-2 md:flex-wrap">
{localFilters.map((localFilter) => (
<DataTableFilter
key={localFilter.id}
id={localFilter.id}
filter={localFilter.value}
isNew={localFilter.isNew}
onUpdate={(value) => updateLocalFilter(localFilter.id, value)}
onRemove={() => removeLocalFilter(localFilter.id)}
/>
))}
{hasAvailableFilters && (
<DataTableFilterMenu onAddFilter={addLocalFilter} />
)}
</div>
<div className="flex flex-shrink-0 items-center gap-2">
{hasSorting && <DataTableSortingMenu tooltip={sortingTooltip} />}
{enableColumnVisibility && <DataTableColumnVisibilityMenu tooltip={columnsTooltip} />}
{children}
</div>
</div>
)
}

View File

@@ -12,13 +12,17 @@ interface DataTableFilterMenuProps {
* The tooltip to show when hovering over the filter menu.
*/
tooltip?: string
/**
* Callback when a filter is added
*/
onAddFilter?: (id: string, value: unknown) => void
}
/**
* This component adds a filter menu to the data table, allowing users
* to filter the table's data.
*/
const DataTableFilterMenu = (props: DataTableFilterMenuProps) => {
const DataTableFilterMenu = ({ tooltip, onAddFilter }: DataTableFilterMenuProps) => {
const { instance } = useDataTableContext()
const enabledFilters = Object.keys(instance.getFiltering())
@@ -33,9 +37,9 @@ const DataTableFilterMenu = (props: DataTableFilterMenuProps) => {
)
}
const Wrapper = props.tooltip ? Tooltip : React.Fragment
const wrapperProps = props.tooltip
? { content: props.tooltip, hidden: filterOptions.length === 0 }
const Wrapper = tooltip ? Tooltip : React.Fragment
const wrapperProps = tooltip
? { content: tooltip, hidden: filterOptions.length === 0 }
: ({} as any)
if (instance.showSkeleton) {
@@ -51,17 +55,45 @@ const DataTableFilterMenu = (props: DataTableFilterMenuProps) => {
</IconButton>
</DropdownMenu.Trigger>
</Wrapper>
<DropdownMenu.Content side="bottom">
{filterOptions.map((filter) => (
<DropdownMenu.Item
key={filter.id}
onClick={() => {
instance.addFilter({ id: filter.id, value: undefined })
}}
>
{filter.label}
</DropdownMenu.Item>
))}
<DropdownMenu.Content side="bottom" align="start">
{filterOptions.map((filter) => {
const getDefaultValue = () => {
switch (filter.type) {
case "select":
case "multiselect":
return []
case "string":
return ""
case "number":
return null
case "date":
return null
case "radio":
return null
case "custom":
return null
default:
return null
}
}
return (
<DropdownMenu.Item
key={filter.id}
onClick={(e) => {
// Prevent any bubbling that might interfere
e.stopPropagation()
if (onAddFilter) {
onAddFilter(filter.id, getDefaultValue())
} else {
instance.addFilter({ id: filter.id, value: getDefaultValue() })
}
}}
>
{filter.label}
</DropdownMenu.Item>
)
})}
</DropdownMenu.Content>
</DropdownMenu>
)

View File

@@ -0,0 +1,64 @@
import * as React from "react"
import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { Table } from "@/components/table"
interface DataTableNonSortableHeaderCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
id: string
children: React.ReactNode
isFirstColumn?: boolean
}
export const DataTableNonSortableHeaderCell = React.forwardRef<
HTMLTableCellElement,
DataTableNonSortableHeaderCellProps
>(({ id, children, className, style: propStyle, isFirstColumn, ...props }, ref) => {
// Still use sortable hook but without listeners
const {
setNodeRef,
transform,
transition,
} = useSortable({
id,
disabled: true, // Disable dragging
})
// Only apply horizontal transform for smooth shifting
const transformStyle = transform ? {
x: transform.x,
y: 0,
scaleX: transform.scaleX,
scaleY: transform.scaleY,
} : null
const style: React.CSSProperties = {
...propStyle,
transform: transformStyle ? CSS.Transform.toString(transformStyle) : undefined,
transition,
position: 'relative' as const,
}
const combineRefs = (element: HTMLTableCellElement | null) => {
setNodeRef(element)
if (ref) {
if (typeof ref === 'function') {
ref(element)
} else {
ref.current = element
}
}
}
return (
<Table.HeaderCell
ref={combineRefs}
style={style}
className={className}
{...props}
>
{children}
</Table.HeaderCell>
)
})
DataTableNonSortableHeaderCell.displayName = "DataTableNonSortableHeaderCell"

View File

@@ -0,0 +1,71 @@
import * as React from "react"
import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { clx } from "@/utils/clx"
import { Table } from "@/components/table"
interface DataTableSortableHeaderCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
id: string
children: React.ReactNode
isFirstColumn?: boolean
}
export const DataTableSortableHeaderCell = React.forwardRef<
HTMLTableCellElement,
DataTableSortableHeaderCellProps
>(({ id, children, className, style: propStyle, isFirstColumn, ...props }, ref) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id,
})
// Only apply horizontal transform, ignore vertical movement
const transformStyle = transform ? {
x: transform.x,
y: 0,
scaleX: 1,
scaleY: 1
} : null
const style: React.CSSProperties = {
...propStyle,
transform: transformStyle ? CSS.Transform.toString(transformStyle) : undefined,
transition,
opacity: isDragging ? 0.8 : 1,
zIndex: isDragging ? 50 : undefined,
backgroundColor: "white",
position: 'relative' as const,
}
const combineRefs = (element: HTMLTableCellElement | null) => {
setNodeRef(element)
if (ref) {
if (typeof ref === 'function') {
ref(element)
} else {
ref.current = element
}
}
}
return (
<Table.HeaderCell
ref={combineRefs}
style={style}
className={clx(className, "group/header-cell relative")}
{...attributes}
{...listeners}
{...props}
>
{children}
</Table.HeaderCell>
)
})
DataTableSortableHeaderCell.displayName = "DataTableSortableHeaderCell"

View File

@@ -4,6 +4,22 @@ import * as React from "react"
import { Table } from "@/components/table"
import { flexRender } from "@tanstack/react-table"
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
DragStartEvent,
} from "@dnd-kit/core"
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
horizontalListSortingStrategy,
} from "@dnd-kit/sortable"
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
import { Skeleton } from "@/components/skeleton"
@@ -15,6 +31,8 @@ import {
DataTableEmptyStateProps,
} from "../types"
import { DataTableSortingIcon } from "./data-table-sorting-icon"
import { DataTableSortableHeaderCell } from "./data-table-sortable-header-cell"
import { DataTableNonSortableHeaderCell } from "./data-table-non-sortable-header-cell"
interface DataTableTableProps {
/**
@@ -42,6 +60,59 @@ const DataTableTable = (props: DataTableTableProps) => {
const hasSelect = columns.find((c) => c.id === "select")
const hasActions = columns.find((c) => c.id === "action")
// Create list of all column IDs for SortableContext
// Use current order if available, otherwise use default order
const sortableItems = React.useMemo(() => {
if (instance.columnOrder && instance.columnOrder.length > 0) {
return instance.columnOrder
}
return columns.map(col => col.id)
}, [columns, instance.columnOrder])
// Setup drag-and-drop sensors
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
)
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
if (active.id !== over?.id && over?.id) {
const activeId = active.id as string
const overId = over.id as string
// Don't allow dragging fixed columns
if (activeId === "select" || activeId === "action") {
return
}
// Don't allow dropping on fixed columns
if (overId === "select" || overId === "action") {
return
}
// Use the current column order from the instance
const currentOrder = instance.columnOrder && instance.columnOrder.length > 0
? instance.columnOrder
: columns.map(col => col.id)
const oldIndex = currentOrder.indexOf(activeId)
const newIndex = currentOrder.indexOf(overId)
if (oldIndex !== -1 && newIndex !== -1) {
const newOrder = arrayMove(currentOrder, oldIndex, newIndex)
instance.setColumnOrderFromArray(newOrder)
}
}
}
React.useEffect(() => {
const onKeyDownHandler = (event: KeyboardEvent) => {
// If an editable element is focused, we don't want to select a row
@@ -100,155 +171,349 @@ const DataTableTable = (props: DataTableTableProps) => {
return (
<div className="flex w-full flex-1 flex-col overflow-hidden">
{instance.emptyState === DataTableEmptyState.POPULATED && (
<div
ref={scrollableRef}
onScroll={handleHorizontalScroll}
className="min-h-0 w-full flex-1 overflow-auto overscroll-none border-y"
>
<Table className="relative isolate w-full">
<Table.Header
className="shadow-ui-border-base sticky inset-x-0 top-0 z-[1] w-full border-b-0 border-t-0 shadow-[0_1px_1px_0]"
style={{ transform: "translate3d(0,0,0)" }}
instance.enableColumnOrder ? (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<div
ref={scrollableRef}
onScroll={handleHorizontalScroll}
className="min-h-0 w-full flex-1 overflow-auto overscroll-none border-y"
>
{instance.getHeaderGroups().map((headerGroup) => (
<Table.Row
key={headerGroup.id}
className={clx("border-b-0", {
"[&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap":
hasActions,
"[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap":
hasSelect,
})}
<Table className="relative isolate w-full">
<Table.Header
className="shadow-ui-border-base sticky inset-x-0 top-0 z-[1] w-full border-b-0 border-t-0 shadow-[0_1px_1px_0]"
style={{ transform: "translate3d(0,0,0)" }}
>
{headerGroup.headers.map((header, idx) => {
const canSort = header.column.getCanSort()
const sortDirection = header.column.getIsSorted()
const sortHandler = header.column.getToggleSortingHandler()
{instance.getHeaderGroups().map((headerGroup) => (
<Table.Row
key={headerGroup.id}
className={clx("border-b-0", {
"[&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap":
hasActions,
"[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap":
hasSelect,
})}
>
<SortableContext
items={sortableItems}
strategy={horizontalListSortingStrategy}
>
{headerGroup.headers.map((header, idx) => {
const canSort = header.column.getCanSort()
const sortDirection = header.column.getIsSorted()
const sortHandler = header.column.getToggleSortingHandler()
const isActionHeader = header.id === "action"
const isSelectHeader = header.id === "select"
const isSpecialHeader = isActionHeader || isSelectHeader
const isActionHeader = header.id === "action"
const isSelectHeader = header.id === "select"
const isSpecialHeader = isActionHeader || isSelectHeader
const isDraggable = !isSpecialHeader
const Wrapper = canSort ? "button" : "div"
const isFirstColumn = hasSelect ? idx === 1 : idx === 0
const Wrapper = canSort ? "button" : "div"
const isFirstColumn = hasSelect ? idx === 1 : idx === 0
return (
<Table.HeaderCell
key={header.id}
className={clx("whitespace-nowrap", {
"w-[calc(20px+24px+24px)] min-w-[calc(20px+24px+24px)] max-w-[calc(20px+24px+24px)]":
isSelectHeader,
"w-[calc(28px+24px+4px)] min-w-[calc(28px+24px+4px)] max-w-[calc(28px+24px+4px)]":
isActionHeader,
"after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isFirstColumn,
"after:bg-ui-border-base":
showStickyBorder && isFirstColumn,
"bg-ui-bg-subtle sticky":
isFirstColumn || isSelectHeader,
"left-0":
isSelectHeader || (isFirstColumn && !hasSelect),
"left-[calc(20px+24px+24px)]":
isFirstColumn && hasSelect,
// Get header alignment from column metadata
const headerAlign = (header.column.columnDef.meta as any)?.___alignMetaData?.headerAlign || 'left'
const isRightAligned = headerAlign === 'right'
const isCenterAligned = headerAlign === 'center'
const HeaderCellComponent = isDraggable ? DataTableSortableHeaderCell : DataTableNonSortableHeaderCell
return (
<HeaderCellComponent
key={header.id}
id={header.id}
isFirstColumn={isFirstColumn}
className={clx("whitespace-nowrap", {
"w-[calc(20px+24px+24px)] min-w-[calc(20px+24px+24px)] max-w-[calc(20px+24px+24px)]":
isSelectHeader,
"w-[calc(28px+24px+4px)] min-w-[calc(28px+24px+4px)] max-w-[calc(28px+24px+4px)]":
isActionHeader,
"after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isFirstColumn,
"after:bg-ui-border-base":
showStickyBorder && isFirstColumn,
"bg-ui-bg-subtle sticky":
isFirstColumn || isSelectHeader,
"left-0":
isSelectHeader || (isFirstColumn && !hasSelect),
"left-[calc(20px+24px+24px)]":
isFirstColumn && hasSelect,
})}
style={
!isSpecialHeader
? {
width: header.column.columnDef.size,
maxWidth: header.column.columnDef.maxSize,
minWidth: header.column.columnDef.minSize,
}
: undefined
}
>
<Wrapper
type={canSort ? "button" : undefined}
onClick={canSort ? sortHandler : undefined}
onMouseDown={canSort ? (e) => e.stopPropagation() : undefined}
className={clx(
"group flex cursor-default items-center gap-2",
{
"cursor-pointer": canSort,
"w-full": isRightAligned || isCenterAligned,
"w-fit": !isRightAligned && !isCenterAligned,
"justify-end": isRightAligned,
"justify-center": isCenterAligned,
}
)}
>
{canSort && isRightAligned && (
<DataTableSortingIcon direction={sortDirection} />
)}
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{canSort && !isRightAligned && (
<DataTableSortingIcon direction={sortDirection} />
)}
</Wrapper>
</HeaderCellComponent>
)
})}
style={
!isSpecialHeader
? {
</SortableContext>
</Table.Row>
))}
</Table.Header>
<Table.Body className="border-b-0 border-t-0">
{instance.getRowModel().rows.map((row) => {
return (
<Table.Row
key={row.id}
onMouseEnter={() => (hoveredRowId.current = row.id)}
onMouseLeave={() => (hoveredRowId.current = null)}
onClick={(e) => instance.onRowClick?.(e, row)}
className={clx("group/row last-of-type:border-b-0", {
"cursor-pointer": !!instance.onRowClick,
})}
>
{row.getVisibleCells().map((cell, idx) => {
const isSelectCell = cell.column.id === "select"
const isActionCell = cell.column.id === "action"
const isSpecialCell = isSelectCell || isActionCell
const isFirstColumn = hasSelect ? idx === 1 : idx === 0
return (
<Table.Cell
key={cell.id}
className={clx(
"items-stretch truncate whitespace-nowrap",
{
"w-[calc(20px+24px+24px)] min-w-[calc(20px+24px+24px)] max-w-[calc(20px+24px+24px)]":
isSelectCell,
"w-[calc(28px+24px+4px)] min-w-[calc(28px+24px+4px)] max-w-[calc(28px+24px+4px)]":
isActionCell,
"bg-ui-bg-base group-hover/row:bg-ui-bg-base-hover transition-fg sticky h-full":
isFirstColumn || isSelectCell,
"after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isFirstColumn,
"after:bg-ui-border-base":
showStickyBorder && isFirstColumn,
"left-0":
isSelectCell || (isFirstColumn && !hasSelect),
"left-[calc(20px+24px+24px)]":
isFirstColumn && hasSelect,
}
)}
style={
!isSpecialCell
? {
width: cell.column.columnDef.size,
maxWidth: cell.column.columnDef.maxSize,
minWidth: cell.column.columnDef.minSize,
}
: undefined
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Table.Cell>
)
})}
</Table.Row>
)
})}
</Table.Body>
</Table>
</div>
</DndContext>
) : (
<div
ref={scrollableRef}
onScroll={handleHorizontalScroll}
className="min-h-0 w-full flex-1 overflow-auto overscroll-none border-y"
>
<Table className="relative isolate w-full">
<Table.Header
className="shadow-ui-border-base sticky inset-x-0 top-0 z-[1] w-full border-b-0 border-t-0 shadow-[0_1px_1px_0]"
style={{ transform: "translate3d(0,0,0)" }}
>
{instance.getHeaderGroups().map((headerGroup) => (
<Table.Row
key={headerGroup.id}
className={clx("border-b-0", {
"[&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap":
hasActions,
"[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap":
hasSelect,
})}
>
{headerGroup.headers.map((header, idx) => {
const canSort = header.column.getCanSort()
const sortDirection = header.column.getIsSorted()
const sortHandler = header.column.getToggleSortingHandler()
const isActionHeader = header.id === "action"
const isSelectHeader = header.id === "select"
const isSpecialHeader = isActionHeader || isSelectHeader
const Wrapper = canSort ? "button" : "div"
const isFirstColumn = hasSelect ? idx === 1 : idx === 0
// Get header alignment from column metadata
const headerAlign = (header.column.columnDef.meta as any)?.___alignMetaData?.headerAlign || 'left'
const isRightAligned = headerAlign === 'right'
const isCenterAligned = headerAlign === 'center'
return (
<Table.HeaderCell
key={header.id}
className={clx("whitespace-nowrap", {
"w-[calc(20px+24px+24px)] min-w-[calc(20px+24px+24px)] max-w-[calc(20px+24px+24px)]":
isSelectHeader,
"w-[calc(28px+24px+4px)] min-w-[calc(28px+24px+4px)] max-w-[calc(28px+24px+4px)]":
isActionHeader,
"after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isFirstColumn,
"after:bg-ui-border-base":
showStickyBorder && isFirstColumn,
"bg-ui-bg-subtle sticky":
isFirstColumn || isSelectHeader,
"left-0":
isSelectHeader || (isFirstColumn && !hasSelect),
"left-[calc(20px+24px+24px)]":
isFirstColumn && hasSelect,
})}
style={
!isSpecialHeader
? {
width: header.column.columnDef.size,
maxWidth: header.column.columnDef.maxSize,
minWidth: header.column.columnDef.minSize,
}
: undefined
}
>
<Wrapper
type={canSort ? "button" : undefined}
onClick={canSort ? sortHandler : undefined}
className={clx(
"group flex w-fit cursor-default items-center gap-2",
{
"cursor-pointer": canSort,
}
)}
: undefined
}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{canSort && (
<DataTableSortingIcon direction={sortDirection} />
)}
</Wrapper>
</Table.HeaderCell>
)
})}
</Table.Row>
))}
</Table.Header>
<Table.Body className="border-b-0 border-t-0">
{instance.getRowModel().rows.map((row) => {
return (
<Table.Row
key={row.id}
onMouseEnter={() => (hoveredRowId.current = row.id)}
onMouseLeave={() => (hoveredRowId.current = null)}
onClick={(e) => instance.onRowClick?.(e, row)}
className={clx("group/row last-of-type:border-b-0", {
"cursor-pointer": !!instance.onRowClick,
<Wrapper
type={canSort ? "button" : undefined}
onClick={canSort ? sortHandler : undefined}
onMouseDown={canSort ? (e) => e.stopPropagation() : undefined}
className={clx(
"group flex cursor-default items-center gap-2",
{
"cursor-pointer": canSort,
"w-full": isRightAligned || isCenterAligned,
"w-fit": !isRightAligned && !isCenterAligned,
"justify-end": isRightAligned,
"justify-center": isCenterAligned,
}
)}
>
{canSort && isRightAligned && (
<DataTableSortingIcon direction={sortDirection} />
)}
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{canSort && !isRightAligned && (
<DataTableSortingIcon direction={sortDirection} />
)}
</Wrapper>
</Table.HeaderCell>
)
})}
>
{row.getVisibleCells().map((cell, idx) => {
const isSelectCell = cell.column.id === "select"
const isActionCell = cell.column.id === "action"
const isSpecialCell = isSelectCell || isActionCell
</Table.Row>
))}
</Table.Header>
<Table.Body className="border-b-0 border-t-0">
{instance.getRowModel().rows.map((row) => {
return (
<Table.Row
key={row.id}
onMouseEnter={() => (hoveredRowId.current = row.id)}
onMouseLeave={() => (hoveredRowId.current = null)}
onClick={(e) => instance.onRowClick?.(e, row)}
className={clx("group/row last-of-type:border-b-0", {
"cursor-pointer": !!instance.onRowClick,
})}
>
{row.getVisibleCells().map((cell, idx) => {
const isSelectCell = cell.column.id === "select"
const isActionCell = cell.column.id === "action"
const isSpecialCell = isSelectCell || isActionCell
const isFirstColumn = hasSelect ? idx === 1 : idx === 0
const isFirstColumn = hasSelect ? idx === 1 : idx === 0
return (
<Table.Cell
key={cell.id}
className={clx(
"items-stretch truncate whitespace-nowrap",
{
"w-[calc(20px+24px+24px)] min-w-[calc(20px+24px+24px)] max-w-[calc(20px+24px+24px)]":
isSelectCell,
"w-[calc(28px+24px+4px)] min-w-[calc(28px+24px+4px)] max-w-[calc(28px+24px+4px)]":
isActionCell,
"bg-ui-bg-base group-hover/row:bg-ui-bg-base-hover transition-fg sticky h-full":
isFirstColumn || isSelectCell,
"after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isFirstColumn,
"after:bg-ui-border-base":
showStickyBorder && isFirstColumn,
"left-0":
isSelectCell || (isFirstColumn && !hasSelect),
"left-[calc(20px+24px+24px)]":
isFirstColumn && hasSelect,
}
)}
style={
!isSpecialCell
? {
return (
<Table.Cell
key={cell.id}
className={clx(
"items-stretch truncate whitespace-nowrap",
{
"w-[calc(20px+24px+24px)] min-w-[calc(20px+24px+24px)] max-w-[calc(20px+24px+24px)]":
isSelectCell,
"w-[calc(28px+24px+4px)] min-w-[calc(28px+24px+4px)] max-w-[calc(28px+24px+4px)]":
isActionCell,
"bg-ui-bg-base group-hover/row:bg-ui-bg-base-hover transition-fg sticky h-full":
isFirstColumn || isSelectCell,
"after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isFirstColumn,
"after:bg-ui-border-base":
showStickyBorder && isFirstColumn,
"left-0":
isSelectCell || (isFirstColumn && !hasSelect),
"left-[calc(20px+24px+24px)]":
isFirstColumn && hasSelect,
}
)}
style={
!isSpecialCell
? {
width: cell.column.columnDef.size,
maxWidth: cell.column.columnDef.maxSize,
minWidth: cell.column.columnDef.minSize,
}
: undefined
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Table.Cell>
)
})}
</Table.Row>
)
})}
</Table.Body>
</Table>
</div>
: undefined
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Table.Cell>
)
})}
</Table.Row>
)
})}
</Table.Body>
</Table>
</div>
)
)}
<DataTableEmptyStateDisplay
state={instance.emptyState}

View File

@@ -1,4 +1,5 @@
import { DataTableFilterBar } from "@/blocks/data-table/components/data-table-filter-bar"
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
import { clx } from "@/utils/clx"
import * as React from "react"
@@ -8,6 +9,14 @@ interface DataTableToolbarTranslations {
* @default "Clear all"
*/
clearAll?: string
/**
* The tooltip for the sorting menu
*/
sort?: string
/**
* The tooltip for the columns menu
*/
columns?: string
}
interface DataTableToolbarProps {
@@ -23,12 +32,19 @@ interface DataTableToolbarProps {
* The translations of strings in the toolbar.
*/
translations?: DataTableToolbarTranslations
/**
* Custom content to render in the filter bar
*/
filterBarContent?: React.ReactNode
}
/**
* Toolbar shown for the data table.
*/
const DataTableToolbar = (props: DataTableToolbarProps) => {
const { instance } = useDataTableContext()
const hasFilters = instance.getFilters().length > 0
return (
<div className="flex flex-col divide-y">
<div className={clx("flex items-center px-6 py-4", props.className)}>
@@ -36,7 +52,12 @@ const DataTableToolbar = (props: DataTableToolbarProps) => {
</div>
<DataTableFilterBar
clearAllFiltersLabel={props.translations?.clearAll}
/>
alwaysShow={hasFilters}
sortingTooltip={props.translations?.sort}
columnsTooltip={props.translations?.columns}
>
{props.filterBarContent}
</DataTableFilterBar>
</div>
)
}

View File

@@ -15,7 +15,13 @@ const DataTableContextProvider = <TData,>({
children,
}: DataTableContextProviderProps<TData>) => {
return (
<DataTableContext.Provider value={{ instance }}>
<DataTableContext.Provider
value={{
instance,
enableColumnVisibility: instance.enableColumnVisibility,
enableColumnOrder: instance.enableColumnOrder
}}
>
{children}
</DataTableContext.Provider>
)

View File

@@ -3,6 +3,8 @@ import { UseDataTableReturn } from "../use-data-table"
export interface DataTableContextValue<TData> {
instance: UseDataTableReturn<TData>
enableColumnVisibility: boolean
enableColumnOrder: boolean
}
export const DataTableContext =

View File

@@ -248,24 +248,24 @@ const columns = [
[
{
label: "Edit",
onClick: () => {},
onClick: () => { },
icon: <PencilSquare />,
},
{
label: "Edit",
onClick: () => {},
onClick: () => { },
icon: <PencilSquare />,
},
{
label: "Edit",
onClick: () => {},
onClick: () => { },
icon: <PencilSquare />,
},
],
[
{
label: "Delete",
onClick: () => {},
onClick: () => { },
icon: <Trash />,
},
],
@@ -385,6 +385,13 @@ const KitchenSinkDemo = () => {
},
})
const handleFilteringChange = (
state: DataTableFilteringState,
) => {
console.log("Filtering changed:", state)
setFiltering(state)
}
const [pagination, setPagination] = React.useState<DataTablePaginationState>({
pageIndex: 0,
pageSize: 10,
@@ -414,7 +421,7 @@ const KitchenSinkDemo = () => {
},
filtering: {
state: filtering,
onFilteringChange: setFiltering,
onFilteringChange: handleFilteringChange,
},
rowSelection: {
state: rowSelection,

View File

@@ -5,6 +5,8 @@ import * as React from "react"
import { clx } from "@/utils/clx"
import { DataTableCommandBar } from "./components/data-table-command-bar"
import { DataTableColumnVisibilityMenu } from "./components/data-table-column-visibility-menu"
import { DataTableFilterBar } from "./components/data-table-filter-bar"
import { DataTableFilterMenu } from "./components/data-table-filter-menu"
import { DataTablePagination } from "./components/data-table-pagination"
import { DataTableSearch } from "./components/data-table-search"
@@ -58,6 +60,8 @@ const DataTable = Object.assign(Root, {
Search: DataTableSearch,
SortingMenu: DataTableSortingMenu,
FilterMenu: DataTableFilterMenu,
FilterBar: DataTableFilterBar,
ColumnVisibilityMenu: DataTableColumnVisibilityMenu,
Pagination: DataTablePagination,
CommandBar: DataTableCommandBar,
})

View File

@@ -12,6 +12,7 @@ export type {
DataTableColumnFilter,
DataTableCommand,
DataTableDateComparisonOperator,
DataTableNumberComparisonOperator,
DataTableEmptyState,
DataTableEmptyStateContent,
DataTableEmptyStateProps,
@@ -25,3 +26,6 @@ export type {
DataTableSortDirection,
DataTableSortingState,
} from "./types"
// Re-export types from @tanstack/react-table that are used in the public API
export type { VisibilityState, ColumnOrderState } from "@tanstack/react-table"

View File

@@ -73,10 +73,24 @@ export type DataTableSortableColumnDef = {
enableSorting?: boolean
}
export type DataTableHeaderAlignment = 'left' | 'center' | 'right'
export type DataTableAlignableColumnDef = {
/**
* The alignment of the header content.
* @default 'left'
*/
headerAlign?: DataTableHeaderAlignment
}
export type DataTableSortableColumnDefMeta = {
___sortMetaData?: DataTableSortableColumnDef
}
export type DataTableAlignableColumnDefMeta = {
___alignMetaData?: DataTableAlignableColumnDef
}
export type DataTableActionColumnDefMeta<TData> = {
___actions?:
| DataTableAction<TData>[]
@@ -151,8 +165,8 @@ export interface DataTableColumnHelper<TData> {
>(
accessor: TAccessor,
column: TAccessor extends AccessorFn<TData>
? DataTableDisplayColumnDef<TData, TValue> & DataTableSortableColumnDef
: DataTableIdentifiedColumnDef<TData, TValue> & DataTableSortableColumnDef
? DataTableDisplayColumnDef<TData, TValue> & DataTableSortableColumnDef & DataTableAlignableColumnDef
: DataTableIdentifiedColumnDef<TData, TValue> & DataTableSortableColumnDef & DataTableAlignableColumnDef
) => TAccessor extends AccessorFn<TData>
? AccessorFnColumnDef<TData, TValue>
: AccessorKeyColumnDef<TData, TValue>
@@ -192,7 +206,7 @@ export type DataTableFilteringState<
[K in keyof T]: T[K]
}
export type DataTableFilterType = "radio" | "select" | "date"
export type DataTableFilterType = "radio" | "select" | "date" | "multiselect" | "string" | "number" | "custom"
export type DataTableFilterOption<T = string> = {
label: string
value: T
@@ -259,10 +273,57 @@ export interface DataTableDateFilterProps extends DataTableBaseFilterProps {
options: DataTableFilterOption<DataTableDateComparisonOperator>[]
}
export interface DataTableMultiselectFilterProps extends DataTableBaseFilterProps {
type: "multiselect"
options: DataTableFilterOption[]
/**
* Whether to show a search input for the options.
* @default true
*/
searchable?: boolean
}
export interface DataTableStringFilterProps extends DataTableBaseFilterProps {
type: "string"
/**
* Placeholder text for the input.
*/
placeholder?: string
}
export interface DataTableNumberFilterProps extends DataTableBaseFilterProps {
type: "number"
/**
* Placeholder text for the input.
*/
placeholder?: string
/**
* Whether to include comparison operators.
* @default true
*/
includeOperators?: boolean
}
export interface DataTableCustomFilterProps extends DataTableBaseFilterProps {
type: "custom"
/**
* Custom render function for the filter.
*/
render: (props: {
value: any
onChange: (value: any) => void
onRemove: () => void
}) => React.ReactNode
}
export type DataTableFilterProps =
| DataTableRadioFilterProps
| DataTableSelectFilterProps
| DataTableDateFilterProps
| DataTableMultiselectFilterProps
| DataTableStringFilterProps
| DataTableNumberFilterProps
| DataTableCustomFilterProps
export type DataTableFilter<
T extends DataTableFilterProps = DataTableFilterProps
@@ -295,6 +356,29 @@ export type DataTableDateComparisonOperator = {
$gt?: string
}
export type DataTableNumberComparisonOperator = {
/**
* The filtered number must be greater than or equal to this value.
*/
$gte?: number
/**
* The filtered number must be less than or equal to this value.
*/
$lte?: number
/**
* The filtered number must be less than this value.
*/
$lt?: number
/**
* The filtered number must be greater than this value.
*/
$gt?: number
/**
* The filtered number must be equal to this value.
*/
$eq?: number
}
type DataTableCommandAction = (
selection: DataTableRowSelectionState
) => void | Promise<void>

View File

@@ -2,6 +2,7 @@ import {
ColumnFilter,
ColumnFiltersState,
type ColumnSort,
type ColumnOrderState,
getCoreRowModel,
PaginationState,
type RowSelectionState,
@@ -9,6 +10,7 @@ import {
type TableOptions,
type Updater,
useReactTable,
type VisibilityState,
} from "@tanstack/react-table"
import * as React from "react"
import {
@@ -106,6 +108,20 @@ interface DataTableOptions<TData>
* @default true
*/
autoResetPageIndex?: boolean
/**
* The state and callback for the column visibility.
*/
columnVisibility?: {
state: VisibilityState
onColumnVisibilityChange: (state: VisibilityState) => void
}
/**
* The state and callback for the column order.
*/
columnOrder?: {
state: ColumnOrderState
onColumnOrderChange: (state: ColumnOrderState) => void
}
}
interface UseDataTableReturn<TData>
@@ -119,6 +135,8 @@ interface UseDataTableReturn<TData>
| "previousPage"
| "getPageCount"
| "getAllColumns"
| "setColumnVisibility"
| "setColumnOrder"
> {
getSorting: () => DataTableSortingState | null
setSorting: (
@@ -156,6 +174,10 @@ interface UseDataTableReturn<TData>
enableFiltering: boolean
enableSorting: boolean
enableSearch: boolean
enableColumnVisibility: boolean
enableColumnOrder: boolean
columnOrder: ColumnOrderState
setColumnOrderFromArray: (order: string[]) => void
}
const useDataTable = <TData,>({
@@ -170,6 +192,8 @@ const useDataTable = <TData,>({
onRowClick,
autoResetPageIndex = true,
isLoading = false,
columnVisibility,
columnOrder,
...options
}: DataTableOptions<TData>): UseDataTableReturn<TData> => {
const { state: sortingState, onSortingChange } = sorting ?? {}
@@ -180,6 +204,10 @@ const useDataTable = <TData,>({
onRowSelectionChange,
enableRowSelection,
} = rowSelection ?? {}
const { state: columnVisibilityState, onColumnVisibilityChange } = columnVisibility ?? {}
const { state: columnOrderState, onColumnOrderChange } = columnOrder ?? {}
// Store filter metadata like openOnMount
const autoResetPageIndexHandler = React.useCallback(() => {
return autoResetPageIndex
@@ -230,6 +258,32 @@ const useDataTable = <TData,>({
: undefined
}, [onPaginationChange, paginationState])
const columnVisibilityStateHandler = React.useCallback(() => {
return onColumnVisibilityChange
? (updaterOrValue: Updater<VisibilityState>) => {
const value =
typeof updaterOrValue === "function"
? updaterOrValue(columnVisibilityState ?? {})
: updaterOrValue
onColumnVisibilityChange(value)
}
: undefined
}, [onColumnVisibilityChange, columnVisibilityState])
const columnOrderStateHandler = React.useCallback(() => {
return onColumnOrderChange
? (updaterOrValue: Updater<ColumnOrderState>) => {
const value =
typeof updaterOrValue === "function"
? updaterOrValue(columnOrderState ?? [])
: updaterOrValue
onColumnOrderChange(value)
}
: undefined
}, [onColumnOrderChange, columnOrderState])
const instance = useReactTable({
...options,
getCoreRowModel: getCoreRowModel(),
@@ -243,6 +297,8 @@ const useDataTable = <TData,>({
})
),
pagination: paginationState,
columnVisibility: columnVisibilityState ?? {},
columnOrder: columnOrderState ?? [],
},
enableRowSelection,
rowCount,
@@ -250,6 +306,8 @@ const useDataTable = <TData,>({
onRowSelectionChange: rowSelectionStateHandler(),
onSortingChange: sortingStateHandler(),
onPaginationChange: paginationStateHandler(),
onColumnVisibilityChange: columnVisibilityStateHandler(),
onColumnOrderChange: columnOrderStateHandler(),
manualSorting: true,
manualPagination: true,
manualFiltering: true,
@@ -289,7 +347,7 @@ const useDataTable = <TData,>({
return null
}
return filter.options as DataTableFilterOption<T>[]
return ((filter as any).options as DataTableFilterOption<T>[]) || null
},
[getFilters]
)
@@ -307,11 +365,11 @@ const useDataTable = <TData,>({
}, [instance])
const addFilter = React.useCallback(
(filter: ColumnFilter) => {
if (filter.value) {
autoResetPageIndexHandler()?.()
}
onFilteringChange?.({ ...getFiltering(), [filter.id]: filter.value })
(filter: DataTableColumnFilter) => {
const currentFilters = getFiltering()
const newFilters = { ...currentFilters, [filter.id]: filter.value }
autoResetPageIndexHandler()?.()
onFilteringChange?.(newFilters)
},
[onFilteringChange, getFiltering, autoResetPageIndexHandler]
)
@@ -424,12 +482,20 @@ const useDataTable = <TData,>({
const enableFiltering: boolean = !!filtering
const enableSorting: boolean = !!sorting
const enableSearch: boolean = !!search
const enableColumnVisibility: boolean = !!columnVisibility
const enableColumnOrder: boolean = !!columnOrder
const setColumnOrderFromArray = React.useCallback((order: string[]) => {
instance.setColumnOrder(order)
}, [instance])
return {
// Table
getHeaderGroups: instance.getHeaderGroups,
getRowModel: instance.getRowModel,
getAllColumns: instance.getAllColumns,
setColumnVisibility: instance.setColumnVisibility,
setColumnOrder: instance.setColumnOrder,
// Pagination
enablePagination,
getCanNextPage: instance.getCanNextPage,
@@ -468,6 +534,12 @@ const useDataTable = <TData,>({
// Loading
isLoading,
showSkeleton,
// Column Visibility
enableColumnVisibility,
// Column Order
enableColumnOrder,
columnOrder: instance.getState().columnOrder,
setColumnOrderFromArray,
}
}
@@ -516,7 +588,7 @@ function onFilteringChangeTransformer(
: updaterOrValue
const transformedValue = Object.fromEntries(
value.map((filter) => [filter.id, filter])
value.map((filter) => [filter.id, filter.value])
)
onFilteringChange(transformedValue)

View File

@@ -13,6 +13,8 @@ import {
DataTableSelectColumnDef,
DataTableSortableColumnDef,
DataTableSortableColumnDefMeta,
DataTableAlignableColumnDef,
DataTableAlignableColumnDefMeta,
} from "../types"
const createDataTableColumnHelper = <
@@ -27,13 +29,15 @@ const createDataTableColumnHelper = <
sortLabel,
sortAscLabel,
sortDescLabel,
headerAlign,
meta,
enableSorting,
...rest
} = column as any & DataTableSortableColumnDef
} = column as any & DataTableSortableColumnDef & DataTableAlignableColumnDef
const extendedMeta: DataTableSortableColumnDefMeta = {
const extendedMeta: DataTableSortableColumnDefMeta & DataTableAlignableColumnDefMeta = {
___sortMetaData: { sortLabel, sortAscLabel, sortDescLabel },
___alignMetaData: { headerAlign },
...(meta || {}),
}

View File

@@ -3753,6 +3753,31 @@ __metadata:
languageName: node
linkType: hard
"@dnd-kit/accessibility@npm:^3.1.1":
version: 3.1.1
resolution: "@dnd-kit/accessibility@npm:3.1.1"
dependencies:
tslib: ^2.0.0
peerDependencies:
react: ">=16.8.0"
checksum: be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8
languageName: node
linkType: hard
"@dnd-kit/core@npm:^6.0.0":
version: 6.3.1
resolution: "@dnd-kit/core@npm:6.3.1"
dependencies:
"@dnd-kit/accessibility": ^3.1.1
"@dnd-kit/utilities": ^3.2.2
tslib: ^2.0.0
peerDependencies:
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52
languageName: node
linkType: hard
"@dnd-kit/core@npm:^6.1.0":
version: 6.1.0
resolution: "@dnd-kit/core@npm:6.1.0"
@@ -3767,6 +3792,19 @@ __metadata:
languageName: node
linkType: hard
"@dnd-kit/sortable@npm:^7.0.0":
version: 7.0.2
resolution: "@dnd-kit/sortable@npm:7.0.2"
dependencies:
"@dnd-kit/utilities": ^3.2.0
tslib: ^2.0.0
peerDependencies:
"@dnd-kit/core": ^6.0.7
react: ">=16.8.0"
checksum: 06aeb113eeeb470bb2443bf1c48d597157bb3a1caa9740e60c2fa73a3076e753cd083a2d381f0556bd7e9873e851a49ce8ea14796ac02e2d796eabea4e27196d
languageName: node
linkType: hard
"@dnd-kit/sortable@npm:^8.0.0":
version: 8.0.0
resolution: "@dnd-kit/sortable@npm:8.0.0"
@@ -3780,7 +3818,7 @@ __metadata:
languageName: node
linkType: hard
"@dnd-kit/utilities@npm:^3.2.2":
"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.2":
version: 3.2.2
resolution: "@dnd-kit/utilities@npm:3.2.2"
dependencies:
@@ -7533,6 +7571,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "@medusajs/ui@workspace:packages/design-system/ui"
dependencies:
"@dnd-kit/core": ^6.0.0
"@dnd-kit/sortable": ^7.0.0
"@dnd-kit/utilities": ^3.2.0
"@faker-js/faker": ^9.2.0
"@medusajs/icons": 2.10.1
"@medusajs/ui-preset": 2.10.1