@nanisoft/prism-ui 0.16.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 (173) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/blocks/activity-feed-01/activity-feed-01.d.ts +120 -190
  3. package/dist/blocks/activity-feed-01/activity-feed-01.d.ts.map +1 -1
  4. package/dist/blocks/activity-feed-01/activity-feed-01.js +107 -97
  5. package/dist/blocks/activity-feed-01/index.d.ts +1 -1
  6. package/dist/blocks/activity-feed-01/index.d.ts.map +1 -1
  7. package/dist/blocks/auth-form-01/auth-form.d.ts +86 -53
  8. package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -1
  9. package/dist/blocks/auth-form-01/auth-form.js +95 -31
  10. package/dist/blocks/auth-form-01/index.d.ts +1 -1
  11. package/dist/blocks/auth-form-01/index.d.ts.map +1 -1
  12. package/dist/blocks/calendar-01/calendar.d.ts +28 -0
  13. package/dist/blocks/calendar-01/calendar.d.ts.map +1 -1
  14. package/dist/blocks/calendar-01/calendar.js +15 -1
  15. package/dist/blocks/card-index-01/card-index.d.ts +225 -0
  16. package/dist/blocks/card-index-01/card-index.d.ts.map +1 -0
  17. package/dist/blocks/card-index-01/card-index.js +154 -0
  18. package/dist/blocks/card-index-01/index.d.ts +3 -0
  19. package/dist/blocks/card-index-01/index.d.ts.map +1 -0
  20. package/dist/blocks/card-index-01/index.js +1 -0
  21. package/dist/blocks/chart-card-01/chart-card-01.d.ts +23 -21
  22. package/dist/blocks/chart-card-01/chart-card-01.d.ts.map +1 -1
  23. package/dist/blocks/chart-card-01/chart-card-01.js +46 -1
  24. package/dist/blocks/chart-card-01/index.d.ts +1 -1
  25. package/dist/blocks/chart-card-01/index.d.ts.map +1 -1
  26. package/dist/blocks/chart-group-01/chart-group-01.d.ts +3 -3
  27. package/dist/blocks/chart-group-01/chart-group-01.d.ts.map +1 -1
  28. package/dist/blocks/contact-01/contact.d.ts +30 -82
  29. package/dist/blocks/contact-01/contact.d.ts.map +1 -1
  30. package/dist/blocks/contact-01/contact.js +59 -30
  31. package/dist/blocks/contact-01/index.d.ts +1 -1
  32. package/dist/blocks/contact-01/index.d.ts.map +1 -1
  33. package/dist/blocks/dashboard-01/dashboard-01.d.ts +26 -71
  34. package/dist/blocks/dashboard-01/dashboard-01.d.ts.map +1 -1
  35. package/dist/blocks/dashboard-01/dashboard-01.js +34 -23
  36. package/dist/blocks/dashboard-01/index.d.ts +1 -1
  37. package/dist/blocks/dashboard-01/index.d.ts.map +1 -1
  38. package/dist/blocks/data-table-01/data-table.d.ts +276 -59
  39. package/dist/blocks/data-table-01/data-table.d.ts.map +1 -1
  40. package/dist/blocks/data-table-01/data-table.js +217 -35
  41. package/dist/blocks/data-table-01/index.d.ts +1 -1
  42. package/dist/blocks/data-table-01/index.d.ts.map +1 -1
  43. package/dist/blocks/directory-01/directory-01.d.ts +23 -2
  44. package/dist/blocks/directory-01/directory-01.d.ts.map +1 -1
  45. package/dist/blocks/directory-01/directory-01.js +3 -3
  46. package/dist/blocks/empty-state-01/empty-state.d.ts +60 -21
  47. package/dist/blocks/empty-state-01/empty-state.d.ts.map +1 -1
  48. package/dist/blocks/empty-state-01/empty-state.js +52 -17
  49. package/dist/blocks/index-detail-01/index-detail.d.ts +85 -0
  50. package/dist/blocks/index-detail-01/index-detail.d.ts.map +1 -0
  51. package/dist/blocks/index-detail-01/index-detail.js +58 -0
  52. package/dist/blocks/index-detail-01/index.d.ts +3 -0
  53. package/dist/blocks/index-detail-01/index.d.ts.map +1 -0
  54. package/dist/blocks/index-detail-01/index.js +1 -0
  55. package/dist/blocks/index.d.ts +20 -8
  56. package/dist/blocks/index.d.ts.map +1 -1
  57. package/dist/blocks/index.js +7 -0
  58. package/dist/blocks/issue-detail-01/issue-detail.d.ts +18 -0
  59. package/dist/blocks/issue-detail-01/issue-detail.d.ts.map +1 -1
  60. package/dist/blocks/issue-detail-01/issue-detail.js +18 -0
  61. package/dist/blocks/nothing-chosen-01/index.d.ts +3 -0
  62. package/dist/blocks/nothing-chosen-01/index.d.ts.map +1 -0
  63. package/dist/blocks/nothing-chosen-01/index.js +1 -0
  64. package/dist/blocks/nothing-chosen-01/nothing-chosen.d.ts +119 -0
  65. package/dist/blocks/nothing-chosen-01/nothing-chosen.d.ts.map +1 -0
  66. package/dist/blocks/nothing-chosen-01/nothing-chosen.js +79 -0
  67. package/dist/blocks/project-dashboard-01/index.d.ts +1 -1
  68. package/dist/blocks/project-dashboard-01/index.d.ts.map +1 -1
  69. package/dist/blocks/project-dashboard-01/project-dashboard.d.ts +21 -27
  70. package/dist/blocks/project-dashboard-01/project-dashboard.d.ts.map +1 -1
  71. package/dist/blocks/project-dashboard-01/project-dashboard.js +45 -5
  72. package/dist/blocks/provisioning-01/index.d.ts +1 -1
  73. package/dist/blocks/provisioning-01/index.d.ts.map +1 -1
  74. package/dist/blocks/provisioning-01/provisioning.d.ts +9 -97
  75. package/dist/blocks/provisioning-01/provisioning.d.ts.map +1 -1
  76. package/dist/blocks/provisioning-01/provisioning.js +26 -13
  77. package/dist/blocks/record-detail-01/index.d.ts +3 -0
  78. package/dist/blocks/record-detail-01/index.d.ts.map +1 -0
  79. package/dist/blocks/record-detail-01/index.js +1 -0
  80. package/dist/blocks/record-detail-01/record-detail.d.ts +239 -0
  81. package/dist/blocks/record-detail-01/record-detail.d.ts.map +1 -0
  82. package/dist/blocks/record-detail-01/record-detail.js +150 -0
  83. package/dist/blocks/record-form-01/index.d.ts +3 -0
  84. package/dist/blocks/record-form-01/index.d.ts.map +1 -0
  85. package/dist/blocks/record-form-01/index.js +1 -0
  86. package/dist/blocks/record-form-01/record-form.d.ts +148 -0
  87. package/dist/blocks/record-form-01/record-form.d.ts.map +1 -0
  88. package/dist/blocks/record-form-01/record-form.js +107 -0
  89. package/dist/blocks/record-grid-01/index.d.ts +3 -0
  90. package/dist/blocks/record-grid-01/index.d.ts.map +1 -0
  91. package/dist/blocks/record-grid-01/index.js +1 -0
  92. package/dist/blocks/record-grid-01/record-grid.d.ts +145 -0
  93. package/dist/blocks/record-grid-01/record-grid.d.ts.map +1 -0
  94. package/dist/blocks/record-grid-01/record-grid.js +114 -0
  95. package/dist/blocks/record-wizard-01/index.d.ts +3 -0
  96. package/dist/blocks/record-wizard-01/index.d.ts.map +1 -0
  97. package/dist/blocks/record-wizard-01/index.js +1 -0
  98. package/dist/blocks/record-wizard-01/record-wizard.d.ts +152 -0
  99. package/dist/blocks/record-wizard-01/record-wizard.d.ts.map +1 -0
  100. package/dist/blocks/record-wizard-01/record-wizard.js +144 -0
  101. package/dist/blocks/settings-members-01/settings-members.d.ts +2 -1
  102. package/dist/blocks/settings-members-01/settings-members.d.ts.map +1 -1
  103. package/dist/blocks/settings-panel-01/index.d.ts +1 -1
  104. package/dist/blocks/settings-panel-01/index.d.ts.map +1 -1
  105. package/dist/blocks/settings-panel-01/settings-panel.d.ts +165 -67
  106. package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -1
  107. package/dist/blocks/settings-panel-01/settings-panel.js +92 -33
  108. package/dist/blocks/signup-01/index.d.ts +1 -1
  109. package/dist/blocks/signup-01/index.d.ts.map +1 -1
  110. package/dist/blocks/signup-01/signup-01.d.ts +10 -84
  111. package/dist/blocks/signup-01/signup-01.d.ts.map +1 -1
  112. package/dist/blocks/signup-01/signup-01.js +41 -23
  113. package/dist/blocks/stats-01/index.d.ts +5 -5
  114. package/dist/blocks/stats-01/index.d.ts.map +1 -1
  115. package/dist/blocks/stats-01/index.js +4 -4
  116. package/dist/blocks/stats-01/stats.d.ts +49 -14
  117. package/dist/blocks/stats-01/stats.d.ts.map +1 -1
  118. package/dist/blocks/stats-01/stats.js +65 -15
  119. package/dist/blocks/trend-01/index.d.ts +1 -1
  120. package/dist/blocks/trend-01/index.d.ts.map +1 -1
  121. package/dist/blocks/trend-01/trend.d.ts +26 -73
  122. package/dist/blocks/trend-01/trend.d.ts.map +1 -1
  123. package/dist/blocks/trend-01/trend.js +47 -30
  124. package/dist/catalog.d.ts.map +1 -1
  125. package/dist/catalog.js +93 -3
  126. package/dist/components/ui/metric.d.ts +4 -2
  127. package/dist/components/ui/metric.d.ts.map +1 -1
  128. package/dist/index.d.ts +10 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +6 -0
  131. package/dist/lib/field-render.d.ts +107 -0
  132. package/dist/lib/field-render.d.ts.map +1 -0
  133. package/dist/lib/field-render.js +251 -0
  134. package/dist/lib/spec.d.ts +609 -0
  135. package/dist/lib/spec.d.ts.map +1 -0
  136. package/dist/lib/spec.js +1 -0
  137. package/dist/live/index.d.ts +2 -0
  138. package/dist/live/index.d.ts.map +1 -1
  139. package/dist/live/index.js +6 -0
  140. package/dist/live/message-thread-01/index.d.ts +3 -0
  141. package/dist/live/message-thread-01/index.d.ts.map +1 -0
  142. package/dist/live/message-thread-01/index.js +1 -0
  143. package/dist/live/message-thread-01/message-thread.d.ts +282 -0
  144. package/dist/live/message-thread-01/message-thread.d.ts.map +1 -0
  145. package/dist/live/message-thread-01/message-thread.js +221 -0
  146. package/dist/live/run-stream-01/run-stream.d.ts +2 -1
  147. package/dist/live/run-stream-01/run-stream.d.ts.map +1 -1
  148. package/dist/live/run-stream-01/run-stream.js +1 -7
  149. package/dist/live/status.d.ts +27 -0
  150. package/dist/live/status.d.ts.map +1 -0
  151. package/dist/live/status.js +15 -0
  152. package/dist/live/tool-call-row.d.ts +26 -0
  153. package/dist/live/tool-call-row.d.ts.map +1 -0
  154. package/dist/live/tool-call-row.js +60 -0
  155. package/dist/live/tool-ledger-01/tool-ledger.d.ts.map +1 -1
  156. package/dist/live/tool-ledger-01/tool-ledger.js +2 -42
  157. package/dist/pages/contact-page/index.d.ts +7 -7
  158. package/dist/pages/contact-page/index.d.ts.map +1 -1
  159. package/dist/pages/contact-page/index.js +17 -5
  160. package/dist/pages/docs-shell/docs-shell.d.ts +42 -30
  161. package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
  162. package/dist/pages/docs-shell/docs-shell.js +70 -48
  163. package/dist/pages/document-page/document-page.d.ts +146 -0
  164. package/dist/pages/document-page/document-page.d.ts.map +1 -0
  165. package/dist/pages/document-page/document-page.js +80 -0
  166. package/dist/pages/document-page/index.d.ts +3 -0
  167. package/dist/pages/document-page/index.d.ts.map +1 -0
  168. package/dist/pages/document-page/index.js +1 -0
  169. package/dist/pages/index.d.ts +2 -0
  170. package/dist/pages/index.d.ts.map +1 -1
  171. package/dist/pages/index.js +6 -0
  172. package/dist/styles.css +59 -3
  173. package/package.json +7 -2
