@datum-cloud/datum-ui 2.9.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,11 @@
1
1
  import { t as cn } from "../utils-Bu32wN-x.mjs";
2
+ import { t as Button } from "../button-DoMqfQLu.mjs";
2
3
  import { t as Icon } from "../icon-wrapper-DKfJlJd0.mjs";
4
+ import { t as Dialog } from "../dialog-Gu70fban.mjs";
3
5
  import { a as DropdownMenuRadioGroup, c as DropdownMenuTrigger, i as DropdownMenuLabel, o as DropdownMenuRadioItem, r as DropdownMenuContent, s as DropdownMenuSeparator, t as DropdownMenu } from "../dropdown-menu-b8zY5Cs6.mjs";
4
6
  import { t as Tooltip } from "../tooltip-DhbN1BDK.mjs";
5
7
  import { n as EASE, t as DURATION } from "../motion-DvGRWdsm.mjs";
6
- import { ArrowDown, ArrowRight, ArrowUp, Brain, Check, ChevronDown, Copy, Download, MessagesSquare, Mic, MicOff, PanelLeft, Plus, RotateCw, Square, Trash2 } from "lucide-react";
8
+ import { Archive, ArchiveRestore, ArrowDown, ArrowRight, ArrowUp, Brain, Check, ChevronDown, Copy, Download, MessagesSquare, Mic, MicOff, PanelLeft, Plus, RotateCw, Square, Trash2 } from "lucide-react";
7
9
  import { Fragment, createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
8
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
11
  import { AnimatePresence, motion } from "motion/react";
@@ -842,12 +844,32 @@ function downloadChat(chat) {
842
844
  a.click();
843
845
  URL.revokeObjectURL(url);
844
846
  }
845
- function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, header }) {
847
+ const ROW_ACTION_CLASS = "text-muted-foreground/40 shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100";
848
+ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete = false, header }) {
849
+ const archiveEnabled = Boolean(onArchiveChat);
846
850
  const [query, setQuery] = useState("");
851
+ const [showArchived, setShowArchived] = useState(false);
852
+ const [pendingDelete, setPendingDelete] = useState(null);
853
+ const inArchivedView = archiveEnabled && showArchived;
847
854
  const filtered = useMemo(() => {
855
+ const inView = archiveEnabled ? chatList.filter((c) => Boolean(c.archived) === showArchived) : chatList;
848
856
  const q = query.trim().toLowerCase();
849
- return q ? chatList.filter((c) => c.title.toLowerCase().includes(q)) : chatList;
850
- }, [chatList, query]);
857
+ return q ? inView.filter((c) => c.title.toLowerCase().includes(q)) : inView;
858
+ }, [
859
+ archiveEnabled,
860
+ chatList,
861
+ query,
862
+ showArchived
863
+ ]);
864
+ const emptyText = inArchivedView ? query ? "No matching archived chats" : "No archived chats" : query ? "No matching chats" : "No saved chats";
865
+ const handleDelete = (e, chat) => {
866
+ if (!confirmDelete) {
867
+ onDeleteChat(e, chat.id);
868
+ return;
869
+ }
870
+ e.stopPropagation();
871
+ setPendingDelete(chat);
872
+ };
851
873
  return /* @__PURE__ */ jsxs("div", {
852
874
  className: "bg-background flex h-full w-64 shrink-0 flex-col border-r",
853
875
  children: [
@@ -855,36 +877,90 @@ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, heade
855
877
  className: "shrink-0 border-b px-3 py-2",
856
878
  children: header
857
879
  }),
858
- /* @__PURE__ */ jsx("div", {
859
- className: "flex h-12 items-center border-b px-2",
860
- children: /* @__PURE__ */ jsx("input", {
880
+ /* @__PURE__ */ jsxs("div", {
881
+ className: "flex h-12 items-center gap-1 border-b px-2",
882
+ children: [/* @__PURE__ */ jsx("input", {
861
883
  value: query,
862
884
  onChange: (e) => setQuery(e.target.value),
863
- placeholder: "Search chats…",
885
+ placeholder: inArchivedView ? "Search archived chats…" : "Search chats…",
864
886
  className: "bg-muted text-foreground placeholder:text-muted-foreground/70 focus:ring-primary/40 w-full rounded-md px-2.5 py-1.5 text-xs focus:ring-1 focus:outline-none"
865
- })
887
+ }), archiveEnabled && /* @__PURE__ */ jsx(Tooltip, {
888
+ message: showArchived ? "Show active chats" : "Show archived chats",
889
+ side: "bottom",
890
+ children: /* @__PURE__ */ jsx("button", {
891
+ type: "button",
892
+ "aria-label": "Archived",
893
+ "aria-pressed": showArchived,
894
+ onClick: () => setShowArchived((v) => !v),
895
+ className: cn("shrink-0 rounded-md p-1.5 transition-colors", showArchived ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-accent"),
896
+ children: /* @__PURE__ */ jsx(Icon, {
897
+ icon: Archive,
898
+ className: "size-3.5"
899
+ })
900
+ })
901
+ })]
866
902
  }),
867
903
  /* @__PURE__ */ jsx("div", {
868
904
  className: "flex flex-1 flex-col gap-0.5 overflow-y-auto p-2",
869
905
  children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", {
870
906
  className: "text-muted-foreground/60 px-2 py-1 text-xs",
871
- children: query ? "No matching chats" : "No saved chats"
907
+ children: emptyText
872
908
  }) : filtered.map((chat) => /* @__PURE__ */ jsxs("button", {
873
909
  type: "button",
874
910
  onClick: () => onLoadChat(chat),
875
911
  className: cn("group w-full rounded-lg px-2 py-1.5 text-left transition-colors", chat.id === currentChatId ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-accent/50"),
876
912
  children: [/* @__PURE__ */ jsxs("span", {
877
913
  className: "flex items-center gap-1",
878
- children: [/* @__PURE__ */ jsx("span", {
879
- className: "min-w-0 flex-1 truncate text-xs font-medium",
880
- children: chat.title
881
- }), /* @__PURE__ */ jsx("span", {
882
- role: "button",
883
- onClick: (e) => onDeleteChat(e, chat.id),
884
- "aria-label": "Delete chat",
885
- className: "text-muted-foreground/40 hover:text-destructive shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100",
886
- children: /* @__PURE__ */ jsx(Trash2, { className: "size-3" })
887
- })]
914
+ children: [
915
+ /* @__PURE__ */ jsx("span", {
916
+ className: "min-w-0 flex-1 truncate text-xs font-medium",
917
+ children: chat.title
918
+ }),
919
+ archiveEnabled && !inArchivedView && /* @__PURE__ */ jsx(Tooltip, {
920
+ message: "Archive chat",
921
+ side: "top",
922
+ children: /* @__PURE__ */ jsx("span", {
923
+ role: "button",
924
+ onClick: (e) => {
925
+ e.stopPropagation();
926
+ onArchiveChat?.(e, chat.id);
927
+ },
928
+ "aria-label": "Archive chat",
929
+ className: cn(ROW_ACTION_CLASS, "hover:text-foreground"),
930
+ children: /* @__PURE__ */ jsx(Icon, {
931
+ icon: Archive,
932
+ className: "size-3"
933
+ })
934
+ })
935
+ }),
936
+ inArchivedView && onUnarchiveChat && /* @__PURE__ */ jsx(Tooltip, {
937
+ message: "Unarchive chat",
938
+ side: "top",
939
+ children: /* @__PURE__ */ jsx("span", {
940
+ role: "button",
941
+ onClick: (e) => {
942
+ e.stopPropagation();
943
+ onUnarchiveChat(e, chat.id);
944
+ },
945
+ "aria-label": "Unarchive chat",
946
+ className: cn(ROW_ACTION_CLASS, "hover:text-foreground"),
947
+ children: /* @__PURE__ */ jsx(Icon, {
948
+ icon: ArchiveRestore,
949
+ className: "size-3"
950
+ })
951
+ })
952
+ }),
953
+ /* @__PURE__ */ jsx("span", {
954
+ role: "button",
955
+ onClick: (e) => handleDelete(e, chat),
956
+ "aria-label": "Delete chat",
957
+ className: cn(ROW_ACTION_CLASS, "hover:text-destructive"),
958
+ children: /* @__PURE__ */ jsx(Icon, {
959
+ icon: Trash2,
960
+ className: "size-3"
961
+ })
962
+ })
963
+ ]
888
964
  }), /* @__PURE__ */ jsxs("span", {
889
965
  className: "flex items-center gap-1",
890
966
  children: [/* @__PURE__ */ jsx("span", {
@@ -900,8 +976,11 @@ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, heade
900
976
  downloadChat(chat);
901
977
  },
902
978
  "aria-label": "Download chat as Markdown",
903
- className: "text-muted-foreground/40 hover:text-foreground shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100",
904
- children: /* @__PURE__ */ jsx(Download, { className: "size-3" })
979
+ className: cn(ROW_ACTION_CLASS, "hover:text-foreground"),
980
+ children: /* @__PURE__ */ jsx(Icon, {
981
+ icon: Download,
982
+ className: "size-3"
983
+ })
905
984
  })
906
985
  })]
907
986
  })]
@@ -910,13 +989,40 @@ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, heade
910
989
  /* @__PURE__ */ jsx("p", {
911
990
  className: "text-muted-foreground mt-auto shrink-0 border-t px-3 py-2 text-[10px]",
912
991
  children: "Chats are saved to your browser's local storage."
992
+ }),
993
+ confirmDelete && /* @__PURE__ */ jsx(Dialog, {
994
+ open: pendingDelete !== null,
995
+ onOpenChange: (open) => !open && setPendingDelete(null),
996
+ children: /* @__PURE__ */ jsxs(Dialog.Content, {
997
+ className: "sm:max-w-md",
998
+ children: [/* @__PURE__ */ jsx(Dialog.Header, {
999
+ title: "Delete chat?",
1000
+ description: pendingDelete ? `"${pendingDelete.title}" will be permanently deleted. This can't be undone.` : void 0
1001
+ }), /* @__PURE__ */ jsxs(Dialog.Footer, {
1002
+ className: "border-t-0",
1003
+ children: [/* @__PURE__ */ jsx(Button, {
1004
+ type: "tertiary",
1005
+ theme: "outline",
1006
+ onClick: () => setPendingDelete(null),
1007
+ children: "Cancel"
1008
+ }), /* @__PURE__ */ jsx(Button, {
1009
+ type: "danger",
1010
+ theme: "solid",
1011
+ onClick: (e) => {
1012
+ if (pendingDelete) onDeleteChat(e, pendingDelete.id);
1013
+ setPendingDelete(null);
1014
+ },
1015
+ children: "Delete"
1016
+ })]
1017
+ })]
1018
+ })
913
1019
  })
914
1020
  ]
915
1021
  });
916
1022
  }
917
1023
  //#endregion
918
1024
  //#region src/components/features/assistant/components/assistant-workspace.tsx
919
- function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
1025
+ function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
920
1026
  const hasMessages = messages.length > 0;
921
1027
  const promptCard = /* @__PURE__ */ jsx(PromptCard, {
922
1028
  editor,
@@ -961,7 +1067,10 @@ function AssistantWorkspace({ config, userName, title, messages, status, error,
961
1067
  currentChatId,
962
1068
  header: sidebarHeader,
963
1069
  onLoadChat,
964
- onDeleteChat
1070
+ onDeleteChat,
1071
+ onArchiveChat,
1072
+ onUnarchiveChat,
1073
+ confirmDelete
965
1074
  })
966
1075
  })
967
1076
  }),
