@nexub/react 7.5.0 → 7.7.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.
Files changed (33) hide show
  1. package/accessibility.json +1 -1
  2. package/dist/components/Badge/Badge.d.ts +9 -0
  3. package/dist/components/Badge/Badge.js +40 -2
  4. package/dist/components/Button/Button.d.ts +1 -1
  5. package/dist/components/CommandPalette/CommandPalette.d.ts +34 -13
  6. package/dist/components/CommandPalette/CommandPalette.js +174 -40
  7. package/dist/components/CommandPalette/index.d.ts +1 -1
  8. package/dist/components/DataTable/DataTable.d.ts +16 -2
  9. package/dist/components/DataTable/DataTable.js +131 -76
  10. package/dist/components/DataTable/ViewOptions.d.ts +15 -0
  11. package/dist/components/DataTable/ViewOptions.js +33 -0
  12. package/dist/components/DataTableVirtual/DataTableVirtual.d.ts +1 -1
  13. package/dist/components/Dialog/Dialog.d.ts +7 -0
  14. package/dist/components/Dialog/Dialog.js +35 -8
  15. package/dist/components/EmptyState/EmptyState.d.ts +7 -1
  16. package/dist/components/EmptyState/EmptyState.js +21 -5
  17. package/dist/components/EmptyState/EmptyStatePreset.d.ts +2 -1
  18. package/dist/components/FilterBuilder/FilterBuilder.js +33 -15
  19. package/dist/components/Heading/Heading.d.ts +1 -1
  20. package/dist/components/IconButton/IconButton.d.ts +5 -1
  21. package/dist/components/IconButton/IconButton.js +14 -3
  22. package/dist/components/Kanban/Kanban.d.ts +1 -1
  23. package/dist/components/Sortable/Sortable.d.ts +2 -2
  24. package/dist/components/Sortable/reorder.d.ts +10 -11
  25. package/dist/components/Sortable/reorder.js +185 -27
  26. package/dist/components/Tabs/Tabs.d.ts +5 -1
  27. package/dist/components/Tabs/Tabs.js +98 -35
  28. package/dist/components/Text/Text.d.ts +1 -1
  29. package/dist/components/Tooltip/Tooltip.d.ts +10 -3
  30. package/dist/components/Tooltip/Tooltip.js +16 -4
  31. package/dist/index.d.ts +2 -0
  32. package/dist/styles.css +1 -1
  33. package/package.json +8 -7
@@ -20,7 +20,6 @@ import { useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
20
20
  import { Table } from "../Table/Table.js";
21
21
  import { Checkbox } from "../Checkbox/Checkbox.js";
22
22
  import { Button } from "../Button/Button.js";
23
- import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
24
23
  import { createGridNavigation } from "./gridNavigation.js";
25
24
  import { useReorder } from "../Sortable/reorder.js";
26
25
  import { Skeleton } from "../Skeleton/Skeleton.js";
@@ -134,6 +133,67 @@ function TreeCell({
134
133
  ] });
135
134
  }
136
135
  const px = (n) => typeof n === "number" ? `${n}px` : n;
