@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
@@ -1,15 +1,30 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { ChevronDown, ListFilter, MoreHorizontal, Search } from 'lucide-react';
4
- import { useId, useState } from 'react';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { ChevronDown, ListFilter, Search, XIcon } from 'lucide-react';
4
+ import { Fragment, useEffect, useId, useRef, useState } from 'react';
5
+ import { Avatar } from '../../components/ui/avatar.js';
6
+ import { Badge } from '../../components/ui/badge.js';
5
7
  import { Button } from '../../components/ui/button.js';
6
8
  import { Checkbox } from '../../components/ui/checkbox.js';
7
- import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '../../components/ui/dropdown-menu.js';
9
+ import { Checklist } from '../../components/ui/checklist.js';
10
+ import { CodeBlock } from '../../components/ui/code-block.js';
11
+ import { Diff } from '../../components/ui/diff.js';
12
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '../../components/ui/dropdown-menu.js';
8
13
  import { Input } from '../../components/ui/input.js';
14
+ import { Kbd } from '../../components/ui/kbd.js';
15
+ import { LiveRegion } from '../../components/ui/live-region.js';
16
+ import { Mark } from '../../components/ui/mark.js';
9
17
  import { Pagination, PaginationContent, PaginationItem } from '../../components/ui/pagination.js';
10
18
  import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover.js';
19
+ import { Price } from '../../components/ui/price.js';
20
+ import { RelativeTime } from '../../components/ui/relative-time.js';
11
21
  import { headingSizeClass } from '../../components/ui/section.js';
22
+ import { Status } from '../../components/ui/status.js';
12
23
  import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from '../../components/ui/table.js';
24
+ import { TableSort } from '../../components/ui/table-sort.js';
25
+ import { TagGroup } from '../../components/ui/tag-group.js';
26
+ import { Text } from '../../components/ui/typography.js';
27
+ import { cn } from '../../lib/utils.js';
13
28
  /** The page numbers to show, with `ellipsis` where a run is held back. */
14
29
  function pageWindow(page, pageCount) {
15
30
  if (pageCount <= 7) {
@@ -27,47 +42,193 @@ function pageWindow(page, pageCount) {
27
42
  pages.push(pageCount);
28
43
  return pages;
29
44
  }
30
- function RowMenu({ actions, label }) {
31
- if (!actions.length)
32
- return null;
33
- return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { "aria-label": label, className: "size-8 p-0 pointer-coarse:size-11", children: _jsx(MoreHorizontal, { className: "size-4" }) }), _jsx(DropdownMenuContent, { align: "end", children: actions.map((action, index) => (
34
- // Positional: an action label is display content and two may repeat.
35
- _jsx(DropdownMenuItem, { variant: action.variant, onClick: action.onSelect, children: action.label }, index))) })] }));
45
+ /** Where a value belongs in a cell, defaulting to the start. */
46
+ function alignClass(column) {
47
+ if (column.align === 'center')
48
+ return 'text-center';
49
+ if (column.align === 'end')
50
+ return 'text-end';
51
+ return 'text-start';
36
52
  }