@@ -38,6 +38,16 @@ export interface AssistantWorkspaceProps {
38
38
  onNewChat: () => void;
39
39
  onLoadChat: (chat: ChatSummary) => void;
40
40
  onDeleteChat: (e: ReactMouseEvent, chatId: string) => void;
41
+ /**
42
+ * Opt-in archive support. When provided, `chatList` may mix archived and
43
+ * active chats; the history panel lists active chats by default and adds an
44
+ * "Archived" view toggle plus per-row archive actions.
45
+ */
46
+ onArchiveChat?: (e: ReactMouseEvent, chatId: string) => void;
47
+ /** Restores an archived chat from the history panel's archived view. */
48
+ onUnarchiveChat?: (e: ReactMouseEvent, chatId: string) => void;
49
+ /** Confirm (deletion can't be undone) before calling `onDeleteChat`. */
50
+ confirmDelete?: boolean;
41
51
  onSuggestion: (text: string) => void;
42
52
  modelId: string;
43
53
  effortId: EffortId;
@@ -50,4 +60,4 @@ export interface AssistantWorkspaceProps {
50
60
  historyOpen: boolean;
51
61
  onToggleHistory: () => void;
52
62
  }
53
- export declare function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory, }: AssistantWorkspaceProps): import("react").JSX.Element;
63
+ export declare function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory, }: AssistantWorkspaceProps): import("react").JSX.Element;
@@ -1,10 +1,24 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { ChatSummary } from '../../types';
3
- interface HistoryPanelProps {
3
+ export interface HistoryPanelProps {
4
4
  chatList: ChatSummary[];
5
5
  currentChatId: string;
6
6
  onLoadChat: (chat: ChatSummary) => void;
7
7
  onDeleteChat: (e: React.MouseEvent, chatId: string) => void;
8
+ /**
9
+ * Opt-in archive support. When provided, `chatList` may mix archived and
10
+ * active chats: the panel lists active chats by default, adds an "Archived"
11
+ * view toggle next to the search input, and gives active rows an archive
12
+ * action. When omitted, the `archived` flag is ignored and no archive UI renders.
13
+ */
14
+ onArchiveChat?: (e: React.MouseEvent, chatId: string) => void;
15
+ /** Restores an archived chat. Rendered on rows in the archived view. */
16
+ onUnarchiveChat?: (e: React.MouseEvent, chatId: string) => void;
17
+ /**
18
+ * Ask for confirmation (deletion can't be undone) before calling
19
+ * `onDeleteChat`. Defaults to false, which deletes immediately.
20
+ */
21
+ confirmDelete?: boolean;
8
22
  /**
9
23
  * Optional host-supplied block pinned above the search input, naming the scope
10
24
  * the listed chats belong to — cloud-portal shows the current project. Hosts
@@ -12,5 +26,4 @@ interface HistoryPanelProps {
12
26
  */
13
27
  header?: ReactNode;
14
28
  }
15
- export declare function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, header, }: HistoryPanelProps): import("react").JSX.Element;
16
- export {};
29
+ export declare function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, header, }: HistoryPanelProps): import("react").JSX.Element;
@@ -63,4 +63,9 @@ export interface ChatSummary {
63
63
  title: string;
64
64
  updatedAt: number;
65
65
  messages: UIMessage[];
66
+ /**
67
+ * Archived (hidden but restorable). Only honoured when the host passes
68
+ * `onArchiveChat`; otherwise every chat is listed regardless of this flag.
69
+ */
70
+ archived?: boolean;
66
71
  }
