@neasg/design-system 0.4.10 → 0.4.12

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.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/dist/avatar-profile-popover.d.ts +34 -0
  3. package/dist/avatar-profile-popover.js +27 -0
  4. package/dist/badge.d.ts +1 -1
  5. package/dist/button.d.ts +1 -1
  6. package/dist/card.d.ts +2 -0
  7. package/dist/card.js +72 -3
  8. package/dist/command-search.d.ts +47 -2
  9. package/dist/command-search.js +173 -24
  10. package/dist/command.d.ts +2 -0
  11. package/dist/command.js +2 -2
  12. package/dist/date-input.d.ts +2 -0
  13. package/dist/date-input.js +44 -4
  14. package/dist/dialog-primitive.js +1 -1
  15. package/dist/draggable-tabs.d.ts +3 -0
  16. package/dist/draggable-tabs.js +4 -0
  17. package/dist/editable-table.js +2 -2
  18. package/dist/filter-popover.d.ts +2 -1
  19. package/dist/filter-popover.js +2 -2
  20. package/dist/guidance-tip.d.ts +26 -0
  21. package/dist/guidance-tip.js +162 -0
  22. package/dist/index.d.ts +13 -5
  23. package/dist/index.js +5 -1
  24. package/dist/jump-target-highlight.d.ts +13 -0
  25. package/dist/jump-target-highlight.js +95 -0
  26. package/dist/layout.d.ts +27 -5
  27. package/dist/layout.js +128 -34
  28. package/dist/message-item.js +9 -10
  29. package/dist/notification.js +1 -1
  30. package/dist/page-layout.d.ts +22 -0
  31. package/dist/page-layout.js +38 -0
  32. package/dist/page-section.js +1 -1
  33. package/dist/popover-menu.js +2 -2
  34. package/dist/routing-timeline.js +1 -1
  35. package/dist/table-column-visibility.d.ts +3 -1
  36. package/dist/table-column-visibility.js +74 -27
  37. package/dist/table-toolbar.d.ts +2 -1
  38. package/dist/table-toolbar.js +7 -2
  39. package/dist/table.d.ts +32 -3
  40. package/dist/table.js +318 -29
  41. package/dist/tabs.d.ts +2 -1
  42. package/dist/tabs.js +8 -5
  43. package/dist/theme-switcher.d.ts +1 -1
  44. package/dist/theme-switcher.js +4 -6
  45. package/package.json +2 -2
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "./lib/utils";
3
+ const pageLayoutGapClassNames = {
4
+ page: "space-y-[var(--layout-page-gap)]",
5
+ header: "space-y-[var(--layout-page-header-gap)]",
6
+ section: "space-y-[var(--layout-page-section-gap)]",
7
+ control: "space-y-[var(--layout-page-control-gap)]",
8
+ context: "space-y-[var(--layout-page-context-gap)]",
9
+ };
10
+ const pageLayoutStackGapClassNames = {
11
+ header: pageLayoutGapClassNames.header,
12
+ section: pageLayoutGapClassNames.section,
13
+ control: pageLayoutGapClassNames.control,
14
+ context: pageLayoutGapClassNames.context,
15
+ title: "space-y-[var(--space-1)]",
16
+ };
17
+ function PageLayout({ gap = "page", className, ...props }) {
18
+ return (_jsx("div", { "data-slot": "page-layout", className: cn(pageLayoutGapClassNames[gap], className), ...props }));
19
+ }
20
+ function PageLayoutStack({ gap = "section", titleOffset = false, className, ...props }) {
21
+ return (_jsx("div", { "data-slot": "page-layout-stack", className: cn(pageLayoutStackGapClassNames[gap], titleOffset && "pt-[var(--layout-page-title-offset)]", className), ...props }));
22
+ }
23
+ function PageLayoutTitle(props) {
24
+ return (_jsx(PageLayoutStack, { gap: "title", "data-slot": "page-layout-title", ...props }));
25
+ }
26
+ function PageLayoutHeader(props) {
27
+ return (_jsx(PageLayoutStack, { gap: "header", "data-slot": "page-layout-header", ...props }));
28
+ }
29
+ function PageLayoutSection(props) {
30
+ return (_jsx(PageLayoutStack, { gap: "section", "data-slot": "page-layout-section", ...props }));
31
+ }
32
+ function PageLayoutControls(props) {
33
+ return (_jsx(PageLayoutStack, { gap: "control", "data-slot": "page-layout-controls", ...props }));
34
+ }
35
+ function PageLayoutContext(props) {
36
+ return (_jsx(PageLayoutStack, { gap: "context", "data-slot": "page-layout-context", ...props }));
37
+ }
38
+ export { PageLayout, PageLayoutStack, PageLayoutTitle, PageLayoutHeader, PageLayoutSection, PageLayoutControls, PageLayoutContext, pageLayoutGapClassNames, pageLayoutStackGapClassNames, };
@@ -9,6 +9,6 @@ function PageSectionGroup({ variant = "divided", className, ...props }) {
9
9
  function PageSection({ title, description, headerAction, titleAdornment, className, headerClassName, contentClassName, children, ...props }) {
10
10
  const renderedTitle = React.isValidElement(title) ? (title) : (_jsx(Typography, { as: "h3", variant: "subheading", children: title }));
11
11
  const renderedDescription = description == null ? null : React.isValidElement(description) ? (description) : (_jsx(Typography, { as: "p", variant: "caption", children: description }));
12
- return (_jsxs("section", { "data-slot": "page-section", className: cn("py-6", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", headerClassName), children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [renderedTitle, titleAdornment] }), renderedDescription] }), headerAction ? (_jsx("div", { className: "flex flex-wrap items-center justify-end gap-1.5 sm:shrink-0", children: headerAction })) : null] }), children ? (_jsx("div", { className: cn("pt-4", contentClassName), children: children })) : null] }));
12
+ return (_jsxs("section", { "data-slot": "page-section", className: cn("py-6", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", headerClassName), children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [renderedTitle, titleAdornment] }), renderedDescription] }), headerAction ? (_jsx("div", { className: "flex flex-wrap items-center justify-end gap-3 sm:shrink-0", children: headerAction })) : null] }), children ? (_jsx("div", { className: cn("pt-4", contentClassName), children: children })) : null] }));
13
13
  }
14
14
  export { PageSection, PageSectionGroup };
@@ -19,7 +19,7 @@ function PopoverMenuSection({ className, children, ...props }) {
19
19
  return (_jsx("div", { className: cn("space-y-0", className), ...props, children: children }));
20
20
  }
21
21
  function PopoverMenuItem({ className, children, active, type = "button", ...props }) {
22
- return (_jsxs(Button, { type: type, variant: "ghost", className: cn("h-auto min-h-8 w-full justify-start gap-2 rounded-sm px-2 py-2 text-left font-normal whitespace-normal", active &&
22
+ return (_jsxs(Button, { type: type, variant: "ghost", className: cn("h-auto min-h-8 w-full justify-start gap-2 rounded-sm px-2 py-2 text-left font-normal whitespace-normal focus-visible:ring-inset focus-visible:ring-1 focus-visible:ring-offset-0", active &&
23
23
  "bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground", className), ...props, children: [children, active ? _jsx(Check, { "aria-hidden": "true", className: "ml-auto h-4 w-4 shrink-0" }) : null] }));
24
24
  }
25
25
  function PopoverMenuCheckboxItem({ className, children, checked = false, description, disabled, onChange, readOnly, ...props }) {
@@ -33,7 +33,7 @@ function PopoverMenuFooter({ className, children, inset = "menu", ...props }) {
33
33
  return (_jsx("div", { className: cn("flex items-center gap-2 border-t p-1.5", inset === "menu" && "-mx-1 -mb-1", inset === "flush" && "m-0", className), ...props, children: children }));
34
34
  }
35
35
  function PopoverMenuFooterAction({ className, type = "button", ...props }) {
36
- return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal", className), ...props }));
36
+ return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal focus-visible:ring-inset focus-visible:ring-1 focus-visible:ring-offset-0", className), ...props }));
37
37
  }
38
38
  function PopoverMenuFooterMeta({ className, children, ...props }) {
39
39
  return (_jsx(Typography, { as: "span", variant: "bodySm", color: "muted", className: cn("shrink-0 px-2 font-medium", className), ...props, children: children }));
@@ -52,7 +52,7 @@ function RoutingTimeline({ items, emptyMessage = "No routing activity yet.", cla
52
52
  const hasCompleteRoute = Boolean(item.from && item.to);
53
53
  const title = (_b = item.title) !== null && _b !== void 0 ? _b : config.title;
54
54
  const remark = (_c = item.remark) !== null && _c !== void 0 ? _c : item.description;
55
- return (_jsxs("li", { className: "relative grid grid-cols-[var(--space-3)_minmax(0,1fr)] gap-[var(--space-3)] pb-[var(--space-5)] last:pb-0", children: [_jsx("div", { className: "relative flex justify-center", children: _jsx("span", { "aria-hidden": "true", "data-slot": "routing-timeline-marker", className: cn("relative z-10 mt-[var(--space-1)] size-[var(--space-2)] rounded-full border", config.markerClassName) }) }), _jsxs("div", { "data-slot": "routing-timeline-event-card", className: "min-w-0 rounded-surface border border-border/70 bg-card p-[var(--space-3)] text-card-foreground", children: [_jsxs("div", { className: "min-w-0 space-y-[var(--space-1)]", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-[var(--space-2)]", children: [_jsx(Typography, { as: "p", variant: "label", className: "min-w-0 flex-1 font-semibold", children: title }), _jsx(Badge, { variant: config.badgeVariant, bordered: true, className: "max-w-full shrink-0 self-start", children: (_d = item.badgeLabel) !== null && _d !== void 0 ? _d : config.label })] }), item.timestamp || item.actor ? (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-[var(--space-1)] gap-y-[var(--space-1)]", children: [item.timestamp ? (_jsx(Typography, { as: "time", variant: "caption", className: "shrink-0", children: item.timestamp })) : null, item.actor ? (_jsxs(Typography, { as: "p", variant: "caption", className: "inline-flex min-w-0 items-center gap-x-[var(--space-1)]", children: [item.timestamp ? (_jsx("span", { "aria-hidden": "true", children: "\u00B7" })) : null, "By ", item.actor] })) : null] })) : null] }), remark ? (_jsx(Typography, { as: "div", variant: "bodySm", "data-slot": "routing-timeline-remark", className: "mt-[var(--space-4)] font-normal", children: remark })) : null, hasRouteEndpoints ? (_jsxs("footer", { "data-slot": "routing-timeline-handoff", "data-complete-route": hasCompleteRoute ? "true" : undefined, className: "mt-[var(--space-4)] flex flex-col space-y-[var(--space-2)] text-xs text-muted-foreground", children: [item.from ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.from })) : null, item.from && item.to ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "sr-only", children: "to" }), _jsx(ArrowDown, { "aria-hidden": "true", "data-slot": "routing-timeline-handoff-arrow", className: "ml-1 size-4 shrink-0 text-muted-foreground" })] })) : null, item.to ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.to })) : null] })) : null] })] }, item.id));
55
+ return (_jsxs("li", { className: "relative grid grid-cols-[var(--space-3)_minmax(0,1fr)] gap-[var(--space-3)] pb-[var(--space-5)] last:pb-0", children: [_jsx("div", { className: "relative flex justify-center", children: _jsx("span", { "aria-hidden": "true", "data-slot": "routing-timeline-marker", className: cn("relative z-10 mt-[var(--space-1)] size-[var(--space-2)] rounded-full border", config.markerClassName) }) }), _jsxs("div", { "data-slot": "routing-timeline-event-card", className: "min-w-0 rounded-surface border border-border/70 bg-card p-[var(--space-3)] text-card-foreground", children: [_jsxs("div", { className: "min-w-0 space-y-[var(--space-1)]", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-[var(--space-2)]", children: [_jsx(Typography, { as: "p", variant: "label", className: "min-w-0 flex-1 font-semibold", children: title }), _jsx(Badge, { variant: config.badgeVariant, bordered: true, className: "max-w-full shrink-0 self-start", children: (_d = item.badgeLabel) !== null && _d !== void 0 ? _d : config.label })] }), item.timestamp || item.actor ? (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-[var(--space-1)] gap-y-[var(--space-1)]", children: [item.timestamp ? (_jsx(Typography, { as: "time", variant: "caption", className: "shrink-0 text-muted-foreground/60", children: item.timestamp })) : null, item.actor ? (_jsxs(Typography, { as: "p", variant: "caption", className: "inline-flex min-w-0 items-center gap-x-[var(--space-1)]", children: [item.timestamp ? (_jsx("span", { "aria-hidden": "true", children: "\u00B7" })) : null, "By ", item.actor] })) : null] })) : null] }), remark ? (_jsx(Typography, { as: "div", variant: "bodySm", "data-slot": "routing-timeline-remark", className: "mt-[var(--space-4)] font-normal", children: remark })) : null, hasRouteEndpoints ? (_jsxs("footer", { "data-slot": "routing-timeline-handoff", "data-complete-route": hasCompleteRoute ? "true" : undefined, className: "mt-[var(--space-4)] flex flex-col space-y-[var(--space-2)] text-xs text-muted-foreground", children: [item.from ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.from })) : null, item.from && item.to ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "sr-only", children: "to" }), _jsx(ArrowDown, { "aria-hidden": "true", "data-slot": "routing-timeline-handoff-arrow", className: "ml-1 size-4 shrink-0 text-muted-foreground" })] })) : null, item.to ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.to })) : null] })) : null] })] }, item.id));
56
56
  }) }) }));