37
53
  /**
38
- * A table section with a toolbar, filters, row selection and pagination.
54
+ * One cell, drawn from the column's kind and the row's value at its key.
39
55
  *
40
- * The block renders exactly the rows it is given for the current page and
41
- * reports every interaction through a callback. It performs no filtering,
42
- * sorting or fetching of its own: the page, the search text, the filter values
43
- * and the selection are the consumer's state, so a server-rendered page and a
44
- * client data layer can each drive it. Every label is a prop.
56
+ * The value at `column.key` is the cell's own content. A kind that names a
57
+ * single-node cell (`Typography`, `Badge`, `Avatar`, `Kbd`) draws the value as that
58
+ * Component's child; a kind that names a structured cell (`Price`, `RelativeTime`,
59
+ * `Status`, `TagGroup`, `Checklist`, `Diff`, `Mark`, `CodeBlock`) takes the value as
60
+ * that Component's props; and `slot` draws the value as the caller's own node.
61
+ *
62
+ * The map is keyed by the kind's lowercase name and every member of `ColumnKind`
63
+ * has one entry, so a kind added to the union fails the compiler here until it is
64
+ * drawn. The lowercase keys are machine values rather than copy, which is the same
65
+ * reason a variant name is written in lowercase everywhere else in this package.
66
+ */
67
+ const CELL = {
68
+ typography: (value) => _jsx(Text, { as: "span", children: value }),
69
+ price: (value) => _jsx(Price, { ...value }),
70
+ relativetime: (value) => _jsx(RelativeTime, { ...value }),
71
+ status: (value) => _jsx(Status, { ...value }),
72
+ badge: (value) => _jsx(Badge, { children: value }),
73
+ avatar: (value) => _jsx(Avatar, { children: value }),
74
+ taggroup: (value) => _jsx(TagGroup, { ...value }),
75
+ checklist: (value) => _jsx(Checklist, { ...value }),
76
+ diff: (value) => _jsx(Diff, { ...value }),
77
+ mark: (value) => _jsx(Mark, { ...value }),
78
+ kbd: (value) => _jsx(Kbd, { children: value }),
79
+ codeblock: (value) => _jsx(CodeBlock, { ...value }),
80
+ slot: (value) => value,
81
+ };
82
+ /** The cell for one column and one row. */
83
+ function renderCell(column, row) {
84
+ return CELL[column.kind.toLowerCase()](row[column.key]);
85
+ }
86
+ /**
87
+ * The record index: a table of rows a reader scans and acts on.
88
+ *
89
+ * It draws the page of rows it was handed for the page it was told about, and it
90
+ * fetches nothing, sorts nothing, filters nothing and slices nothing. `columns` is
91
+ * the shared `ColumnSpec` typed data rather than a render function per column, so
92
+ * alignment, width, the header and the sort affordance are drawn once for every
93
+ * consumer. `getRowId` is required, because a row's position moves under sorting,
94
+ * filtering and paging and an index-keyed selection would act on the wrong records.
95
+ *
96
+ * **It is also the log and the event stream, drawing event rows from the shared event
97
+ * specification.** A consumer composing a delivery log or a console events screen
98
+ * passes the same `EventSpec` values an activity trail takes, and draws columns from
99
+ * them: the sending process in `actor`, the event type in `action`, the endpoint in
100
+ * `target`, the payload in a `CodeBlock` cell the caller composes, and the response
101
+ * status in a `Status` cell the caller composes rather than declared as a member,
102
+ * because the event specification has no kind and no severity. A log is a different
103
+ * job from the trail on the arrangement signal: it gains a filter region the trail
104
+ * does not draw, a second axis on which entries are compared, and a page boundary in
105
+ * place of a silent limit, and every one of those belongs to this Block rather than
106
+ * to a rail. So a screen that wants the events of one record composes
107
+ * `ActivityFeed01`, a screen that wants to find one entry among many composes this
108
+ * Block, and there is no second log Item beside it.
109
+ *
110
+ * **The Block composes no disclosure.** Seeing one entry in full is a destination the
111
+ * caller names with its required words, expanding a payload in place is the caller's
112
+ * own `Collapsible` placed in a `slot` cell, and taking an identifier away is the
113
+ * caller's own control composed beside a code block. A control whose handler arrives
114
+ * through a spread of unknown shape reads as carrying no handler, so the escape for a
115
+ * per-entry action is the `renderRowActions` node rather than a spread.
116
+ *
117
+ * **It draws no retention window, no archive, no purge, no legal hold and no export**,
118
+ * and no immutability mark, no time axis, no live tail, no aggregate over the
119
+ * population and no command of its own. A filter is a narrowing the caller passed and
120
+ * the Block states nothing about what is behind it. Row selection exists so the caller
121
+ * can drive a command against its own store.
122
+ *
123
+ * **It holds the selection set it was given, and reports every change.** This is the
124
+ * one piece of state a block in this repository keeps, and it is scoped by what the
125
+ * state is: a set of keys the caller passed for the page about to be drawn. Nothing
126
+ * in the block fetches, derives from application state or decides. A consumer that
127
+ * wants the set mirrored passes `selectedIds` and writes one line in
128
+ * `onSelectedIdsChange`; a consumer that wants nothing passes neither and the block
129
+ * still draws the tick, the header's three-state control, the count, the batch bar
130
+ * and the announcement.
131
+ *
132
+ * **The count is announced once, politely.** One `LiveRegion` at `polite` carries the
133
+ * count and is never assertive, and it is mounted while there is an announcement
134
+ * rather than always, so its appearance is the first thing spoken rather than a
135
+ * silent mutation of a node that was always there. It announces the count and never
136
+ * a list of labels, because each row's own checkbox already names its record. When
137
+ * the set empties, the region speaks its own sentence rather than a count of zero.
138
+ * The visible summary in the footer and the announced count are the same string.
139
+ *
140
+ * **The batch bar is the block's region and the caller's controls.** It mounts only
141
+ * while the set is non-empty, displaces the filter controls rather than sitting
142
+ * beside them, and holds one required `batchActions` node the caller wrote plus the
143
+ * block's own dismiss. The dismiss is the only control the block draws, and its whole
144
+ * body is the same report every tick makes with an empty set.
145
+ *
146
+ * **Per-row actions are a node per row.** The block draws the trailing cell and
147
+ * places `renderRowActions(row)` inside it, so a row that navigates and a row that is
148
+ * ticked are two separately reachable targets and the block owns no command.
149
+ *
150
+ * **Grouping is a key on a row, not a second arrangement.** `groupBy` returns the key
151
+ * a row belongs to and the block draws `groupLabel(key)` in a full-width row above
152
+ * each run of rows that shares one, in the caller's own order. A heading is a row in
153
+ * the same body as any other and it is not a record: it is never in the selection
154
+ * set, never in the count and never what the header's page scope counts.
155
+ *
156
+ * **The two selection scopes are announced differently.** A page-wide selection is
157
+ * one the block can count, so it speaks `labels.selectedCount`. A filter-wide
158
+ * selection is a population the block never fetched, so the caller reports it
159
+ * through `selectionScope` and the block speaks `labels.selectedAllMatching` with the
160
+ * caller's own number rather than printing the page as if it were the total.
45
161
  */