@@ -1,3 +1,3 @@
1
1
  import type { RowData } from '@tanstack/react-table';
2
2
  import type { ColumnHeaderProps } from '../types';
3
- export declare function DataTableColumnHeader<TData extends RowData, TValue>({ column, title, className, }: ColumnHeaderProps<TData, TValue>): import("react").JSX.Element;
3
+ export declare function DataTableColumnHeader<TData extends RowData, TValue>({ column, title, className, density, }: ColumnHeaderProps<TData, TValue>): import("react").JSX.Element;
@@ -1,2 +1,3 @@
1
+ import type { RowData } from '@tanstack/react-table';
1
2
  import type { ContentProps } from '../types';
2
- export declare function DataTableContent({ emptyMessage, className, tableClassName, headerClassName, headerRowClassName, headerCellClassName, bodyClassName, rowClassName, cellClassName, }: ContentProps): import("react").JSX.Element;
3
+ export declare function DataTableContent<TData extends RowData = Record<string, any>>({ emptyMessage, className, tableClassName, headerClassName, headerRowClassName, headerCellClassName, bodyClassName, rowClassName, cellClassName, density, onRowClick, }: ContentProps<TData>): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { ListPaginationProps } from '../types';
2
+ /**
3
+ * Compact list-page footer: `[pageSize ▾] Rows per page … 1-100 of N [resource]`
4
+ * + joined prev/next buttons. Pass `labels` to localize — this package ships no
5
+ * i18n of its own.
6
+ */
7
+ export declare function DataTableListPagination({ pageSizes, resourceLabel, hideWhenSinglePage, labels, className, }: ListPaginationProps): import("react").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import type { RowData } from '@tanstack/react-table';
2
+ import type { ListPanelProps } from '../types';
3
+ /**
4
+ * Compact "list table" card: search row + dense sticky-header table, matching
5
+ * staff-portal's Figma org-list pattern. Renders inside `DataTable.Client` /
6
+ * `DataTable.Server`, same as every other `DataTable.*` part. Pagination is
7
+ * deliberately not included — render `DataTable.ListPagination` as a sibling,
8
+ * below the panel.
9
+ */
10
+ export declare function DataTableListPanel<TData extends RowData = Record<string, any>>({ search, searchSlot, toolbar, emptyMessage, loading, onRowClick, className, panelClassName, }: ListPanelProps<TData>): import("react").JSX.Element;
@@ -2,6 +2,8 @@ import { DataTableBulkActions } from './components/bulk-actions';
2
2
  import { DataTableColumnHeader } from './components/column-header';
3
3
  import { DataTableContent } from './components/content';
4
4
  import { DataTableInlineContent } from './components/inline-content';
5
+ import { DataTableListPagination } from './components/list-pagination';
6
+ import { DataTableListPanel } from './components/list-panel';
5
7
  import { DataTableLoading } from './components/loading';
6
8
  import { DataTablePagination } from './components/pagination';
7
9
  import { DataTableRowActions } from './components/row-actions';