@@ -0,0 +1,154 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { XIcon } from 'lucide-react';
4
+ import { useEffect, useId, useRef, useState } from 'react';
5
+ import { Button } from '../../components/ui/button.js';
6
+ import { Checkbox } from '../../components/ui/checkbox.js';
7
+ import { LiveRegion } from '../../components/ui/live-region.js';
8
+ import { Pagination, PaginationContent, PaginationItem } from '../../components/ui/pagination.js';
9
+ import { headingSizeClass } from '../../components/ui/section.js';
10
+ import { cn } from '../../lib/utils.js';
11
+ /** The page numbers to show, with `ellipsis` where a run is held back. */
12
+ function pageWindow(page, pageCount) {
13
+ if (pageCount <= 7) {
14
+ return Array.from({ length: pageCount }, (_, index) => index + 1);
15
+ }
16
+ const pages = [1];
17
+ const start = Math.max(2, page - 1);
18
+ const end = Math.min(pageCount - 1, page + 1);
19
+ if (start > 2)
20
+ pages.push('ellipsis');
21
+ for (let value = start; value <= end; value += 1)
22
+ pages.push(value);
23
+ if (end < pageCount - 1)
24
+ pages.push('ellipsis');
25
+ pages.push(pageCount);
26
+ return pages;
27
+ }
28
+ /** The grid track count at the widest width, as the one static class per arm. */
29
+ function gridClass(columnsPerRow) {
30
+ if (columnsPerRow === 2)
31
+ return 'grid grid-cols-1 gap-4 sm:grid-cols-2';
32
+ if (columnsPerRow === 4)
33
+ return 'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4';
34
+ return 'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3';
35
+ }
36
+ /**
37
+ * The record index as a grid of picture cards a reader compares and acts on.
38
+ *
39
+ * **It is the record index's second arrangement, and the job-identity test is
40
+ * what settles it.** A reader here is doing the index's job, deciding which
41
+ * record to act on, and the difference from `DataTable01` is the arrangement
42
+ * rather than the content: records are compared as pictures rather than
43
+ * across columns, so a screen that wants this has not found the index with one
44
+ * more prop, it has found the picture arrangement. `DESIGN.md` names it as an
45
+ * arrangement that forces a second Item, and this is that Item. A card carries
46
+ * the caller's own picture, name and price through `renderCard`, and per card
47
+ * actions through `renderCardActions`.
48
+ *
49
+ * **It shares the record index's selection and announcement contract.** The
50
+ * same `selectable` union, the same held set, the same three-state header
51
+ * control, the same polite `LiveRegion` carrying a count, the same batch bar
52
+ * that mounts only while the set is non-empty, and the same `selectionScope`
53
+ * that distinguishes a page-wide selection from a filter-wide one, all because
54
+ * ticket 166 made one contract the law for the index family. The count is
55
+ * announced once, politely, and never assertively; when the set empties the
56
+ * region speaks its own sentence rather than a count of zero; and the visible
57
+ * summary and the announced count are the same string.
58
+ *
59
+ * **It draws the page of cards it was handed, and it fetches nothing.** It sorts
60
+ * nothing, filters nothing and slices nothing, and every control in the footer
61
+ * is a report. It owns no action, batch or per card, so it owns no confirmation
62
+ * dialog, no toast and no undo; the batch bar is the Block's region and the
63
+ * caller's controls, and the dismiss is the Block's one control because its
64
+ * whole body is a call on the set the Block already holds.
65
+ *
66
+ * **The card is the caller's composition, and the arrangement is the Block's.**
67
+ * A picture, a name and a price are three different value shapes, and the read
68
+ * vocabulary this package publishes (`ColumnSpec` and `ColumnKind`) describes a
69
+ * value a reader reads in a table rather than the anatomy of a card. So the card
70
+ * body is one node per record and the picture, the name and the price are the
71
+ * caller's own composition, exactly as a per card action is. The Block owns the
72
+ * grid, the selection set, the count, the batch bar and the page boundary, which
73
+ * is the whole of what makes this an index rather than a list of pictures.
74
+ */
75
+ export function CardIndex01(props) {
76
+ const { title, description, headingLevel = 'h2', rows, getRowId, renderCard, renderCardActions, columnsPerRow = 3, page: pageProp, defaultPage = 1, pageCount, onPageChange, emptyMessage, labels, className, } = props;
77
+ const Heading = headingLevel;
78
+ const headingId = useId();
79
+ const isSelectable = props.selectable === true;
80
+ const [internalSelected, setInternalSelected] = useState(() => [
81
+ ...(props.defaultSelectedIds ?? []),
82
+ ]);
83
+ const [internalPage, setInternalPage] = useState(defaultPage);
84
+ const selected = isSelectable
85
+ ? props.selectedIds === undefined
86
+ ? internalSelected
87
+ : [...props.selectedIds]
88
+ : [];
89
+ const page = pageProp ?? internalPage;
90
+ const clampPage = (value) => Math.min(Math.max(1, value), Math.max(1, pageCount));
91
+ const currentPage = clampPage(page);
92
+ const hasCardActions = renderCardActions !== undefined;
93
+ const scope = isSelectable ? props.selectionScope : undefined;
94
+ const filterScope = scope?.scope === 'filter' ? scope : undefined;
95
+ // A filter-wide selection is a selection even though its keys are not on
96
+ // screen, so the bar mounts and the region speaks from the scope rather than
97
+ // from the page's keys. The number is the caller's and is never the rows on
98
+ // screen.
99
+ const selectionActive = isSelectable && (filterScope !== undefined || selected.length > 0);
100
+ const selectionSummary = filterScope !== undefined
101
+ ? labels.selectedAllMatching(filterScope.count)
102
+ : labels.selectedCount(selected.length);
103
+ const rowIds = rows.map(getRowId);
104
+ const allSelected = filterScope !== undefined || (rowIds.length > 0 && rowIds.every((id) => selected.includes(id)));
105
+ const someSelected = filterScope !== undefined || rowIds.some((id) => selected.includes(id));
106
+ // The emptying sentence. A live region speaks when its text changes, and the
107
+ // sequence where the selection falls to nothing is the one the reader has to
108
+ // be told about because the batch bar they were about to use disappears with
109
+ // it. The previous presence is ref-held, so the render that reaches nothing
110
+ // still knows there was a selection and can speak the sentence rather than the
111
+ // absence of one.
112
+ const previousActive = useRef(selectionActive);
113
+ useEffect(() => {
114
+ previousActive.current = selectionActive;
115
+ }, [selectionActive]);
116
+ const announcement = selectionActive
117
+ ? selectionSummary
118
+ : previousActive.current
119
+ ? labels.clearedSelection
120
+ : null;
121
+ function changeSelection(ids) {
122
+ if (isSelectable === false)
123
+ return;
124
+ if (props.selectedIds === undefined)
125
+ setInternalSelected(ids);
126
+ props.onSelectedIdsChange?.(ids);
127
+ }
128
+ function clearSelection() {
129
+ changeSelection([]);
130
+ }
131
+ function toggleAll(checked) {
132
+ if (checked) {
133
+ changeSelection([...new Set([...selected, ...rowIds])]);
134
+ }
135
+ else {
136
+ changeSelection(selected.filter((id) => !rowIds.includes(id)));
137
+ }
138
+ }
139
+ function toggleRow(id, checked) {
140
+ changeSelection(checked ? [...selected, id] : selected.filter((item) => item !== id));
141
+ }
142
+ function changePage(next) {
143
+ const clamped = clampPage(next);
144
+ if (pageProp === undefined)
145
+ setInternalPage(clamped);
146
+ onPageChange?.(clamped);
147
+ }
148
+ return (_jsxs("div", { "data-slot": "card-index-01", className: cn('flex flex-col gap-4', className), children: [title || description ? (_jsxs("div", { "data-slot": "card-index-01-header", className: "flex flex-col gap-1", children: [title ? (_jsx(Heading, { id: headingId, className: cn('font-semibold tracking-tight text-balance', headingSizeClass(Heading)), children: title })) : null, description ? (_jsx("p", { className: "text-muted-foreground text-sm", children: description })) : null] })) : null, selectionActive ? (_jsxs("div", { "data-slot": "card-index-01-batch-bar", role: "toolbar", "aria-label": selectionSummary, className: "bg-card flex w-full items-center gap-1 rounded-md border p-1", children: [_jsx("span", { className: "text-muted-foreground px-2 text-sm font-medium", children: selectionSummary }), _jsx("div", { className: "flex flex-1 flex-wrap items-center gap-2", children: props.selectable === true ? props.batchActions : null }), _jsx(Button, { "data-slot": "card-index-01-dismiss", type: "button", variant: "ghost", size: "sm", "aria-label": labels.dismissSelection, onClick: clearSelection, children: _jsx(XIcon, { "aria-hidden": "true" }) })] })) : null, _jsx(LiveRegion, { "data-slot": "card-index-01-announcement", politeness: "polite", className: "sr-only", children: announcement }), isSelectable ? (_jsx("div", { "data-slot": "card-index-01-select-all", className: "flex items-center gap-2", children: _jsx(Checkbox, { checked: allSelected, indeterminate: !allSelected && someSelected, onCheckedChange: toggleAll, "aria-label": labels.selectAll }) })) : null, rows.length ? (_jsx("ul", { "data-slot": "card-index-01-grid", className: gridClass(columnsPerRow), children: rows.map((row) => {
149
+ const id = getRowId(row);
150
+ const isSelected = selected.includes(id);
151
+ return (_jsxs("li", { "data-slot": "card-index-01-card", "data-state": isSelected ? 'selected' : undefined, className: "bg-card flex flex-col gap-3 rounded-xl border p-3", children: [isSelectable || hasCardActions ? (_jsxs("div", { className: "flex items-start justify-between gap-2", children: [isSelectable ? (_jsx(Checkbox, { checked: isSelected, onCheckedChange: (checked) => toggleRow(id, checked), "aria-label": labels.selectCard(row) })) : (_jsx("span", {})), hasCardActions ? (_jsx("div", { role: "group", "aria-label": labels.cardActions, className: "flex items-center gap-1", children: renderCardActions?.(row) })) : null] })) : null, _jsx("div", { className: "min-w-0 flex-1", children: renderCard(row) })] }, id));
152
+ }) })) : (_jsx("div", { "data-slot": "card-index-01-empty", className: "text-muted-foreground border-border flex h-24 items-center justify-center rounded-xl border text-center text-sm", children: emptyMessage ?? labels.empty })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [selectionActive ? (_jsx("span", { className: "text-muted-foreground text-sm", children: selectionSummary })) : (_jsx("span", {})), _jsx(Pagination, { className: "mx-0 w-auto justify-end", children: _jsxs(PaginationContent, { children: [_jsx(PaginationItem, { children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: currentPage <= 1, "aria-label": labels.previous, onClick: () => changePage(currentPage - 1), children: labels.previous }) }), pageWindow(currentPage, Math.max(1, pageCount)).map((item, index) => item === 'ellipsis' ? (_jsx(PaginationItem, { children: _jsx("span", { "aria-hidden": "true", className: "text-muted-foreground px-2", children: "..." }) }, `ellipsis-${index}`)) : (_jsx(PaginationItem, { children: _jsx(Button, { type: "button", variant: item === currentPage ? 'outline' : 'ghost', size: "icon", "aria-label": labels.page(item), "aria-current": item === currentPage ? 'page' : undefined, onClick: () => changePage(item), children: item }) }, item))), _jsx(PaginationItem, { children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: currentPage >= Math.max(1, pageCount), "aria-label": labels.next, onClick: () => changePage(currentPage + 1), children: labels.next }) })] }) })] })] }));
153
+ }
154
+ export default CardIndex01;
@@ -0,0 +1,3 @@
1
+ export { CardIndex01, default } from './card-index.js';
2
+ export type { CardIndex01Labels, CardIndex01Props, CardIndex01Row, CardIndex01SelectionScope, } from './card-index.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/card-index-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACnD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,yBAAyB,GAC1B,MAAM,cAAc,CAAA"}
@@ -0,0 +1 @@
1
+ export { CardIndex01, default } from './card-index.js';
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { type HeadingLevel } from '../../components/ui/section.js';
3
+ import type { MetricSpec } from '../../lib/spec.js';
3
4
  /**
4
5
  * How much of a six-track grid one card takes.
5
6
  *
@@ -18,25 +19,6 @@ import { type HeadingLevel } from '../../components/ui/section.js';
18
19
  * grid the caller composed them into.
19
20
  */
