@sincpro/web-ui 0.2.0 → 1.0.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 (128) hide show
  1. package/README.md +3 -3
  2. package/dist/components/activity/ActivityBadge.d.ts +16 -0
  3. package/dist/components/activity/ActivityBadge.js +29 -0
  4. package/dist/components/activity/index.d.ts +1 -0
  5. package/dist/components/activity/index.js +1 -0
  6. package/dist/components/calendar/RecordCalendar.d.ts +48 -0
  7. package/dist/components/calendar/RecordCalendar.js +101 -0
  8. package/dist/components/calendar/index.d.ts +1 -0
  9. package/dist/components/calendar/index.js +1 -0
  10. package/dist/components/form/Sheet.js +5 -1
  11. package/dist/components/gantt/RecordGantt.d.ts +44 -0
  12. package/dist/components/gantt/RecordGantt.js +110 -0
  13. package/dist/components/gantt/index.d.ts +1 -0
  14. package/dist/components/gantt/index.js +1 -0
  15. package/dist/components/index.d.ts +3 -0
  16. package/dist/components/index.js +3 -0
  17. package/dist/components/lines/LineItems.d.ts +5 -2
  18. package/dist/components/lines/LineItems.js +3 -3
  19. package/dist/components/listing/BoardColumns.d.ts +39 -0
  20. package/dist/components/listing/BoardColumns.js +138 -0
  21. package/dist/components/listing/ListingBar.d.ts +5 -0
  22. package/dist/components/listing/ListingBar.js +5 -2
  23. package/dist/components/listing/PivotExplorer.d.ts +34 -0
  24. package/dist/components/listing/PivotExplorer.js +86 -0
  25. package/dist/components/listing/RecordCard.d.ts +6 -8
  26. package/dist/components/listing/RecordCard.js +13 -7
  27. package/dist/components/listing/Row.d.ts +21 -2
  28. package/dist/components/listing/Row.js +29 -13
  29. package/dist/components/listing/index.d.ts +2 -0
  30. package/dist/components/listing/index.js +2 -0
  31. package/dist/components/listing/set.d.ts +1 -1
  32. package/dist/components/navigation/index.d.ts +0 -2
  33. package/dist/components/navigation/index.js +2 -3
  34. package/dist/components/record/RecordAudit.d.ts +17 -0
  35. package/dist/components/record/RecordAudit.js +27 -0
  36. package/dist/components/record/RecordHeader.d.ts +7 -1
  37. package/dist/components/record/RecordHeader.js +30 -18
  38. package/dist/components/record/RecordRibbon.d.ts +13 -0
  39. package/dist/components/record/RecordRibbon.js +22 -0
  40. package/dist/components/record/index.d.ts +2 -0
  41. package/dist/components/record/index.js +2 -0
  42. package/dist/components/search/CriteriaChips.d.ts +6 -1
  43. package/dist/components/search/CriteriaChips.js +71 -20
  44. package/dist/components/search/SearchBar.d.ts +4 -1
  45. package/dist/components/search/SearchBar.js +15 -7
  46. package/dist/components/timeline/RecordTimeline.d.ts +60 -0
  47. package/dist/components/timeline/RecordTimeline.js +214 -0
  48. package/dist/components/timeline/TimelineToolbar.d.ts +30 -0
  49. package/dist/components/timeline/TimelineToolbar.js +31 -0
  50. package/dist/components/timeline/index.d.ts +3 -0
  51. package/dist/components/timeline/index.js +3 -0
  52. package/dist/components/timeline/time.d.ts +43 -0
  53. package/dist/components/timeline/time.js +135 -0
  54. package/dist/foundations/copy.d.ts +106 -14
  55. package/dist/foundations/copy.es.js +83 -9
  56. package/dist/foundations/copy.js +83 -9
  57. package/dist/foundations/index.d.ts +1 -1
  58. package/dist/foundations/match.d.ts +23 -0
  59. package/dist/foundations/match.js +63 -0
  60. package/dist/foundations/slots.d.ts +1 -1
  61. package/dist/foundations/slots.js +8 -3
  62. package/dist/foundations/types.d.ts +7 -0
  63. package/dist/foundations/types.js +0 -36
  64. package/dist/primitives/block/index.d.ts +1 -1
  65. package/dist/primitives/block/index.js +1 -1
  66. package/dist/primitives/cell/CellStack.d.ts +7 -1
  67. package/dist/primitives/cell/CellStack.js +5 -2
  68. package/dist/primitives/choice/ComboBox.js +8 -4
  69. package/dist/primitives/choice/KanbanState.d.ts +14 -0
  70. package/dist/primitives/choice/KanbanState.js +37 -0
  71. package/dist/primitives/choice/Priority.d.ts +20 -0
  72. package/dist/primitives/choice/Priority.js +41 -0
  73. package/dist/primitives/choice/Slider.d.ts +6 -1
  74. package/dist/primitives/choice/Slider.js +2 -2
  75. package/dist/primitives/choice/index.d.ts +2 -0
  76. package/dist/primitives/choice/index.js +2 -0
  77. package/dist/primitives/dialog/Dialog.d.ts +9 -3
  78. package/dist/primitives/dialog/Dialog.js +41 -5
  79. package/dist/primitives/dialog/index.d.ts +0 -1
  80. package/dist/primitives/dialog/index.js +0 -2
  81. package/dist/primitives/notice/index.d.ts +1 -1
  82. package/dist/primitives/notice/index.js +1 -1
  83. package/dist/primitives/overlay/Preview.js +13 -4
  84. package/dist/primitives/picker/MatchedText.d.ts +10 -0
  85. package/dist/primitives/picker/MatchedText.js +12 -0
  86. package/dist/primitives/picker/Picker.d.ts +29 -7
  87. package/dist/primitives/picker/Picker.js +88 -23
  88. package/dist/primitives/table/EditableTable.d.ts +6 -1
  89. package/dist/primitives/table/EditableTable.js +70 -9
  90. package/dist/primitives/table/ReportTable.js +2 -1
  91. package/dist/primitives/table/RowActions.js +7 -2
  92. package/dist/primitives/table/Table.d.ts +5 -0
  93. package/dist/primitives/table/Table.js +17 -8
  94. package/dist/primitives/tag/Tags.d.ts +2 -0
  95. package/dist/primitives/tag/Tags.js +2 -1
  96. package/dist/primitives/tag/TagsField.d.ts +10 -0
  97. package/dist/primitives/tag/TagsField.js +34 -9
  98. package/dist/primitives/tag/choices.d.ts +2 -0
  99. package/dist/primitives/value/Avatar.d.ts +16 -0
  100. package/dist/primitives/value/Avatar.js +91 -0
  101. package/dist/primitives/value/Person.d.ts +6 -51
  102. package/dist/primitives/value/Person.js +9 -148
  103. package/dist/primitives/value/Relation.d.ts +21 -9
  104. package/dist/primitives/value/Relation.js +35 -24
  105. package/dist/primitives/value/contact.d.ts +30 -0
  106. package/dist/primitives/value/contact.js +51 -0
  107. package/dist/primitives/value/index.d.ts +1 -1
  108. package/dist/primitives/value/index.js +1 -1
  109. package/dist/tokens.css +136 -1
  110. package/dist/views/list/List.d.ts +25 -2
  111. package/dist/views/list/List.js +48 -8
  112. package/dist/views/list/ListView.d.ts +1 -1
  113. package/dist/views/list/ListView.js +39 -8
  114. package/dist/views/list/columns.js +8 -1
  115. package/dist/views/list/presentations.d.ts +5 -2
  116. package/dist/views/list/presentations.js +32 -2
  117. package/dist/views/list/types.d.ts +44 -0
  118. package/dist/views/list/useNarrowList.d.ts +10 -0
  119. package/dist/views/list/useNarrowList.js +20 -6
  120. package/dist/views/record/Record.d.ts +14 -0
  121. package/dist/views/record/Record.js +22 -7
  122. package/package.json +11 -4
  123. package/dist/components/navigation/CommandPalette.d.ts +0 -40
  124. package/dist/components/navigation/CommandPalette.js +0 -114
  125. package/dist/components/navigation/commands.d.ts +0 -30
  126. package/dist/components/navigation/commands.js +0 -30
  127. package/dist/primitives/dialog/Drill.d.ts +0 -90
  128. package/dist/primitives/dialog/Drill.js +0 -172