46
- export function DataTable01({ title, description, columns, rows, getRowId, caption, selectable = false, selectedIds, defaultSelectedIds, onSelectedIdsChange, searchValue, onSearchChange, filters, filterValues, onFilterChange, page: pageProp, defaultPage = 1, pageCount, onPageChange, rowActions, toolbarActions, emptyMessage, labels, headingLevel = 'h2', }) {
162
+ export function DataTable01(props) {
163
+ const { title, description, columns, rows, getRowId, caption, headingLevel = 'h2', sort: sortProp, onSortChange, searchValue, onSearchChange, filters, filterValues, onFilterChange, page: pageProp, defaultPage = 1, pageCount, onPageChange, renderRowActions, toolbarActions, emptyMessage, labels, groupBy, groupLabel, } = props;
47
164
  const Heading = headingLevel;
48
165
  // The handle the table names itself from; see the note on the table.
49
166
  const headingId = useId();
50
- const [internalSelected, setInternalSelected] = useState(defaultSelectedIds ?? []);
167
+ // The prefix the per-cell help ids are built from.
168
+ const helpPrefix = useId();
169
+ const isSelectable = props.selectable === true;
170
+ const [internalSelected, setInternalSelected] = useState(() => [
171
+ ...(props.defaultSelectedIds ?? []),
172
+ ]);
51
173
  const [internalPage, setInternalPage] = useState(defaultPage);
52
174
  const [internalSearch, setInternalSearch] = useState('');
53
175
  const [internalFilters, setInternalFilters] = useState({});
176
+ const [internalSort, setInternalSort] = useState(null);
54
177
  const [hiddenColumns, setHiddenColumns] = useState([]);
55
- const selected = selectedIds ?? internalSelected;
178
+ const selected = isSelectable
179
+ ? props.selectedIds === undefined
180
+ ? internalSelected
181
+ : [...props.selectedIds]
182
+ : [];
56
183
  const page = pageProp ?? internalPage;
57
184
  const search = searchValue ?? internalSearch;
58
185
  const activeFilters = filterValues ?? internalFilters;
186
+ const sortState = sortProp !== undefined ? sortProp : internalSort;
59
187
  const clampPage = (value) => Math.min(Math.max(1, value), Math.max(1, pageCount));
60
188
  const currentPage = clampPage(page);
61
- const visibleColumns = columns.filter((column) => !hiddenColumns.includes(column.id));
62
- const hasRowActions = Boolean(rowActions);
63
- const columnCount = (selectable ? 1 : 0) + visibleColumns.length + (hasRowActions ? 1 : 0);
189
+ const visibleColumns = columns.filter((column) => !hiddenColumns.includes(column.key));
190
+ const hasRowActions = renderRowActions !== undefined;
191
+ const columnCount = (isSelectable ? 1 : 0) + visibleColumns.length + (hasRowActions ? 1 : 0);
192
+ // The scope the caller reports. Absent means the page, which is the one
193
+ // population the block can count for itself.
194
+ const scope = isSelectable ? props.selectionScope : undefined;
195
+ const filterScope = scope?.scope === 'filter' ? scope : undefined;
196
+ // A filter-wide selection is a selection even though its keys are not on screen,
197
+ // so the bar mounts and the region speaks from the scope rather than from the
198
+ // page's keys. The number is the caller's and is never the rows on screen.
199
+ const selectionActive = isSelectable && (filterScope !== undefined || selected.length > 0);
200
+ const selectionSummary = filterScope !== undefined
201
+ ? labels.selectedAllMatching(filterScope.count)
202
+ : labels.selectedCount(selected.length);
64
203
  const rowIds = rows.map(getRowId);
65
- const allSelected = rowIds.length > 0 && rowIds.every((id) => selected.includes(id));
66
- const someSelected = rowIds.some((id) => selected.includes(id));
204
+ // A filter-wide selection covers the page whether or not the page's keys were
205
+ // passed, so the header reads as fully selected on that arm. A heading is not in
206
+ // `rowIds`, so grouping never changes what these two say.
207
+ const allSelected = filterScope !== undefined || (rowIds.length > 0 && rowIds.every((id) => selected.includes(id)));
208
+ const someSelected = filterScope !== undefined || rowIds.some((id) => selected.includes(id));
209
+ // The emptying sentence. A live region speaks when its text changes, and the
210
+ // sequence where the selection falls to nothing is the one the reader has to be told
211
+ // about because the batch bar they were about to use disappears with it. The
212
+ // previous presence is ref-held, so the render that reaches nothing still knows
213
+ // there was a selection and can speak the sentence rather than the absence of one.
214
+ const previousActive = useRef(selectionActive);
215
+ useEffect(() => {
216
+ previousActive.current = selectionActive;
217
+ }, [selectionActive]);
218
+ const announcement = selectionActive
219
+ ? selectionSummary
220
+ : previousActive.current
221
+ ? labels.clearedSelection
222
+ : null;
67
223
  function changeSelection(ids) {
68
- if (selectedIds === undefined)
224
+ if (isSelectable === false)
225
+ return;
226
+ if (props.selectedIds === undefined)
69
227
  setInternalSelected(ids);
70
- onSelectedIdsChange?.(ids);
228
+ props.onSelectedIdsChange?.(ids);
229
+ }
230
+ function clearSelection() {
231
+ changeSelection([]);
71
232
  }
72
233
  function toggleAll(checked) {
73
234
  if (checked) {
@@ -103,18 +264,39 @@ export function DataTable01({ title, description, columns, rows, getRowId, capti
103
264
  setInternalPage(clamped);
104
265
  onPageChange?.(clamped);
105
266
  }
106
- function toggleColumn(id, visible) {
107
- setHiddenColumns((previous) => visible ? previous.filter((item) => item !== id) : [...previous, id]);
267
+ function changeSort(column, direction) {
268
+ const next = direction === 'none' ? null : { column, direction };
269
+ if (sortProp === undefined)
270
+ setInternalSort(next);
271
+ onSortChange?.(next);
272
+ }
273
+ function toggleColumn(key, visible) {
274
+ setHiddenColumns((previous) => visible ? previous.filter((item) => item !== key) : [...previous, key]);
108
275
  }
109
276
  const activeFilterCount = Object.values(activeFilters).filter(Boolean).length;
110
- return (_jsxs("div", { className: "flex flex-col gap-4", children: [title || description ? (_jsxs("div", { className: "flex flex-col gap-1", children: [title ? (_jsx(Heading, { id: headingId, className: `font-semibold tracking-tight text-balance ${headingSizeClass(headingLevel)}`, children: title })) : null, description ? (_jsx("p", { className: "text-muted-foreground text-sm", children: description })) : null] })) : null, _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" }), _jsx(Input, { value: search, onChange: (event) => changeSearch(event.target.value), placeholder: labels.search, "aria-label": labels.search, className: "h-9 w-56 pl-8" })] }), filters?.length ? (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { children: [_jsx(ListFilter, { className: "size-4" }), labels.filters, activeFilterCount > 0 ? (_jsx("span", { className: "bg-primary text-primary-foreground rounded-full px-1.5 text-xs", children: activeFilterCount })) : null] }), _jsxs(PopoverContent, { align: "start", className: "flex w-64 flex-col gap-4", children: [filters.map((filter) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "text-sm font-medium", children: filter.label }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: filter.options.map((option) => {
111
- const active = activeFilters[filter.id] === option.value;
112
- return (_jsx(Button, { type: "button", size: "sm", variant: active ? 'default' : 'outline', onClick: () => changeFilter(filter.id, active ? undefined : option.value), children: option.label }, option.value));
113
- }) })] }, filter.id))), _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: activeFilterCount === 0, onClick: resetFilters, children: labels.reset })] })] })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [toolbarActions, _jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { children: [labels.viewColumns, _jsx(ChevronDown, { className: "size-4" })] }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuLabel, { children: labels.columns }), _jsx(DropdownMenuSeparator, {}), columns.map((column) => (_jsx(DropdownMenuCheckboxItem, { checked: !hiddenColumns.includes(column.id), closeOnClick: false, onCheckedChange: (checked) => toggleColumn(column.id, checked), children: column.label ?? column.id }, column.id)))] })] })] })] }), _jsx("div", { className: "border-border overflow-hidden rounded-xl border", children: _jsxs(Table, { "aria-labelledby": caption === undefined && title ? headingId : undefined, children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx(TableHeader, { children: _jsxs(TableRow, { children: [selectable ? (_jsx(TableHead, { className: "w-10", children: _jsx(Checkbox, { checked: allSelected, indeterminate: !allSelected && someSelected, onCheckedChange: toggleAll, "aria-label": labels.selectAll }) })) : null, visibleColumns.map((column) => (_jsx(TableHead, { className: column.className, children: column.header }, column.id))), hasRowActions ? (_jsx(TableHead, { className: "w-10", children: _jsx("span", { className: "sr-only", children: labels.rowActions }) })) : null] }) }), _jsx(TableBody, { children: rows.length ? (rows.map((row) => {
277
+ // The key of the row before this one, so a heading belongs to a run of rows and
278
+ // not to a row. It resets every render and is read by the body below.
279
+ let previousGroupKey;
280
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [title || description ? (_jsxs("div", { className: "flex flex-col gap-1", children: [title ? (_jsx(Heading, { id: headingId, className: `font-semibold tracking-tight text-balance ${headingSizeClass(headingLevel)}`, children: title })) : null, description ? (_jsx("p", { className: "text-muted-foreground text-sm", children: description })) : null] })) : null, _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: selectionActive ? (_jsxs("div", { "data-slot": "data-table-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": "data-table-01-dismiss", type: "button", variant: "ghost", size: "sm", "aria-label": labels.dismissSelection, onClick: clearSelection, children: _jsx(XIcon, { "aria-hidden": "true" }) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" }), _jsx(Input, { value: search, onChange: (event) => changeSearch(event.target.value), placeholder: labels.search, "aria-label": labels.search, className: "h-9 w-56 pl-8" })] }), filters?.length ? (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { children: [_jsx(ListFilter, { className: "size-4" }), labels.filters, activeFilterCount > 0 ? (_jsx("span", { className: "bg-primary text-primary-foreground rounded-full px-1.5 text-xs", children: activeFilterCount })) : null] }), _jsxs(PopoverContent, { align: "start", className: "flex w-64 flex-col gap-4", children: [filters.map((filter) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "text-sm font-medium", children: filter.label }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: filter.options.map((option) => {
281
+ const active = activeFilters[filter.id] === option.value;
282
+ return (_jsx(Button, { type: "button", size: "sm", variant: active ? 'default' : 'outline', onClick: () => changeFilter(filter.id, active ? undefined : option.value), children: option.label }, option.value));
283
+ }) })] }, filter.id))), _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: activeFilterCount === 0, onClick: resetFilters, children: labels.reset })] })] })) : null] })) }), _jsxs("div", { className: "flex items-center gap-2", children: [toolbarActions, _jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { children: [labels.viewColumns, _jsx(ChevronDown, { className: "size-4" })] }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuLabel, { children: labels.columns }), _jsx(DropdownMenuSeparator, {}), columns.map((column) => (_jsx(DropdownMenuCheckboxItem, { checked: !hiddenColumns.includes(column.key), closeOnClick: false, onCheckedChange: (checked) => toggleColumn(column.key, checked), children: typeof column.header === 'string' ? column.header : column.key }, column.key)))] })] })] })] }), _jsx(LiveRegion, { "data-slot": "data-table-01-announcement", politeness: "polite", className: "sr-only", children: announcement }), _jsx("div", { className: "border-border overflow-hidden rounded-xl border", children: _jsxs(Table, { "aria-labelledby": caption === undefined && title ? headingId : undefined, children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx(TableHeader, { children: _jsxs(TableRow, { children: [isSelectable ? (_jsx(TableHead, { className: "w-10", children: _jsx(Checkbox, { checked: allSelected, indeterminate: !allSelected && someSelected, onCheckedChange: toggleAll, "aria-label": labels.selectAll }) })) : null, visibleColumns.map((column) => column.sortable ? (_jsx(TableSort, { column: column.key, direction: sortState?.column === column.key ? sortState.direction : 'none', onDirectionChange: (direction) => changeSort(column.key, direction), announce: (direction) => labels.sort(column.key, direction), className: cn(column.width), children: column.header }, column.key)) : (_jsx(TableHead, { className: cn(column.width, alignClass(column)), children: column.header }, column.key))), hasRowActions ? (_jsx(TableHead, { className: "w-10", children: _jsx("span", { className: "sr-only", children: labels.rowActions }) })) : null] }) }), _jsx(TableBody, { children: rows.length ? (rows.map((row, rowIndex) => {
114
284
  const id = getRowId(row);
115
285
  const isSelected = selected.includes(id);
116
- const actions = rowActions?.(row) ?? [];
117
- return (_jsxs(TableRow, { "data-state": isSelected ? 'selected' : undefined, children: [selectable ? (_jsx(TableCell, { children: _jsx(Checkbox, { checked: isSelected, onCheckedChange: (checked) => toggleRow(id, checked), "aria-label": labels.selectRow }) })) : null, visibleColumns.map((column) => (_jsx(TableCell, { className: column.className, children: column.cell(row) }, column.id))), hasRowActions ? (_jsx(TableCell, { className: "text-right", children: _jsx(RowMenu, { actions: actions, label: labels.rowActions }) })) : null] }, id));
118
- })) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columnCount, className: "text-muted-foreground h-24 text-center", children: emptyMessage ?? labels.empty }) })) })] }) }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [selectable ? (_jsx("span", { className: "text-muted-foreground text-sm", children: labels.selectedCount(selected.length) })) : (_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 }) })] }) })] })] }));
286
+ // A heading is drawn once per run of rows with one key, in the
287
+ // caller's own order. A row with no key starts no heading and is not
288
+ // a record that a group owns; it is simply ungrouped.
289
+ const rowGroupKey = groupBy?.(row);
290
+ const startsGroup = groupBy !== undefined &&
291
+ rowGroupKey !== undefined &&
292
+ rowGroupKey !== previousGroupKey;
293
+ previousGroupKey = rowGroupKey;
294
+ return (_jsxs(Fragment, { children: [startsGroup ? (_jsx(TableRow, { "data-slot": "data-table-01-group", children: _jsx(TableCell, { colSpan: columnCount, className: "text-muted-foreground bg-muted text-sm font-medium", children: groupLabel?.(rowGroupKey) }) })) : null, _jsxs(TableRow, { "data-state": isSelected ? 'selected' : undefined, children: [isSelectable ? (_jsx(TableCell, { className: "w-10", children: _jsx(Checkbox, { checked: isSelected, onCheckedChange: (checked) => toggleRow(id, checked), "aria-label": labels.selectRow(row) }) })) : null, visibleColumns.map((column, columnIndex) => {
295
+ const helpId = column.help === undefined
296
+ ? undefined
297
+ : `${helpPrefix}-help-${columnIndex}-${rowIndex}`;
298
+ return (_jsxs(TableCell, { className: cn(column.width, alignClass(column)), "aria-describedby": helpId, children: [renderCell(column, row), helpId === undefined ? null : (_jsx("span", { id: helpId, className: "sr-only", children: column.help }))] }, column.key));
299
+ }), hasRowActions ? (_jsx(TableCell, { className: "text-right", children: renderRowActions?.(row) })) : null] })] }, id));
300
+ })) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columnCount, className: "text-muted-foreground h-24 text-center", 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 }) })] }) })] })] }));
119
301
  }