@@ -19,6 +21,8 @@ export declare const DataTable: {
19
21
  readonly InlineContent: typeof DataTableInlineContent;
20
22
  readonly ColumnHeader: typeof DataTableColumnHeader;
21
23
  readonly Pagination: typeof DataTablePagination;
24
+ readonly ListPanel: typeof DataTableListPanel;
25
+ readonly ListPagination: typeof DataTableListPagination;
22
26
  readonly Search: typeof DataTableSearch;
23
27
  readonly RowActions: typeof DataTableRowActions;
24
28
  readonly BulkActions: typeof DataTableBulkActions;
@@ -11,4 +11,4 @@ export type { SearchConfig } from './core/filter-engine';
11
11
  export { createDataTableStore } from './core/store';
12
12
  export { DataTable } from './data-table';
13
13
  export { useDataTableFilters, useDataTableInlineContents, useDataTableLoading, useDataTablePagination, useDataTableRows, useDataTableSearch, useDataTableSelection, useDataTableSorting, } from './hooks/use-selectors';
14
- export type { ActionItem, ActiveFiltersProps, BulkActionsProps, ClientPaginationControls, ClientPaginationState, ColumnHeaderProps, ContentProps, CreateStoreOptions, DataTableClientProps, DataTableContextValue, DataTableServerProps, DataTableState, DataTableStore, DataTableStoreState, FilterCheckboxProps, FilterDatePickerProps, FilterFn, FilterFnMap, FilterOption, FilterSelectProps, FilterStrategy, FilterValue, InlineContentEntry, InlineContentProps, InlineContentRenderParams, LoadingProps, PaginationProps, PaginationState, RowActionsProps, SearchProps, SelectionColumnOptions, ServerFetchArgs, ServerPaginationControls, ServerPaginationState, ServerTransformResult, StateAdapter, UseDataTableClientOptions, UseDataTableServerOptions, } from './types';
14
+ export type { ActionItem, ActiveFiltersProps, BulkActionsProps, ClientPaginationControls, ClientPaginationState, ColumnHeaderProps, ContentProps, CreateStoreOptions, DataTableClientProps, DataTableContextValue, DataTableServerProps, DataTableState, DataTableStore, DataTableStoreState, FilterCheckboxProps, FilterDatePickerProps, FilterFn, FilterFnMap, FilterOption, FilterSelectProps, FilterStrategy, FilterValue, InlineContentEntry, InlineContentProps, InlineContentRenderParams, ListPaginationProps, ListPanelProps, ListPanelSearchConfig, LoadingProps, PaginationProps, PaginationState, RowActionsProps, SearchProps, SelectionColumnOptions, ServerFetchArgs, ServerPaginationControls, ServerPaginationState, ServerTransformResult, StateAdapter, UseDataTableClientOptions, UseDataTableServerOptions, } from './types';
@@ -229,6 +229,8 @@ export interface ColumnHeaderProps<TData extends RowData, TValue> {
229
229
  readonly column: Column<DataTableFeatures, TData, TValue>;
230
230
  readonly title: string;
231
231
  readonly className?: string;
232
+ /** `'compact'` swaps in the dense uppercase header label + dual-caret sort icon used by list pages. Default `'default'` renders unchanged. */
233
+ readonly density?: 'default' | 'compact';
232
234
  }
233
235
  export interface RowActionsProps<TData extends RowData> {
234
236
  readonly row: Row<DataTableFeatures, TData>;
@@ -252,6 +254,10 @@ export interface ContentProps<TData extends RowData = Record<string, any>> {
252
254
  readonly bodyClassName?: string;
253
255
  readonly rowClassName?: string | ((row: Row<DataTableFeatures, TData>) => string);
254
256
  readonly cellClassName?: string | ((cell: Cell<DataTableFeatures, TData, unknown>) => string);
257
+ /** `'compact'` applies the sticky mist header, dense rows, and borderless dividers used by list pages. Default `'default'` renders unchanged. */
258
+ readonly density?: 'default' | 'compact';
259
+ /** Fires on row click. Skipped when the click lands on a link, button, checkbox, form control, or the row-actions menu, so nested interactive elements keep working. */
260
+ readonly onRowClick?: (row: TData) => void;
255
261
  }
256
262
  export interface PaginationProps {
257
263
  readonly pageSizes?: readonly number[];
@@ -282,6 +288,49 @@ export interface LoadingProps {
282
288
  readonly columns?: number;
283
289
  readonly className?: string;
284
290
  }
291
+ /** Search-row config for {@link ListPanelProps}. */
292
+ export interface ListPanelSearchConfig {
293
+ readonly placeholder?: string;
294
+ /**
295
+ * Set together with `onChange` to drive the search input externally (server-side
296
+ * search). When set, the built-in store search is not read — the panel renders
297
+ * whatever rows the caller already scoped.
298
+ */
299
+ readonly value?: string;
300
+ readonly onChange?: (value: string) => void;
301
+ }
302
+ export interface ListPanelProps<TData extends RowData = Record<string, any>> {
303
+ /** `false` hides the search row entirely. Defaults to the built-in (client-filtered) search. */
304
+ readonly search?: false | ListPanelSearchConfig;
305
+ /** Rendered to the right of the search input, inside the search row (e.g. a mobile filter trigger). */
306
+ readonly searchSlot?: ReactNode;
307
+ /** Rendered between the search row and the table (e.g. active-filter chips). */
308
+ readonly toolbar?: ReactNode;
309
+ readonly emptyMessage?: ReactNode;
310
+ /** Renders a skeleton in place of the table body. */
311
+ readonly loading?: boolean;
312
+ /** See {@link ContentProps.onRowClick}. */
313
+ readonly onRowClick?: (row: TData) => void;
314
+ /** Class on the panel's outer element. */
315
+ readonly className?: string;
316
+ /** Class on the card container (border/rounding/background) — merges with, and can override, the default chrome. */
317
+ readonly panelClassName?: string;
318
+ }
319
+ export interface ListPaginationProps {
320
+ readonly pageSizes?: readonly number[];
321
+ /** Appended to the row-count summary, e.g. `"1-20 of 42 organizations"`. */
322
+ readonly resourceLabel?: string;
323
+ /** Hides the whole control when there's one page or fewer. */
324
+ readonly hideWhenSinglePage?: boolean;
325
+ /** English defaults; pass translated strings to localize. */
326
+ readonly labels?: {
327
+ readonly rowsPerPage?: string;
328
+ readonly of?: string;
329
+ readonly previousPage?: string;
330
+ readonly nextPage?: string;
331
+ };
332
+ readonly className?: string;
333
+ }
285
334
  export type FilterStrategy = 'checkbox' | 'select' | 'date-gte' | 'date-lte' | FilterFn;
286
335
  export interface DataTableStoreState<TData extends RowData> {
287
336
  readonly data: TData[];
@@ -1,2 +1,2 @@
1
- import { _ as useNuqsAdapter, a as useDataTablePagination, b as DEFAULT_PAGE_SIZE, c as useDataTableSelection, d as resolvePath, f as rowMatchesSearch, g as createSelectionColumn, h as DataTableColumnHeader, i as useDataTableLoading, l as useDataTableSorting, m as DataTableRowActions, n as useDataTableFilters, o as useDataTableRows, p as dataTableFeatures, r as useDataTableInlineContents, s as useDataTableSearch, t as DataTable, u as createDataTableStore, v as DEFAULT_DEBOUNCE_MS, x as DEFAULT_PAGE_SIZES, y as DEFAULT_LOADING_ROWS } from "../data-table-CW3-xqkg.mjs";
1
+ import { _ as useNuqsAdapter, a as useDataTablePagination, b as DEFAULT_PAGE_SIZE, c as useDataTableSelection, d as resolvePath, f as rowMatchesSearch, g as createSelectionColumn, h as DataTableColumnHeader, i as useDataTableLoading, l as useDataTableSorting, m as DataTableRowActions, n as useDataTableFilters, o as useDataTableRows, p as dataTableFeatures, r as useDataTableInlineContents, s as useDataTableSearch, t as DataTable, u as createDataTableStore, v as DEFAULT_DEBOUNCE_MS, x as DEFAULT_PAGE_SIZES, y as DEFAULT_LOADING_ROWS } from "../data-table-DrSsRKRi.mjs";
2
2
  export { DEFAULT_DEBOUNCE_MS, DEFAULT_LOADING_ROWS, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, DataTable, DataTableColumnHeader, DataTableRowActions, createDataTableStore, createSelectionColumn, dataTableFeatures, resolvePath, rowMatchesSearch, useDataTableFilters, useDataTableInlineContents, useDataTableLoading, useDataTablePagination, useDataTableRows, useDataTableSearch, useDataTableSelection, useDataTableSorting, useNuqsAdapter };
@@ -12,7 +12,7 @@ import { t as CalendarDatePicker } from "./calendar-date-picker-DW31tDSa.mjs";
12
12
  import { t as ActionRow } from "./action-row-DTvs91_3.mjs";
13
13
  import { n as useDebouncedSearchInput } from "./use-debounced-search-input-yu_F0QBt.mjs";
14
14
  import { t as MultiSelect } from "./multi-select-CH8KERkt.mjs";
15
- import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, X } from "lucide-react";
15
+ import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, Search, X } from "lucide-react";
16
16
  import { createContext, memo, use, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
17
17
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
18
18
  import { parseAsInteger, parseAsString, useQueryStates } from "nuqs";
@@ -151,13 +151,54 @@ function withSelectionColumn(columns, options = {}) {
151
151
  }
152
152
  //#endregion
153
153
  //#region src/components/features/data-table/components/column-header.tsx
154
- function DataTableColumnHeader({ column, title, className }) {
154
+ /** Figma `Datum App UI/Tables/Header/Dropdown` — two carets, one dimmed to show sort direction. */
155
+ function CompactSortIcon({ sorted }) {
156
+ return /* @__PURE__ */ jsxs("span", {
157
+ className: "text-muted-foreground relative h-[15px] w-[5px] shrink-0",
158
+ "aria-hidden": "true",
159
+ children: [/* @__PURE__ */ jsx("svg", {
160
+ viewBox: "0 0 5.28572 3.25",
161
+ fill: "none",
162
+ className: cn("absolute inset-[26.66%_6.67%_58.34%_7.62%] overflow-visible", sorted === "desc" ? "opacity-35" : "opacity-100"),
163
+ children: /* @__PURE__ */ jsx("path", {
164
+ d: "M0.5 2.75L2.64286 0.5L4.78571 2.75",
165
+ stroke: "currentColor",
166
+ strokeLinecap: "round",
167
+ strokeLinejoin: "round"
168
+ })
169
+ }), /* @__PURE__ */ jsx("svg", {
170
+ viewBox: "0 0 5.28572 3.25",
171
+ fill: "none",
172
+ className: cn("absolute inset-[61.66%_6.67%_23.34%_7.62%] overflow-visible", sorted === "asc" ? "opacity-35" : "opacity-100"),
173
+ children: /* @__PURE__ */ jsx("path", {
174
+ d: "M0.5 0.5L2.64286 2.75L4.78571 0.5",
175
+ stroke: "currentColor",
176
+ strokeLinecap: "round",
177
+ strokeLinejoin: "round"
178
+ })
179
+ })]
180
+ });
181
+ }
182
+ function DataTableColumnHeader({ column, title, className, density = "default" }) {
183
+ const isCompact = density === "compact";
155
184
  if (!column.getCanSort()) return /* @__PURE__ */ jsx("div", {
156
- className: cn(className),
185
+ className: cn(isCompact && "text-xs leading-4 font-normal tracking-normal text-inherit uppercase", className),
157
186
  "data-slot": "dt-column-header",
158
187
  children: title
159
188
  });
160
189
  const sorted = column.getIsSorted();
190
+ const sortLabel = `Sort by ${title}${sorted === "asc" ? ", sorted ascending" : sorted === "desc" ? ", sorted descending" : ""}`;
191
+ if (isCompact) return /* @__PURE__ */ jsx("div", {
192
+ className: cn("flex items-center", className),
193
+ "data-slot": "dt-column-header",
194
+ children: /* @__PURE__ */ jsxs("button", {
195
+ type: "button",
196
+ className: "hover:text-foreground inline-flex h-9 cursor-pointer items-center gap-2 text-xs leading-4 font-normal tracking-normal text-inherit uppercase",
197
+ onClick: column.getToggleSortingHandler(),
198
+ "aria-label": sortLabel,
199
+ children: [/* @__PURE__ */ jsx("span", { children: title }), /* @__PURE__ */ jsx(CompactSortIcon, { sorted })]
200
+ })
201
+ });
161
202
  return /* @__PURE__ */ jsx("div", {
162
203
  className: cn("flex items-center gap-2", className),
163
204
  "data-slot": "dt-column-header",
@@ -165,7 +206,7 @@ function DataTableColumnHeader({ column, title, className }) {
165
206
  type: "button",
166
207
  className: "flex items-center gap-1 hover:text-foreground -ml-3 h-8 px-3 cursor-pointer",
167
208
  onClick: column.getToggleSortingHandler(),
168
- "aria-label": `Sort by ${title}${sorted === "asc" ? ", sorted ascending" : sorted === "desc" ? ", sorted descending" : ""}`,
209
+ "aria-label": sortLabel,
169
210
  children: [/* @__PURE__ */ jsx("span", { children: title }), sorted === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "size-4" }) : sorted === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "size-4" })]
170
211
  })
171
212
  });