136
+ const ViewOptions = React.lazy(() => import("./ViewOptions.js").then((m) => ({ default: m.ViewOptions })));
137
+ const placeCell = (column, resizable) => (el) => {
138
+ if (!el) return;
139
+ el.style.width = resizable ? px(column.getSize()) : "";
140
+ const side = column.getIsPinned();
141
+ el.style.insetInlineStart = side === "left" ? px(column.getStart("left")) : "";
142
+ el.style.insetInlineEnd = side === "right" ? px(column.getAfter("right")) : "";
143
+ };
144
+ const pinClass = (column) => {
145
+ const side = column.getIsPinned();
146
+ if (!side) return void 0;
147
+ return cn(
148
+ "sticky z-[1] bg-[var(--color-surface)]",
149
+ "[tr:hover>&]:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,var(--color-surface))]",
150
+ "[tr[data-state=selected]>&]:bg-[color-mix(in_srgb,var(--color-primary)_10%,var(--color-surface))]",
151
+ side === "left" && column.getIsLastColumn("left") && "border-e border-[var(--color-outline-variant)]",
152
+ side === "right" && column.getIsFirstColumn("right") && "border-s border-[var(--color-outline-variant)]"
153
+ );
154
+ };
155
+ function RowViewImpl({
156
+ row,
157
+ index,
158
+ selected,
159
+ compact,
160
+ pinned,
161
+ resizable,
162
+ selectable,
163
+ tree,
164
+ virtual,
165
+ measure,
166
+ expandLabel,
167
+ collapseLabel,
168
+ dragProps
169
+ }) {
170
+ return /* @__PURE__ */ jsx(
171
+ TableRow,
172
+ {
173
+ ref: measure,
174
+ "data-index": index,
175
+ "aria-rowindex": virtual ? index + 2 : void 0,
176
+ "data-state": selectable && selected ? "selected" : void 0,
177
+ ...dragProps,
178
+ children: row.getVisibleCells().map((cell, _i, cells) => /* @__PURE__ */ jsx(
179
+ TableCell,
180
+ {
181
+ ref: pinned || resizable ? placeCell(cell.column, resizable) : void 0,
182
+ className: cn(
183
+ alignText[cell.column.columnDef.meta?.align ?? "start"],
184
+ compact && "py-1.5",
185
+ pinClass(cell.column),
186
+ resizable && "truncate"
187
+ ),
188
+ children: tree && cell === cells.find((c) => c.column.id !== "__select") ? /* @__PURE__ */ jsx(TreeCell, { row, expandLabel, collapseLabel, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }) : flexRender(cell.column.columnDef.cell, cell.getContext())
189
+ },
190
+ cell.id
191
+ ))
192
+ }
193
+ );
194
+ }
195
+ const sameRow = (a, b) => Object.keys(a).every((k) => k === "row" || a[k] === b[k]) && a.row.id === b.row.id && a.row.original === b.row.original && a.row.index === b.row.index && a.row.depth === b.row.depth;
196
+ const RowView = React.memo(RowViewImpl, sameRow);
137
197
  const apply = (u, prev) => typeof u === "function" ? u(prev) : u;
138
198
  const columnLabel = (c) => typeof c.columnDef.header === "string" ? c.columnDef.header : c.id;