@@ -1,14 +1,19 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { RecordCalendar } from "../../components/calendar/RecordCalendar.js";
4
+ import { RecordGantt } from "../../components/gantt/RecordGantt.js";
5
+ import { BoardColumns } from "../../components/listing/BoardColumns.js";
3
6
  import { useColumnControl, visibleColumns } from "../../components/listing/columns.js";
4
7
  import { ColumnsMenu, specsFromColumns, } from "../../components/listing/ColumnsMenu.js";
5
8
  import { Listing } from "../../components/listing/Listing.js";
6
9
  import { ListingBar } from "../../components/listing/ListingBar.js";
10
+ import { PivotExplorer } from "../../components/listing/PivotExplorer.js";
7
11
  import { RecordCard } from "../../components/listing/RecordCard.js";
8
12
  import { GroupHeader, Row, RowHeader } from "../../components/listing/Row.js";
9
13
  import { sortItems, SortMenu, } from "../../components/listing/SortMenu.js";
10
14
  import { ViewSwitcher } from "../../components/listing/ViewSwitcher.js";
11
15
  import { Breadcrumb } from "../../components/record/Breadcrumb.js";
16
+ import { RecordTimeline } from "../../components/timeline/RecordTimeline.js";
12
17
  import { useUiCopy } from "../../foundations/copy.js";
13
18
  import { useDensity } from "../../foundations/density.js";
14
19
  import { pathKey, pileIsOpen, pilesOf, } from "../../foundations/grouping.js";
@@ -21,7 +26,7 @@ import { groupedColumnIds } from "./columns.js";
21
26
  import { groupLevelsOf, reportColumnsOf, reportRowsOf, sheetFromColumns, sheetRowsOf, } from "./derive.js";
22
27
  import { SheetPresentation, TablePresentation, } from "./presentations.js";
23
28
  import { useNarrowList } from "./useNarrowList.js";
24
- import { Grid3x3, LayoutGrid, List, Sheet, Sigma, Table2 } from "lucide-react";
29
+ import { CalendarDays, CalendarRange, ChartGantt, Grid3x3, LayoutGrid, List, Sheet, Sigma, Table2, } from "lucide-react";
25
30
  import { Fragment, useCallback, useMemo } from "react";
26
31
  /**
27
32
  * EL DIBUJO de una lista. **La puerta es `List`**, que se escribe como árbol y
@@ -57,8 +62,11 @@ const PRESENT_ICONS = [
57
62
  { id: "report", icon: Sigma },
58
63
  /* La cuadrícula: dos ejes, y lo que importa está adentro. */
59
64
  { id: "pivot", icon: Grid3x3 },
65
+ { id: "calendar", icon: CalendarDays },
66
+ { id: "gantt", icon: ChartGantt },
67
+ { id: "timeline", icon: CalendarRange },
60
68
  ];
61
- export function ListView({ items, keyOf, identity, crumbs, embedded = false, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, sheet, report, pivot, present: requested = "rows", presents, onPresentChange, sort, onSortChange, sortable, totals = false, rowActions, create, group, selection, onOpen, onPeek, state, filtered = false, empty, children, }) {
69
+ export function ListView({ items, keyOf, identity, crumbs, embedded = false, surface: requestedSurface, metrics, search, pagination, views, actions, row, rowHeader, columns, visibleColumnIds, onVisibleColumnIdsChange, card, boardStages, calendar, gantt, timeline, sheet, report, pivot, present: requested = "rows", presents, onPresentChange, sort, onSortChange, sortable, totals = false, rowActions, create, group, selection, onOpen, onPeek, rowClick = "peek", onChoose, state, filtered = false, empty, children, }) {
62
70
  const { frame, offered, present } = useNarrowList(presents, requested);
63
71
  /* Embedded, the list sits on the record's paper: there is no dark band to inherit, and a
64
72
  navy switcher inside a white sheet would be a second chrome on the screen. */
@@ -134,6 +142,7 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
134
142
  /* Embedded, the line is drawn only if it has something to OFFER: a count alone repeats
135
143
  what the rows (and the footer) already say. */
136
144
  const barHasControls = !embedded ||
145
+ identity?.title !== undefined ||
137
146
  search !== undefined ||
138
147
  viewsSlot !== undefined ||
139
148
  actions !== undefined ||
@@ -150,12 +159,26 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
150
159
  count: selection.count,
151
160
  onClear: selection.clear,
152
161
  ...(selection.actions === undefined ? {} : { actions: selection.actions }),
162
+ ...(selection.matching === undefined ? {} : { matching: selection.matching }),
153
163
  },
154
164
  }), ...(search === undefined ? {} : { search }), ...(pagination === undefined ? {} : { pagination }), ...(viewsSlot === undefined ? {} : { views: viewsSlot }) }));