120
302
  export default DataTable01;
@@ -1,3 +1,3 @@
1
1
  export { DataTable01, default } from './data-table.js';
2
- export type { DataTable01Props, DataTable01Labels, DataTableColumn, DataTableFilter, DataTableFilterOption, DataTableRow, DataTableRowAction, } from './data-table.js';
2
+ export type { DataTable01Props, DataTable01Labels, DataTable01SelectionScope, DataTableFilter, DataTableFilterOption, DataTableRow, DataTableSort, } from './data-table.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/data-table-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACnD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,YAAY,EACZ,kBAAkB,GACnB,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/data-table-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACnD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,YAAY,EACZ,aAAa,GACd,MAAM,cAAc,CAAA"}
@@ -182,7 +182,7 @@ export type Directory01Props = {
182
182
  */
183
183
  empty: ReactNode;
184
184
  /**
185
- * The caller's own line about what the query found.
185
+ * The result line, written the way four products write it.
186
186
  *
187
187
  * A function and not a node, because the count a reader reads is a sentence whose
188
188
  * word order and noun inflection belong to their language, and a Block that composed
@@ -192,6 +192,27 @@ export type Directory01Props = {
192
192
  * paint.
193
193
  */
194
194
  summary?: (matches: number) => ReactNode;
195
+ /**
196
+ * The caller's own control for one member, placed without styling at the member's
197
+ * trailing edge.
198
+ *
199
+ * A node per member rather than one node for the directory, because what a caller
200
+ * puts here acts on the member it sits beside: a plugin's enable is a `Switch`
201
+ * whose `onCheckedChange` names that plugin, and one node reused across fifty
202
+ * members could not. It is a function in the shape `DataTable01`'s
203
+ * `renderRowActions` already takes, so the two per-row action shapes in this
204
+ * package are one shape and not two.
205
+ *
206
+ * **The Block draws no control of its own here, and that is the whole reason the
207
+ * prop is a node.** `scripts/check-block-controls.mjs` classifies `Switch` and
208
+ * fails a Block that renders one without its handler, and a Block ships no
209
+ * behaviour and so cannot supply one. So a member's action arrives as the caller's
210
+ * own node, holding the caller's own control and its own handler, and the Block
211
+ * places it in the member's trailing edge in both arrangements and draws nothing
212
+ * where it is not passed. Until a directory wants one of these, this is absent and
213
+ * a member is a name, a role, the tags and a destination.
214
+ */
215
+ renderMemberAction?: (member: Directory01Member) => ReactNode;
195
216
  /**
196
217
  * How the set is drawn.
197
218
  *
@@ -311,6 +332,6 @@ export type Directory01Props = {
311
332
  * itself is a substring test per member and holds no index of its own, so the client
312
333
  * cost is the derivation and not a running loop.
313
334
  */
314
- export declare function Directory01({ eyebrow, title, description, value, onValueChange, label, clearLabel, categories, empty, summary, variant, headingLevel, className, }: Directory01Props): import("react").JSX.Element;
335
+ export declare function Directory01({ eyebrow, title, description, value, onValueChange, label, clearLabel, categories, empty, summary, renderMemberAction, variant, headingLevel, className, }: Directory01Props): import("react").JSX.Element;
315
336
  export default Directory01;
316
337
  //# sourceMappingURL=directory-01.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-01.d.ts","sourceRoot":"","sources":["../../../src/blocks/directory-01/directory-01.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAA;AA4EpC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAA;IACV,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;;OAWG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE;QACP,2EAA2E;QAC3E,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,wDAAwD;QACxD,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAChD,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,0FAA0F;IAC1F,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,sEAAsE;IACtE,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,mFAAmF;IACnF,KAAK,EAAE,SAAS,CAAA;IAChB,kFAAkF;IAClF,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;;;OAQG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,UAAU,EAAE,mBAAmB,EAAE,CAAA;IACjC;;;;;;;;;;OAUG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,CAAA;IACxC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,iEAAiE;IACjE,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,aAAa,EACb,KAAK,EACL,UAAU,EACV,UAAU,EACV,KAAK,EACL,OAAO,EACP,OAAiB,EACjB,YAAmB,EACnB,SAAS,GACV,EAAE,gBAAgB,+BAgSlB;AAED,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"directory-01.d.ts","sourceRoot":"","sources":["../../../src/blocks/directory-01/directory-01.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAA;AA4EpC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAA;IACV,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;;OAWG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE;QACP,2EAA2E;QAC3E,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,wDAAwD;QACxD,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAChD,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,0FAA0F;IAC1F,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,sEAAsE;IACtE,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,mFAAmF;IACnF,KAAK,EAAE,SAAS,CAAA;IAChB,kFAAkF;IAClF,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;;;OAQG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,UAAU,EAAE,mBAAmB,EAAE,CAAA;IACjC;;;;;;;;;;OAUG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,CAAA;IACxC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,SAAS,CAAA;IAC7D;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,iEAAiE;IACjE,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,aAAa,EACb,KAAK,EACL,UAAU,EACV,UAAU,EACV,KAAK,EACL,OAAO,EACP,kBAAkB,EAClB,OAAiB,EACjB,YAAmB,EACnB,SAAS,GACV,EAAE,gBAAgB,+BAyTlB;AAED,eAAe,WAAW,CAAA"}
@@ -196,7 +196,7 @@ function assertMembers(members) {
196
196
  * itself is a substring test per member and holds no index of its own, so the client
197
197
  * cost is the derivation and not a running loop.
198
198
  */
199
- export function Directory01({ eyebrow, title, description, value, onValueChange, label, clearLabel, categories, empty, summary, variant = 'cards', headingLevel = 'h2', className, }) {
199
+ export function Directory01({ eyebrow, title, description, value, onValueChange, label, clearLabel, categories, empty, summary, renderMemberAction, variant = 'cards', headingLevel = 'h2', className, }) {
200
200
  // A category's title is a heading one step below the section that introduces the
201
201
  // set, and a member's name is a step below that, so a reader who searches a
202
202
  // directory and then navigates by heading lands on the team and then on the person
@@ -239,7 +239,7 @@ export function Directory01({ eyebrow, title, description, value, onValueChange,
239
239
  // is one set of elements whichever arrangement is drawn and a consumer
240
240
  // who switches `variant` at render time gets one list in the
241
241
  // accessibility tree rather than two.
242
- asCards ? 'grid items-start gap-6 md:grid-cols-2 lg:grid-cols-3' : null), children: visible.map((category) => (_jsxs("section", { "data-slot": "directory-01-category", "data-category": category.id, "aria-labelledby": `${category.id}-heading`, className: "flex min-w-0 flex-col gap-4", children: [_jsxs("div", { "data-slot": "directory-01-category-head", className: "flex flex-col gap-1.5", children: [_jsx(Title, { id: `${category.id}-heading`, "data-slot": "directory-01-category-title", className: "text-lg font-semibold tracking-tight", children: category.title }), category.description === undefined ? null : (_jsx("p", { "data-slot": "directory-01-category-description", className: "text-muted-foreground text-pretty text-sm", children: category.description }))] }), asCards ? (_jsx("ul", { "data-slot": "directory-01-members", className: "grid gap-4 sm:grid-cols-2", children: category.members.map((member) => (_jsx("li", { "data-slot": "directory-01-card", "data-member": member.id, className: "flex", children: _jsxs(Card, { "data-slot": "directory-01-card-surface", className: "w-full gap-4 py-5", children: [_jsxs(CardHeader, { className: "gap-3", children: [_jsxs("div", { "data-slot": "directory-01-identity", className: "flex min-w-0 gap-3", children: [member.avatar === undefined ? null : (_jsxs(Avatar, { className: "size-10 shrink-0", children: [member.avatar.src === undefined ? null : (_jsx(AvatarImage, { src: member.avatar.src, alt: "" })), _jsx(AvatarFallback, { className: "text-mono", children: initialsOf(member.avatar.name) })] })), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(CardTitle, { as: Member, "data-slot": "directory-01-name", className: "truncate text-sm leading-snug", children: member.name }), member.role === undefined ? null : (_jsx("span", { "data-slot": "directory-01-role", className: "text-muted-foreground text-xs", children: member.role }))] })] }), member.tags === undefined || member.tags.length === 0 ? null : (_jsx("div", { "data-slot": "directory-01-tags", className: "flex flex-wrap items-center gap-1.5", children: member.tags.map((tag) => (_jsx(Badge, { variant: "secondary", children: tag.label }, tag.id))) }))] }), _jsx(CardFooter, { children: _jsx(CtaLink, { href: member.href, variant: "outline", size: "sm", children: member.hrefLabel }) })] }) }, member.id))) })) : (
242
+ asCards ? 'grid items-start gap-6 md:grid-cols-2 lg:grid-cols-3' : null), children: visible.map((category) => (_jsxs("section", { "data-slot": "directory-01-category", "data-category": category.id, "aria-labelledby": `${category.id}-heading`, className: "flex min-w-0 flex-col gap-4", children: [_jsxs("div", { "data-slot": "directory-01-category-head", className: "flex flex-col gap-1.5", children: [_jsx(Title, { id: `${category.id}-heading`, "data-slot": "directory-01-category-title", className: "text-lg font-semibold tracking-tight", children: category.title }), category.description === undefined ? null : (_jsx("p", { "data-slot": "directory-01-category-description", className: "text-muted-foreground text-pretty text-sm", children: category.description }))] }), asCards ? (_jsx("ul", { "data-slot": "directory-01-members", className: "grid gap-4 sm:grid-cols-2", children: category.members.map((member) => (_jsx("li", { "data-slot": "directory-01-card", "data-member": member.id, className: "flex", children: _jsxs(Card, { "data-slot": "directory-01-card-surface", className: "w-full gap-4 py-5", children: [_jsxs(CardHeader, { className: "gap-3", children: [_jsxs("div", { "data-slot": "directory-01-identity", className: "flex min-w-0 gap-3", children: [member.avatar === undefined ? null : (_jsxs(Avatar, { className: "size-10 shrink-0", children: [member.avatar.src === undefined ? null : (_jsx(AvatarImage, { src: member.avatar.src, alt: "" })), _jsx(AvatarFallback, { className: "text-mono", children: initialsOf(member.avatar.name) })] })), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(CardTitle, { as: Member, "data-slot": "directory-01-name", className: "truncate text-sm leading-snug", children: member.name }), member.role === undefined ? null : (_jsx("span", { "data-slot": "directory-01-role", className: "text-muted-foreground text-xs", children: member.role }))] })] }), member.tags === undefined || member.tags.length === 0 ? null : (_jsx("div", { "data-slot": "directory-01-tags", className: "flex flex-wrap items-center gap-1.5", children: member.tags.map((tag) => (_jsx(Badge, { variant: "secondary", children: tag.label }, tag.id))) }))] }), _jsxs(CardFooter, { className: "gap-2", children: [_jsx(CtaLink, { href: member.href, variant: "outline", size: "sm", children: member.hrefLabel }), renderMemberAction === undefined ? null : (_jsx("span", { "data-slot": "directory-01-member-action", className: "flex items-center", children: renderMemberAction(member) }))] })] }) }, member.id))) })) : (
243
243
  /*
244
244
  * A list rather than a grid of cards, because in this arrangement
245
245
  * the portrait belongs at the leading edge of a line and the badges
@@ -247,6 +247,6 @@ export function Directory01({ eyebrow, title, description, value, onValueChange,
247
247
  * border rather than a gap alone, so the boundary survives at any
248
248
  * zoom level where the gap stops being visible.
249
249
  */
250
- _jsx("ul", { "data-slot": "directory-01-members", className: "border-border flex flex-col border-t", children: category.members.map((member) => (_jsxs("li", { "data-slot": "directory-01-row", "data-member": member.id, className: "border-border flex flex-wrap items-center gap-x-4 gap-y-2 border-b py-4 last:border-b-0", children: [member.avatar === undefined ? null : (_jsxs(Avatar, { className: "size-8 shrink-0", children: [member.avatar.src === undefined ? null : (_jsx(AvatarImage, { src: member.avatar.src, alt: "" })), _jsx(AvatarFallback, { className: "text-mono", children: initialsOf(member.avatar.name) })] })), _jsx(Member, { "data-slot": "directory-01-name", className: "min-w-0 truncate text-sm font-medium", children: member.name }), member.role === undefined ? null : (_jsx("span", { "data-slot": "directory-01-role", className: "text-muted-foreground min-w-0 flex-1 truncate text-sm", children: member.role })), member.tags === undefined || member.tags.length === 0 ? null : (_jsx("span", { "data-slot": "directory-01-tags", className: "flex flex-wrap items-center gap-1.5", children: member.tags.map((tag) => (_jsx(Badge, { variant: "secondary", children: tag.label }, tag.id))) })), _jsx(CtaLink, { href: member.href, variant: "outline", size: "sm", className: "ms-auto shrink-0", children: member.hrefLabel })] }, member.id))) }))] }, category.id))) }))] }) }));
250
+ _jsx("ul", { "data-slot": "directory-01-members", className: "border-border flex flex-col border-t", children: category.members.map((member) => (_jsxs("li", { "data-slot": "directory-01-row", "data-member": member.id, className: "border-border flex flex-wrap items-center gap-x-4 gap-y-2 border-b py-4 last:border-b-0", children: [member.avatar === undefined ? null : (_jsxs(Avatar, { className: "size-8 shrink-0", children: [member.avatar.src === undefined ? null : (_jsx(AvatarImage, { src: member.avatar.src, alt: "" })), _jsx(AvatarFallback, { className: "text-mono", children: initialsOf(member.avatar.name) })] })), _jsx(Member, { "data-slot": "directory-01-name", className: "min-w-0 truncate text-sm font-medium", children: member.name }), member.role === undefined ? null : (_jsx("span", { "data-slot": "directory-01-role", className: "text-muted-foreground min-w-0 flex-1 truncate text-sm", children: member.role })), member.tags === undefined || member.tags.length === 0 ? null : (_jsx("span", { "data-slot": "directory-01-tags", className: "flex flex-wrap items-center gap-1.5", children: member.tags.map((tag) => (_jsx(Badge, { variant: "secondary", children: tag.label }, tag.id))) })), _jsx(CtaLink, { href: member.href, variant: "outline", size: "sm", className: "ms-auto shrink-0", children: member.hrefLabel }), renderMemberAction === undefined ? null : (_jsx("span", { "data-slot": "directory-01-member-action", className: "flex shrink-0 items-center", children: renderMemberAction(member) }))] }, member.id))) }))] }, category.id))) }))] }) }));
251
251
  }
