@sincpro/web-ui 1.3.0 → 1.5.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 (46) hide show
  1. package/dist/components/form/Sheet.js +4 -1
  2. package/dist/components/listing/RecordCard.d.ts +4 -0
  3. package/dist/components/listing/RecordCard.js +4 -1
  4. package/dist/components/listing/Row.d.ts +5 -4
  5. package/dist/components/listing/Row.js +5 -4
  6. package/dist/components/record/DirtyActions.d.ts +20 -6
  7. package/dist/components/record/DirtyActions.js +55 -4
  8. package/dist/components/record/RecordHeader.d.ts +1 -1
  9. package/dist/components/record/RecordHeader.js +32 -13
  10. package/dist/components/record/RecordSkeleton.js +1 -1
  11. package/dist/components/search/SearchBar.js +12 -3
  12. package/dist/foundations/copy.d.ts +15 -0
  13. package/dist/foundations/copy.es.js +8 -0
  14. package/dist/foundations/copy.js +8 -0
  15. package/dist/foundations/grouping.d.ts +28 -0
  16. package/dist/foundations/grouping.js +0 -0
  17. package/dist/foundations/index.d.ts +1 -1
  18. package/dist/foundations/index.js +1 -1
  19. package/dist/primitives/button/Button.d.ts +12 -2
  20. package/dist/primitives/button/Button.js +12 -2
  21. package/dist/primitives/cell/CellStack.js +1 -1
  22. package/dist/primitives/field/Field.d.ts +8 -3
  23. package/dist/primitives/field/Field.js +7 -3
  24. package/dist/primitives/notice/Notice.js +3 -1
  25. package/dist/primitives/picker/MatchedText.d.ts +3 -2
  26. package/dist/primitives/picker/MatchedText.js +3 -2
  27. package/dist/primitives/picker/Picker.d.ts +18 -1
  28. package/dist/primitives/picker/Picker.js +5 -5
  29. package/dist/primitives/picker/TagsField.d.ts +10 -0
  30. package/dist/primitives/picker/TagsField.js +2 -2
  31. package/dist/primitives/picker/index.d.ts +2 -1
  32. package/dist/primitives/picker/index.js +2 -0
  33. package/dist/primitives/picker/tagChoices.d.ts +7 -0
  34. package/dist/primitives/picker/tagChoices.js +9 -4
  35. package/dist/primitives/table/DataTable.js +10 -11
  36. package/dist/primitives/table/EditableTable.js +5 -1
  37. package/dist/primitives/table/PileFootLine.d.ts +14 -0
  38. package/dist/primitives/table/PileFootLine.js +22 -0
  39. package/dist/primitives/table/index.d.ts +1 -0
  40. package/dist/primitives/table/index.js +1 -0
  41. package/dist/tokens.css +42 -0
  42. package/dist/views/list/List.d.ts +11 -3
  43. package/dist/views/list/ListView.js +19 -8
  44. package/dist/views/list/index.d.ts +1 -1
  45. package/dist/views/list/types.d.ts +24 -2
  46. package/package.json +1 -1
@@ -16,12 +16,13 @@ import { Breadcrumb } from "../../components/record/Breadcrumb.js";
16
16
  import { RecordTimeline } from "../../components/timeline/RecordTimeline.js";
17
17
  import { useUiCopy } from "../../foundations/copy.js";
18
18
  import { useDensity } from "../../foundations/density.js";
19
- import { pathKey, pileIsOpen, pilesOf, } from "../../foundations/grouping.js";
19
+ import { pathKey, pileFoot, pileIsOpen, pilesOf, } from "../../foundations/grouping.js";
20
20
  import { pivotFrom, pivotIsEmpty } from "../../foundations/pivot.js";
21
21
  import { Button } from "../../primitives/button/Button.js";
22
22
  import { Empty, Failed, Skeleton } from "../../primitives/notice/Notice.js";
23
23
  import { PivotTable } from "../../primitives/table/PivotTable.js";
24
24
  import { ReportTable } from "../../primitives/table/ReportTable.js";
25
+ import { ProgressBar } from "../../primitives/value/ProgressBar.js";
25
26
  import { groupedColumnIds, WIDE_PRESENTS } from "./columns.js";
26
27
  import { groupLevelsOf, reportColumnsOf, reportRowsOf, sheetFromColumns, sheetRowsOf, } from "./derive.js";
27
28
  import { SheetPresentation, TablePresentation, } from "./presentations.js";
