@nexub/react 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "6.3.0",
2
+ "version": "6.5.0",
3
3
  "standard": "WCAG 2.2 Level AA",
4
4
  "method": {
5
5
  "automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark, and on open popups (menus, listboxes, dialogs), on every pull request; a new violation fails the build.",
@@ -42,6 +42,15 @@
42
42
  "keyboard": [],
43
43
  "screenReader": []
44
44
  },
45
+ {
46
+ "slug": "action-bar",
47
+ "name": "Action Bar",
48
+ "package": "@nexub/react",
49
+ "automated": "pass",
50
+ "exceptions": [],
51
+ "keyboard": [],
52
+ "screenReader": []
53
+ },
45
54
  {
46
55
  "slug": "alert-dialog",
47
56
  "name": "Alert Dialog",
@@ -613,6 +622,18 @@
613
622
  "Visible focus ring on `:focus-visible`. Pair with a `<Label htmlFor>` so clicking the label moves focus into the field."
614
623
  ]
615
624
  },
625
+ {
626
+ "slug": "kanban",
627
+ "name": "Kanban",
628
+ "package": "@nexub/react",
629
+ "automated": "pass",
630
+ "exceptions": [],
631
+ "keyboard": [
632
+ "Alt+ArrowUp / Alt+ArrowDown: On a handle: moves the card within its column.",
633
+ "Alt+ArrowLeft / Alt+ArrowRight: Moves the card to the previous or next column. Mirrored in RTL."
634
+ ],
635
+ "screenReader": []
636
+ },
616
637
  {
617
638
  "slug": "kbd",
618
639
  "name": "Kbd",
@@ -925,6 +946,19 @@
925
946
  "keyboard": [],
926
947
  "screenReader": []
927
948
  },
949
+ {
950
+ "slug": "resizable-panels",
951
+ "name": "Resizable Panels",
952
+ "package": "@nexub/react",
953
+ "automated": "pass",
954
+ "exceptions": [],
955
+ "keyboard": [
956
+ "Arrow keys: Move the focused separator by 5%. Mirrored in RTL.",
957
+ "Home / End: To the smallest or largest size.",
958
+ "Enter: Back to the default split."
959
+ ],
960
+ "screenReader": []
961
+ },
928
962
  {
929
963
  "slug": "saved-views",
930
964
  "name": "Saved Views",
@@ -1028,6 +1062,17 @@
1028
1062
  "keyboard": [],
1029
1063
  "screenReader": []
1030
1064
  },
1065
+ {
1066
+ "slug": "sortable",
1067
+ "name": "Sortable",
1068
+ "package": "@nexub/react",
1069
+ "automated": "pass",
1070
+ "exceptions": [],
1071
+ "keyboard": [
1072
+ "Alt+ArrowUp / Alt+ArrowDown: On a handle: moves the item; focus stays on its handle."
1073
+ ],
1074
+ "screenReader": []
1075
+ },
1031
1076
  {
1032
1077
  "slug": "sparkline",
1033
1078
  "name": "Sparkline",
@@ -1067,6 +1112,15 @@
1067
1112
  "keyboard": [],
1068
1113
  "screenReader": []
1069
1114
  },