57
57
  }
58
58
  export { RoutingTimeline };
@@ -4,6 +4,7 @@ import type { TableColumn } from "./table";
4
4
  type PopoverPlacementProps = Pick<PopoverProps, "align" | "side" | "sideOffset">;
5
5
  export interface TableColumnVisibilityMenuProps<Row> extends PopoverPlacementProps {
6
6
  columns: TableColumn<Row>[];
7
+ /** Ordered visible column keys. Reordering in the menu emits the next order through `onVisibleColumnKeysChange`. */
7
8
  visibleColumnKeys: readonly string[];
8
9
  onVisibleColumnKeysChange: (visibleColumnKeys: string[]) => void;
9
10
  defaultVisibleColumnKeys?: readonly string[];
@@ -12,10 +13,11 @@ export interface TableColumnVisibilityMenuProps<Row> extends PopoverPlacementPro
12
13
  triggerClassName?: string;
13
14
  contentClassName?: string;
14
15
  className?: string;
16
+ title?: React.ReactNode;
15
17
  searchPlaceholder?: string;
16
18
  restoreDefaultsLabel?: string;
17
19
  emptyMessage?: React.ReactNode;
18
20
  minVisibleColumns?: number;
19
21
  }
20
- declare function TableColumnVisibilityMenu<Row>({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel, triggerClassName, contentClassName, className, searchPlaceholder, restoreDefaultsLabel, emptyMessage, minVisibleColumns, align, sideOffset, side, }: TableColumnVisibilityMenuProps<Row>): import("react/jsx-runtime").JSX.Element;
22
+ declare function TableColumnVisibilityMenu<Row>({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel, triggerClassName, contentClassName, className, title, searchPlaceholder, restoreDefaultsLabel, emptyMessage, minVisibleColumns, align, sideOffset, side, }: TableColumnVisibilityMenuProps<Row>): import("react/jsx-runtime").JSX.Element;
21
23
  export { TableColumnVisibilityMenu };
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Columns3 } from "lucide-react";
4
+ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, } from "@dnd-kit/core";
5
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable";
6
+ import { CSS } from "@dnd-kit/utilities";
7
+ import { Columns3, GripVertical } from "lucide-react";
5
8
  import { Button } from "./button";