@@ -79,6 +80,7 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
79
80
  const density = useDensity();
80
81
  const copy = useUiCopy();
81
82
  const loading = state?.loading === true;
83
+ const refreshing = state?.refreshing === true && !loading;
82
84
  const error = state?.error ?? null;
83
85
  const columnSpecs = useMemo(() => specsFromColumns(columns ?? [], false), [columns]);
84
86
  const columnControl = useColumnControl(columnSpecs, {
@@ -240,6 +242,11 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
240
242
  los decide la vista para la lista entera. */
241
243
  const first = items[0];
242
244
  const firstRow = first === undefined ? undefined : row(first);
245
+ /* The same foot the table draws: «Ver 50 más de 49.950», or «Se muestran 500 de …». */
246
+ function footOf(pile) {
247
+ const foot = pileFoot(pile, group?.onMore !== undefined);
248
+ return foot === null ? null : (_jsx(GroupMore, { foot: foot, depth: pile.depth, onMore: () => group?.onMore?.(pile) }));
249
+ }
243
250
  /** Los montones, con sus filas adentro. Se llama a sí misma porque el árbol
244
251
  * tiene N niveles: un montón dibuja su encabezado y, adentro, o bien los
245
252
  * montones de abajo o bien las filas — nunca las dos cosas. */
@@ -253,10 +260,7 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
253
260
  ? null
254
261
  : pile.piles.length > 0
255
262
  ? renderPiles(pile.piles, nodes)
256
- : nodes(pile.items), open &&
257
- pile.piles.length === 0 &&
258
- pile.items.length < pile.count &&
259
- group?.onMore !== undefined ? (_jsx(GroupMore, { left: pile.count - pile.items.length, depth: pile.depth, onMore: () => group.onMore?.(pile) })) : null] }, key));
263
+ : nodes(pile.items), open ? footOf(pile) : null] }, key));
260
264
  });
261
265
  }
262
266
  const reportRows = useMemo(() => reportRowsOf(piles, columns, row), [piles, columns, row]);
@@ -265,9 +269,14 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
265
269
  return nodes(ordered);
266
270
  return renderPiles(piles, nodes);
267
271
  }
272
+ const waysOf = (item) => ({
273
+ ...(press === undefined ? {} : { press: () => press(item) }),
274
+ ...(onPeek === undefined || choosing ? {} : { peek: () => onPeek(item) }),
275
+ ...(onOpen === undefined || choosing ? {} : { open: () => onOpen(item) }),
276
+ });
268
277
  const renderCard = (item) => {
269
278
  if (card !== undefined) {
270
- return _jsx(Fragment, { children: card(item) }, keyOf(item));
279
+ return _jsx(Fragment, { children: card(item, waysOf(item)) }, keyOf(item));
271
280
  }
272
281
  const d = row(item);
273
282
  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)));
@@ -331,7 +340,7 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
331
340
  /* The list is its own container (`@container`: the presentations adapt to the width
332
341
  the LIST has, not the window) and fills the space it is given. Before, every page
333
342
  wrapped it in a `div` to get both. */