139
199
  function DataTable({
@@ -164,7 +224,15 @@ function DataTable({
164
224
  maxHeight,
165
225
  grid = false,
166
226
  rowWindow,
167
- pagination,
227
+ pagination: paginationProp,
228
+ sort,
229
+ onSortChange,
230
+ manualSort = false,
231
+ page,
232
+ pageSize: pageSizeProp,
233
+ onPageChange,
234
+ manualPagination = false,
235
+ rowCount,
168
236
  getSubRows,
169
237
  expanded: expandedProp,
170
238
  defaultExpanded,
@@ -182,7 +250,11 @@ function DataTable({
182
250
  }) {
183
251
  const messages = useNexubMessages();
184
252
  const { locale, dir } = useLocale();
185
- const [sorting, setSorting] = React.useState([]);
253
+ const [sorting = [], setSorting] = useControllableState({
254
+ value: sort,
255
+ defaultValue: [],
256
+ onChange: onSortChange
257
+ });
186
258
  const [rowSelection = {}, setRowSelection] = useControllableState({
187
259
  value: rowSelectionProp,
188
260
  defaultValue: {},
@@ -222,10 +294,14 @@ function DataTable({
222
294
  const tableRef = React.useRef(null);
223
295
  const gridNav = React.useMemo(() => createGridNavigation(dir), [dir]);
224
296
  const dragged = React.useRef(null);
225
- const [paginationState, setPaginationState] = React.useState({
226
- pageIndex: 0,
227
- pageSize: pagination?.pageSize ?? 20
297
+ const pagination = !!paginationProp || page !== void 0 || pageSizeProp !== void 0 || manualPagination;
298
+ const [pageIndex = 0, setPageIndex] = useControllableState({
299
+ value: page,
300
+ defaultValue: 0,
301
+ onChange: onPageChange
228
302
  });
303
+ const pageSize = pageSizeProp ?? paginationProp?.pageSize ?? 20;
304
+ const paginationState = React.useMemo(() => ({ pageIndex, pageSize }), [pageIndex, pageSize]);
229
305
  const withSelectColumn = enableRowSelection && selectionColumn;
230
306
  const reorderRows = Boolean(onRowOrderChange) && sorting.length === 0;
231
307
  const rowReorder = useReorder({
@@ -312,7 +388,8 @@ function DataTable({
312
388
  columns: allColumns,
313
389
  state: tableState,
314
390
  getRowId,
315
- onSortingChange: enableSorting ? setSorting : void 0,
391
+ onSortingChange: enableSorting ? (u) => setSorting(apply(u, sorting)) : void 0,
392
+ manualSorting: manualSort,
316
393
  onRowSelectionChange: enableRowSelection ? (u) => setRowSelection(apply(u, rowSelection)) : void 0,
317
394
  onColumnVisibilityChange: (u) => setColumnVisibility(apply(u, columnVisibility)),
318
395
  onColumnSizingChange: (u) => setColumnSizing(apply(u, columnSizing)),
@@ -321,11 +398,15 @@ function DataTable({
321
398
  enableColumnResizing,
322
399
  columnResizeMode: "onChange",
323
400
  columnResizeDirection: dir,
324
- onPaginationChange: pagination ? setPaginationState : void 0,
401
+ onPaginationChange: pagination ? (u) => {
402
+ const next = apply(u, paginationState).pageIndex;
403
+ if (next !== pageIndex) setPageIndex(next);
404
+ } : void 0,
325
405
  getCoreRowModel: getCoreRowModel(),
326
- getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
406
+ getSortedRowModel: enableSorting && !manualSort ? getSortedRowModel() : void 0,
327
407
  getFilteredRowModel: getFilteredRowModel(),
328
- getPaginationRowModel: pagination ? getPaginationRowModel() : void 0,
408
+ getPaginationRowModel: pagination && !manualPagination ? getPaginationRowModel() : void 0,
409
+ rowCount,
329
410
  getSubRows,
330
411
  getExpandedRowModel: getSubRows ? getExpandedRowModel() : void 0,
331
412
  onExpandedChange: getSubRows ? (u) => setExpanded(apply(u, expanded)) : void 0,
@@ -333,7 +414,7 @@ function DataTable({
333
414
  // (TanStack then expands inside the page model; without pages it must not).
334
415
  paginateExpandedRows: !pagination,
335
416
  enableRowSelection,
336
- manualPagination: false
417
+ manualPagination
337
418
  });
338
419
  const compact = density === "compact";
339
420
  const selectedRows = enableRowSelection ? table.getSelectedRowModel().rows : [];
@@ -354,24 +435,11 @@ function DataTable({
354
435
  if (scroller) scroller.style.maxHeight = maxHeight == null ? "" : px(maxHeight);
355
436
  if (grid) gridNav.sync(t);
356
437
  });
357
- const placeCell = (column) => (el) => {
358
- if (!el) return;
359
- el.style.width = enableColumnResizing ? px(column.getSize()) : "";
360
- const side = column.getIsPinned();
361
- el.style.insetInlineStart = side === "left" ? px(column.getStart("left")) : "";
362
- el.style.insetInlineEnd = side === "right" ? px(column.getAfter("right")) : "";
363
- };
364
- const pinClass = (column) => {
365
- const side = column.getIsPinned();
366
- if (!side) return void 0;
367
- return cn(
368
- "sticky z-[1] bg-[var(--color-surface)]",
369
- "[tr:hover>&]:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,var(--color-surface))]",
370
- "[tr[data-state=selected]>&]:bg-[color-mix(in_srgb,var(--color-primary)_10%,var(--color-surface))]",
371
- side === "left" && column.getIsLastColumn("left") && "border-e border-[var(--color-outline-variant)]",
372
- side === "right" && column.getIsFirstColumn("right") && "border-s border-[var(--color-outline-variant)]"
373
- );
374
- };
438
+ const visibleColumns = table.getVisibleLeafColumns();
439
+ const viewButton = /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "ms-auto", children: [
440
+ /* @__PURE__ */ jsx(SlidersHorizontal, { size: "sm", "aria-hidden": "true" }),
441
+ messages.view
442
+ ] });
375
443
  const moveColumn = (id, to) => {
376
444
  const ids = table.getAllLeafColumns().map((c) => c.id);
377
445
  const order = (columnOrder.length ? columnOrder : ids).filter((c) => c !== "__select" && ids.includes(c));
@@ -390,31 +458,21 @@ function DataTable({
390
458
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), "data-density": density, ...props, children: [
391
459
  (toolbar != null || viewOptions) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
392
460
  toolbar,
393
- viewOptions && /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
394
- /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "ms-auto", children: [
395
- /* @__PURE__ */ jsx(SlidersHorizontal, { size: "sm", "aria-hidden": "true" }),
396
- messages.view
397
- ] }) }),
398
- /* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
399
- /* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.density }),
400
- /* @__PURE__ */ jsxs(DropdownMenu.RadioGroup, { value: density, onValueChange: (v) => setDensity(v), children: [
401
- /* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "comfortable", children: messages.comfortable }),
402
- /* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "compact", children: messages.compact })
403
- ] }),
404
- /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
405
- /* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.columns }),
406
- table.getAllLeafColumns().filter((c) => c.getCanHide()).map((c) => /* @__PURE__ */ jsx(
407
- DropdownMenu.CheckboxItem,
408
- {
409
- checked: c.getIsVisible(),
410
- onCheckedChange: (v) => c.toggleVisibility(v === true),
411
- onSelect: (e) => e.preventDefault(),
412
- children: columnLabel(c)
413
- },
414
- c.id
415
- ))
416
- ] })
417
- ] })
461
+ viewOptions && // The plain button stands in (same size, no layout shift) until the menu loads.
462
+ /* @__PURE__ */ jsx(React.Suspense, { fallback: viewButton, children: /* @__PURE__ */ jsx(
463
+ ViewOptions,
464
+ {
465
+ trigger: viewButton,
466
+ density,
467
+ onDensityChange: setDensity,
468
+ columns: table.getAllLeafColumns().filter((c) => c.getCanHide()).map((c) => ({
469
+ id: c.id,
470
+ label: columnLabel(c),
471
+ visible: c.getIsVisible(),
472
+ toggle: (v) => c.toggleVisibility(v)
473
+ }))
474
+ }
475
+ ) })
418
476
  ] }),
419
477
  offerAll && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-shape-container bg-[var(--color-surface-container-low)] px-4 py-2 text-body-sm", children: table.getIsAllRowsSelected() ? /* @__PURE__ */ jsxs(Fragment, { children: [
420
478
  /* @__PURE__ */ jsx("span", { children: messages.selectedCount(fmt(totalRows)) }),
@@ -447,7 +505,7 @@ function DataTable({
447
505
  return /* @__PURE__ */ jsxs(
448
506
  TableHead,
449
507
  {
450
- ref: placeCell(col),
508
+ ref: placeCell(col, enableColumnResizing),
451
509
  className: cn(
452
510
  alignText[align],
453
511
  compact && "h-8",
@@ -532,32 +590,29 @@ function DataTable({
532
590
  header.id
533
591
  );
534
592
  }) }, headerGroup.id)) }),
535
- /* @__PURE__ */ jsx(TableBody, { children: loading ? Array.from({ length: pagination?.pageSize ?? 5 }, (_, i) => /* @__PURE__ */ jsx(TableRow, { "data-loading": "", className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((c) => /* @__PURE__ */ jsx(TableCell, { className: cn(compact && "py-1.5"), children: /* @__PURE__ */ jsx(Skeleton, {}) }, c.id)) }, i)) : rows.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
593
+ /* @__PURE__ */ jsx(TableBody, { children: loading ? Array.from({ length: pagination ? pageSize : 5 }, (_, i) => /* @__PURE__ */ jsx(TableRow, { "data-loading": "", className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((c) => /* @__PURE__ */ jsx(TableCell, { className: cn(compact && "py-1.5"), children: /* @__PURE__ */ jsx(Skeleton, {}) }, c.id)) }, i)) : rows.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
536
594
  rowWin.before > 0 && /* @__PURE__ */ jsx(Spacer, { height: rowWin.before }),
537
595
  rowWin.indexes.map((i) => {
538
596
  const row = rows[i];
539
597
  return /* @__PURE__ */ jsx(
540
- TableRow,
598
+ RowView,
541
599
  {
542
- ref: rowWin.measure,
543
- "data-index": i,
544
- "aria-rowindex": rowWin.virtual ? i + 2 : void 0,
545
- "data-state": enableRowSelection && row.getIsSelected() ? "selected" : void 0,
546
- ...reorderRows && row.depth === 0 ? rowReorder.itemProps([0, row.index]) : {},
547
- children: row.getVisibleCells().map((cell, _i, cells) => /* @__PURE__ */ jsx(
548
- TableCell,
549
- {
550
- ref: pinned || enableColumnResizing ? placeCell(cell.column) : void 0,
551
- className: cn(
552
- alignText[cell.column.columnDef.meta?.align ?? "start"],
553
- compact && "py-1.5",
554
- pinClass(cell.column),
555
- enableColumnResizing && "truncate"
556
- ),
557
- children: getSubRows && cell === cells.find((c) => c.column.id !== "__select") ? /* @__PURE__ */ jsx(TreeCell, { row, expandLabel: messages.expandRow, collapseLabel: messages.collapseRow, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }) : flexRender(cell.column.columnDef.cell, cell.getContext())
558
- },
559
- cell.id
560
- ))
600
+ row,
601
+ index: i,
602
+ selected: enableRowSelection && row.getIsSelected(),
603
+ expanded: !!getSubRows && row.getIsExpanded(),
604
+ columns: visibleColumns,
605
+ columnSizing,
606
+ compact,
607
+ pinned,
608
+ resizable: enableColumnResizing,
609
+ selectable: enableRowSelection,
610
+ tree: !!getSubRows,
611
+ virtual: rowWin.virtual,
612
+ measure: rowWin.measure,
613
+ expandLabel: messages.expandRow,
614
+ collapseLabel: messages.collapseRow,
615
+ dragProps: reorderRows && row.depth === 0 ? rowReorder.itemProps([0, row.index]) : void 0
561
616
  },
562
617
  row.id
563
618
  );
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import type { DataTableDensity } from './DataTable.js';
3
+ export interface ViewOptionsProps {
4
+ trigger: React.ReactElement;
5
+ density: DataTableDensity;
6
+ onDensityChange: (density: DataTableDensity) => void;
7
+ /** Hideable columns. */
8
+ columns: {
9
+ id: string;
10
+ label: string;
11
+ visible: boolean;
12
+ toggle: (visible: boolean) => void;
13
+ }[];
14
+ }
15
+ export declare function ViewOptions({ trigger, density, onDensityChange, columns }: ViewOptionsProps): React.JSX.Element;
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
5
+ import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
6
+ function ViewOptions({ trigger, density, onDensityChange, columns }) {
7
+ const messages = useNexubMessages();
8
+ return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
9
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: trigger }),
10
+ /* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
11
+ /* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.density }),
12
+ /* @__PURE__ */ jsxs(DropdownMenu.RadioGroup, { value: density, onValueChange: (v) => onDensityChange(v), children: [
13
+ /* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "comfortable", children: messages.comfortable }),
14
+ /* @__PURE__ */ jsx(DropdownMenu.RadioItem, { value: "compact", children: messages.compact })
15
+ ] }),
16
+ /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
17
+ /* @__PURE__ */ jsx(DropdownMenu.Label, { children: messages.columns }),
18
+ columns.map((c) => /* @__PURE__ */ jsx(
19
+ DropdownMenu.CheckboxItem,
20
+ {
21
+ checked: c.visible,
22
+ onCheckedChange: (v) => c.toggle(v === true),
23
+ onSelect: (e) => e.preventDefault(),
24
+ children: c.label
25
+ },
26
+ c.id
27
+ ))
28
+ ] })
29
+ ] });
30
+ }
31
+ export {
32
+ ViewOptions
33
+ };
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { type DataTableProps } from '../DataTable/DataTable.js';
3
- export interface DataTableVirtualProps<TData> extends Omit<DataTableProps<TData>, 'pagination' | 'rowWindow'> {
3
+ export interface DataTableVirtualProps<TData> extends Omit<DataTableProps<TData>, 'pagination' | 'rowWindow' | 'page' | 'pageSize' | 'onPageChange' | 'manualPagination' | 'rowCount'> {
4
4
  /** Height of the scrolling area. Required: rows are windowed within it. */
5
5
  maxHeight: number | string;
6
6
  /** Row height before measuring, in px. Default 49, or 37 when compact. */
@@ -15,6 +15,12 @@ declare function Root({ scrollLock, modal, ...props }: DialogRootProps): React.J
15
15
  interface ContentProps extends RadixDialog.DialogContentProps {
16
16
  size?: Size | 'xl' | 'full';
17
17
  surface?: SurfaceValue;
18
+ /** Adds a close button in the top-end corner. @default false */
19
+ showClose?: boolean;
20
+ /** Accessible name of the close button. @default messages.close */
21
+ closeLabel?: string;
22
+ /** `true`: bottom sheet. `'auto'`: bottom sheet below the `md` breakpoint, centred above. @default false */
23
+ sheet?: boolean | 'auto';
18
24
  }
19
25
  export declare const Dialog: {
20
26
  Root: typeof Root;
@@ -26,6 +32,7 @@ export declare const Dialog: {
26
32
  Title: React.ForwardRefExoticComponent<RadixDialog.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>>;
27
33
  Description: React.ForwardRefExoticComponent<RadixDialog.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>>;
28
34
  Header: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
35
+ Body: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
29
36
  Footer: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
30
37
  };
31
38
  export {};
@@ -1,11 +1,15 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as RadixDialog from "@radix-ui/react-dialog";
5
5
  import { cn } from "../../utils/cn.js";
6
6
  import { overlaySurface, scrim } from "../../utils/surface.js";
7
7
  import { glassSurfaceClass } from "../../utils/glass.js";
8
8
  import { ClassLockOverlay, ScrollLockContext } from "../../hooks/useClassScrollLock.js";
9
+ import { useMediaQuery } from "../../hooks/useMediaQuery.js";
10
+ import { X } from "@nexub/icons";
11
+ import { IconButton } from "../IconButton/IconButton.js";
12
+ import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
9
13
  function Root({ scrollLock = "style", modal = true, ...props }) {
10
14
  const lock = React.useMemo(() => ({ scrollLock, modal }), [scrollLock, modal]);
11
15
  return /* @__PURE__ */ jsx(ScrollLockContext.Provider, { value: lock, children: /* @__PURE__ */ jsx(RadixDialog.Root, { modal, ...props }) });
@@ -33,8 +37,19 @@ const Overlay = React.forwardRef(
33
37
  }
34
38
  );
35
39
  Overlay.displayName = "Dialog.Overlay";
40
+ const StackContext = React.createContext(null);
36
41
  const Content = React.forwardRef(
37
- function DialogContent({ className, size = "md", surface = "solid", children, ...props }, ref) {
42
+ function DialogContent({ className, size = "md", surface = "solid", showClose = false, closeLabel, sheet = false, children, ...props }, ref) {
43
+ const messages = useNexubMessages();
44
+ const narrow = useMediaQuery("(max-width: 47.99rem)");
45
+ const asSheet = sheet === true || sheet === "auto" && narrow;
46
+ const parent = React.useContext(StackContext);
47
+ const [openChildren, setOpenChildren] = React.useState(0);
48
+ React.useEffect(() => {
49
+ if (!parent) return;
50
+ parent((n) => n + 1);
51
+ return () => parent((n) => n - 1);
52
+ }, [parent]);
38
53
  const sizeMap = {
39
54
  sm: "max-w-sm",
40
55
  md: "max-w-lg",
@@ -42,23 +57,28 @@ const Content = React.forwardRef(
42
57
  xl: "max-w-4xl",
43
58
  full: "max-w-[calc(100vw-2rem)]"
44
59
  };
45
- return /* @__PURE__ */ jsx(
60
+ return /* @__PURE__ */ jsxs(
46
61
  RadixDialog.Content,
47
62
  {
48
63
  ref,
49
64
  className: cn(
50
- "fixed left-1/2 top-1/2 z-[var(--z-modal)] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2",
65
+ asSheet ? "fixed inset-x-0 bottom-0 z-[var(--z-modal)] w-full" : "fixed left-1/2 top-1/2 z-[var(--z-modal)] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2",
51
66
  "nx-motion nx-dialog",
52
67
  overlaySurface,
53
68
  "max-h-[90vh] overflow-y-auto p-6 focus:outline-none",
54
- sizeMap[size],
69
+ asSheet ? "rounded-b-none" : sizeMap[size],
55
70
  surface === "glass" && glassSurfaceClass,
56
71
  className
57
72
  ),
58
73
  "data-material": surface === "glass" ? "glass" : void 0,
59
74
  "data-glass-thickness": surface === "glass" ? "thick" : void 0,
75
+ "data-sheet": asSheet ? "" : void 0,
76
+ "data-nested": openChildren > 0 ? "" : void 0,
60
77
  ...props,
61
- children
78
+ children: [
79
+ /* @__PURE__ */ jsx(StackContext.Provider, { value: setOpenChildren, children }),
80
+ showClose && /* @__PURE__ */ jsx(RadixDialog.Close, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { label: closeLabel ?? messages.close, variant: "ghost", size: "sm", className: "absolute end-3 top-3", children: /* @__PURE__ */ jsx(X, { size: "sm", "aria-hidden": "true" }) }) })
81
+ ]
62
82
  }
63
83
  );
64
84
  }
@@ -92,16 +112,22 @@ const Description = React.forwardRef(
92
112
  Description.displayName = "Dialog.Description";
93
113
  const Header = React.forwardRef(
94
114
  function DialogHeader({ className, ...props }, ref) {
95
- return /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4 flex flex-col gap-1", className), ...props });
115
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("nx-dialog-header mb-4 flex flex-col gap-1", className), ...props });
96
116
  }
97
117
  );
98
118
  Header.displayName = "Dialog.Header";
99
119
  const Footer = React.forwardRef(
100
120
  function DialogFooter({ className, ...props }, ref) {
101
- return /* @__PURE__ */ jsx("div", { ref, className: cn("mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props });
121
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("nx-dialog-footer mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props });
102
122
  }
103
123
  );
104
124
  Footer.displayName = "Dialog.Footer";
125
+ const Body = React.forwardRef(
126
+ function DialogBody({ className, ...props }, ref) {
127
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("nx-dialog-body", className), ...props });
128
+ }
129
+ );
130
+ Body.displayName = "Dialog.Body";
105
131
  const Dialog = {
106
132
  Root,
107
133
  Trigger,
@@ -112,6 +138,7 @@ const Dialog = {
112
138
  Title,
113
139
  Description,
114
140
  Header,
141
+ Body,
115
142
  Footer
116
143
  };
117
144
  export {
@@ -1,6 +1,12 @@
1
1
  import * as React from 'react';
2
+ export interface EmptyStateRootProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /** Scales spacing, icon and text. @default 'md' */
4
+ size?: 'sm' | 'md' | 'lg';
5
+ /** Fades in on mount (nx-reveal; reduced motion keeps the crossfade). */
6
+ animate?: boolean;
7
+ }
2
8
  export declare const EmptyState: {
3
- Root: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
9
+ Root: React.ForwardRefExoticComponent<EmptyStateRootProps & React.RefAttributes<HTMLDivElement>>;
4
10
  Illustration: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
5
11
  Title: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & React.RefAttributes<HTMLHeadingElement>>;
6
12
  Description: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
@@ -1,13 +1,17 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cn } from "../../utils/cn.js";
4
+ const rootSizes = { sm: "gap-3 py-8", md: "gap-4 py-16", lg: "gap-6 py-24" };
4
5
  const Root = React.forwardRef(
5
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
6
+ ({ className, size = "md", animate, ...props }, ref) => /* @__PURE__ */ jsx(
6
7
  "div",
7
8
  {
8
9
  ref,
10
+ "data-size": size,
9
11
  className: cn(
10
- "flex flex-col items-center justify-center gap-4 py-16 text-center",
12
+ "group/empty-state flex flex-col items-center justify-center text-center",
13
+ rootSizes[size],
14
+ animate && "nx-reveal",
11
15
  className
12
16
  ),
13
17
  ...props
@@ -20,7 +24,11 @@ const Illustration = React.forwardRef(
20
24
  "div",
21
25
  {
22
26
  ref,
23
- className: cn("text-[var(--color-on-surface-muted)]", className),
27
+ className: cn(
28
+ "text-[var(--color-on-surface-muted)]",
29
+ "group-data-[size=sm]/empty-state:[&_svg]:size-8 group-data-[size=lg]/empty-state:[&_svg]:size-16",
30
+ className
31
+ ),
24
32
  ...props
25
33
  }
26
34
  )
@@ -31,7 +39,11 @@ const Title = React.forwardRef(
31
39
  "h3",
32
40
  {
33
41
  ref,
34
- className: cn("text-lg font-semibold text-[var(--color-on-surface)]", className),
42
+ className: cn(
43
+ "text-lg font-semibold text-[var(--color-on-surface)]",
44
+ "group-data-[size=sm]/empty-state:text-base group-data-[size=lg]/empty-state:text-xl",
45
+ className
46
+ ),
35
47
  ...props
36
48
  }
37
49
  )
@@ -41,7 +53,11 @@ const Description = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
41
53
  "p",
42
54
  {
43
55
  ref,
44
- className: cn("max-w-sm text-sm text-[var(--color-on-surface-muted)]", className),
56
+ className: cn(
57
+ "max-w-sm text-sm text-[var(--color-on-surface-muted)]",
58
+ "group-data-[size=sm]/empty-state:text-xs group-data-[size=lg]/empty-state:text-base",
59
+ className
60
+ ),
45
61
  ...props
46
62
  }
47
63
  ));
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import type { NexubMessages } from '@nexub/core';
3
+ import { type EmptyStateRootProps } from './EmptyState.js';
3
4
  export type EmptyStateKind = keyof NexubMessages['emptyStates'];
4
- export interface EmptyStatePresetProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
5
+ export interface EmptyStatePresetProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>, Pick<EmptyStateRootProps, 'size' | 'animate'> {
5
6
  /** noData, noResults, noAccess (403), serverError (500) or offline. */
6
7
  kind: EmptyStateKind;
7
8
  /** Overrides the preset's title; the description too. Both default to localised text. */