@@ -870,10 +911,33 @@ function DataTableBulkActions({ children, className }) {
870
911
  }
871
912
  //#endregion
872
913
  //#region src/components/features/data-table/components/content.tsx
914
+ /**
915
+ * Per-slot class presets keyed by `density`. `'default'` is intentionally empty
916
+ * so omitting `density` (or passing `'default'`) renders identically to before
917
+ * this prop existed. Every preset only ever *adds* to what a caller passes via
918
+ * the matching `*ClassName` prop — `cn(preset, callerClassName)` below lets the
919
+ * caller's own classes win on conflict.
920
+ */
921
+ const DENSITY_PRESETS = {
922
+ default: {},
923
+ compact: {
924
+ className: "overflow-auto [&>div]:overflow-visible",
925
+ headerClassName: "[&_tr]:border-0",
926
+ headerRowClassName: "border-0 hover:bg-transparent",
927
+ headerCellClassName: cn("sticky top-0 z-10 h-9 border-b border-border bg-table-header-background px-4", "text-xs leading-4 font-normal tracking-normal text-table-header-foreground uppercase"),
928
+ bodyClassName: "[&_tr:last-child]:border-b-0",
929
+ rowClassName: "border-0 hover:bg-muted/30",
930
+ cellClassName: "border-b border-border px-4 py-0.5 text-sm"
931
+ }
932
+ };
873
933
  function resolveClassName(value, item) {
874
934
  if (typeof value === "function") return value(item);
875
935
  return value;
876
936
  }
937
+ /** Clicks inside these should drive their own control, not the row's `onRowClick`. */
938
+ function isInteractiveTarget(target) {
939
+ return target instanceof HTMLElement && target.closest("a, button, [role=\"checkbox\"], input, textarea, select, [data-slot=\"dt-row-actions\"]") != null;
940
+ }
877
941
  function renderInlineContentRow(entry, colSpan, rows) {
878
942
  return /* @__PURE__ */ jsx(TableRow, {
879
943
  "data-slot": "dt-inline-content",
@@ -888,7 +952,7 @@ function renderInlineContentRow(entry, colSpan, rows) {
888
952
  })
889
953
  }, entry.id);
890
954
  }