165
+ /* The body of a row, a card: the look first unless the list says the page. */
166
+ /* Choosing, the press chooses and the ways in (👁 ↗) are not offered: a chooser's row answers. */
167
+ const choosing = onChoose !== undefined;
168
+ const press = choosing
169
+ ? onChoose
170
+ : rowClick === "open"
171
+ ? (onOpen ?? onPeek)
172
+ : (onPeek ?? onOpen);
155
173
  const renderRow = (item) => {
156
174
  const d = row(item);
157
175
  const id = keyOf(item);
158
- return (_jsx(Row, { ...(d.icon === undefined ? {} : { icon: d.icon }), name: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(d.actions === undefined ? {} : { actions: d.actions }), ...(onOpen === undefined ? {} : { onOpen: () => onOpen(item) }), ...(onPeek === undefined ? {} : { onPeek: () => onPeek(item) }), ...(selection === undefined
176
+ return (_jsx(Row, { ...(d.icon === undefined ? {} : { icon: d.icon }), ...(d.avatar === undefined ? {} : { avatar: d.avatar }), name: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(d.actions === undefined ? {} : { actions: d.actions }), ...(choosing
177
+ ? { onOpen: () => onChoose(item), openers: false }
178
+ : {
179
+ ...(onOpen === undefined ? {} : { onOpen: () => onOpen(item) }),
180
+ ...(onPeek === undefined ? {} : { onPeek: () => onPeek(item) }),
181
+ }), click: rowClick, ...(selection === undefined
159
182
  ? {}
160
183
  : { selected: selection.has(id), onSelectedChange: () => selection.toggle(id) }), children: d.children }, id));
161
184
  };
@@ -235,11 +258,11 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
235
258
  return _jsx(Fragment, { children: card(item) }, keyOf(item));
236
259
  }
237
260
  const d = row(item);
238
- return (_jsx(RecordCard, { title: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(onOpen === undefined ? {} : { onOpen: () => onOpen(item) }) }, keyOf(item)));
261
+ return (_jsx(RecordCard, { title: d.name, ...(d.tags === undefined ? {} : { tags: d.tags }), ...(d.meta === undefined ? {} : { meta: d.meta }), ...(press === undefined ? {} : { onOpen: () => press(item) }) }, keyOf(item)));
239
262
  };
240
263
  const body = present === "table" && shownColumns !== undefined ? (
241
264
  /* The table receives EVERYTHING that belongs to the set: see `TablePresentation`. */
242
- _jsx(TablePresentation, { items: items, columns: shownColumns, keyOf: keyOf, levels: groupLevels, ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), totals: totals, ...(rowActions === undefined ? {} : { rowActions }), ...(create === undefined ? {} : { create }), ...(onPeek === undefined ? {} : { onPeek }), ...(onOpen === undefined ? {} : { onOpen }) })) : present === "sheet" && sheetSpec !== undefined ? (
265
+ _jsx(TablePresentation, { items: items, columns: shownColumns, keyOf: keyOf, levels: groupLevels, ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(group === undefined ? {} : { group }), ...(selection === undefined ? {} : { selection }), totals: totals, ...(rowActions === undefined ? {} : { rowActions }), ...(create === undefined ? {} : { create }), ...(onPeek === undefined || choosing ? {} : { onPeek }), ...(onOpen === undefined || choosing ? {} : { onOpen }), ...(choosing ? { onChoose } : {}), rowClick: rowClick })) : present === "sheet" && sheetSpec !== undefined ? (
243
266
  /* La planilla recibe el conjunto por ÍNDICE y no una matriz ya armada: ver `SheetPresentation`. */
244
267
  _jsx(SheetPresentation, { spec: sheetSpec, entries: sheetRows, ordered: ordered, ...(group === undefined ? {} : { group }) })) : present === "report" && report !== undefined ? (
245
268
  /* El informe declarado por el dominio no se ORDENA desde la barra: el
@@ -255,20 +278,28 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
255
278
  are written. Declared by the domain, the columns are the domain's. */
256
279
  columns: report.rows === undefined
257
280
  ? reportColumnsOf(report.columns, columns)
258
- : report.columns, rows: report.rows ?? reportRows, ...(report.defaultOpen === undefined ? {} : { defaultOpen: report.defaultOpen }), ...(report.height === undefined ? {} : { height: report.height }) })) : present === "pivot" && pivotShown !== undefined ? (
281
+ : report.columns, rows: report.rows ?? reportRows, ...(report.defaultOpen === undefined ? {} : { defaultOpen: report.defaultOpen }), ...(report.height === undefined ? {} : { height: report.height }) })) : present === "pivot" && pivot?.spec !== undefined && pivot.fields !== undefined ? (
282
+ /* Explored: the person moves the axes. Its rows follow the search bar's grouping. */
283
+ _jsx("div", { className: "p-3", children: _jsx(PivotExplorer, { items: ordered, of: pivot.spec.of, fields: pivot.fields, measures: pivot.spec.measures, ...(pivot.defaultMeasures === undefined
284
+ ? {}
285
+ : { defaultMeasures: pivot.defaultMeasures }), ...((group?.levels.length ?? 0) > 0 ? { rows: group.levels } : {}), defaultRows: pivot.spec.rows, defaultColumns: pivot.spec.columns, ...(pivot.onCell === undefined ? {} : { onCell: pivot.onCell }), ...(pivot.height === undefined ? {} : { height: pivot.height }) }) })) : present === "pivot" && pivotShown !== undefined ? (
259
286
  /* El pivote recibe la grilla y nada más: no hay selección que pasarle
260
287
  —una casilla por montón elegiría cosas que no son registros—, no hay
261
288
  orden —cada eje viene en el orden de sus claves— y no hay menú de
262
289
  columnas, porque las columnas son un eje y no una lista de campos que
263
290
  se puedan esconder de a uno. */
264
- _jsx(PivotTable, { pivot: pivotShown, ...(pivot?.onCell === undefined ? {} : { onCell: pivot.onCell }), ...(pivot?.height === undefined ? {} : { height: pivot.height }) })) : present === "board" ? (_jsx("div", { className: "flex flex-col", children: wrapGroups((inside) => (_jsx("div", { className: "grid gap-3 p-3 @2xl:grid-cols-2 @5xl:grid-cols-3", children: inside.map(renderCard) }))) })) : (
291
+ _jsx(PivotTable, { pivot: pivotShown, ...(pivot?.onCell === undefined ? {} : { onCell: pivot.onCell }), ...(pivot?.height === undefined ? {} : { height: pivot.height }) })) : present === "calendar" && calendar !== undefined ? (_jsx("div", { className: "p-3", children: _jsx(RecordCalendar, { ...calendar, items: ordered, keyOf: keyOf, ...(press === undefined ? {} : { onOpen: press }) }) })) : present === "gantt" && gantt !== undefined ? (_jsx("div", { className: "p-3", children: _jsx(RecordGantt, { ...gantt, items: ordered, keyOf: keyOf, ...(press === undefined ? {} : { onOpen: press }) }) })) : present === "timeline" && timeline !== undefined ? (_jsx("div", { className: "p-3", children: _jsx(RecordTimeline, { ...timeline, items: ordered, keyOf: keyOf, ...(press === undefined ? {} : { onOpen: press }) }) })) : present === "board" && boardStages !== undefined ? (_jsx(BoardColumns, { items: ordered, keyOf: keyOf, stages: boardStages, card: renderCard })) : present === "board" ? (_jsx("div", { className: "flex flex-col", children: wrapGroups((inside) => (_jsx("div", { className: "grid gap-3 p-3 @2xl:grid-cols-2 @5xl:grid-cols-3", children: inside.map(renderCard) }))) })) : (
265
292
  /* El encabezado va ANTES de los montones y no adentro de cada uno:
266
293
  rotula la fila, y la fila es la misma en los cinco montones. */
267
294
  _jsxs(_Fragment, { children: [rowHeader === undefined || items.length === 0 ? null : (_jsx(RowHeader, { name: rowHeader.name, ...(rowHeader.meta === undefined ? {} : { meta: rowHeader.meta }), ...(rowHeader.tags === undefined ? {} : { tags: rowHeader.tags }), withSelection: selection !== undefined,
268
295
  /* Los espaciadores se preguntan por la PRIMERA fila: el ícono y las
269
296
  acciones son de la lista entera, no de una fila — si la primera no
270
297
  tiene ícono, ninguna lo tiene. */
271
- withIcon: firstRow?.icon !== undefined, withActions: onOpen !== undefined || firstRow?.actions !== undefined })), wrapGroups((inside) => inside.map(renderRow))] }));
298
+ withIcon: firstRow?.icon !== undefined, withAvatar: firstRow?.avatar !== undefined, trail: {
299
+ ...(firstRow?.actions === undefined ? {} : { actions: firstRow.actions }),
300
+ peek: onPeek !== undefined && !choosing,
301
+ open: onOpen !== undefined && !choosing,
302
+ } })), wrapGroups((inside) => inside.map(renderRow))] }));
272
303
  return (
273
304
  /* The list is its own container (`@container`: the presentations adapt to the width
274
305
  the LIST has, not the window) and fills the space it is given. Before, every page
@@ -16,7 +16,14 @@ export function groupedColumnIds(group, columns) {
16
16
  .filter((id) => id !== undefined);
17
17
  }
18
18
  /** The presentations that need width: a grid of columns. */
19
- export const WIDE_PRESENTS = ["table", "sheet", "report", "pivot"];
19
+ export const WIDE_PRESENTS = [
20
+ "table",
21
+ "sheet",
22
+ "report",
23
+ "pivot",
24
+ "gantt",
25
+ "timeline",
26
+ ];
20
27
  /** The id of the column that answers for a model field, when there is one. */
21
28
  function columnAnswering(field, columns) {
22
29
  if (columns === undefined)
@@ -15,11 +15,14 @@ type TableProps<T extends object> = {
15
15
  levels: NonNullable<Parameters<typeof DataTable<T>>[0]["levels"]>;
16
16
  group?: ListGroup<T>;
17
17
  totals: boolean;
18
- } & Pick<ListViewProps<T>, "sort" | "onSortChange" | "selection" | "rowActions" | "create" | "onPeek" | "onOpen">;
18
+ } & Pick<ListViewProps<T>, "sort" | "onSortChange" | "selection" | "rowActions" | "create" | "onPeek" | "onOpen"> & {
19
+ rowClick?: "peek" | "open";
20
+ onChoose?: (item: T) => void;
21
+ };
19
22
  /** The table receives EVERYTHING that belongs to the set —sorting, selection, grouping,
20
23
  * which columns are on screen— and not a trimmed down version: that was exactly the trap
21
24
  * this archetype exists to avoid, that switching presentation loses capabilities. */
22
- export declare function TablePresentation<T extends object>({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, }: TableProps<T>): import("react").JSX.Element;
25
+ export declare function TablePresentation<T extends object>({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, rowClick, onChoose, }: TableProps<T>): import("react").JSX.Element;
23
26
  type SheetProps<T extends object> = {
24
27
  spec: ListSheet<T>;
25
28
  /** The flattened bands and rows when the set is grouped; `null` when it is not. */
@@ -3,10 +3,40 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useUiCopy } from "../../foundations/copy.js";
4
4
  import { DataTable } from "../../primitives/table/DataTable.js";
5
5
  import { SheetTable } from "../../primitives/table/SheetTable.js";
6
+ import { Eye, SquareArrowOutUpRight } from "lucide-react";
6
7
  /** The table receives EVERYTHING that belongs to the set —sorting, selection, grouping,
7
8
  * which columns are on screen— and not a trimmed down version: that was exactly the trap
8
9
  * this archetype exists to avoid, that switching presentation loses capabilities. */
9
- export function TablePresentation({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, }) {
10
+ export function TablePresentation({ items, columns, keyOf, levels, group, totals, sort, onSortChange, selection, rowActions, create, onPeek, onOpen, rowClick = "peek", onChoose, }) {
11
+ const copy = useUiCopy();
12
+ const press = onChoose ?? (rowClick === "open" ? (onOpen ?? onPeek) : (onPeek ?? onOpen));
13
+ /* The row's ways in go FIRST among its actions, so they are always the inline ones and
14
+ always in the same place; the domain's actions follow (and fold into ⋯ past two). */
15
+ const withOpeners = onPeek === undefined && onOpen === undefined
16
+ ? rowActions
17
+ : (item) => [
18
+ ...(onPeek === undefined
19
+ ? []
20
+ : [
21
+ {
22
+ id: "peek",
23
+ label: copy.list.peek,
24
+ icon: Eye,
25
+ onSelect: () => onPeek(item),
26
+ },
27
+ ]),
28
+ ...(onOpen === undefined
29
+ ? []
30
+ : [
31
+ {
32
+ id: "open",
33
+ label: copy.list.open,
34
+ icon: SquareArrowOutUpRight,
35
+ onSelect: () => onOpen(item),
36
+ },
37
+ ]),
38
+ ...(rowActions?.(item) ?? []),
39
+ ];
10
40
  return (_jsx(DataTable, { data: items, columns: columns, getRowId: keyOf, ...(sort === undefined ? {} : { sort }), ...(onSortChange === undefined ? {} : { onSortChange }), ...(levels.length === 0 ? {} : { levels: levels }), ...(group?.collapsed === undefined ? {} : { collapsed: group.collapsed }), ...(group?.onToggle === undefined ? {} : { onGroupToggle: group.onToggle }), ...(selection === undefined
11
41
  ? {}
12
42
  : {
@@ -22,7 +52,7 @@ export function TablePresentation({ items, columns, keyOf, levels, group, totals
22
52
  selection.toggle(id);
23
53
  }
24
54
  },
25
- }), ...(totals ? { totals: true } : {}), ...(rowActions === undefined ? {} : { rowActions }), ...(create === undefined ? {} : { create }), ...((onPeek ?? onOpen) === undefined ? {} : { onRowClick: onPeek ?? onOpen }) }));
55
+ }), ...(totals ? { totals: true } : {}), ...(withOpeners === undefined ? {} : { rowActions: withOpeners }), ...(create === undefined ? {} : { create }), ...(press === undefined ? {} : { onRowClick: press }) }));
26
56
  }