252
252
  export default Directory01;
@@ -1,34 +1,55 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /**
3
- * The three reasons a region has nothing in it.
3
+ * The four reasons a region has nothing in it.
4
4
  *
5
- * **Three, and the set is closed because the three want different words and
5
+ * **Four, and the set is closed at four because the four want different words and
6
6
  * different actions, so a consumer who cannot say which one they have writes
7
7
  * "No data".** That is the whole failure this Block exists to prevent, and it is
8
- * a failure rather than a style preference because the three are answers to three
9
- * different questions and the reader acts on each one differently:
8
+ * a failure rather than a style preference because all four answer one question
9
+ * and the reader acts on each answer differently. The question is what happened
10
+ * to the collection this region would have drawn:
10
11
  *
11
12
  * - `first-run` - nothing has ever existed here. The reader is not blocked, they
12
13
  * are at the beginning, and the action is to *make the first one*. A sentence
13
14
  * that says nothing is available and an action that creates is the whole state.
14
- * - `no-match` - things exist and the reader's own filter removed all of them.
15
+ * - `no-match` - things exist and the reader's own narrowing removed all of them.
15
16
  * The reader is not at the beginning and nothing is missing; the action is to
16
- * *widen or clear the filter*, and offering to create something here invents
17
+ * *widen or clear the narrowing*, and offering to create something here invents
17
18
  * work the reader does not need.
19
+ * - `emptied-by-reader` - things existed here and this reader's own earlier action
20
+ * moved every one of them out of it. The reader is not at the beginning either,
21
+ * and nothing is missing or hidden: this is a bin the reader has just cleared.
22
+ * There is no action *in this region*, because a reader who has deleted nothing
23
+ * has nothing to restore, and restoring is a per row command and a batch action
24
+ * in the surrounding surface, on the caller's own nodes.
18
25
  * - `not-permitted` - things exist and this reader may not see them. The action
19
26
  * is to *ask for access or change context*, and a create action here is a lie
20
27
  * about what the reader can do, which is worse than no action at all.
21
28
  *
22
- * A fourth such as `error` or `loading` is deliberately absent: those are states
29
+ * **Two of the four are the reader's own doing, and a narrowing and a deletion are
30
+ * not the same doing, so they are two members rather than one.** `no-match` hides
31
+ * rows out of a set that is still there, so clearing it brings them back;
32
+ * `emptied-by-reader` is a record that is gone from the live set rather than hidden
33
+ * in it, and the reader put it there. Every saved view a caller builds out of the
34
+ * filter values it holds is already `no-match` and wants no member of its own,
35
+ * because a view that matches nothing is a narrowing that came back empty. A trash
36
+ * view is the one that differs in kind rather than in content: it is a different
37
+ * collection the caller chose to fetch rather than a narrowing of this one. So the
38
+ * two differ in exactly the fact a reader must be told honestly, which is whether
39
+ * the records are still there behind the narrowing or gone and only the caller can
40
+ * bring them back. One sentence for both would tell a reader who has deleted
41
+ * everything that their own search found nothing.
42
+ *
43
+ * **A fifth such as `error` or `loading` is deliberately absent:** those are states
23
44
  * the surrounding surface already owns, and a region that is erroring has an
24
45
  * `Alert` and a region that is loading has a `Spinner`. A Block that drew them
25
46
  * would give a consumer two places to put the same fact.
26
47
  *
27
48
  * @see EMPTY_REASONS
28
49
  */