1115
+ {
1116
+ "slug": "stepper",
1117
+ "name": "Stepper",
1118
+ "package": "@nexub/react",
1119
+ "automated": "pass",
1120
+ "exceptions": [],
1121
+ "keyboard": [],
1122
+ "screenReader": []
1123
+ },
1070
1124
  {
1071
1125
  "slug": "streaming-text",
1072
1126
  "name": "Streaming Text",
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ export interface ActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /** Status on the start side, e.g. "Unsaved changes". Announced politely. */
4
+ message?: React.ReactNode;
5
+ /** Names the region. @default the message when it's a string */
6
+ 'aria-label'?: string;
7
+ }
8
+ /**
9
+ * Save / cancel that stay in view at the bottom of a long form or settings
10
+ * page. Put it last inside the scrolling area.
11
+ */
12
+ export declare const ActionBar: React.ForwardRefExoticComponent<ActionBarProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,27 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../utils/cn.js";
4
+ import { floatingSurface } from "../../utils/surface.js";
5
+ const ActionBar = React.forwardRef(function ActionBar2({ message, className, children, "aria-label": ariaLabel, ...props }, ref) {
6
+ return /* @__PURE__ */ jsxs(
7
+ "div",
8
+ {
9
+ ref,
10
+ role: "region",
11
+ "aria-label": ariaLabel ?? (typeof message === "string" ? message : void 0),
12
+ className: cn(
13
+ floatingSurface,
14
+ "sticky bottom-4 z-10 flex flex-wrap items-center gap-3 px-4 py-2",
15
+ className
16
+ ),
17
+ ...props,
18
+ children: [
19
+ message !== void 0 && /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "me-auto text-label-lg text-[var(--color-on-surface)]", children: message }),
20
+ /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-2", message === void 0 && "ms-auto"), children })
21
+ ]
22
+ }
23
+ );
24
+ });
25
+ export {
26
+ ActionBar
27
+ };
@@ -0,0 +1,2 @@
1
+ export { ActionBar } from './ActionBar.js';
2
+ export type { ActionBarProps } from './ActionBar.js';
@@ -0,0 +1,4 @@
1
+ import { ActionBar } from "./ActionBar.js";
2
+ export {
3
+ ActionBar
4
+ };
@@ -84,5 +84,12 @@ export interface DataTableProps<TData> extends React.HTMLAttributes<HTMLDivEleme
84
84
  expanded?: ExpandedState;
85
85
  defaultExpanded?: ExpandedState;
86
86
  onExpandedChange?: (expanded: ExpandedState) => void;
87
+ /**
88
+ * Lets people reorder rows: a handle column (drag, or Alt+ArrowUp/Down on the
89
+ * handle). Called with the reordered data. Hidden while a column is sorted.
90
+ */
91
+ onRowOrderChange?: (data: TData[]) => void;
92
+ /** Names a row in "Move ‹label›" and the move announcements. @default its position */
93
+ getRowLabel?: (row: TData) => string;
87
94
  }