6
9
  import { Checkbox } from "./checkbox";
7
10
  import { InputControl } from "./input-control";
8
11
  import { cn } from "./lib/utils";
9
12
  import { Popover } from "./popover";
10
- import { PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, } from "./popover-menu";
13
+ import { PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroupLabel, } from "./popover-menu";
11
14
  import { SearchInputShell } from "./search-input-shell";
12
15
  function getColumnVisibilityLabel(column) {
13
16
  if (column.visibilityLabel) {
@@ -19,11 +22,18 @@ function getColumnVisibilityLabel(column) {
19
22
  }
20
23
  return column.key;
21
24
  }
22
- function orderVisibleColumnKeys(columns, visibleColumnKeys) {
23
- const visibleColumnKeySet = new Set(visibleColumnKeys);
24
- return columns
25
- .map((column) => column.key)
26
- .filter((columnKey) => visibleColumnKeySet.has(columnKey));
25
+ function orderColumnKeys(columns, columnKeys) {
26
+ const columnKeySet = new Set(columns.map((column) => column.key));
27
+ const seenColumnKeys = new Set();
28
+ const orderedColumnKeys = [];
29
+ Array.from(columnKeys).forEach((columnKey) => {
30
+ if (!columnKeySet.has(columnKey) || seenColumnKeys.has(columnKey)) {
31
+ return;
32
+ }
33
+ orderedColumnKeys.push(columnKey);
34
+ seenColumnKeys.add(columnKey);
35
+ });
36
+ return orderedColumnKeys;
27
37
  }
28
38
  function arraysMatch(left, right) {
29
39
  if (left.length !== right.length) {
@@ -31,44 +41,81 @@ function arraysMatch(left, right) {
31
41
  }
32
42
  return left.every((value, index) => value === right[index]);
33
43
  }
34
- function TableColumnVisibilityMenu({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel = "Customize columns", triggerClassName, contentClassName, className, searchPlaceholder = "Search columns", restoreDefaultsLabel = "Restore defaults", emptyMessage = "No columns found", minVisibleColumns = 1, align = "end", sideOffset = 4, side, }) {
44
+ function SortableColumnOption({ columnKey, label, checked, disabled, onToggle, }) {
45
+ const checkboxId = React.useId();
46
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: columnKey, disabled: !checked });
47
+ return (_jsxs("div", { ref: setNodeRef, style: {
48
+ transform: CSS.Transform.toString(transform),
49
+ transition,
50
+ zIndex: isDragging ? 50 : undefined,
51
+ }, className: cn("flex h-9 items-center gap-2 rounded-sm px-1.5 pr-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground", isDragging && "opacity-50 shadow-sm"), children: [_jsx("button", { type: "button", "aria-label": `Reorder ${label}`, disabled: !checked, className: cn("inline-flex size-6 shrink-0 touch-none items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-background hover:text-foreground", checked
52
+ ? "cursor-grab active:cursor-grabbing"
53
+ : "cursor-not-allowed opacity-40"), ...(checked ? { ...attributes, ...listeners } : {}), children: _jsx(GripVertical, { className: "h-4 w-4", "aria-hidden": "true" }) }), _jsx(Checkbox, { id: checkboxId, checked: checked, disabled: disabled, onChange: onToggle, "aria-label": label }), _jsx("label", { htmlFor: checkboxId, className: cn("min-w-0 flex-1 cursor-pointer truncate", disabled && "cursor-not-allowed text-muted-foreground"), children: label })] }));
54
+ }
55
+ function TableColumnVisibilityMenu({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel = "Customize columns", triggerClassName, contentClassName, className, title = "Customize columns", searchPlaceholder = "Search columns", restoreDefaultsLabel = "Restore defaults", emptyMessage = "No columns found", minVisibleColumns = 1, align = "end", sideOffset = 4, side, }) {
35
56
  const [query, setQuery] = React.useState("");
57
+ const sensors = useSensors(useSensor(PointerSensor, {
58
+ activationConstraint: {
59
+ distance: 8,
60
+ },
61
+ }), useSensor(KeyboardSensor, {
62
+ coordinateGetter: sortableKeyboardCoordinates,
63
+ }));
36
64
  const searchInputId = React.useId();
65
+ const titleId = React.useId();
37
66
  const requiredVisibleColumns = Math.max(0, minVisibleColumns);
38
- const visibleColumnKeySet = React.useMemo(() => new Set(visibleColumnKeys), [visibleColumnKeys]);
39
- const visibleColumnCount = columns.filter((column) => visibleColumnKeySet.has(column.key)).length;
40
67
  const selectableColumns = React.useMemo(() => columns.filter((column) => column.hideable !== false), [columns]);
41
- const defaultKeys = React.useMemo(() => orderVisibleColumnKeys(columns, defaultVisibleColumnKeys !== null && defaultVisibleColumnKeys !== void 0 ? defaultVisibleColumnKeys : columns
68
+ const defaultKeys = React.useMemo(() => orderColumnKeys(columns, defaultVisibleColumnKeys !== null && defaultVisibleColumnKeys !== void 0 ? defaultVisibleColumnKeys : columns
42
69
  .filter((column) => column.defaultVisible !== false)
43
70
  .map((column) => column.key)), [columns, defaultVisibleColumnKeys]);
44
- const currentKeys = React.useMemo(() => orderVisibleColumnKeys(columns, visibleColumnKeys), [columns, visibleColumnKeys]);
71
+ const currentKeys = React.useMemo(() => orderColumnKeys(columns, visibleColumnKeys), [columns, visibleColumnKeys]);
72
+ const visibleColumnKeySet = React.useMemo(() => new Set(currentKeys), [currentKeys]);
73
+ const visibleColumnCount = currentKeys.length;
74
+ const orderedSelectableColumns = React.useMemo(() => {
75
+ const selectableColumnsByKey = new Map(selectableColumns.map((column) => [column.key, column]));
76
+ const orderedVisibleColumns = currentKeys
77
+ .map((columnKey) => selectableColumnsByKey.get(columnKey))
78
+ .filter((column) => Boolean(column));
79
+ const orderedVisibleColumnKeySet = new Set(orderedVisibleColumns.map((column) => column.key));
80
+ const hiddenColumns = selectableColumns.filter((column) => !orderedVisibleColumnKeySet.has(column.key));
81
+ return [...orderedVisibleColumns, ...hiddenColumns];
82
+ }, [currentKeys, selectableColumns]);
45
83
  const canRestoreDefaults = defaultKeys.length >= requiredVisibleColumns &&
46
84
  !arraysMatch(currentKeys, defaultKeys);
47
85
  const normalizedQuery = query.trim().toLowerCase();
48
- const filteredColumns = selectableColumns.filter((column) => getColumnVisibilityLabel(column).toLowerCase().includes(normalizedQuery));
86
+ const filteredColumns = orderedSelectableColumns.filter((column) => getColumnVisibilityLabel(column).toLowerCase().includes(normalizedQuery));
49
87
  const commitVisibleColumnKeys = React.useCallback((nextVisibleColumnKeys) => {
50
- const orderedKeys = orderVisibleColumnKeys(columns, nextVisibleColumnKeys);
88
+ const orderedKeys = orderColumnKeys(columns, nextVisibleColumnKeys);
51
89
  if (orderedKeys.length < requiredVisibleColumns) {
52
90
  return;
53
91
  }
54
92
  onVisibleColumnKeysChange(orderedKeys);
55
93
  }, [columns, onVisibleColumnKeysChange, requiredVisibleColumns]);
56
94
  const toggleColumn = React.useCallback((columnKey) => {
57
- const nextVisibleColumnKeys = new Set(visibleColumnKeys);
58
- if (nextVisibleColumnKeys.has(columnKey)) {
59
- nextVisibleColumnKeys.delete(columnKey);
60
- }
61
- else {
62
- nextVisibleColumnKeys.add(columnKey);
95
+ const nextVisibleColumnKeys = currentKeys.filter((currentColumnKey) => currentColumnKey !== columnKey);
96
+ if (!visibleColumnKeySet.has(columnKey)) {
97
+ nextVisibleColumnKeys.push(columnKey);
63
98
  }
64
99
  commitVisibleColumnKeys(nextVisibleColumnKeys);
65
- }, [commitVisibleColumnKeys, visibleColumnKeys]);
100
+ }, [commitVisibleColumnKeys, currentKeys, visibleColumnKeySet]);
101
+ const handleDragEnd = React.useCallback((event) => {
102
+ const { active, over } = event;
103
+ if (!over || active.id === over.id) {
104
+ return;
105
+ }
106
+ const oldIndex = currentKeys.indexOf(String(active.id));
107
+ const newIndex = currentKeys.indexOf(String(over.id));
108
+ if (oldIndex < 0 || newIndex < 0) {
109
+ return;
110
+ }
111
+ commitVisibleColumnKeys(arrayMove(currentKeys, oldIndex, newIndex));
112
+ }, [commitVisibleColumnKeys, currentKeys]);
66
113
  const defaultTrigger = (_jsx(Button, { type: "button", variant: "outline", size: "icon", "aria-label": triggerLabel, className: triggerClassName, children: _jsx(Columns3, { "aria-hidden": "true" }) }));
67
- return (_jsx(Popover, { trigger: trigger !== null && trigger !== void 0 ? trigger : defaultTrigger, align: align, side: side, sideOffset: sideOffset, contentClassName: cn("w-80 p-0", contentClassName), children: _jsxs("div", { className: cn("flex max-h-96 flex-col", className), children: [_jsxs("div", { className: "border-b p-2", children: [_jsx("label", { htmlFor: searchInputId, className: "sr-only", children: searchPlaceholder }), _jsx(SearchInputShell, { children: _jsx(InputControl, { id: searchInputId, value: query, onChange: (event) => setQuery(event.target.value), placeholder: searchPlaceholder, className: "h-8 bg-background pl-8 pr-2" }) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-1", children: filteredColumns.length ? (filteredColumns.map((column) => {
68
- const label = getColumnVisibilityLabel(column);
69
- const checked = visibleColumnKeySet.has(column.key);
70
- const disabled = checked && visibleColumnCount <= requiredVisibleColumns;
71
- return (_jsxs("label", { className: cn("flex h-9 cursor-pointer items-center gap-2 rounded-sm px-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground", disabled && "cursor-not-allowed opacity-50 hover:bg-transparent hover:text-inherit"), children: [_jsx(Checkbox, { checked: checked, disabled: disabled, onChange: () => toggleColumn(column.key), "aria-label": label }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label })] }, column.key));
72
- })) : (_jsx("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: emptyMessage })) }), _jsxs(PopoverMenuFooter, { inset: "flush", className: "justify-between", children: [_jsx(PopoverMenuFooterAction, { onClick: () => commitVisibleColumnKeys(defaultKeys), disabled: !canRestoreDefaults, className: "w-auto min-w-0 flex-1", children: restoreDefaultsLabel }), _jsxs(PopoverMenuFooterMeta, { children: [visibleColumnCount, " of ", columns.length] })] })] }) }));
114
+ return (_jsx(Popover, { trigger: trigger !== null && trigger !== void 0 ? trigger : defaultTrigger, align: align, side: side, sideOffset: sideOffset, contentClassName: cn("w-80 p-0", contentClassName), "aria-labelledby": title ? titleId : undefined, children: _jsxs("div", { className: cn("flex max-h-96 flex-col", className), children: [_jsxs("div", { className: "border-b p-2", children: [_jsx("label", { htmlFor: searchInputId, className: "sr-only", children: searchPlaceholder }), _jsx(SearchInputShell, { children: _jsx(InputControl, { id: searchInputId, value: query, onChange: (event) => setQuery(event.target.value), placeholder: searchPlaceholder, className: "h-8 bg-background pl-8 pr-2" }) })] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto p-1", children: [title ? (_jsx(PopoverMenuGroupLabel, { id: titleId, children: title })) : null, filteredColumns.length ? (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: filteredColumns.map((column) => column.key), strategy: verticalListSortingStrategy, children: filteredColumns.map((column) => {
115
+ const label = getColumnVisibilityLabel(column);
116
+ const checked = visibleColumnKeySet.has(column.key);
117
+ const disabled = checked && visibleColumnCount <= requiredVisibleColumns;
118
+ return (_jsx(SortableColumnOption, { columnKey: column.key, label: label, checked: checked, disabled: disabled, onToggle: () => toggleColumn(column.key) }, column.key));
119
+ }) }) })) : (_jsx("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: emptyMessage }))] }), _jsxs(PopoverMenuFooter, { inset: "flush", className: "justify-between", children: [_jsx(PopoverMenuFooterAction, { onClick: () => commitVisibleColumnKeys(defaultKeys), disabled: !canRestoreDefaults, className: "w-auto min-w-0 flex-1", children: restoreDefaultsLabel }), _jsxs(PopoverMenuFooterMeta, { children: [visibleColumnCount, " of ", columns.length] })] })] }) }));
73
120
  }
74
121
  export { TableColumnVisibilityMenu };
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  import { type SearchInputProps } from "./search-input";
3
3
  export interface TableToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
4
4
  search?: SearchInputProps;
5
+ actionsClassName?: string;
5
6
  }
6
- declare function TableToolbar({ search, className, children, ...props }: TableToolbarProps): import("react/jsx-runtime").JSX.Element;
7
+ declare function TableToolbar({ search, className, actionsClassName, children, ...props }: TableToolbarProps): import("react/jsx-runtime").JSX.Element;
7
8
  export { TableToolbar };
@@ -1,7 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { SearchInput } from "./search-input";
3
3
  import { cn } from "./lib/utils";
4
- function TableToolbar({ search, className, children, ...props }) {
5
- return (_jsxs("div", { className: cn("flex flex-wrap items-center gap-1.5", className), ...props, children: [search ? (_jsx(SearchInput, { ...search, containerClassName: cn("min-w-[200px] flex-1 max-w-md", search.containerClassName) })) : null, children] }));
4
+ function TableToolbar({ search, className, actionsClassName, children, ...props }) {
5
+ const hasSearch = Boolean(search);
6
+ return (_jsxs("div", { className: cn(hasSearch
7
+ ? "grid w-full grid-cols-1 items-center gap-[calc(var(--space-1)+0.125rem)] sm:grid-cols-[minmax(calc(var(--space-10)*5),28rem)_minmax(0,1fr)]"
8
+ : "flex w-full flex-wrap items-center justify-end gap-[calc(var(--space-1)+0.125rem)]", className), ...props, children: [search ? (_jsx(SearchInput, { ...search, containerClassName: cn("w-full min-w-[calc(var(--space-10)*5)] max-w-md", search.containerClassName) })) : null, children ? (_jsx("div", { className: cn("flex flex-wrap items-center gap-[calc(var(--space-1)+0.125rem)]", hasSearch
9
+ ? "justify-start sm:justify-end sm:justify-self-end"
10
+ : "justify-end", actionsClassName), children: children })) : null] }));
6
11
  }
7
12
  export { TableToolbar };
package/dist/table.d.ts CHANGED
@@ -7,6 +7,7 @@ declare const TableRoot: React.ForwardRefExoticComponent<TableRootProps & React.
7
7
  interface TableContainerProps extends React.HTMLAttributes<HTMLDivElement> {
8
8
  }
9
9
  declare const TableContainer: React.ForwardRefExoticComponent<TableContainerProps & React.RefAttributes<HTMLDivElement>>;
10
+ export type TableColumnWidths = Record<string, number>;
10
11
  declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
11
12
  declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
12
13
  declare const TableFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
@@ -16,12 +17,16 @@ declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttributes<
16
17
  minWidth?: string;
17
18
  maxWidth?: string;
18
19
  wrap?: boolean;
20
+ treatWidthAsMinWidth?: boolean;
21
+ containOverflow?: boolean;
19
22
  } & React.RefAttributes<HTMLTableCellElement>>;
20
23
  declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & {
21
24
  width?: string;
22
25
  minWidth?: string;
23
26
  maxWidth?: string;
24
27
  wrap?: boolean;
28
+ treatWidthAsMinWidth?: boolean;
29
+ containOverflow?: boolean;
25
30
  } & React.RefAttributes<HTMLTableCellElement>>;
26
31
  declare const TableCaption: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableCaptionElement> & React.RefAttributes<HTMLTableCaptionElement>>;
27
32
  type TableAlign = "left" | "center" | "right";
@@ -76,6 +81,12 @@ export interface TableColumn<Row> {
76
81
  cellClassName?: string;
77
82
  /** Pins the column to the left or right edge while the table scrolls horizontally. */
78
83
  sticky?: "left" | "right";
84
+ /** Enables a resize handle for this column. Use `resizableColumns` on Table to opt every column in by default. */
85
+ resizable?: boolean;
86
+ /** Minimum pixel width while resizing. Defaults to `minWidth`, then 64. */
87
+ resizeMinWidth?: number;
88
+ /** Maximum pixel width while resizing. Omit to allow the column to grow. */
89
+ resizeMaxWidth?: number;
79
90
  }
80
91
  export interface TableSortingProps {
81
92
  field: string | null;
@@ -95,8 +106,16 @@ interface SortableTableHeadProps<T extends string> {
95
106
  maxWidth?: string;
96
107
  align?: TableAlign;
97
108
  sortLabel?: string;
109
+ resizeHandle?: React.ReactNode;
110
+ treatWidthAsMinWidth?: boolean;
111
+ containOverflow?: boolean;
112
+ }
113
+ declare function SortableTableHead<T extends string>({ field, children, currentSortField, currentSortDirection, onSort, className, allowWrap, width, minWidth, maxWidth, align, sortLabel, resizeHandle, treatWidthAsMinWidth, containOverflow, }: SortableTableHeadProps<T>): import("react/jsx-runtime").JSX.Element;
114
+ export interface TableColumnResizeHandleRenderProps<Row> {
115
+ column: TableColumn<Row>;
116
+ nextColumn: TableColumn<Row>;
117
+ handle: React.ReactElement;
98
118
  }
99
- declare function SortableTableHead<T extends string>({ field, children, currentSortField, currentSortDirection, onSort, className, allowWrap, width, minWidth, maxWidth, align, sortLabel, }: SortableTableHeadProps<T>): import("react/jsx-runtime").JSX.Element;
100
119
  export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
101
120
  columns: TableColumn<Row>[];
102
121
  rows: Row[];
@@ -115,12 +134,22 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
115
134
  pagination?: Omit<PaginationProps, "className">;
116
135
  loading?: boolean;
117
136
  loadingRows?: number;
118
- /** Optional controlled set of visible column keys. Omit to render every column. */
137
+ /** Optional controlled ordered list of visible column keys. Omit to render every column in config order. */
119
138
  visibleColumnKeys?: readonly string[];
139
+ /** When true, every column is resizable unless its column config sets `resizable: false`. */
140
+ resizableColumns?: boolean;
141
+ /** Controlled pixel widths keyed by column key. */
142
+ columnWidths?: TableColumnWidths;
143
+ /** Initial pixel widths keyed by column key for uncontrolled resizing. */
144
+ defaultColumnWidths?: TableColumnWidths;
145
+ /** Emits whenever a resize handle changes column widths. */
146
+ onColumnWidthsChange?: (columnWidths: TableColumnWidths) => void;
147
+ /** Optional hook for decorating resize handles without replacing table sizing behavior. */
148
+ renderColumnResizeHandle?: (props: TableColumnResizeHandleRenderProps<Row>) => React.ReactNode;
120
149
  /** When true, applies alternating row backgrounds for easier scanning. */
121
150
  striped?: boolean;
122
151
  /** When set, rows become interactive: cursor-pointer, hover highlight, and click handling. */
123
152
  onRowClick?: (row: Row, index: number) => void;
124
153
  }
125
- declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, sorting, pagination, loading, loadingRows, visibleColumnKeys, striped, onRowClick, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
154
+ declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, sorting, pagination, loading, loadingRows, visibleColumnKeys, resizableColumns, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, striped, onRowClick, onScroll, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
126
155
  export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, SortableTableHead, TableRowSkeleton, };