@nexub/react 6.4.0 → 6.6.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.
- package/accessibility.json +44 -1
- package/dist/components/ChartZoom/ChartZoom.d.ts +18 -0
- package/dist/components/ChartZoom/ChartZoom.js +42 -0
- package/dist/components/ChartZoom/index.d.ts +2 -0
- package/dist/components/ChartZoom/index.js +4 -0
- package/dist/components/DataTable/DataTable.d.ts +8 -1
- package/dist/components/DataTable/DataTable.js +31 -4
- package/dist/components/Kanban/Kanban.d.ts +20 -0
- package/dist/components/Kanban/Kanban.js +50 -0
- package/dist/components/Kanban/index.d.ts +2 -0
- package/dist/components/Kanban/index.js +4 -0
- package/dist/components/Sortable/Sortable.d.ts +18 -0
- package/dist/components/Sortable/Sortable.js +31 -0
- package/dist/components/Sortable/index.d.ts +2 -0
- package/dist/components/Sortable/index.js +4 -0
- package/dist/components/Sortable/reorder.d.ts +29 -0
- package/dist/components/Sortable/reorder.js +114 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/styles.css +1 -1
- package/dist/utils/csv.d.ts +11 -0
- package/dist/utils/csv.js +17 -0
- package/package.json +6 -6
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "6.
|
|
2
|
+
"version": "6.6.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.",
|
|
@@ -238,6 +238,17 @@
|
|
|
238
238
|
"Each series is a toggle button with `aria-pressed`. The last visible series cannot be switched off."
|
|
239
239
|
]
|
|
240
240
|
},
|
|
241
|
+
{
|
|
242
|
+
"slug": "chart-zoom",
|
|
243
|
+
"name": "Chart Zoom",
|
|
244
|
+
"package": "@nexub/react",
|
|
245
|
+
"automated": "pass",
|
|
246
|
+
"exceptions": [],
|
|
247
|
+
"keyboard": [
|
|
248
|
+
"Arrow keys / Home / End: Move the focused thumb."
|
|
249
|
+
],
|
|
250
|
+
"screenReader": []
|
|
251
|
+
},
|
|
241
252
|
{
|
|
242
253
|
"slug": "checkbox-card",
|
|
243
254
|
"name": "Checkbox Card",
|
|
@@ -622,6 +633,18 @@
|
|
|
622
633
|
"Visible focus ring on `:focus-visible`. Pair with a `<Label htmlFor>` so clicking the label moves focus into the field."
|
|
623
634
|
]
|
|
624
635
|
},
|
|
636
|
+
{
|
|
637
|
+
"slug": "kanban",
|
|
638
|
+
"name": "Kanban",
|
|
639
|
+
"package": "@nexub/react",
|
|
640
|
+
"automated": "pass",
|
|
641
|
+
"exceptions": [],
|
|
642
|
+
"keyboard": [
|
|
643
|
+
"Alt+ArrowUp / Alt+ArrowDown: On a handle: moves the card within its column.",
|
|
644
|
+
"Alt+ArrowLeft / Alt+ArrowRight: Moves the card to the previous or next column. Mirrored in RTL."
|
|
645
|
+
],
|
|
646
|
+
"screenReader": []
|
|
647
|
+
},
|
|
625
648
|
{
|
|
626
649
|
"slug": "kbd",
|
|
627
650
|
"name": "Kbd",
|
|
@@ -1050,6 +1073,17 @@
|
|
|
1050
1073
|
"keyboard": [],
|
|
1051
1074
|
"screenReader": []
|
|
1052
1075
|
},
|
|
1076
|
+
{
|
|
1077
|
+
"slug": "sortable",
|
|
1078
|
+
"name": "Sortable",
|
|
1079
|
+
"package": "@nexub/react",
|
|
1080
|
+
"automated": "pass",
|
|
1081
|
+
"exceptions": [],
|
|
1082
|
+
"keyboard": [
|
|
1083
|
+
"Alt+ArrowUp / Alt+ArrowDown: On a handle: moves the item; focus stays on its handle."
|
|
1084
|
+
],
|
|
1085
|
+
"screenReader": []
|
|
1086
|
+
},
|
|
1053
1087
|
{
|
|
1054
1088
|
"slug": "sparkline",
|
|
1055
1089
|
"name": "Sparkline",
|
|
@@ -1407,6 +1441,15 @@
|
|
|
1407
1441
|
"keyboard": [],
|
|
1408
1442
|
"screenReader": []
|
|
1409
1443
|
},
|
|
1444
|
+
{
|
|
1445
|
+
"slug": "notification-center",
|
|
1446
|
+
"name": "NotificationCenter",
|
|
1447
|
+
"package": "@nexub/patterns",
|
|
1448
|
+
"automated": "pass",
|
|
1449
|
+
"exceptions": [],
|
|
1450
|
+
"keyboard": [],
|
|
1451
|
+
"screenReader": []
|
|
1452
|
+
},
|
|
1410
1453
|
{
|
|
1411
1454
|
"slug": "notification-item",
|
|
1412
1455
|
"name": "NotificationItem",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ChartZoomProps<T> {
|
|
3
|
+
data: T[];
|
|
4
|
+
/** The category key, shown as the range's ends. */
|
|
5
|
+
index: keyof T;
|
|
6
|
+
/** Render the chart for the visible slice (charts stay server-friendly; only this wrapper is client). */
|
|
7
|
+
children: (slice: T[]) => React.ReactNode;
|
|
8
|
+
/** Start and end indexes. @default the whole range */
|
|
9
|
+
defaultRange?: [number, number];
|
|
10
|
+
/** Fewest categories shown. @default 2 */
|
|
11
|
+
minSpan?: number;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Zoom a chart to part of its range with a two-thumb slider under it (keyboard
|
|
16
|
+
* and touch friendly), and reset to the whole range.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ChartZoom<T>({ data, index, children, defaultRange, minSpan, className }: ChartZoomProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Slider } from "../Slider/Slider.js";
|
|
5
|
+
import { Button } from "../Button/Button.js";
|
|
6
|
+
import { cn } from "../../utils/cn.js";
|
|
7
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
8
|
+
function ChartZoom({ data, index, children, defaultRange, minSpan = 2, className }) {
|
|
9
|
+
const t = useNexubMessages();
|
|
10
|
+
const full = [0, Math.max(0, data.length - 1)];
|
|
11
|
+
const [range, setRange] = React.useState(defaultRange ?? full);
|
|
12
|
+
const [a, b] = [Math.max(0, range[0]), Math.min(full[1], range[1])];
|
|
13
|
+
const zoomed = a > 0 || b < full[1];
|
|
14
|
+
const label = (i) => String(data[i]?.[index] ?? "");
|
|
15
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", className), children: [
|
|
16
|
+
children(data.slice(a, b + 1)),
|
|
17
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
18
|
+
/* @__PURE__ */ jsx(
|
|
19
|
+
Slider,
|
|
20
|
+
{
|
|
21
|
+
min: 0,
|
|
22
|
+
max: full[1],
|
|
23
|
+
step: 1,
|
|
24
|
+
minStepsBetweenThumbs: Math.max(1, minSpan - 1),
|
|
25
|
+
value: [a, b],
|
|
26
|
+
onValueChange: (v) => setRange([v[0], v[1]]),
|
|
27
|
+
thumbLabels: [`${t.zoomStart}: ${label(a)}`, `${t.zoomEnd}: ${label(b)}`],
|
|
28
|
+
className: "flex-1"
|
|
29
|
+
}
|
|
30
|
+
),
|
|
31
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "min-w-max text-label-md tabular-nums text-[var(--color-on-surface-muted)]", children: [
|
|
32
|
+
label(a),
|
|
33
|
+
" \u2013 ",
|
|
34
|
+
label(b)
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsx(Button, { variant: "text", size: "sm", disabled: !zoomed, onClick: () => setRange(full), children: t.resetZoom })
|
|
37
|
+
] })
|
|
38
|
+
] });
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
ChartZoom
|
|
42
|
+
};
|
|
@@ -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,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,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 {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { GripVertical } from "@nexub/icons";
|
|
5
|
+
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
7
|
+
function moveItem(lists, [fc, fi], [tc, ti]) {
|
|
8
|
+
const next = lists.map((l) => [...l]);
|
|
9
|
+
const [item] = next[fc].splice(fi, 1);
|
|
10
|
+
if (item === void 0) return lists;
|
|
11
|
+
next[tc].splice(Math.min(ti, next[tc].length), 0, item);
|
|
12
|
+
return next;
|
|
13
|
+
}
|
|
14
|
+
function useReorder({ lists, getId, getLabel, listLabels, onMove }) {
|
|
15
|
+
const t = useNexubMessages();
|
|
16
|
+
const { dir } = useLocale();
|
|
17
|
+
const dragged = React.useRef(null);
|
|
18
|
+
const handles = React.useRef(/* @__PURE__ */ new Map());
|
|
19
|
+
const refocus = React.useRef(null);
|
|
20
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
21
|
+
const hintId = React.useId();
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
if (refocus.current) handles.current.get(refocus.current)?.focus();
|
|
24
|
+
refocus.current = null;
|
|
25
|
+
});
|
|
26
|
+
const move = (from, to, keepFocus) => {
|
|
27
|
+
if (from[0] === to[0] && from[1] === to[1]) return;
|
|
28
|
+
const item = lists[from[0]][from[1]];
|
|
29
|
+
const next = moveItem(lists, from, to);
|
|
30
|
+
const [tc] = to;
|
|
31
|
+
const ti = next[tc].indexOf(item);
|
|
32
|
+
if (keepFocus) refocus.current = getId(item);
|
|
33
|
+
onMove(next);
|
|
34
|
+
const total = next[tc].length;
|
|
35
|
+
setAnnouncement(
|
|
36
|
+
from[0] !== tc && listLabels?.[tc] ? t.itemMovedToColumn(getLabel(item), listLabels[tc], ti + 1, total) : t.itemMoved(getLabel(item), ti + 1, total)
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
const itemProps = (pos) => ({
|
|
40
|
+
draggable: true,
|
|
41
|
+
onDragStart: (e) => {
|
|
42
|
+
dragged.current = pos;
|
|
43
|
+
e.dataTransfer.effectAllowed = "move";
|
|
44
|
+
},
|
|
45
|
+
onDragOver: (e) => {
|
|
46
|
+
if (dragged.current) e.preventDefault();
|
|
47
|
+
},
|
|
48
|
+
onDrop: (e) => {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
if (dragged.current) move(dragged.current, pos, false);
|
|
52
|
+
dragged.current = null;
|
|
53
|
+
},
|
|
54
|
+
onDragEnd: () => {
|
|
55
|
+
dragged.current = null;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const listProps = (list) => ({
|
|
59
|
+
onDragOver: (e) => {
|
|
60
|
+
if (dragged.current) e.preventDefault();
|
|
61
|
+
},
|
|
62
|
+
onDrop: (e) => {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
if (dragged.current) move(dragged.current, [list, lists[list].length], false);
|
|
65
|
+
dragged.current = null;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const handle = (pos, className) => {
|
|
69
|
+
const item = lists[pos[0]][pos[1]];
|
|
70
|
+
const id = getId(item);
|
|
71
|
+
return /* @__PURE__ */ jsx(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
type: "button",
|
|
75
|
+
ref: (el) => {
|
|
76
|
+
if (el) handles.current.set(id, el);
|
|
77
|
+
else handles.current.delete(id);
|
|
78
|
+
},
|
|
79
|
+
"aria-label": t.moveItem(getLabel(item)),
|
|
80
|
+
"aria-describedby": hintId,
|
|
81
|
+
onKeyDown: (e) => {
|
|
82
|
+
if (!e.altKey) return;
|
|
83
|
+
const [c, i] = pos;
|
|
84
|
+
const sideways = lists.length > 1;
|
|
85
|
+
const toNext = dir === "rtl" ? "ArrowLeft" : "ArrowRight";
|
|
86
|
+
const toPrev = dir === "rtl" ? "ArrowRight" : "ArrowLeft";
|
|
87
|
+
let to = null;
|
|
88
|
+
if (e.key === "ArrowUp" && i > 0) to = [c, i - 1];
|
|
89
|
+
else if (e.key === "ArrowDown" && i < lists[c].length - 1) to = [c, i + 1];
|
|
90
|
+
else if (sideways && e.key === toNext && c < lists.length - 1) to = [c + 1, i];
|
|
91
|
+
else if (sideways && e.key === toPrev && c > 0) to = [c - 1, i];
|
|
92
|
+
if (!to) return;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
move(pos, to, true);
|
|
95
|
+
},
|
|
96
|
+
className: cn(
|
|
97
|
+
"inline-flex size-7 shrink-0 cursor-grab items-center justify-center rounded-shape-min text-[var(--color-on-surface-variant)]",
|
|
98
|
+
"hover:bg-[var(--color-surface-container-high)] active:cursor-grabbing",
|
|
99
|
+
className
|
|
100
|
+
),
|
|
101
|
+
children: /* @__PURE__ */ jsx(GripVertical, { size: "sm", "aria-hidden": "true" })
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
const live = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
106
|
+
/* @__PURE__ */ jsx("span", { id: hintId, hidden: true, children: t.reorderHint }),
|
|
107
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: announcement })
|
|
108
|
+
] });
|
|
109
|
+
return { itemProps, listProps, handle, live };
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
moveItem,
|
|
113
|
+
useReorder
|
|
114
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export type { AuroraProps } from './components/Aurora/index.js';
|
|
|
53
53
|
export { BarChart, LineChart, DonutChart, Sparkline, ChartLegend, niceMax, niceDomain } from './components/Chart/index.js';
|
|
54
54
|
export { Gauge, Heatmap, Funnel, KpiTile } from './components/Chart/index.js';
|
|
55
55
|
export { ChartToggle } from './components/Chart/index.js';
|
|
56
|
+
export { ChartZoom } from './components/ChartZoom/index.js';
|
|
57
|
+
export type { ChartZoomProps } from './components/ChartZoom/index.js';
|
|
56
58
|
export type { ChartToggleProps } from './components/Chart/index.js';
|
|
57
59
|
export type { GaugeProps, HeatmapProps, FunnelProps, KpiTileProps } from './components/Chart/index.js';
|
|
58
60
|
export type { BarChartProps, LineChartProps, DonutChartProps, SparklineProps, ChartLegendProps, ChartSeries, ChartColor } from './components/Chart/index.js';
|
|
@@ -234,6 +236,12 @@ export { Stepper } from './components/Stepper/index.js';
|
|
|
234
236
|
export type { StepperProps, StepperStep } from './components/Stepper/index.js';
|
|
235
237
|
export { ActionBar } from './components/ActionBar/index.js';
|
|
236
238
|
export type { ActionBarProps } from './components/ActionBar/index.js';
|
|
239
|
+
export { Sortable } from './components/Sortable/index.js';
|
|
240
|
+
export type { SortableProps } from './components/Sortable/index.js';
|
|
241
|
+
export { Kanban } from './components/Kanban/index.js';
|
|
242
|
+
export type { KanbanProps, KanbanColumn } from './components/Kanban/index.js';
|
|
243
|
+
export { toCSV } from './utils/csv.js';
|
|
244
|
+
export type { CSVColumn } from './utils/csv.js';
|
|
237
245
|
export type { OTPInputProps } from './components/OTPInput/index.js';
|
|
238
246
|
export { DatePicker } from './components/DatePicker/index.js';
|
|
239
247
|
export type { DatePickerProps } from './components/DatePicker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { Aurora } from "./components/Aurora/index.js";
|
|
|
32
32
|
import { BarChart, LineChart, DonutChart, Sparkline, ChartLegend, niceMax, niceDomain } from "./components/Chart/index.js";
|
|
33
33
|
import { Gauge, Heatmap, Funnel, KpiTile } from "./components/Chart/index.js";
|
|
34
34
|
import { ChartToggle } from "./components/Chart/index.js";
|
|
35
|
+
import { ChartZoom } from "./components/ChartZoom/index.js";
|
|
35
36
|
import { NumberTicker } from "./components/NumberTicker/index.js";
|
|
36
37
|
import { SearchInput } from "./components/SearchInput/index.js";
|
|
37
38
|
import { PasswordInput } from "./components/PasswordInput/index.js";
|
|
@@ -131,6 +132,9 @@ import { TreeView } from "./components/TreeView/index.js";
|
|
|
131
132
|
import { ResizablePanels } from "./components/ResizablePanels/index.js";
|
|
132
133
|
import { Stepper } from "./components/Stepper/index.js";
|
|
133
134
|
import { ActionBar } from "./components/ActionBar/index.js";
|
|
135
|
+
import { Sortable } from "./components/Sortable/index.js";
|
|
136
|
+
import { Kanban } from "./components/Kanban/index.js";
|
|
137
|
+
import { toCSV } from "./utils/csv.js";
|
|
134
138
|
import { DatePicker } from "./components/DatePicker/index.js";
|
|
135
139
|
import { DateRangePicker } from "./components/DateRangePicker/index.js";
|
|
136
140
|
import { todayIn } from "./components/DateRangePicker/index.js";
|
|
@@ -160,6 +164,7 @@ export {
|
|
|
160
164
|
Celebrate,
|
|
161
165
|
ChartLegend,
|
|
162
166
|
ChartToggle,
|
|
167
|
+
ChartZoom,
|
|
163
168
|
CheckList,
|
|
164
169
|
CheckListItem,
|
|
165
170
|
Checkbox,
|
|
@@ -211,6 +216,7 @@ export {
|
|
|
211
216
|
IconButton,
|
|
212
217
|
InlineEdit,
|
|
213
218
|
Input,
|
|
219
|
+
Kanban,
|
|
214
220
|
Kbd,
|
|
215
221
|
KeyboardShortcut,
|
|
216
222
|
KpiTile,
|
|
@@ -261,6 +267,7 @@ export {
|
|
|
261
267
|
SkeletonSwap,
|
|
262
268
|
Slide,
|
|
263
269
|
Slider,
|
|
270
|
+
Sortable,
|
|
264
271
|
SourceChip,
|
|
265
272
|
SourceList,
|
|
266
273
|
Sparkline,
|
|
@@ -327,6 +334,7 @@ export {
|
|
|
327
334
|
startViewTransition,
|
|
328
335
|
textVariants,
|
|
329
336
|
textareaVariants,
|
|
337
|
+
toCSV,
|
|
330
338
|
toast,
|
|
331
339
|
todayIn,
|
|
332
340
|
toggleVariants,
|