29
- export declare const EMPTY_REASONS: readonly ["first-run", "no-match", "not-permitted"];
50
+ export declare const EMPTY_REASONS: readonly ["first-run", "no-match", "emptied-by-reader", "not-permitted"];
30
51
  /**
31
- * One of the three reasons a region has nothing in it.
52
+ * One of the four reasons a region has nothing in it.
32
53
  *
33
54
  * The reason is a machine value and never reader-facing text. The words are the
34
55
  * consumer's: two products call the same state "Nothing here yet" and "Your
@@ -45,12 +66,15 @@ export type EmptyReason = (typeof EMPTY_REASONS)[number];
45
66
  */
46
67
  export type EmptyState01Props = {
47
68
  /**
48
- * Which of the three situations this is. Required rather than defaulted,
69
+ * Which of the four situations this is. Required rather than defaulted,
49
70
  * because the default would be the one that hides the bug: a Block that
50
71
  * defaulted to `first-run` would render a create action beside a permission
51
72
  * boundary, and a Block that defaulted to `no-match` would tell every reader
52
73
  * their own filter removed something on their first visit. Making the caller
53
- * name the reason is what stops either from happening quietly.
74
+ * name the reason is what stops either from happening quietly, and it is what
75
+ * separates the two the reader emptied themselves: passing `no-match` beside a
76
+ * bin the reader just cleared says their search found nothing in it, which is
77
+ * false, because nothing there was there to be filtered.
54
78
  */
55
79
  reason: EmptyReason;
56
80
  /**
@@ -69,11 +93,15 @@ export type EmptyState01Props = {
69
93
  * The label of the one action, in the product's own words.
70
94
  *
71
95
  * Optional, and its absence is a legitimate state rather than a gap: a region
72
- * a reader cannot act on - a permission boundary, an archived-only view, a
73
- * feature that is switched off for their plan - has no next step, and inventing
74
- * one is how an empty state ends up offering a reader a button that cannot
75
- * help them. `reason` does not require it either, because the same rule applies
76
- * across the three: the action exists when the reader has something to do.
96
+ * a reader cannot act on - a permission boundary, a bin the reader has just
97
+ * emptied, an archived-only view, a feature that is switched off for their plan
98
+ * - has no next step *in this region*, and inventing one is how an empty state
99
+ * ends up offering a reader a button that cannot help them. `reason` does not
100
+ * require it either, because the same rule applies across the four: the action
101
+ * exists when the reader has something to do here. For `emptied-by-reader` the
102
+ * next step is elsewhere by construction, since the records to restore are the
103
+ * caller's own nodes and their restore command belongs to the index around this
104
+ * region.
77
105
  */
78
106
  actionLabel?: string;
79
107
  /**
@@ -88,7 +116,7 @@ export type EmptyState01Props = {
88
116
  * A slot and not an icon the Block picks, because the mark that means "nothing
89
117
  * here yet" and the mark that means "you cannot see this" are different marks,
90
118
  * and a Block that chose one would give four products an icon that is wrong for
91
- * one of the three reasons. It is `aria-hidden` wherever a consumer puts it: the
119
+ * one of the four reasons. It is `aria-hidden` wherever a consumer puts it: the
92
120
  * reason is carried by the words, and an icon a screen reader reads is a second
93
121
  * announcement of the same sentence.
94
122
  */
@@ -105,12 +133,13 @@ export type EmptyState01Props = {
105
133
  *
106
134
  * **The design decision is the `reason` prop, and it is the reason this is a Block
107
135
  * rather than a Component.** An empty state looks like one thing, which is why it
108
- * has been written by hand in every product that has one, and it is actually three
136
+ * has been written by hand in every product that has one, and it is actually four
109
137
  * things that need different words and different actions. A component with a
110
138
  * `title` and a `body` and an `action` cannot express the difference, so every
111
139
  * consumer re-decides it in their own words and at least one of them gets it
112
- * wrong: a permission boundary that offers "Create your first project", or a
113
- * first-run state that says "No results found". Naming the reason forces the
140
+ * wrong: a permission boundary that offers "Create your first project", a
141
+ * first-run state that says "No results found", or a bin the reader has just
142
+ * emptied that says their filter matched nothing. Naming the reason forces the
114
143
  * decision into the open and gives the Block something to enforce.
115
144
  *
116
145
  * **A Block and not a Component because it is a composition of five decisions
@@ -132,7 +161,17 @@ export type EmptyState01Props = {
132
161
  * actions is a form, and a form in a region that has nothing in it is a form with
133
162
  * nothing to submit. One action is the next step; the rest belong in the
134
163
  * surrounding surface, where the reader can see them without this region
135
- * competing.
164
+ * competing. `emptied-by-reader` is the case where there is provably no next step
165
+ * in the region at all, because the records a reader could restore are not in it.
166
+ *
167
+ * **All four are statements about a collection, and that is what keeps the fourth
168
+ * one here rather than beside `NothingChosen01`.** Each answers what happened to
169
+ * the set this region would have drawn, so the frame the four share stays true of
170
+ * all four: there is nothing here to read. A bin the reader emptied has no
171
+ * readable neighbour to make that frame a lie beside it, because everything it
172
+ * would have drawn is gone. `NothingChosen01` is the opposite claim in a region
173
+ * whose records are all still there, which is why it is a Block of its own and
174
+ * not a fifth reason here; `DESIGN.md` holds that argument in full.
136
175
  *
137
176
  * It is a server Component. It holds no state and imports no client code, so a
138
177
  * consumer that passes a client action inside it pays for the action and not for
@@ -1 +1 @@
1
- {"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../../src/blocks/empty-state-01/empty-state.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,aAAa,qDAAsD,CAAA;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAuBxD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;OAOG;IACH,MAAM,EAAE,WAAW,CAAA;IACnB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,KAAK,EACL,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,IAAI,EACJ,SAAS,GACV,EAAE,iBAAiB,+BAoEnB;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../../src/blocks/empty-state-01/empty-state.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,aAAa,0EAA2E,CAAA;AAErG;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AA0BxD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,EAAE,WAAW,CAAA;IACnB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,KAAK,EACL,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,IAAI,EACJ,SAAS,GACV,EAAE,iBAAiB,+BAoEnB;AAED,eAAe,YAAY,CAAA"}