334
- _jsxs("div", { ref: frame, className: "@container flex min-w-0 flex-1 flex-col bg-surface", children: [_jsxs(Listing, { bar: bar,
343
+ _jsxs("div", { ref: frame, "aria-busy": refreshing || loading, className: "@container flex min-w-0 flex-1 flex-col bg-surface", children: [_jsxs(Listing, { bar: bar,
335
344
  /* La barra se queda arriba: con 187 filas, scrollear para ver la última
336
345
  dejaba sin buscador, sin paginador y sin el conmutador.
337
346
  Embedded it does NOT: the record has its own sticky parts (its rail, its tabs),
@@ -344,7 +353,9 @@ export function ListView({ items, keyOf, identity, crumbs, embedded = false, sur
344
353
  metrics: _jsx("div", { className: "px-3.5", children: metrics }),
345
354
  }), children: [loading ? _jsx(Skeleton, { label: copy.failed.loading }) : null, isEmpty ? (filtered ? (_jsx(Empty, { sentence: empty?.whenFiltered?.sentence ?? copy.list.emptyFiltered, ...(empty?.whenFiltered?.action === undefined
346
355
  ? {}
347
- : { action: empty.whenFiltered.action }) })) : (_jsx(Empty, { sentence: empty?.sentence ?? copy.list.empty, ...(empty?.action === undefined ? {} : { action: empty.action }) }))) : null, loading || isEmpty ? null : body] }), error === null ? null : (_jsx("div", { className: "px-3 pt-(--spacing-block)", children: _jsx(Failed, { heading: copy.failed.list, sentence: error }) })), children] }));
356
+ : { action: empty.whenFiltered.action }) })) : (_jsx(Empty, { sentence: empty?.sentence ?? copy.list.empty, ...(empty?.action === undefined ? {} : { action: empty.action }) }))) : null, _jsxs("div", { className: "relative min-w-0", children: [refreshing ? (
357
+ /* Over the top edge of the rows, not above them: the rows do not move. */
358
+ _jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 z-20", children: _jsx(ProgressBar, { label: copy.list.refreshing, width: "w-full" }) })) : null, loading || isEmpty ? null : body] })] }), error === null ? null : (_jsx("div", { className: "px-3 pt-(--spacing-block)", children: _jsx(Failed, { heading: copy.failed.list, sentence: error }) })), children] }));
348
359
  }
349
360
  /** What a list offers when it fails and can be retried. */
350
361
  export function RetryAction({ onRetry }) {
@@ -1,2 +1,2 @@
1
1
  export { List, type ListPresentProps, type ListProps } from "./List.js";
2
- export { type ListCrumb, type ListEmpty, type ListGroup, type ListPivot, type ListPresent, type ListReport, type ListSet, type ListSheet, type RowOf, } from "./types.js";
2
+ export { type CardWays, type ListCrumb, type ListEmpty, type ListGroup, type ListPivot, type ListPresent, type ListReport, type ListSet, type ListSheet, type RowOf, } from "./types.js";
@@ -41,6 +41,20 @@ export type RowOf = {
41
41
  children?: ReactNode;
42
42
  actions?: ReactNode;
43
43
  };
44
+ /**
45
+ * What a press on ONE card does in this list, already bound to its record.
46
+ *
47
+ * Context: a board's own card replaced the built-in one and lost, with it, the rule of what
48
+ * a press does — the application had to pass it by hand. It gets the same three ways as a
49
+ * `List.Present` (`ListSet`), so a card written by the domain answers a click like the
50
+ * list's own: `press` is the rule (choose, look or open, as the list decided); `peek` and
51
+ * `open` are the two ways in, absent while the list is choosing.
52
+ */
53
+ export type CardWays = {
54
+ press?: () => void;
55
+ peek?: () => void;
56
+ open?: () => void;
57
+ };
44
58
  /**
45
59
  * The set as a presentation receives it — what a `List.Present` draws from.
46
60
  *
@@ -286,8 +300,8 @@ export type ListViewProps<T extends object> = {
286
300
  * columns that declare `input` get a control in that row. See `DataTable`'s header.
287
301
  * While a record is being written the table shows even if the set is empty. */
288
302
  create?: DataTableCreate<T>;
289
- /** The board presentation. */
290
- card?: (item: T) => ReactNode;
303
+ /** The board presentation: the card of one record, with what a press on it does. */
304
+ card?: (item: T, ways: CardWays) => ReactNode;
291
305
  /** The board by stages: one column each, cards moved between them. */
292
306
  boardStages?: BoardStages<T>;
293
307
  /** The calendar presentation: when each record happens. */
@@ -359,8 +373,16 @@ export type ListViewProps<T extends object> = {
359
373
  /** The three states, in a single place. Every view used to re-assemble them
360
374
  * with three nested ternaries, and that is why «empty because of a filter»
361
375
  * and «really empty» got mixed up in half the screens. */
376
+ /** Los estados del conjunto, en un solo lugar.
377
+ *
378
+ * `loading` es la PRIMERA lectura: no hay filas todavía, y se dibuja el esqueleto.
379
+ * `refreshing` es volver a preguntar con filas en la mano —se tipeó en la búsqueda, se
380
+ * cambió de página—: las filas se quedan y una barra discreta dice que se está
381
+ * preguntando. Cambiarlas por un esqueleto en cada pregunta hacía parpadear la tabla, y un
382
+ * clic podía caer sobre una fila que ya no estaba. */
362
383
  state?: {
363
384
  loading?: boolean;
385
+ refreshing?: boolean;
364
386
  error?: string | null;
365
387
  };
366
388
  filtered?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/web-ui",
3
- "version": "1.3.0",
3
+ "version": "1.5.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",