891
- function DataTableContent({ emptyMessage, className, tableClassName, headerClassName, headerRowClassName, headerCellClassName, bodyClassName, rowClassName, cellClassName }) {
955
+ function DataTableContent({ emptyMessage, className, tableClassName, headerClassName, headerRowClassName, headerCellClassName, bodyClassName, rowClassName, cellClassName, density = "default", onRowClick }) {
892
956
  const { rows, headerGroups, totalColumns } = useDataTableRows();
893
957
  const { isLoading, columnCount } = useDataTableLoading();
894
958
  const { pageSize } = useDataTablePagination();
@@ -896,38 +960,42 @@ function DataTableContent({ emptyMessage, className, tableClassName, headerClass
896
960
  const openInlineContents = useMemo(() => inlineContents.filter((e) => e.open), [inlineContents]);
897
961
  const colSpan = totalColumns;
898
962
  const skeletonColumns = totalColumns || columnCount || 5;
963
+ const preset = DENSITY_PRESETS[density];
899
964
  return /* @__PURE__ */ jsx("div", {
900
- className: cn("datum-ui-data-table", className),
965
+ className: cn("datum-ui-data-table", preset.className, className),
901
966
  "data-slot": "dt",
902
967
  style: { overflowX: "auto" },
903
968
  children: /* @__PURE__ */ jsxs(Table, {
904
969
  className: cn(tableClassName),
905
970
  "data-slot": "dt-table",
906
971
  children: [/* @__PURE__ */ jsx(TableHeader, {
907
- className: cn(headerClassName),
972
+ className: cn(preset.headerClassName, headerClassName),
908
973
  "data-slot": "dt-header",
909
974
  children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
910
- className: cn(headerRowClassName),
975
+ className: cn(preset.headerRowClassName, headerRowClassName),
911
976
  "data-slot": "dt-header-row",
912
977
  children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, {
913
- className: cn(headerCellClassName),
978
+ className: cn(preset.headerCellClassName, headerCellClassName),
914
979
  "data-slot": "dt-header-cell",
915
980
  children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
916
981
  }, header.id))
917
982
  }, headerGroup.id))
918
983
  }), /* @__PURE__ */ jsxs(TableBody, {
919
- className: cn(bodyClassName),
984
+ className: cn(preset.bodyClassName, bodyClassName),
920
985
  "data-slot": "dt-body",
921
986
  children: [openInlineContents.filter((e) => e.position === "top").map((entry) => renderInlineContentRow(entry, colSpan, rows)), rows.length > 0 ? rows.map((row) => {
922
987
  const rowEntry = openInlineContents.find((e) => e.position === "row" && e.rowId === row.id);
923
988
  if (rowEntry) return renderInlineContentRow(rowEntry, colSpan, rows);
924
989
  return /* @__PURE__ */ jsx(TableRow, {
925
- className: cn(resolveClassName(rowClassName, row)),
990
+ className: cn(preset.rowClassName, resolveClassName(rowClassName, row), onRowClick && "cursor-pointer"),
926
991
  style: { transitionProperty: "none" },
927
992
  "data-slot": "dt-row",
928
993
  "data-state": row.getIsSelected() ? "selected" : void 0,
994
+ onClick: onRowClick ? (e) => {
995
+ if (!isInteractiveTarget(e.target)) onRowClick(row.original);
996
+ } : void 0,
929
997
  children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
930
- className: cn(resolveClassName(cellClassName, cell)),
998
+ className: cn(preset.cellClassName, resolveClassName(cellClassName, cell)),
931
999
  "data-slot": "dt-cell",
932
1000
  children: flexRender(cell.column.columnDef.cell, cell.getContext())
933
1001
  }, cell.id))
@@ -1002,6 +1070,168 @@ function DataTableInlineContent({ position, rowId, open, onClose, className, chi
1002
1070
  return null;
1003
1071
  }
1004
1072
  //#endregion
1073
+ //#region src/components/features/data-table/components/list-pagination.tsx
1074
+ const DEFAULT_LIST_PAGE_SIZES = [
1075
+ 10,
1076
+ 20,
1077
+ 50,
1078
+ 100
1079
+ ];
1080
+ const DEFAULT_LABELS = {
1081
+ rowsPerPage: "Rows per page",
1082
+ of: "of",
1083
+ previousPage: "Previous page",
1084
+ nextPage: "Next page"
1085
+ };
1086
+ /**
1087
+ * Compact list-page footer: `[pageSize ▾] Rows per page … 1-100 of N [resource]`
1088
+ * + joined prev/next buttons. Pass `labels` to localize — this package ships no
1089
+ * i18n of its own.
1090
+ */
1091
+ function DataTableListPagination({ pageSizes = DEFAULT_LIST_PAGE_SIZES, resourceLabel, hideWhenSinglePage = false, labels, className }) {
1092
+ const { canNextPage, canPrevPage, nextPage, prevPage, pageIndex, pageCount, pageSize, setPageSize, totalRows } = useDataTablePagination();
1093
+ const t = {
1094
+ ...DEFAULT_LABELS,
1095
+ ...labels
1096
+ };
1097
+ if (hideWhenSinglePage && pageCount <= 1) return null;
1098
+ const startRow = totalRows === 0 ? 0 : pageIndex * pageSize + 1;
1099
+ const endRow = Math.min((pageIndex + 1) * pageSize, totalRows);
1100
+ return /* @__PURE__ */ jsxs("div", {
1101
+ className: cn("flex h-7 w-full items-center justify-between gap-3", className),
1102
+ "data-slot": "dt-list-pagination",
1103
+ children: [/* @__PURE__ */ jsxs("div", {
1104
+ className: "flex items-center gap-3",
1105
+ children: [/* @__PURE__ */ jsxs(Select, {
1106
+ value: String(pageSize),
1107
+ onValueChange: (value) => setPageSize(Number(value)),
1108
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1109
+ className: cn("border-border text-muted-foreground h-7 w-[4.5rem] gap-1.5 rounded-md px-2.5 py-0", "min-h-0 text-xs font-normal shadow-none"),
1110
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: String(pageSize) })
1111
+ }), /* @__PURE__ */ jsx(SelectContent, {
1112
+ side: "top",
1113
+ children: pageSizes.map((size) => /* @__PURE__ */ jsx(SelectItem, {
1114
+ value: String(size),
1115
+ children: size
1116
+ }, size))
1117
+ })]
1118
+ }), /* @__PURE__ */ jsx("span", {
1119
+ className: "text-muted-foreground text-xs leading-4 whitespace-nowrap",
1120
+ children: t.rowsPerPage
1121
+ })]
1122
+ }), /* @__PURE__ */ jsxs("div", {
1123
+ className: "flex items-center gap-3",
1124
+ children: [/* @__PURE__ */ jsx("span", {
1125
+ className: "text-muted-foreground text-xs leading-4 whitespace-nowrap tabular-nums",
1126
+ children: `${startRow}-${endRow} ${t.of} ${totalRows}${resourceLabel ? ` ${resourceLabel}` : ""}`
1127
+ }), /* @__PURE__ */ jsxs("div", {
1128
+ className: "border-border flex items-center overflow-hidden rounded-md border",
1129
+ children: [/* @__PURE__ */ jsx(Button, {
1130
+ theme: "outline",
1131
+ size: "icon",
1132
+ className: "border-border size-7 rounded-none border-0 border-r shadow-none",
1133
+ onClick: prevPage,
1134
+ disabled: !canPrevPage,
1135
+ "aria-label": t.previousPage,
1136
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" })
1137
+ }), /* @__PURE__ */ jsx(Button, {
1138
+ theme: "outline",
1139
+ size: "icon",
1140
+ className: "size-7 rounded-none border-0 shadow-none",
1141
+ onClick: nextPage,
1142
+ disabled: !canNextPage,
1143
+ "aria-label": t.nextPage,
1144
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5" })
1145
+ })]
1146
+ })]
1147
+ })]
1148
+ });
1149
+ }
1150
+ //#endregion
1151
+ //#region src/components/features/data-table/components/search.tsx
1152
+ function DataTableSearch({ placeholder = "Search...", debounceMs = 300, className, disabled }) {
1153
+ const { search, setSearch } = useDataTableSearch();
1154
+ const [inputValue, setInputValue] = useDebouncedSearchInput(search, setSearch, debounceMs);
1155
+ return /* @__PURE__ */ jsx(Input, {
1156
+ placeholder,
1157
+ value: inputValue,
1158
+ onChange: (e) => setInputValue(e.target.value),
1159
+ className,
1160
+ disabled,
1161
+ "aria-label": placeholder,
1162
+ "data-slot": "dt-search"
1163
+ });
1164
+ }
1165
+ //#endregion
1166
+ //#region src/components/features/data-table/components/list-panel.tsx
1167
+ const LOADING_SKELETON_ROWS = 8;
1168
+ const SEARCH_INPUT_CLASS = "h-10 border-0 bg-transparent pl-9 shadow-none focus-visible:shadow-none focus-visible:ring-0";
1169
+ function ListPanelSkeleton({ columnCount }) {
1170
+ const cols = Math.max(columnCount, 1);
1171
+ return /* @__PURE__ */ jsxs("div", {
1172
+ className: "min-h-0 flex-1 overflow-hidden",
1173
+ "data-slot": "dt-list-panel-loading",
1174
+ "aria-busy": "true",
1175
+ children: [/* @__PURE__ */ jsx("div", {
1176
+ className: "border-border bg-table-header-background flex h-9 items-center border-b px-4",
1177
+ children: Array.from({ length: cols }, (_, i) => /* @__PURE__ */ jsx("div", {
1178
+ className: "min-w-0 flex-1 px-2 first:pl-0 last:pr-0",
1179
+ children: /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-16" })
1180
+ }, i))
1181
+ }), Array.from({ length: LOADING_SKELETON_ROWS }, (_, row) => /* @__PURE__ */ jsx("div", {
1182
+ className: "border-border flex h-8 items-center border-b px-4",
1183
+ "data-slot": "dt-list-panel-skeleton-row",
1184
+ children: Array.from({ length: cols }, (_, col) => /* @__PURE__ */ jsx("div", {
1185
+ className: "min-w-0 flex-1 px-2 first:pl-0 last:pr-0",
1186
+ children: /* @__PURE__ */ jsx(Skeleton, { className: cn("h-3", col % 3 === 0 ? "w-3/4" : col % 3 === 1 ? "w-1/2" : "w-2/3") })
1187
+ }, col))
1188
+ }, row))]
1189
+ });
1190
+ }
1191
+ /**
1192
+ * Compact "list table" card: search row + dense sticky-header table, matching
1193
+ * staff-portal's Figma org-list pattern. Renders inside `DataTable.Client` /
1194
+ * `DataTable.Server`, same as every other `DataTable.*` part. Pagination is
1195
+ * deliberately not included — render `DataTable.ListPagination` as a sibling,
1196
+ * below the panel.
1197
+ */
1198
+ function DataTableListPanel({ search = {}, searchSlot, toolbar, emptyMessage, loading = false, onRowClick, className, panelClassName }) {
1199
+ const { totalColumns } = useDataTableRows();
1200
+ const showSearch = search !== false;
1201
+ const isControlledSearch = showSearch && (search.value !== void 0 || search.onChange !== void 0);
1202
+ return /* @__PURE__ */ jsxs("div", {
1203
+ className: cn("bg-card text-card-foreground border-card-border flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border", panelClassName, className),
1204
+ "data-slot": "dt-list-panel",
1205
+ children: [
1206
+ showSearch && /* @__PURE__ */ jsxs("div", {
1207
+ className: "flex shrink-0 items-center gap-1 border-b pr-2",
1208
+ children: [/* @__PURE__ */ jsxs("div", {
1209
+ className: "relative min-w-0 flex-1",
1210
+ children: [/* @__PURE__ */ jsx(Search, {
1211
+ className: "text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2",
1212
+ "aria-hidden": "true"
1213
+ }), isControlledSearch ? /* @__PURE__ */ jsx(Input, {
1214
+ placeholder: search.placeholder,
1215
+ value: search.value,
1216
+ onChange: (e) => search.onChange?.(e.target.value),
1217
+ className: SEARCH_INPUT_CLASS
1218
+ }) : /* @__PURE__ */ jsx(DataTableSearch, {
1219
+ placeholder: search.placeholder,
1220
+ className: SEARCH_INPUT_CLASS
1221
+ })]
1222
+ }), searchSlot]
1223
+ }),
1224
+ toolbar,
1225
+ loading ? /* @__PURE__ */ jsx(ListPanelSkeleton, { columnCount: totalColumns }) : /* @__PURE__ */ jsx(DataTableContent, {
1226
+ density: "compact",
1227
+ emptyMessage,
1228
+ onRowClick,
1229
+ className: "min-h-0 flex-1"
1230
+ })
1231
+ ]
1232
+ });
1233
+ }
1234
+ //#endregion
1005
1235
  //#region src/components/features/data-table/components/loading.tsx
1006
1236
  function DataTableLoading({ rows = 5, columns = 4, className }) {
1007
1237
  return /* @__PURE__ */ jsx("div", {
@@ -1135,21 +1365,6 @@ function DataTablePagination({ pageSizes = DEFAULT_PAGE_SIZES, className }) {
1135
1365
  });
1136
1366
  }
1137
1367
  //#endregion
1138
- //#region src/components/features/data-table/components/search.tsx
1139
- function DataTableSearch({ placeholder = "Search...", debounceMs = 300, className, disabled }) {
1140
- const { search, setSearch } = useDataTableSearch();
1141
- const [inputValue, setInputValue] = useDebouncedSearchInput(search, setSearch, debounceMs);
1142
- return /* @__PURE__ */ jsx(Input, {
1143
- placeholder,
1144
- value: inputValue,
1145
- onChange: (e) => setInputValue(e.target.value),
1146
- className,
1147
- disabled,
1148
- "aria-label": placeholder,
1149
- "data-slot": "dt-search"
1150
- });
1151
- }
1152
- //#endregion
1153
1368
  //#region src/components/features/data-table/hooks/use-data-table-client.ts
1154
1369
  /**
1155
1370
  * Creates a TanStack Table instance from an existing store.
@@ -1622,6 +1837,8 @@ const DataTable = {
1622
1837
  InlineContent: DataTableInlineContent,
1623
1838
  ColumnHeader: DataTableColumnHeader,
1624
1839
  Pagination: DataTablePagination,
1840
+ ListPanel: DataTableListPanel,
1841
+ ListPagination: DataTableListPagination,
1625
1842
  Search: DataTableSearch,
1626
1843
  RowActions: DataTableRowActions,
1627
1844
  BulkActions: DataTableBulkActions,
@@ -1,2 +1,2 @@
1
- import { t as GroupedTable } from "../grouped-table-DKUsSHpe.mjs";
1
+ import { t as GroupedTable } from "../grouped-table-DIFPMKLL.mjs";
2
2
  export { GroupedTable };
@@ -6,7 +6,7 @@ import { t as useControllableState } from "./use-controllable-state-Dna7u3r9.mjs
6
6
  import { Skeleton } from "./skeleton/index.mjs";
7
7
  import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table/index.mjs";
8
8
  import { n as EASE } from "./motion-DvGRWdsm.mjs";
9
- import { f as rowMatchesSearch, g as createSelectionColumn, h as DataTableColumnHeader, m as DataTableRowActions, p as dataTableFeatures } from "./data-table-CW3-xqkg.mjs";
9
+ import { f as rowMatchesSearch, g as createSelectionColumn, h as DataTableColumnHeader, m as DataTableRowActions, p as dataTableFeatures } from "./data-table-DrSsRKRi.mjs";
10
10
  import { n as useDebouncedSearchInput } from "./use-debounced-search-input-yu_F0QBt.mjs";
11
11
  import { InputWithAddons } from "./input-with-addons/index.mjs";
12
12
  import { ChevronRight, Search, X } from "lucide-react";
package/dist/index.mjs CHANGED
@@ -55,7 +55,7 @@ import { i as ClientOnly, n as useTheme, r as ThemeScript, t as ThemeProvider }
55
55
  import { a as formatJson, c as isValidYaml, d as CodeEditor, i as CodeEditorTabs, l as jsonToYaml, n as jsonSchema, o as formatYaml, r as yamlSchema, s as isValidJson, t as createCodeEditorSchema, u as yamlToJson } from "./types-agDpVXBG.mjs";
56
56
  import { t as toast } from "./toast-BUPlDkN3.mjs";
57
57
  import { n as Toaster, t as useToast } from "./use-toast-Bpq0yJIT.mjs";
58
- import { _ as useNuqsAdapter, a as useDataTablePagination, b as DEFAULT_PAGE_SIZE, c as useDataTableSelection, d as resolvePath, f as rowMatchesSearch, g as createSelectionColumn, h as DataTableColumnHeader, i as useDataTableLoading, l as useDataTableSorting, m as DataTableRowActions, n as useDataTableFilters, o as useDataTableRows, p as dataTableFeatures, r as useDataTableInlineContents, s as useDataTableSearch, t as DataTable, u as createDataTableStore, v as DEFAULT_DEBOUNCE_MS, x as DEFAULT_PAGE_SIZES, y as DEFAULT_LOADING_ROWS } from "./data-table-CW3-xqkg.mjs";
58
+ import { _ as useNuqsAdapter, a as useDataTablePagination, b as DEFAULT_PAGE_SIZE, c as useDataTableSelection, d as resolvePath, f as rowMatchesSearch, g as createSelectionColumn, h as DataTableColumnHeader, i as useDataTableLoading, l as useDataTableSorting, m as DataTableRowActions, n as useDataTableFilters, o as useDataTableRows, p as dataTableFeatures, r as useDataTableInlineContents, s as useDataTableSearch, t as DataTable, u as createDataTableStore, v as DEFAULT_DEBOUNCE_MS, x as DEFAULT_PAGE_SIZES, y as DEFAULT_LOADING_ROWS } from "./data-table-DrSsRKRi.mjs";
59
59
  import { t as ActionRow } from "./action-row-DTvs91_3.mjs";
60
60
  import { n as useDebouncedSearchInput, t as DEFAULT_SEARCH_DEBOUNCE_MS } from "./use-debounced-search-input-yu_F0QBt.mjs";
61
61
  import { t as MultiSelect } from "./multi-select-CH8KERkt.mjs";
@@ -71,7 +71,7 @@ import { InputWithAddons } from "./input-with-addons/index.mjs";
71
71
  import { t as TimePicker } from "./time-picker-BzNSUazl.mjs";
72
72
  import { t as Transfer } from "./transfer-DsshYgIk.mjs";
73
73
  import { a as getResponsiveValue, c as GRID_COLUMNS, d as RESPONSIVE_MAP, i as getGutter, l as GRID_PREFIX, n as Row, o as registerMediaQuery, r as RowContext, s as GRID_BREAKPOINTS, t as Col, u as RESPONSIVE_ARRAY } from "./col-BYGWTB4j.mjs";
74
- import { t as GroupedTable } from "./grouped-table-DKUsSHpe.mjs";
74
+ import { t as GroupedTable } from "./grouped-table-DIFPMKLL.mjs";
75
75
  import { t as InputNumber } from "./input-number-TXmW-X9o.mjs";
76
76
  import { a as logoStyles, i as LogoFlat, n as LogoStacked, r as LogoIcon, t as LogoText } from "./logo-text-Dpm5O6Kg.mjs";
77
77
  import { t as Logo } from "./logo-C3FxD7jb.mjs";
@@ -118,6 +118,9 @@ Light-mode tokens on :root, dark-mode tokens on .dark
118
118
  --table-accent: var(--glacier-mist-800);
119
119
  --table-cell: hsl(0 0% 100%);
120
120
  --table-cell-hover: var(--glacier-mist-700);
121
+ /* Sticky "mist" list-table header (DataTable.Content density="compact" / DataTable.ListPanel) */
122
+ --table-header-background: var(--glacier-mist-700);
123
+ --table-header-foreground: color-mix(in oklch, var(--midnight-fjord) 60%, transparent);
121
124
 
122
125
  /* Badge */
123
126
  --badge-primary: var(--primary);
@@ -320,6 +323,8 @@ Light-mode tokens on :root, dark-mode tokens on .dark
320
323
  --table-accent: var(--app-dark-utility-1);
321
324
  --table-cell: var(--midnight-fjord);
322
325
  --table-cell-hover: var(--app-dark-utility-1);
326
+ --table-header-background: var(--muted);
327
+ --table-header-foreground: var(--muted-foreground);
323
328
 
324
329
  /* Badge */
325
330
  --badge-primary: var(--primary);
@@ -425,6 +430,15 @@ TAILWIND CUSTOM UTILITIES FOR THEME
425
430
  /* Custom base shadow */
426
431
  --shadow: 0 2px 4px 2px rgba(12, 29, 49, 0.03);
427
432
  --shadow-tooltip: 0 4px 16px 0 rgba(0, 0, 0, 0.05), 1px 8px 8px 0 rgba(0, 0, 0, 0.02);
433
+ /**
434
+ * Section-card shadow (Figma org-overview cards). Aliases the base `--shadow`
435
+ * rather than a standalone value — applies identically in light and dark since
436
+ * `--shadow` itself isn't theme-split. Consumers reference it directly via
437
+ * `shadow-(--section-card-shadow)` (e.g. staff-portal's `SECTION_CARD_CHROME`),
438
+ * which previously pointed at an undefined custom property and rendered no
439
+ * shadow at all.
440
+ */
441
+ --section-card-shadow: var(--shadow);
428
442
 
429
443
  --color-tertiary: var(--tertiary);
430
444
  --color-tertiary-foreground: var(--tertiary-foreground);
@@ -445,6 +459,8 @@ TAILWIND CUSTOM UTILITIES FOR THEME
445
459
  --color-table-accent: var(--table-accent);
446
460
  --color-table-cell: var(--table-cell);
447
461
  --color-table-cell-hover: var(--table-cell-hover);
462
+ --color-table-header-background: var(--table-header-background);
463
+ --color-table-header-foreground: var(--table-header-foreground);
448
464
 
449
465
  --color-dialog-overlay: var(--dialog-overlay);
450
466
  --color-dialog-border: var(--dialog-border);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datum-cloud/datum-ui",
3
3
  "type": "module",
4
- "version": "2.9.1",
4
+ "version": "2.11.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "url": "https://github.com/datum-cloud/datum-ui"