20
21
  export type ChartCard01Span = 'full' | 'half' | 'third';
21
- /**
22
- * One figure the card states about what the figure shows.
23
- *
24
- * A node and not a number for the reason `Metric` gives: a caller's figure is often a
25
- * value it formatted itself, and a number prop would make it either ship that as a
26
- * string outside the Component or lose the unit it belongs to. `deltaFormat` is here
27
- * for the same reason: Prism formats the delta and nothing else, and a delta of `0.12`
28
- * is twelve percent, twelve cents or twelve milliseconds.
29
- */
30
- export type ChartCard01Reading = {
31
- /** What the figure measures, read under the figure rather than above it. */
32
- label: string;
33
- /** The figure itself, in the caller's own units and the caller's own formatting. */
34
- value: ReactNode;
35
- /** The change against the previous reading, where the sign is the direction. */
36
- delta?: number;
37
- /** The words for the delta, given the number. See the note on this type. */
38
- deltaFormat?: (value: number) => string;
39
- };
40
22
  /**
41
23
  * The props a ChartCard01 takes. Every string is a prop and the Block ships none.
42
24
  */
@@ -69,8 +51,19 @@ export type ChartCard01Props = {
69
51
  * here, which is the only reason the swatch and the mark cannot disagree.
70
52
  */
71
53
  legend?: ReactNode;
72
- /** The figures this card states about what the figure shows. */
73
- reading?: ChartCard01Reading[];
54
+ /**
55
+ * The figures this card states about what the figure shows, as the shared
56
+ * `MetricSpec` typed data.
57
+ *
58
+ * **The shared specification and not a shape of this Block's own.** `key`,
59
+ * `label` and `value` are required on each reading, `delta` is a number whose sign
60
+ * is the direction, `deltaFormat` is the caller's own words for that change,
61
+ * `hint` carries the period or the caveat, and a `series` and an `href` each travel
62
+ * with the label or the words they cannot be read without. The type is published as
63
+ * `@nanisoft/prism-ui/spec`, so the words a caller learns on a summary are the
64
+ * words on this card.
65
+ */
66
+ reading?: MetricSpec[];
74
67
  /** The controls that act on the figure: a range, a segment, a download. */
75
68
  actions?: ReactNode;
76
69
  /** The band under the figure, for the line that qualifies it. */
@@ -123,6 +116,15 @@ export type ChartCard01Props = {
123
116
  * so a card composed one level deeper carries its outline with it instead of
124
117
  * announcing three siblings of the section that introduces them.
125
118
  *
119
+ * **The readings are the shared `MetricSpec` and not a shape of this Block's own, so
120
+ * a figure declared for a summary is the same figure here.** Each reading carries a
121
+ * stable `key` that is never the words of the label, a required `label` and `value`,
122
+ * a `delta` whose sign is the direction, an optional `deltaFormat` carrying the
123
+ * caller's own words for the change, an optional `hint` for the period or the
124
+ * caveat, and an optional `series` and `href` with their names. A delta with no
125
+ * formatter prints the number the caller passed and one with a formatter prints the
126
+ * caller's words, which is the disagreement this migration closes.
127
+ *
126
128
  * **The readings sit in a flex row that wraps, and they share the width rather than
127
129
  * holding a fixed one.** The measurement that decides it: a `third` card at the
128
130
  * 72rem container is about 22rem wide, so three readings across it are about 7rem
@@ -1 +1 @@
1
- {"version":3,"file":"chart-card-01.d.ts","sourceRoot":"","sources":["../../../src/blocks/chart-card-01/chart-card-01.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAWtC,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAG3E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,KAAK,EAAE,SAAS,CAAA;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACxC,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;;;OAQG;IACH,MAAM,EAAE,SAAS,CAAA;IACjB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,gEAAgE;IAChE,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC9B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,iEAAiE;IACjE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,OAAY,EACZ,OAAO,EACP,MAAM,EACN,IAAa,EACb,YAAmB,EACnB,SAAS,GACV,EAAE,gBAAgB,+BAmElB;AAED,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"chart-card-01.d.ts","sourceRoot":"","sources":["../../../src/blocks/chart-card-01/chart-card-01.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAYtC,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAGhD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEvD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;;;OAQG;IACH,MAAM,EAAE,SAAS,CAAA;IACjB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,iEAAiE;IACjE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,OAAY,EACZ,OAAO,EACP,MAAM,EACN,IAAa,EACb,YAAmB,EACnB,SAAS,GACV,EAAE,gBAAgB,+BA+FlB;AAED,eAAe,WAAW,CAAA"}
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '../../components/ui/card.js';
3
+ import { CtaLink } from '../../components/ui/cta-link.js';
3
4
  import { Metric } from '../../components/ui/metric.js';
4
5
  import { childLevel } from '../../components/ui/section.js';
6
+ import { Sparkline } from '../../components/ui/sparkline.js';
5
7
  import { cn } from '../../lib/utils.js';
6
8
  /**
7
9
  * What each named span takes of a six-track grid, at the breakpoint the grid appears.
@@ -16,6 +18,32 @@ const SPAN = {
16
18
  half: 'lg:col-span-3',
17
19
  third: 'lg:col-span-2',
18
20
  };
21
+ /**
22
+ * The refusals, as checks, so a reading that would render a link nobody can name is
23
+ * a diagnostic in a console rather than a rendered link.
24
+ *
25
+ * **The type already holds both pairs, and these checks exist for the caller the
26
+ * type never saw.** `MetricSpec` requires a destination's words wherever it has a
27
+ * destination and a series name wherever it has a series, so a typed caller cannot
28
+ * reach either diagnostic. A JavaScript caller can, and the cost of reaching one is
29
+ * a reading that draws a link with no words or a shape no reader can announce, so
30
+ * the same rule is stated once more where a runtime value can be read: the pair rule
31
+ * is the one `Dashboard01` makes.
32
+ */
33
+ function assertMetrics(metrics) {
34
+ for (const metric of metrics) {
35
+ if ((metric.href === undefined) !== (metric.hrefLabel === undefined)) {
36
+ throw new Error(`ChartCard01: the reading "${metric.key}" declares one of href and hrefLabel without the other, so the ` +
37
+ 'reading would carry a link with no words on it, or a name with no link beside it. Pass the words that say ' +
38
+ 'what following it does, or omit the href.');
39
+ }
40
+ if ((metric.series === undefined) !== (metric.seriesLabel === undefined)) {
41
+ throw new Error(`ChartCard01: the reading "${metric.key}" declares one of series and seriesLabel without the other, so the ` +
42
+ 'figure would be a picture with no name, which a screen reader cannot announce and two shapes in one row ' +
43
+ 'cannot be told apart. Pass the series name in the words the product uses, or omit the series.');
44
+ }
45
+ }
46
+ }
19
47
  /**
20
48
  * A titled panel holding one figure, the key for it, the controls that act on it, and
21
49
  * the figures the card states about it.
@@ -51,6 +79,15 @@ const SPAN = {
51
79
  * so a card composed one level deeper carries its outline with it instead of
52
80
  * announcing three siblings of the section that introduces them.
53
81
  *
82
+ * **The readings are the shared `MetricSpec` and not a shape of this Block's own, so
83
+ * a figure declared for a summary is the same figure here.** Each reading carries a
84
+ * stable `key` that is never the words of the label, a required `label` and `value`,
85
+ * a `delta` whose sign is the direction, an optional `deltaFormat` carrying the
86
+ * caller's own words for the change, an optional `hint` for the period or the
87
+ * caveat, and an optional `series` and `href` with their names. A delta with no
88
+ * formatter prints the number the caller passed and one with a formatter prints the
89
+ * caller's words, which is the disagreement this migration closes.
90
+ *
54
91
  * **The readings sit in a flex row that wraps, and they share the width rather than
55
92
  * holding a fixed one.** The measurement that decides it: a `third` card at the
56
93
  * 72rem container is about 22rem wide, so three readings across it are about 7rem
@@ -76,6 +113,14 @@ export function ChartCard01({ title, description, figure, legend, reading = [],
76
113
  // One step below the heading that introduces this card, so the card's own title
77
114
  // nests under the section rather than beside it.
78
115
  const Title = childLevel(headingLevel);
79
- return (_jsx("div", { "data-slot": "chart-card-01", "data-span": span, className: cn(SPAN[span], className), children: _jsxs(Card, { className: "h-full gap-4 py-5", children: [_jsxs(CardHeader, { className: "grid-cols-[minmax(0,1fr)_auto] gap-x-3", children: [_jsxs("div", { "data-slot": "chart-card-01-head", className: "flex min-w-0 flex-col gap-1.5", children: [_jsx(CardTitle, { as: Title, className: "text-base", children: title }), description === undefined ? null : (_jsx(CardDescription, { className: "text-pretty", children: description }))] }), actions === undefined ? null : (_jsx("div", { "data-slot": "chart-card-01-actions", className: "flex shrink-0 items-center gap-2", children: actions }))] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsx("div", { "data-slot": "chart-card-01-figure", className: "min-w-0", children: figure }), legend === undefined ? null : (_jsx("div", { "data-slot": "chart-card-01-legend", className: "min-w-0", children: legend })), reading.length === 0 ? null : (_jsx("div", { "data-slot": "chart-card-01-reading", className: "border-border flex flex-wrap items-end gap-x-6 gap-y-3 border-t pt-4", children: reading.map((entry) => (_jsx("div", { className: "min-w-0 flex-1", children: _jsx(Metric, { value: entry.value, label: entry.label, delta: entry.delta, deltaFormat: entry.deltaFormat }) }, entry.label))) }))] }), footer === undefined ? null : (_jsx(CardFooter, { "data-slot": "chart-card-01-footer", children: footer }))] }) }));
116
+ if (reading.length > 0)
117
+ assertMetrics(reading);
118
+ return (_jsx("div", { "data-slot": "chart-card-01", "data-span": span, className: cn(SPAN[span], className), children: _jsxs(Card, { className: "h-full gap-4 py-5", children: [_jsxs(CardHeader, { className: "grid-cols-[minmax(0,1fr)_auto] gap-x-3", children: [_jsxs("div", { "data-slot": "chart-card-01-head", className: "flex min-w-0 flex-col gap-1.5", children: [_jsx(CardTitle, { as: Title, className: "text-base", children: title }), description === undefined ? null : (_jsx(CardDescription, { className: "text-pretty", children: description }))] }), actions === undefined ? null : (_jsx("div", { "data-slot": "chart-card-01-actions", className: "flex shrink-0 items-center gap-2", children: actions }))] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsx("div", { "data-slot": "chart-card-01-figure", className: "min-w-0", children: figure }), legend === undefined ? null : (_jsx("div", { "data-slot": "chart-card-01-legend", className: "min-w-0", children: legend })), reading.length === 0 ? null : (_jsx("div", { "data-slot": "chart-card-01-reading", className: "border-border flex flex-wrap items-end gap-x-6 gap-y-3 border-t pt-4", children: reading.map((metric) => {
119
+ // The delta's words are the caller's own node, and `Metric` takes a
120
+ // formatter, so the node is placed through one. A delta with no words
121
+ // is left to `Metric`, which prints the number the caller passed.
122
+ const deltaWords = metric.deltaFormat;
123
+ return (_jsxs("div", { "data-slot": "chart-card-01-reading-item", "data-metric": metric.key, className: "flex min-w-0 flex-1 flex-col gap-3", children: [_jsx(Metric, { value: metric.value, label: metric.label, delta: metric.delta, deltaFormat: deltaWords === undefined ? undefined : () => deltaWords, hint: metric.hint }), metric.series === undefined ? null : (_jsx("div", { "data-slot": "chart-card-01-reading-sparkline", className: "flex", children: _jsx(Sparkline, { values: metric.series, label: metric.seriesLabel, className: "ms-auto" }) })), metric.href === undefined ? null : (_jsx(CtaLink, { href: metric.href, variant: "ghost", size: "sm", children: metric.hrefLabel }))] }, metric.key));
124
+ }) }))] }), footer === undefined ? null : (_jsx(CardFooter, { "data-slot": "chart-card-01-footer", children: footer }))] }) }));
80
125
  }
81
126
  export default ChartCard01;
@@ -1,3 +1,3 @@
1
1
  export { ChartCard01, default } from './chart-card-01.js';
2
- export type { ChartCard01Props, ChartCard01Reading, ChartCard01Span, } from './chart-card-01.js';
2
+ export type { ChartCard01Props, ChartCard01Span } from './chart-card-01.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/chart-card-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAKtD,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,GAChB,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/chart-card-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAItD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { type HeadingLevel } from '../../components/ui/section.js';
3
- import { type ChartCard01Reading } from '../chart-card-01/chart-card-01.js';
3
+ import type { MetricSpec } from '../../lib/spec.js';
4
4
  /**
5
5
  * One card in the grid: a title, a figure, and everything the card carries around it.
6
6
  *
@@ -32,8 +32,8 @@ export type ChartGroup01Card = {
32
32
  figure: ReactNode;
33
33
  /** The key for the figure, wherever the caller wants it. */
34
34
  legend?: ReactNode;
35
- /** The figures this card states about what the figure shows. */
36
- reading?: ChartCard01Reading[];
35
+ /** The figures this card states about what the figure shows. See `MetricSpec`. */
36
+ reading?: MetricSpec[];
37
37
  /** The controls that act on the figure: a range, a segment, a download. */
38
38
  actions?: ReactNode;
39
39
  };
@@ -1 +1 @@
1
- {"version":3,"file":"chart-group-01.d.ts","sourceRoot":"","sources":["../../../src/blocks/chart-group-01/chart-group-01.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAExF,OAAO,EAAe,KAAK,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAErF;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAA;IACV,0EAA0E;IAC1E,KAAK,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,CAAA;IACjB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,gEAAgE;IAChE,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC9B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACf;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,OAAW,EACX,KAAK,EACL,YAAmB,EACnB,SAAS,GACV,EAAE,iBAAiB,+BA2CnB;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"chart-group-01.d.ts","sourceRoot":"","sources":["../../../src/blocks/chart-group-01/chart-group-01.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AACxF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAIhD;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAA;IACV,0EAA0E;IAC1E,KAAK,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,CAAA;IACjB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACf;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,OAAW,EACX,KAAK,EACL,YAAmB,EACnB,SAAS,GACV,EAAE,iBAAiB,+BA2CnB;AAED,eAAe,YAAY,CAAA"}
@@ -1,81 +1,21 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { type HeadingLevel } from '../../components/ui/section.js';
3
+ import type { FieldSpecGroup } from '../../lib/spec.js';
3
4
  /**
4
- * One choice in a `select` field, and nothing else.
5
+ * One thing wrong with the form, in the caller's own words.
5
6
  *
6
- * A native `<option>` holds text and nothing more, which is the whole reason a
7
- * contact form's select is a `NativeSelect` rather than the Base UI one. The two
8
- * values are a string and its label, so a caller who needs a description beside an
9
- * option, a count, or anything nested is reaching for a control the platform
10
- * cannot render, and `Contact01` does not pretend otherwise: it names
11
- * `NativeSelect`'s own argument in its JSDoc and stops there.
7
+ * `field` is the `key` of the field the message belongs to, and the key rather
8
+ * than the label because it is what the caller's values and the form's own
9
+ * `FormData` are keyed by: a message matched by a label's words breaks the moment
10
+ * the label is translated. The shape is the one the dialog, the wizard and the
11
+ * write form already publish, so this Block mints no fifth spelling of an issue.
12
12
  */
13
- export type Contact01Option = {
14
- /** What the form submits for this choice, which is a key and not a sentence. */
15
- value: string;
16
- /** The words the reader sees, in the product's own language. */
17
- label: string;
13
+ export type Contact01Issue = {
14
+ /** The `key` of the field this message belongs to. */
15
+ field: string;
16
+ /** The message, in the product's own words. */
17
+ message: string;
18
18
  };
19
- /**
20
- * What every contact field carries, whichever control it draws.
21
- *
22
- * `required` is a boolean rather than an optional prop because there is no third
23
- * state worth having: a field is one the reader must fill or one they may leave,
24
- * and a `required?: boolean` leaves a caller who passed nothing unable to say which
25
- * of the two they meant. A field is a data declaration and nothing else, and the
26
- * two arms below differ only in which control is drawn and in the one property
27
- * that control needs and the other has no use for.
28
- */
29
- type ContactFieldBase = {
30
- /** The caller's key for this field, and the key the value arrives under. */
31
- id: string;
32
- /** The field's visible name, and the accessible name its control announces. */
33
- label: string;
34
- /** Whether the reader may submit the form without filling it. */
35
- required: boolean;
36
- /**
37
- * A line under the control, announced with it.
38
- *
39
- * A node, because the honest line is sometimes not one sentence: a caller's
40
- * hint, a link to the thing being asked about, a piece of the page's own copy.
41
- * It is wired to the control through `aria-describedby` rather than drawn as a
42
- * footnote, so a screen reader hears what this field is for as they reach it.
43
- */
44
- description?: ReactNode;
45
- /** What the control shows while it is empty. A hint, not a name. */
46
- placeholder?: string;
47
- /** The platform's own autofill hint for this field. */
48
- autoComplete?: string;
49
- };
50
- /**
51
- * One field of the caller's form, and the control it draws.
52
- *
53
- * **A union and not one type with an optional `options`, and that is the shape of
54
- * the whole argument in this Block.** A contact form's fields are the caller's
55
- * declaration, not this package's opinion about what a reader may be asked, and
56
- * the one place that shows is the select: a select with no options is a control
57
- * with nothing in it, which is a question no reader can answer, so `options` is
58
- * required inside the select arm and forbidden beside the other four. A
59
- * `options?: string[]` on one type would typecheck the defect and only the
60
- * diagnostic at run time would catch it, which is one gate too late. The union is
61
- * the same mechanism `HeroAction` uses for the same reason: the shape that
62
- * requires something declares it, and the shape that forbids it says so in the
63
- * type rather than in prose.
64
- *
65
- * The cost of the union is that a caller who wants to build a field list
66
- * generically, from data, has to narrow before it can type the result, and the
67
- * answer to that is a narrowing function in the caller's own code rather than a
68
- * looser type here.
69
- */
70
- export type ContactField = (ContactFieldBase & {
71
- type: 'select';
72
- /** The choices, in the order the reader should meet them. */
73
- options: Contact01Option[];
74
- }) | (ContactFieldBase & {
75
- type: 'text' | 'email' | 'tel' | 'textarea';
76
- /** A select with no options is a question nobody can answer, so it is not one here. */
77
- options?: never;
78
- });
79
19
  /**
80
20
  * Where a reader can reach a person, and the one address this Block links.
81
21
  *
@@ -155,7 +95,7 @@ export type Contact01Props = {
155
95
  */
156
96
  onSubmit: (value: ContactValue) => void;
157
97
  /**
158
- * The fields, in the order the reader should meet them.
98
+ * The fields, grouped and ordered, as the shared field specification.
159
99
  *
160
100
  * **Required, and this list is the whole design rather than a convenience.** A
161
101
  * contact form Component with a fixed set of fields is a Component that decides
@@ -165,15 +105,23 @@ export type Contact01Props = {
165
105
  * be abandoned and retried through a support channel, a form that cannot ask for
166
106
  * a company size sends a one-line hello to a sales team that then spends a week
167
107
  * on discovery calls, and a form that cannot ask for a budget guarantees the
168
- * reply nobody wanted. So Prism names the five control types it can draw and
169
- * supplies no fields: a contact desk that asks for a tax number, a preferred
170
- * contact window and three product interests is three fields of the caller's own
171
- * vocabulary and a select, and Prism composes all of them. The cost is stated
172
- * rather than hidden: a consumer that wants a second column for a subset of its
173
- * fields composes two `Contact01`s or reaches for a `Page`, because this Block
174
- * draws one column in the order the list gives.
108
+ * reply nobody wanted. The fields arrive as `FieldSpecGroup`, which is the same
109
+ * type a record write form takes, so the vocabulary a consumer learns on one
110
+ * screen is the vocabulary here, and a control this package does not ship is the
111
+ * `slot` arm rather than a second Block. The cost is stated rather than hidden: a
112
+ * consumer that wants a second column for a subset of its fields composes two
113
+ * `Contact01`s or reaches for a `Page`, because this Block draws one column in
114
+ * the order the list gives.
115
+ */
116
+ groups: readonly FieldSpecGroup[];
117
+ /**
118
+ * The issues to draw, keyed by field. Drawn under the control that owns them.
119
+ *
120
+ * An entry names a field's `key` and a message in the product's own words, and
121
+ * a control's issue is drawn under that control. A message about the submission
122
+ * rather than about a control is the caller's `status`, which names no field.
175
123
  */
176
- fields: ContactField[];
124
+ issues?: readonly Contact01Issue[];
177
125
  /** The label on the one control that submits. */
178
126
  submitLabel: string;
179
127
  /**
@@ -314,6 +262,6 @@ export type Contact01Props = {
314
262
  * nothing. The rest of the rendering is a section and five controls, which is the
315
263
  * price of the argument above and not a claim about what else this Block might do.
316
264
  */
317
- export declare function Contact01({ eyebrow, title, description, onSubmit, fields, submitLabel, consent, address, variant, status, headingLevel, className, }: Contact01Props): import("react").JSX.Element;
265
+ export declare function Contact01({ eyebrow, title, description, onSubmit, groups, issues, submitLabel, consent, address, variant, status, headingLevel, className, }: Contact01Props): import("react").JSX.Element;
318
266
  export default Contact01;
319
267
  //# sourceMappingURL=contact.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../../../src/blocks/contact-01/contact.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../../../src/blocks/contact-01/contact.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,OAAO,CAAA;AAQjD,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAUxF,OAAO,KAAK,EAAwB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG1E;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAA;IACb,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yEAAyE;IACzE,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB,GAAG,CACA;IACE,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAA;CACnB,GACD;IACE,6CAA6C;IAC7C,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,UAAU,CAAC,EAAE,KAAK,CAAA;CACnB,CACJ,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEjD;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,kFAAkF;IAClF,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAA;IAC5C,4DAA4D;IAC5D,OAAO,EAAE,SAAS,CAAA;CACnB,CAAA;AAED,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG;IAC3B,uEAAuE;IACvE,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,iFAAiF;IACjF,KAAK,EAAE,SAAS,CAAA;IAChB,kFAAkF;IAClF,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;;;;OASG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACvC;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,EAAE,SAAS,cAAc,EAAE,CAAA;IACjC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAClC,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,oFAAoF;IACpF,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,iEAAiE;IACjE,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAkED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,WAAW,EACX,QAAQ,EACR,MAAM,EACN,MAAM,EACN,WAAW,EACX,OAAO,EACP,OAAO,EACP,OAAiB,EACjB,MAAM,EACN,YAAmB,EACnB,SAAS,GACV,EAAE,cAAc,+BAiOhB;AAED,eAAe,SAAS,CAAA"}