27
57
  /** The sheet receives the set by INDEX and not a ready-made matrix: `SheetTable` is
28
58
  * virtualized precisely so as not to materialize a million rows, and handing it copies
@@ -1,6 +1,11 @@
1
+ import type { RecordCalendarProps } from "../../components/calendar/RecordCalendar.js";
2
+ import type { RecordGanttProps } from "../../components/gantt/RecordGantt.js";
3
+ import type { BoardStages } from "../../components/listing/BoardColumns.js";
1
4
  import type { ListIdentity } from "../../components/listing/ListingBar.js";
2
5
  import type { ListPresent } from "../../components/listing/set.js";
3
6
  import type { SortField } from "../../components/listing/SortMenu.js";
7
+ import type { RecordTimelineProps } from "../../components/timeline/RecordTimeline.js";
8
+ import type { GroupableField } from "../../foundations/grouping.js";
4
9
  import type { Grouping, GroupLevel } from "../../foundations/grouping.js";
5
10
  import type { Pivot, PivotKey, PivotSpec } from "../../foundations/pivot.js";
6
11
  import type { Surface, Tone } from "../../foundations/types.js";
@@ -23,6 +28,11 @@ export type { ListPresent };
23
28
  /** What an item looks like when the presentation is rows. */
24
29
  export type RowOf = {
25
30
  icon?: LucideIcon;
31
+ /** A person or a company: its face in the icon's place — the photo or the initials. */
32
+ avatar?: {
33
+ name: string;
34
+ src?: string;
35
+ };
26
36
  name: string;
27
37
  tags?: ReactNode;
28
38
  meta?: string;
@@ -153,6 +163,12 @@ export type ListPivot<T> = {
153
163
  /** Qué pasa al tocar un cruce: abrir las filas que cayeron ahí. */
154
164
  onCell?: (row: PivotKey, column: PivotKey) => void;
155
165
  height?: number | string;
166
+ /** The fields the person may pivot by. With them (and `spec`) the pivot is EXPLORED — axes,
167
+ * measures and download on the grid (`PivotExplorer`) — and its rows follow the search
168
+ * bar's grouping while there is one; `spec.rows` / `spec.columns` are where it starts. */
169
+ fields?: readonly GroupableField[];
170
+ /** Which of `spec.measures` start on. The first one by default. */
171
+ defaultMeasures?: readonly string[];
156
172
  };
157
173
  export type ListViewProps<T extends object> = {
158
174
  items: readonly T[];
@@ -226,6 +242,14 @@ export type ListViewProps<T extends object> = {
226
242
  create?: DataTableCreate<T>;
227
243
  /** The board presentation. */
228
244
  card?: (item: T) => ReactNode;
245
+ /** The board by stages: one column each, cards moved between them. */
246
+ boardStages?: BoardStages<T>;
247
+ /** The calendar presentation: when each record happens. */
248
+ calendar?: ListCalendar<T>;
249
+ /** The Gantt presentation: each record as a span on a timeline. */
250
+ gantt?: ListGantt<T>;
251
+ /** The planning presentation: records grouped in rows across time (Odoo's Gantt). */
252
+ timeline?: ListTimeline<T>;
229
253
  /**
230
254
  * La PLANILLA. Con esto, `present="sheet"` dibuja un `SheetTable`.
231
255
  *
@@ -266,10 +290,24 @@ export type ListViewProps<T extends object> = {
266
290
  count: number;
267
291
  clear: () => void;
268
292
  actions?: ReactNode;
293
+ /** Every record that matches, not just the page: «Elegir los 187 que coinciden». The total
294
+ * is the server's; `onSelect` makes the selection mean «all of them». */
295
+ matching?: {
296
+ total: number;
297
+ onSelect: () => void;
298
+ };
269
299
  };
270
300
  onOpen?: (item: T) => void;
271
301
  onPeek?: (item: T) => void;
272
302
  isPeeked?: (item: T) => boolean;
303
+ /** What a press on the row's body does when both ways in exist. `peek` (the default) looks
304
+ * without leaving the list; `open` goes to the record — a workspace whose rule is «a row
305
+ * opens the page» (the composer's) says `open`, and 👁 stays for the look. */
306
+ rowClick?: "peek" | "open";
307
+ /** The list CHOOSES instead of opening — a «Buscar más…», a chooser a workspace asked for:
308
+ * a press on a row (its body, its name, a card) calls this, and the row offers no 👁 / ↗.
309
+ * For several at once, give `selection` too and toggle it here. */
310
+ onChoose?: (item: T) => void;
273
311
  /** The three states, in a single place. Every view used to re-assemble them
274
312
  * with three nested ternaries, and that is why «empty because of a filter»
275
313
  * and «really empty» got mixed up in half the screens. */
@@ -282,3 +320,9 @@ export type ListViewProps<T extends object> = {
282
320
  /** The escape hatch. */
283
321
  children?: ReactNode;
284
322
  };
323
+ /** `List.Calendar`: the calendar's own props; the records, their key and «open» are the list's. */
324
+ export type ListCalendar<T> = Omit<RecordCalendarProps<T>, "items" | "keyOf" | "onOpen">;
325
+ /** `List.Gantt`: the chart's own props; the records, their key and «open» are the list's. */
326
+ export type ListGantt<T> = Omit<RecordGanttProps<T>, "items" | "keyOf" | "onOpen">;
327
+ /** `List.Timeline`: the planning's own props; the records, their key and «open» are the list's. */
328
+ export type ListTimeline<T> = Omit<RecordTimelineProps<T>, "items" | "keyOf" | "onOpen">;
@@ -9,9 +9,19 @@ import { type RefObject } from "react";
9
9
  *
10
10
  * Attach `frame` to the list's root element; `offered` is what the switcher may show and
11
11
  * `present` what to draw (a wide one that was asked for falls back to rows).
12
+ *
13
+ * Measured BEFORE paint: waiting for the observer's first report drew the table and swapped it
14
+ * for rows a frame later, so a click on a row of a list in a panel landed on a detached `<tr>`.
12
15
  */
13
16
  export declare function useNarrowList(presents: readonly ListPresent[] | undefined, requested: ListPresent): {
14
17
  frame: RefObject<HTMLDivElement | null>;
15
18
  offered: readonly ListPresent[];
16
19
  present: ListPresent;
17
20
  };
21
+ /** What a list offers and draws at its width. Only a list that HAS rows folds into them: one
22
+ * that declares just a table fell back to rows it had no `row` for, and drew empty lines — a
23
+ * table that scrolls sideways says more. */
24
+ export declare function narrowed(presents: readonly ListPresent[] | undefined, requested: ListPresent, narrow: boolean): {
25
+ offered: readonly ListPresent[];
26
+ present: ListPresent;
27
+ };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { HANDHELD_WIDTH } from "../../foundations/layout.js";
3
3
  import { WIDE_PRESENTS } from "./columns.js";
4
- import { useEffect, useRef, useState } from "react";
4
+ import { useLayoutEffect, useRef, useState } from "react";
5
5
  /**
6
6
  * A narrow LIST — a phone, a drawer, a panel among four — does not offer the presentations
7
7
  * that need width: a seven-column table at 360 px is a horizontal scroll nobody reads.
@@ -11,13 +11,19 @@ import { useEffect, useRef, useState } from "react";
11
11
  *
12
12
  * Attach `frame` to the list's root element; `offered` is what the switcher may show and
13
13
  * `present` what to draw (a wide one that was asked for falls back to rows).
14
+ *
15
+ * Measured BEFORE paint: waiting for the observer's first report drew the table and swapped it
16
+ * for rows a frame later, so a click on a row of a list in a panel landed on a detached `<tr>`.
14
17
  */
15
18
  export function useNarrowList(presents, requested) {
16
19
  const frame = useRef(null);
17
20
  const [narrow, setNarrow] = useState(false);
18
- useEffect(() => {
21
+ useLayoutEffect(() => {
19
22
  const el = frame.current;
20
- if (el === null || typeof ResizeObserver === "undefined")
23
+ if (el === null)
24
+ return;
25
+ setNarrow(el.getBoundingClientRect().width < HANDHELD_WIDTH);
26
+ if (typeof ResizeObserver === "undefined")
21
27
  return;
22
28
  const watch = new ResizeObserver(([entry]) => {
23
29
  if (entry !== undefined)
@@ -26,7 +32,15 @@ export function useNarrowList(presents, requested) {
26
32
  watch.observe(el);
27
33
  return () => watch.disconnect();
28
34
  }, []);
29
- const offered = (presents ?? []).filter((p) => !narrow || !WIDE_PRESENTS.includes(p));
30
- const present = narrow && WIDE_PRESENTS.includes(requested) ? "rows" : requested;
31
- return { frame, offered, present };
35
+ return { frame, ...narrowed(presents, requested, narrow) };
36
+ }
37
+ /** What a list offers and draws at its width. Only a list that HAS rows folds into them: one
38
+ * that declares just a table fell back to rows it had no `row` for, and drew empty lines — a
39
+ * table that scrolls sideways says more. */
40
+ export function narrowed(presents, requested, narrow) {
41
+ const folds = narrow && (presents === undefined || presents.includes("rows"));
42
+ return {
43
+ offered: (presents ?? []).filter((p) => !folds || !WIDE_PRESENTS.includes(p)),
44
+ present: folds && WIDE_PRESENTS.includes(requested) ? "rows" : requested,
45
+ };
32
46
  }
@@ -2,6 +2,7 @@ import { type ActivityProps } from "../../components/activity/Activity.js";
2
2
  import { type SheetField, type SheetLayout } from "../../components/form/Sheet.js";
3
3
  import type { Crumb } from "../../components/record/Breadcrumb.js";
4
4
  import type { DirtyState } from "../../components/record/DirtyActions.js";
5
+ import { type RecordStamp } from "../../components/record/RecordAudit.js";
5
6
  import { type RecordNoticeSpec } from "../../components/record/RecordNotices.js";
6
7
  import { RecordSkeleton } from "../../components/record/RecordSkeleton.js";
7
8
  import { type Stage } from "../../components/record/StatusBar.js";
@@ -68,6 +69,8 @@ type StatusProps = {
68
69
  /** Cuál de las etapas es la de ahora. Sin esto, la primera. */
69
70
  current?: string;
70
71
  failed?: boolean;
72
+ /** The stages can be chosen: pressing one moves the record there (a CRM's pipeline). */
73
+ onSelect?: (id: string) => void;
71
74
  /** La escotilla del estado, para lo que no es ninguna de las dos. */
72
75
  children?: ReactNode;
73
76
  };
@@ -76,6 +79,9 @@ type PaperProps = {
76
79
  * valor, que es la forma de Odoo. */
77
80
  layout?: SheetLayout;
78
81
  columns?: 1 | 2 | 3 | 4;
82
+ /** `reading` (default): the sheet stops at a reading width, centred, with the rail lined
83
+ * up to it. `full`: the whole page, for a record whose body is mostly a wide table. */
84
+ width?: "reading" | "full";
79
85
  children?: ReactNode;
80
86
  };
81
87
  type FactsProps = {
@@ -142,4 +148,12 @@ export declare namespace Record {
142
148
  var Pages: ((props: PagesProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"pages">;
143
149
  var Page: ((props: PageProps) => ReactNode) & import("../../foundations/slots.js").Slotted<"page">;
144
150
  var Notice: ((props: RecordNoticeSpec) => ReactNode) & import("../../foundations/slots.js").Slotted<"notice">;
151
+ var Ribbon: ((props: {
152
+ label: string;
153
+ tone?: Tone;
154
+ }) => ReactNode) & import("../../foundations/slots.js").Slotted<"ribbon">;
155
+ var Audit: ((props: {
156
+ created?: RecordStamp;
157
+ updated?: RecordStamp;
158
+ }) => ReactNode) & import("../../foundations/slots.js").Slotted<"audit">;
145
159
  }
@@ -3,8 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { Activity } from "../../components/activity/Activity.js";
4
4
  import { Sheet, } from "../../components/form/Sheet.js";
5
5
  import { Pager as PagerControl } from "../../components/record/Pager.js";
6
+ import { RecordAudit } from "../../components/record/RecordAudit.js";
6
7
  import { RecordHeader } from "../../components/record/RecordHeader.js";
7
8
  import { RecordBlocking, RecordNoticeIndicator, splitNotices, } from "../../components/record/RecordNotices.js";
9
+ import { RecordRibbon } from "../../components/record/RecordRibbon.js";
8
10
  import { RecordSkeleton } from "../../components/record/RecordSkeleton.js";
9
11
  import { SmartButton, SmartButtons } from "../../components/record/SmartButtons.js";
10
12
  import { StatusBar } from "../../components/record/StatusBar.js";
@@ -51,6 +53,11 @@ const Status = slot("status", (_props) => null);
51
53
  * notice that was already true when the record opened interrupts nothing (`Callout`
52
54
  * is `role="status"`). */
53
55
  const NoticeSlot = slot("notice", (_props) => null);
56
+ /** What the record IS now, across the paper's corner: «PAGADA», «ARCHIVADA». A word with its
57
+ * tone, written as a child of `Record` or `Record.Paper`; see `RecordRibbon`. */
58
+ const RibbonSlot = slot("ribbon", (_props) => null);
59
+ /** Who created the record and who changed it last, on the paper's last line. */
60
+ const AuditSlot = slot("audit", (_props) => null);
54
61
  /** El papel: el registro. El `<sheet>` blanco sobre el piso. */
55
62
  const Paper = slot("paper", (_props) => null);
56
63
  const ActivitySlot = slot("activity", (_props) => null);
@@ -89,10 +96,12 @@ export function Record({ identity, error = null, dirty, children }) {
89
96
  /* Apiladas en un bloque: arriba dónde está, abajo qué pasó para llegar.
90
97
  Lado a lado competían por el ancho con los botones y quedaban las dos
91
98
  truncadas; una debajo de la otra, cada una tiene su línea entera. */
92
- _jsxs("div", { className: "flex min-w-0 flex-col items-end gap-1.5", children: [status.stages === undefined ? null : (_jsx(StatusBar, { stages: status.stages, current: status.current ?? status.stages[0]?.id ?? "", failed: status.failed ?? false, surface: surface })), status.children] }));
99
+ _jsxs("div", { className: "flex min-w-0 flex-col items-end gap-1.5", children: [status.stages === undefined ? null : (_jsx(StatusBar, { stages: status.stages, current: status.current ?? status.stages[0]?.id ?? "", failed: status.failed ?? false, surface: surface, ...(status.onSelect === undefined ? {} : { onSelect: status.onSelect }) })), status.children] }));
93
100
  const paper = propsOf(paperNode);
94
101
  const inPaper = [...flatten(paper?.children), ...loose];
95
102
  const fields = propsOf(pick(inPaper, "facts"))?.fields;
103
+ const ribbon = propsOf(pick(inPaper, "ribbon"));
104
+ const audit = propsOf(pick(inPaper, "audit"));
96
105
  /* Written at the top of the record or inside the paper: both are «on the record». */
97
106
  const notices = pickAll(inPaper, "notice")
98
107
  .map((node) => propsOf(node))
@@ -157,7 +166,11 @@ export function Record({ identity, error = null, dirty, children }) {
157
166
  shorter than the screen. The extra height has no paint of its own: on a desk
158
167
  nothing changes. */
159
168
  /* The record knows the notes of its fields and rows: their cards register here. */
160
- _jsx(NoteRegistryContext, { value: registry, children: _jsx("article", { className: "flex min-w-0 flex-1 flex-col", children: _jsx(RecordHeader, { railSurface: surface, ...(dirty === undefined ? {} : { dirty }), ...(crumbs === undefined ? {} : { crumbs }), ...(pager === undefined
169
+ _jsx(NoteRegistryContext, { value: registry, children: _jsx("article", { className: "flex min-w-0 flex-1 flex-col", children: _jsx(RecordHeader, { railSurface: surface, ...(ribbon === undefined
170
+ ? {}
171
+ : {
172
+ ribbon: (_jsx(RecordRibbon, { label: ribbon.label, ...(ribbon.tone === undefined ? {} : { tone: ribbon.tone }) })),
173
+ }), ...(paper?.width === undefined ? {} : { width: paper.width }), ...(dirty === undefined ? {} : { dirty }), ...(crumbs === undefined ? {} : { crumbs }), ...(pager === undefined
161
174
  ? {}
162
175
  : {
163
176
  pager: (_jsx(PagerControl, { index: pager.index, total: pager.total, surface: surface, ...(pager.onPrev === undefined ? {} : { onPrev: pager.onPrev }), ...(pager.onNext === undefined ? {} : { onNext: pager.onNext }) })),
@@ -190,11 +203,11 @@ export function Record({ identity, error = null, dirty, children }) {
190
203
  },
191
204
  },
192
205
  }) })),
193
- }), children: _jsx("div", { className: "min-w-0", children: _jsx("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-(--spacing-block) px-4 pt-(--spacing-block) pb-4", "gap-2.5 pt-2.5 pb-2")}`, children: _jsxs(Sheet, { layout: paper?.layout ?? "stacked", columns: paper?.columns ?? 2, ...(hasFacts ? { fields } : {}), ...(above.length > 0 || (globalSheet && aspectsNode !== undefined)
194
- ? {
195
- head: (_jsxs(_Fragment, { children: [above, globalSheet && aspectsNode !== undefined ? (_jsx("div", { className: `border-b border-line-strong ${byDensity(d, "-mx-4 px-4", "")}`, children: aspectsNode })) : null] })),
196
- }
197
- : {}), children: [pageBody, below] }) }) }) }) }) }));
206
+ }), children: _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-(--spacing-block) px-4 pt-(--spacing-block) pb-4", "gap-2.5 pt-2.5 pb-2")}`, children: _jsxs(Sheet, { layout: paper?.layout ?? "stacked", columns: paper?.columns ?? 2, ...(hasFacts ? { fields } : {}), ...(above.length > 0 || (globalSheet && aspectsNode !== undefined)
207
+ ? {
208
+ head: (_jsxs(_Fragment, { children: [above, globalSheet && aspectsNode !== undefined ? (_jsx("div", { className: `border-b border-line-strong ${byDensity(d, "-mx-4 px-4", "")}`, children: aspectsNode })) : null] })),
209
+ }
210
+ : {}), children: [pageBody, below] }) }), audit === undefined ? null : _jsx(RecordAudit, { ...audit })] }) }) }) }));
198
211
  }
199
212
  Record.Skeleton = RecordSkeleton;
200
213
  Record.Rail = Rail;
@@ -209,3 +222,5 @@ Record.Facts = Facts;
209
222
  Record.Pages = Pages;
210
223
  Record.Page = Page;
211
224
  Record.Notice = NoticeSlot;
225
+ Record.Ribbon = RibbonSlot;
226
+ Record.Audit = AuditSlot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/web-ui",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
4
  "description": "An enterprise web design system for business software — ERPs, back offices, management tools — in React and Tailwind: tokens, primitives, components and the two views every management screen is made of.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,19 +53,24 @@
53
53
  "@codemirror/lint": "^6.9.7",
54
54
  "@fontsource/ibm-plex-mono": "^5.3.0",
55
55
  "@fontsource/ibm-plex-sans": "^5.3.0",
56
+ "@fullcalendar/react": "7.1.3",
56
57
  "@internationalized/date": "^3.12.4",
58
+ "@svar-ui/react-gantt": "2.8.2",
57
59
  "@uiw/react-codemirror": "^4.25.11",
58
60
  "lucide-react": "^1.45.0",
59
61
  "react-aria": "^3.53.1",
60
62
  "react-aria-components": "^1.22.1",
61
63
  "tailwind-variants": "^4.0.0",
62
- "tailwindcss-react-aria-components": "^2.2.0"
64
+ "tailwindcss-react-aria-components": "^2.2.0",
65
+ "temporal-polyfill": "^1.0.6"
63
66
  },
64
67
  "devDependencies": {
65
68
  "@eslint/js": "^10.0.1",
69
+ "@sincpro/web-composer": "^1.4.0",
66
70
  "@storybook/addon-a11y": "10.6.0",
67
71
  "@storybook/addon-docs": "10.6.0",
68
72
  "@storybook/addon-mcp": "10.6.0",
73
+ "@storybook/addon-vitest": "10.6.0",
69
74
  "@storybook/react-vite": "10.6.0",
70
75
  "@tailwindcss/vite": "4.3.3",
71
76
  "@tanstack/react-table": "^9.2.4",
@@ -74,12 +79,13 @@
74
79
  "@types/react": "^19.3.0",
75
80
  "@types/react-dom": "^19.3.0",
76
81
  "@vitejs/plugin-react": "6.1.1",
82
+ "@vitest/browser-playwright": "5.0.5",
77
83
  "eslint": "^10.11.0",
78
84
  "eslint-config-prettier": "^10.1.8",
79
85
  "eslint-plugin-react-hooks": "^7.1.1",
80
86
  "eslint-plugin-simple-import-sort": "^12.1.1",
87
+ "playwright": "1.64.1",
81
88
  "prettier": "^3.9.8",
82
- "puppeteer-core": "^23.11.1",
83
89
  "react": "19.3.0",
84
90
  "react-dom": "19.3.0",
85
91
  "storybook": "10.6.0",
@@ -88,7 +94,8 @@
88
94
  "tsx": "^4.23.13",
89
95
  "typescript": "~5.9.3",
90
96
  "typescript-eslint": "^8.70.0",
91
- "vite": "8.3.0"
97
+ "vite": "8.3.0",
98
+ "vitest": "5.0.5"
92
99
  },
93
100
  "keywords": [
94
101
  "design-system",
@@ -1,40 +0,0 @@
1
- import { type PaletteCommand } from "./commands.js";
2
- /**
3
- * The search box and the list, without the modal around them.
4
- *
5
- * Context: split out so the filtering can be rendered on its own — in a test, with a query
6
- * already typed — since the modal is a portal that only exists in a browser.
7
- */
8
- export declare function CommandSearch({ commands, onRun, query, onQueryChange, placeholder, }: {
9
- commands: readonly PaletteCommand[];
10
- onRun: (id: string) => void;
11
- query?: string;
12
- onQueryChange?: (next: string) => void;
13
- placeholder?: string;
14
- }): import("react").JSX.Element;
15
- /**
16
- * A modal search over everything the app can do — go to a screen, create a record — opened
17
- * with ⌘K (Ctrl+K) and run with Enter.
18
- *
19
- * Context: it is controlled (`open`, `onOpenChange`) and does not bind the shortcut itself,
20
- * because an app has ONE palette and many screens: the key is bound once, where the palette
21
- * lives, with `useCommandPaletteShortcut`. Running a command closes it — the palette is a
22
- * path, not a place to stay.
23
- */
24
- export declare function CommandPalette({ open, onOpenChange, commands, onRun, placeholder, }: {
25
- open: boolean;
26
- onOpenChange: (open: boolean) => void;
27
- commands: readonly PaletteCommand[];
28
- /** A command was chosen. The palette closes on its own. */
29
- onRun: (id: string) => void;
30
- /** The search box's prompt and its accessible name. By default, the copy table's. */
31
- placeholder?: string;
32
- }): import("react").JSX.Element;
33
- /**
34
- * Binds ⌘K (Ctrl+K on Windows and Linux) to open the palette, anywhere in the page.
35
- *
36
- * Context: both modifiers are accepted on every platform. Telling them apart by the user
37
- * agent guesses wrong on a Mac with a PC keyboard, and the cost of accepting both is that
38
- * Ctrl+K on a Mac — «delete to the end of the line» in a text box — opens the palette.
39
- */
40
- export declare function useCommandPaletteShortcut(setOpen: (open: boolean) => void): void;