88
- export declare function DataTable<TData>({ columns, data, enableSorting, enableRowSelection, selectionColumn, rowSelection: rowSelectionProp, onRowSelectionChange, getRowId, bulkActions, density: densityProp, defaultDensity, onDensityChange, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, viewOptions, toolbar, enableColumnResizing, columnSizing: columnSizingProp, onColumnSizingChange, enableColumnReordering, columnOrder: columnOrderProp, onColumnOrderChange, columnPinning: columnPinningProp, onColumnPinningChange, maxHeight, grid, rowWindow, pagination, getSubRows, expanded: expandedProp, defaultExpanded, onExpandedChange, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
95
+ export declare function DataTable<TData>({ columns, data, enableSorting, enableRowSelection, selectionColumn, rowSelection: rowSelectionProp, onRowSelectionChange, getRowId, bulkActions, density: densityProp, defaultDensity, onDensityChange, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, viewOptions, toolbar, enableColumnResizing, columnSizing: columnSizingProp, onColumnSizingChange, enableColumnReordering, columnOrder: columnOrderProp, onColumnOrderChange, columnPinning: columnPinningProp, onColumnPinningChange, maxHeight, grid, rowWindow, pagination, getSubRows, expanded: expandedProp, defaultExpanded, onExpandedChange, onRowOrderChange, getRowLabel, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
@@ -22,6 +22,7 @@ import { Checkbox } from "../Checkbox/Checkbox.js";
22
22
  import { Button } from "../Button/Button.js";
23
23
  import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
24
24
  import { createGridNavigation } from "./gridNavigation.js";
25
+ import { useReorder } from "../Sortable/reorder.js";
25
26
  const alignText = {
26
27
  start: "text-start",
27
28
  end: "text-end",
@@ -167,6 +168,8 @@ function DataTable({
167
168
  expanded: expandedProp,
168
169
  defaultExpanded,
169
170
  onExpandedChange,
171
+ onRowOrderChange,
172
+ getRowLabel,
170
173
  caption,
171
174
  captionHidden = false,
172
175
  className,
@@ -221,12 +224,34 @@ function DataTable({
221
224
  pageSize: pagination?.pageSize ?? 20
222
225
  });
223
226
  const withSelectColumn = enableRowSelection && selectionColumn;
227
+ const reorderRows = Boolean(onRowOrderChange) && sorting.length === 0;
228
+ const rowReorder = useReorder({
229
+ lists: [data],
230
+ getId: (r) => getRowId ? getRowId(r, data.indexOf(r)) : String(data.indexOf(r)),
231
+ getLabel: (r) => getRowLabel?.(r) ?? String(data.indexOf(r) + 1),
232
+ onMove: (next) => onRowOrderChange?.(next[0])
233
+ });
224
234
  const columnPinning = React.useMemo(
225
235
  () => withSelectColumn && pinningIn.left?.length && !pinningIn.left.includes("__select") ? { ...pinningIn, left: ["__select", ...pinningIn.left] } : pinningIn,
226
236
  [withSelectColumn, pinningIn]
227
237
  );
238
+ const withRowHandles = React.useCallback(
239
+ (cols) => reorderRows ? [
240
+ {
241
+ id: "__drag",
242
+ enableSorting: false,
243
+ enableHiding: false,
244
+ header: () => null,
245
+ cell: ({ row }) => row.depth === 0 ? rowReorder.handle([0, row.index]) : null
246
+ },
247
+ ...cols
248
+ ] : cols,
249
+ // rowReorder changes every render; the handle reads fresh data through it.
250
+ // eslint-disable-next-line react-hooks/exhaustive-deps
251
+ [reorderRows, data]
252
+ );
228
253
  const allColumns = React.useMemo(
229
- () => withSelectColumn ? [
254
+ () => withRowHandles(withSelectColumn ? [
230
255
  {
231
256
  id: "__select",
232
257
  enableSorting: false,
@@ -250,8 +275,8 @@ function DataTable({
250
275
  )
251
276
  },
252
277
  ...columns
253
- ] : columns,
254
- [withSelectColumn, columns, messages.selectAllOnPage, messages.selectRow]
278
+ ] : columns),
279
+ [withSelectColumn, columns, messages.selectAllOnPage, messages.selectRow, withRowHandles]
255
280
  );
256
281
  const tableState = React.useMemo(
257
282
  () => ({
@@ -396,6 +421,7 @@ function DataTable({
396
421
  /* @__PURE__ */ jsx(Button, { variant: "link", size: "sm", onClick: () => table.toggleAllRowsSelected(true), children: messages.selectAllRows(fmt(totalRows)) })
397
422
  ] }) }),
398
423
  /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: announcement }),
424
+ reorderRows && rowReorder.live,
399
425
  /* @__PURE__ */ jsxs(
400
426
  TableRoot,
401
427
  {
@@ -421,7 +447,7 @@ function DataTable({
421
447
  className: cn(
422
448
  alignText[align],
423
449
  compact && "h-8",
424
- col.id === "__select" && "w-10",
450
+ (col.id === "__select" || col.id === "__drag") && "w-10",
425
451
  // Anchors the resize handle; sticky (below) wins when set and anchors it too.
426
452
  (enableColumnResizing || reorderable) && "relative",
427
453
  maxHeight != null && "sticky top-0 z-[2] bg-[var(--color-surface)]",
@@ -513,6 +539,7 @@ function DataTable({
513
539
  "data-index": i,
514
540
  "aria-rowindex": rowWin.virtual ? i + 2 : void 0,
515
541
  "data-state": enableRowSelection && row.getIsSelected() ? "selected" : void 0,
542
+ ...reorderRows && row.depth === 0 ? rowReorder.itemProps([0, row.index]) : {},
516
543
  children: row.getVisibleCells().map((cell, _i, cells) => /* @__PURE__ */ jsx(
517
544
  TableCell,
518
545
  {
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ export interface KanbanColumn<T> {
3
+ id: string;
4
+ title: string;
5
+ items: T[];
6
+ }
7
+ export interface KanbanProps<T> {
8
+ columns: KanbanColumn<T>[];
9
+ getId: (item: T) => string;
10
+ getLabel: (item: T) => string;
11
+ /** Called with the columns after a move; store them to apply it. */
12
+ onChange: (columns: KanbanColumn<T>[]) => void;
13
+ renderCard: (item: T) => React.ReactNode;
14
+ className?: string;
15
+ }
16
+ /**
17
+ * Columns of cards people can move: drag a card, or focus its handle and press
18
+ * Alt+Arrow keys (up/down within a column, left/right across columns).
19
+ */
20
+ export declare function Kanban<T>({ columns, getId, getLabel, onChange, renderCard, className }: KanbanProps<T>): React.JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { useReorder } from "../Sortable/reorder.js";
6
+ function Kanban({ columns, getId, getLabel, onChange, renderCard, className }) {
7
+ const baseId = React.useId();
8
+ const { itemProps, listProps, handle, live } = useReorder({
9
+ lists: columns.map((c) => c.items),
10
+ getId,
11
+ getLabel,
12
+ listLabels: columns.map((c) => c.title),
13
+ onMove: (next) => onChange(columns.map((c, i) => ({ ...c, items: next[i] })))
14
+ });
15
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-4 overflow-x-auto pb-2", className), children: [
16
+ columns.map((column, c) => {
17
+ const headingId = `${baseId}-${c}`;
18
+ return /* @__PURE__ */ jsxs(
19
+ "section",
20
+ {
21
+ "aria-labelledby": headingId,
22
+ className: "flex w-72 shrink-0 flex-col gap-2 rounded-shape-container bg-[var(--color-surface-container-low)] p-3",
23
+ children: [
24
+ /* @__PURE__ */ jsxs("h3", { id: headingId, className: "flex items-center justify-between text-title-sm font-semibold text-[var(--color-on-surface)]", children: [
25
+ column.title,
26
+ /* @__PURE__ */ jsx("span", { className: "text-label-md font-normal tabular-nums text-[var(--color-on-surface-muted)]", children: column.items.length })
27
+ ] }),
28
+ /* @__PURE__ */ jsx("ul", { ...listProps(c), className: "m-0 flex min-h-16 list-none flex-col gap-2 p-0", children: column.items.map((item, i) => /* @__PURE__ */ jsxs(
29
+ "li",
30
+ {
31
+ ...itemProps([c, i]),
32
+ className: "flex items-start gap-1 rounded-shape-control bg-[var(--color-surface)] p-2 text-body-md shadow-elevation-1",
33
+ children: [
34
+ handle([c, i]),
35
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 pt-0.5", children: renderCard(item) })
36
+ ]
37
+ },
38
+ getId(item)
39
+ )) })
40
+ ]
41
+ },
42
+ column.id
43
+ );
44
+ }),
45
+ live
46
+ ] });
47
+ }
48
+ export {
49
+ Kanban
50
+ };
@@ -0,0 +1,2 @@
1
+ export { Kanban } from './Kanban.js';
2
+ export type { KanbanProps, KanbanColumn } from './Kanban.js';
@@ -0,0 +1,4 @@
1
+ import { Kanban } from "./Kanban.js";
2
+ export {
3
+ Kanban
4
+ };
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ export interface ResizablePanelsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
3
+ /** Side by side (`horizontal`) or stacked (`vertical`). @default 'horizontal' */
4
+ direction?: 'horizontal' | 'vertical';
5
+ /** One percentage per panel, adding up to 100. Persist them yourself. */
6
+ sizes?: number[];
7
+ defaultSizes?: number[];
8
+ onSizesChange?: (sizes: number[]) => void;
9
+ /** Smallest size per panel, in percent. @default 10 each */
10
+ minSizes?: number[];
11
+ /** Names each separator: "Resize panels" by default. */
12
+ handleLabel?: string;
13
+ /** The panels; a separator goes between each pair. */
14
+ children: React.ReactNode;
15
+ }
16
+ /**
17
+ * Panels with draggable separators (ARIA window splitter). Arrow keys move a
18
+ * focused separator by 5%, Home/End to its limits, and Enter resets the pair.
19
+ */
20
+ export declare function ResizablePanels({ direction, sizes: sizesProp, defaultSizes, onSizesChange, minSizes, handleLabel, className, children, ...props }: ResizablePanelsProps): React.JSX.Element;
@@ -0,0 +1,121 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { useControllableState } from "../../hooks/useControllableState.js";
6
+ import { useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
7
+ const STEP = 5;
8
+ function ResizablePanels({
9
+ direction = "horizontal",
10
+ sizes: sizesProp,
11
+ defaultSizes,
12
+ onSizesChange,
13
+ minSizes,
14
+ handleLabel,
15
+ className,
16
+ children,
17
+ ...props
18
+ }) {
19
+ const t = useNexubMessages();
20
+ const { dir } = useLocale();
21
+ const panels = React.Children.toArray(children);
22
+ const n = panels.length;
23
+ const even = Array.from({ length: n }, () => 100 / n);
24
+ const initial = defaultSizes ?? even;
25
+ const [sizes = even, setSizes] = useControllableState({
26
+ value: sizesProp,
27
+ defaultValue: initial,
28
+ onChange: onSizesChange
29
+ });
30
+ const rootRef = React.useRef(null);
31
+ const baseId = React.useId();
32
+ const horizontal = direction === "horizontal";
33
+ const min = (i) => minSizes?.[i] ?? 10;
34
+ const resize = (current, i, to) => {
35
+ const pair = current[i] + current[i + 1];
36
+ const a = Math.min(pair - min(i + 1), Math.max(min(i), to));
37
+ const next = [...current];
38
+ next[i] = Math.round(a * 100) / 100;
39
+ next[i + 1] = Math.round((pair - a) * 100) / 100;
40
+ setSizes(next);
41
+ };
42
+ const startDrag = (i, e) => {
43
+ const root = rootRef.current;
44
+ if (!root) return;
45
+ e.preventDefault();
46
+ const rect = root.getBoundingClientRect();
47
+ const total = horizontal ? rect.width : rect.height;
48
+ const origin = horizontal ? e.clientX : e.clientY;
49
+ const start = [...sizes];
50
+ const flip = horizontal && dir === "rtl" ? -1 : 1;
51
+ const onMove = (ev) => {
52
+ const pos = horizontal ? ev.clientX : ev.clientY;
53
+ resize(start, i, start[i] + (pos - origin) * flip / total * 100);
54
+ };
55
+ const onUp = () => {
56
+ window.removeEventListener("pointermove", onMove);
57
+ window.removeEventListener("pointerup", onUp);
58
+ };
59
+ window.addEventListener("pointermove", onMove);
60
+ window.addEventListener("pointerup", onUp);
61
+ };
62
+ const onKeyDown = (i, e) => {
63
+ const fwd = horizontal ? dir === "rtl" ? "ArrowLeft" : "ArrowRight" : "ArrowDown";
64
+ const back = horizontal ? dir === "rtl" ? "ArrowRight" : "ArrowLeft" : "ArrowUp";
65
+ const pair = sizes[i] + sizes[i + 1];
66
+ let to;
67
+ if (e.key === fwd) to = sizes[i] + STEP;
68
+ else if (e.key === back) to = sizes[i] - STEP;
69
+ else if (e.key === "Home") to = min(i);
70
+ else if (e.key === "End") to = pair - min(i + 1);
71
+ else if (e.key === "Enter") to = initial[i] / (initial[i] + initial[i + 1]) * pair;
72
+ if (to === void 0) return;
73
+ e.preventDefault();
74
+ resize(sizes, i, to);
75
+ };
76
+ return /* @__PURE__ */ jsx(
77
+ "div",
78
+ {
79
+ ref: rootRef,
80
+ className: cn("flex min-h-0 min-w-0", horizontal ? "flex-row" : "flex-col", className),
81
+ ...props,
82
+ children: panels.map((panel, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
83
+ /* @__PURE__ */ jsx(
84
+ "div",
85
+ {
86
+ id: `${baseId}-${i}`,
87
+ ref: (el) => {
88
+ if (el) el.style.flex = `${sizes[i] ?? 0} 1 0px`;
89
+ },
90
+ className: "min-h-0 min-w-0 overflow-auto",
91
+ children: panel
92
+ }
93
+ ),
94
+ i < n - 1 && /* @__PURE__ */ jsx(
95
+ "div",
96
+ {
97
+ role: "separator",
98
+ tabIndex: 0,
99
+ "aria-label": handleLabel ?? t.resizePanels,
100
+ "aria-controls": `${baseId}-${i}`,
101
+ "aria-orientation": horizontal ? "vertical" : "horizontal",
102
+ "aria-valuenow": Math.round(sizes[i]),
103
+ "aria-valuemin": min(i),
104
+ "aria-valuemax": Math.round(sizes[i] + sizes[i + 1] - min(i + 1)),
105
+ onPointerDown: (e) => startDrag(i, e),
106
+ onKeyDown: (e) => onKeyDown(i, e),
107
+ className: cn(
108
+ "relative shrink-0 touch-none bg-[var(--color-outline-variant)] outline-none",
109
+ // A wider invisible hit area around the 1px line.
110
+ horizontal ? "w-px cursor-col-resize before:absolute before:-inset-x-1.5 before:inset-y-0" : "h-px cursor-row-resize before:absolute before:inset-x-0 before:-inset-y-1.5",
111
+ "hover:bg-[var(--color-primary)] focus-visible:bg-[var(--color-primary)] focus-visible:shadow-[0_0_0_1px_var(--color-primary)]"
112
+ )
113
+ }
114
+ )
115
+ ] }, i))
116
+ }
117
+ );
118
+ }
119
+ export {
120
+ ResizablePanels
121
+ };
@@ -0,0 +1,2 @@
1
+ export { ResizablePanels } from './ResizablePanels.js';
2
+ export type { ResizablePanelsProps } from './ResizablePanels.js';
@@ -0,0 +1,4 @@
1
+ import { ResizablePanels } from "./ResizablePanels.js";
2
+ export {
3
+ ResizablePanels
4
+ };
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ export interface SortableProps<T> {
3
+ items: T[];
4
+ getId: (item: T) => string;
5
+ /** Names the item in "Move ‹label›" and the move announcements. */
6
+ getLabel: (item: T) => string;
7
+ /** Called with the reordered items; store them to apply the move. */
8
+ onReorder: (items: T[]) => void;
9
+ /** The item's content; the drag handle sits before it. */
10
+ children: (item: T) => React.ReactNode;
11
+ 'aria-label'?: string;
12
+ className?: string;
13
+ }
14
+ /**
15
+ * A list people can reorder: drag an item, or focus its handle and press
16
+ * Alt+ArrowUp / Alt+ArrowDown. Each move is announced.
17
+ */
18
+ export declare function Sortable<T>({ items, getId, getLabel, onReorder, children, className, ...aria }: SortableProps<T>): React.JSX.Element;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { useReorder } from "./reorder.js";
6
+ function Sortable({ items, getId, getLabel, onReorder, children, className, ...aria }) {
7
+ const { itemProps, listProps, handle, live } = useReorder({
8
+ lists: [items],
9
+ getId,
10
+ getLabel,
11
+ onMove: (next) => onReorder(next[0])
12
+ });
13
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
14
+ /* @__PURE__ */ jsx("ul", { ...aria, ...listProps(0), className: cn("m-0 flex list-none flex-col gap-1 p-0", className), children: items.map((item, i) => /* @__PURE__ */ jsxs(
15
+ "li",
16
+ {
17
+ ...itemProps([0, i]),
18
+ className: "flex items-center gap-2 rounded-shape-control border border-[var(--color-outline-variant)] bg-[var(--color-surface)] py-1.5 pe-3 ps-1.5 text-body-md",
19
+ children: [
20
+ handle([0, i]),
21
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: children(item) })
22
+ ]
23
+ },
24
+ getId(item)
25
+ )) }),
26
+ live
27
+ ] });
28
+ }
29
+ export {
30
+ Sortable
31
+ };
@@ -0,0 +1,2 @@
1
+ export { Sortable } from './Sortable.js';
2
+ export type { SortableProps } from './Sortable.js';
@@ -0,0 +1,4 @@
1
+ import { Sortable } from "./Sortable.js";
2
+ export {
3
+ Sortable
4
+ };
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ /** Internal: lists of items, moved by drag (native) or Alt+Arrow on a handle. */
3
+ export type Pos = [list: number, index: number];
4
+ export declare function moveItem<T>(lists: T[][], [fc, fi]: Pos, [tc, ti]: Pos): T[][];
5
+ interface ReorderOptions<T> {
6
+ lists: T[][];
7
+ getId: (item: T) => string;
8
+ getLabel: (item: T) => string;
9
+ /** List names, for moves across lists. */
10
+ listLabels?: string[];
11
+ onMove: (next: T[][]) => void;
12
+ }
13
+ /** Shared engine: drag state, keyboard moves, focus after a move, announcements. */
14
+ export declare function useReorder<T>({ lists, getId, getLabel, listLabels, onMove }: ReorderOptions<T>): {
15
+ itemProps: (pos: Pos) => {
16
+ draggable: boolean;
17
+ onDragStart: (e: React.DragEvent) => void;
18
+ onDragOver: (e: React.DragEvent) => void;
19
+ onDrop: (e: React.DragEvent) => void;
20
+ onDragEnd: () => void;
21
+ };
22
+ listProps: (list: number) => {
23
+ onDragOver: (e: React.DragEvent) => void;
24
+ onDrop: (e: React.DragEvent) => void;
25
+ };
26
+ handle: (pos: Pos, className?: string) => React.JSX.Element;
27
+ live: React.JSX.Element;
28
+ };
29
+ export {};