@undevy-org/tablewright 0.1.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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/dist/es/components/dashboard/ReportSummaryCard.js +67 -0
  4. package/dist/es/components/data-table/AppliedStateChips.js +113 -0
  5. package/dist/es/components/data-table/ColumnFilterPopover.js +548 -0
  6. package/dist/es/components/data-table/ColumnHeaderMenu.js +144 -0
  7. package/dist/es/components/data-table/CompoundFilterPopover.js +508 -0
  8. package/dist/es/components/data-table/CopyButton.js +35 -0
  9. package/dist/es/components/data-table/CopyableText.js +26 -0
  10. package/dist/es/components/data-table/DataTableShell.js +145 -0
  11. package/dist/es/components/data-table/DrawerActionGroup.js +59 -0
  12. package/dist/es/components/data-table/DrawerEntityCard.js +21 -0
  13. package/dist/es/components/data-table/DrawerField.js +10 -0
  14. package/dist/es/components/data-table/DrawerFlagItem.js +20 -0
  15. package/dist/es/components/data-table/DrawerSection.js +26 -0
  16. package/dist/es/components/data-table/DrawerShell.js +141 -0
  17. package/dist/es/components/data-table/DrawerSummaryCard.js +11 -0
  18. package/dist/es/components/data-table/EmptyState.js +14 -0
  19. package/dist/es/components/data-table/ExpiryTimestampCell.js +43 -0
  20. package/dist/es/components/data-table/FilterChip.js +47 -0
  21. package/dist/es/components/data-table/FilterFormPanel.js +357 -0
  22. package/dist/es/components/data-table/FilterFormToggle.js +77 -0
  23. package/dist/es/components/data-table/FilterPopoverFooter.js +19 -0
  24. package/dist/es/components/data-table/FilterToolbar.js +131 -0
  25. package/dist/es/components/data-table/FlatColumnToggle.js +46 -0
  26. package/dist/es/components/data-table/GapIndicator.js +29 -0
  27. package/dist/es/components/data-table/GroupedColumnToggle.js +112 -0
  28. package/dist/es/components/data-table/RowActionsMenu.js +74 -0
  29. package/dist/es/components/data-table/RowControlCell.js +104 -0
  30. package/dist/es/components/data-table/SortableHeader.js +102 -0
  31. package/dist/es/components/data-table/TableFooter.js +132 -0
  32. package/dist/es/components/data-table/TableLayout.js +14 -0
  33. package/dist/es/components/data-table/TimestampCell.js +16 -0
  34. package/dist/es/components/data-table/TwoPanelMenu.js +64 -0
  35. package/dist/es/components/data-table/ViewChip.js +25 -0
  36. package/dist/es/components/data-table/cells/AmountCell.js +127 -0
  37. package/dist/es/components/data-table/cells/ConfirmedAmountCell.js +115 -0
  38. package/dist/es/components/data-table/cells/CurrencyAmountCell.js +26 -0
  39. package/dist/es/components/data-table/cells/DualLineCell.js +59 -0
  40. package/dist/es/components/data-table/cells/EnumBadgeCell.js +9 -0
  41. package/dist/es/components/data-table/cells/MonoIdCell.js +31 -0
  42. package/dist/es/components/data-table/hooks/use-column-resize.js +39 -0
  43. package/dist/es/components/data-table/hooks/use-row-drag.js +40 -0
  44. package/dist/es/components/data-table/hooks/use-table-pagination.js +14 -0
  45. package/dist/es/components/data-table/hooks/use-table-sort.js +35 -0
  46. package/dist/es/components/data-table/managed-filters.js +34 -0
  47. package/dist/es/components/data-table/sticky-utils.js +25 -0
  48. package/dist/es/components/layout/AppShell.js +113 -0
  49. package/dist/es/components/layout/Sidebar.js +128 -0
  50. package/dist/es/components/layout/SidebarAccountMenu.js +129 -0
  51. package/dist/es/components/layout/SidebarUser.js +38 -0
  52. package/dist/es/components/layout/TopBar.js +25 -0
  53. package/dist/es/components/layout/app-shell-context.js +9 -0
  54. package/dist/es/components/ui/badge.js +31 -0
  55. package/dist/es/components/ui/button-footer.js +25 -0
  56. package/dist/es/components/ui/button.js +44 -0
  57. package/dist/es/components/ui/checkbox.js +24 -0
  58. package/dist/es/components/ui/combobox.js +179 -0
  59. package/dist/es/components/ui/dialog.js +80 -0
  60. package/dist/es/components/ui/dropdown-menu.js +66 -0
  61. package/dist/es/components/ui/input.js +24 -0
  62. package/dist/es/components/ui/popover.js +33 -0
  63. package/dist/es/components/ui/segmented-control.js +60 -0
  64. package/dist/es/components/ui/select.js +66 -0
  65. package/dist/es/components/ui/tabs.js +41 -0
  66. package/dist/es/components/ui/theme-switch.js +47 -0
  67. package/dist/es/constants/drawer.js +4 -0
  68. package/dist/es/context/drawer-expand-context.js +10 -0
  69. package/dist/es/context/portal-container-context.js +10 -0
  70. package/dist/es/hooks/table-orchestration-store.js +165 -0
  71. package/dist/es/hooks/use-scroll-activity.js +33 -0
  72. package/dist/es/hooks/useRowDragOverride.js +18 -0
  73. package/dist/es/hooks/useTableOrchestration.js +452 -0
  74. package/dist/es/index.js +199 -0
  75. package/dist/es/lib/column-gaps.js +23 -0
  76. package/dist/es/lib/filter-stats.js +67 -0
  77. package/dist/es/lib/format-enum.js +6 -0
  78. package/dist/es/lib/format-timestamp.js +37 -0
  79. package/dist/es/lib/style-scope.js +4 -0
  80. package/dist/es/lib/utils.js +8 -0
  81. package/dist/fonts.css +5 -0
  82. package/dist/preflight.css +502 -0
  83. package/dist/styles.css +1731 -0
  84. package/dist/tablewright.cjs +5473 -0
  85. package/dist/tablewright.css +1579 -0
  86. package/dist/tokens.css +150 -0
  87. package/dist/types/components/dashboard/ReportSummaryCard.d.ts +15 -0
  88. package/dist/types/components/data-table/AppliedStateChips.d.ts +28 -0
  89. package/dist/types/components/data-table/ColumnFilterPopover.d.ts +14 -0
  90. package/dist/types/components/data-table/ColumnHeaderMenu.d.ts +27 -0
  91. package/dist/types/components/data-table/CompoundFilterPopover.d.ts +14 -0
  92. package/dist/types/components/data-table/CopyButton.d.ts +8 -0
  93. package/dist/types/components/data-table/CopyableText.d.ts +10 -0
  94. package/dist/types/components/data-table/DataTableShell.d.ts +30 -0
  95. package/dist/types/components/data-table/DrawerActionGroup.d.ts +8 -0
  96. package/dist/types/components/data-table/DrawerEntityCard.d.ts +13 -0
  97. package/dist/types/components/data-table/DrawerField.d.ts +7 -0
  98. package/dist/types/components/data-table/DrawerFlagItem.d.ts +10 -0
  99. package/dist/types/components/data-table/DrawerSection.d.ts +8 -0
  100. package/dist/types/components/data-table/DrawerShell.d.ts +33 -0
  101. package/dist/types/components/data-table/DrawerSummaryCard.d.ts +7 -0
  102. package/dist/types/components/data-table/EmptyState.d.ts +6 -0
  103. package/dist/types/components/data-table/ExpiryTimestampCell.d.ts +6 -0
  104. package/dist/types/components/data-table/FilterChip.d.ts +8 -0
  105. package/dist/types/components/data-table/FilterFormPanel.d.ts +34 -0
  106. package/dist/types/components/data-table/FilterFormToggle.d.ts +18 -0
  107. package/dist/types/components/data-table/FilterPopoverFooter.d.ts +9 -0
  108. package/dist/types/components/data-table/FilterToolbar.d.ts +29 -0
  109. package/dist/types/components/data-table/FlatColumnToggle.d.ts +10 -0
  110. package/dist/types/components/data-table/GapIndicator.d.ts +11 -0
  111. package/dist/types/components/data-table/GroupedColumnToggle.d.ts +12 -0
  112. package/dist/types/components/data-table/RowActionsMenu.d.ts +10 -0
  113. package/dist/types/components/data-table/RowControlCell.d.ts +16 -0
  114. package/dist/types/components/data-table/SortableHeader.d.ts +16 -0
  115. package/dist/types/components/data-table/TableFooter.d.ts +31 -0
  116. package/dist/types/components/data-table/TableLayout.d.ts +12 -0
  117. package/dist/types/components/data-table/TimestampCell.d.ts +3 -0
  118. package/dist/types/components/data-table/TwoPanelMenu.d.ts +22 -0
  119. package/dist/types/components/data-table/ViewChip.d.ts +6 -0
  120. package/dist/types/components/data-table/cells/AmountCell.d.ts +11 -0
  121. package/dist/types/components/data-table/cells/ConfirmedAmountCell.d.ts +10 -0
  122. package/dist/types/components/data-table/cells/CurrencyAmountCell.d.ts +8 -0
  123. package/dist/types/components/data-table/cells/DualLineCell.d.ts +44 -0
  124. package/dist/types/components/data-table/cells/EnumBadgeCell.d.ts +9 -0
  125. package/dist/types/components/data-table/cells/MonoIdCell.d.ts +19 -0
  126. package/dist/types/components/data-table/filter-types.d.ts +79 -0
  127. package/dist/types/components/data-table/hooks/use-column-resize.d.ts +7 -0
  128. package/dist/types/components/data-table/hooks/use-row-drag.d.ts +14 -0
  129. package/dist/types/components/data-table/hooks/use-table-pagination.d.ts +10 -0
  130. package/dist/types/components/data-table/hooks/use-table-sort.d.ts +6 -0
  131. package/dist/types/components/data-table/managed-filters.d.ts +18 -0
  132. package/dist/types/components/data-table/sticky-utils.d.ts +4 -0
  133. package/dist/types/components/data-table/types.d.ts +35 -0
  134. package/dist/types/components/layout/AppShell.d.ts +23 -0
  135. package/dist/types/components/layout/Sidebar.d.ts +16 -0
  136. package/dist/types/components/layout/SidebarAccountMenu.d.ts +20 -0
  137. package/dist/types/components/layout/SidebarUser.d.ts +8 -0
  138. package/dist/types/components/layout/TopBar.d.ts +7 -0
  139. package/dist/types/components/layout/app-shell-context.d.ts +8 -0
  140. package/dist/types/components/layout/index.d.ts +10 -0
  141. package/dist/types/components/layout/types.d.ts +11 -0
  142. package/dist/types/components/ui/badge.d.ts +9 -0
  143. package/dist/types/components/ui/button-footer.d.ts +7 -0
  144. package/dist/types/components/ui/button.d.ts +11 -0
  145. package/dist/types/components/ui/checkbox.d.ts +4 -0
  146. package/dist/types/components/ui/combobox.d.ts +92 -0
  147. package/dist/types/components/ui/dialog.d.ts +15 -0
  148. package/dist/types/components/ui/dropdown-menu.d.ts +10 -0
  149. package/dist/types/components/ui/input.d.ts +6 -0
  150. package/dist/types/components/ui/popover.d.ts +7 -0
  151. package/dist/types/components/ui/segmented-control.d.ts +17 -0
  152. package/dist/types/components/ui/select.d.ts +8 -0
  153. package/dist/types/components/ui/tabs.d.ts +10 -0
  154. package/dist/types/components/ui/theme-switch.d.ts +12 -0
  155. package/dist/types/constants/drawer.d.ts +1 -0
  156. package/dist/types/context/drawer-expand-context.d.ts +7 -0
  157. package/dist/types/context/portal-container-context.d.ts +14 -0
  158. package/dist/types/hooks/table-orchestration-store.d.ts +75 -0
  159. package/dist/types/hooks/use-scroll-activity.d.ts +1 -0
  160. package/dist/types/hooks/useDemoTheme.d.ts +8 -0
  161. package/dist/types/hooks/useRowDragOverride.d.ts +6 -0
  162. package/dist/types/hooks/useTableOrchestration.d.ts +115 -0
  163. package/dist/types/index.d.ts +107 -0
  164. package/dist/types/lib/column-gaps.d.ts +3 -0
  165. package/dist/types/lib/filter-stats.d.ts +2 -0
  166. package/dist/types/lib/format-enum.d.ts +1 -0
  167. package/dist/types/lib/format-timestamp.d.ts +4 -0
  168. package/dist/types/lib/style-scope.d.ts +19 -0
  169. package/dist/types/lib/utils.d.ts +2 -0
  170. package/package.json +114 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 undevy-org
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # tablewright
2
+
3
+ Live Storybook: <https://undevy-org.github.io/tablewright/>
4
+
5
+ A React + TypeScript component library — buttons, inputs, dialogs, and a
6
+ data-table system (drawers, filters, row controls, sortable headers) built
7
+ on Radix UI, TanStack Table, and Tailwind CSS.
8
+
9
+ ## Using it in another project
10
+
11
+ ```bash
12
+ npm i @undevy-org/tablewright
13
+ ```
14
+
15
+ Peer dependencies you install yourself, so there is exactly one copy of each in
16
+ the final bundle:
17
+
18
+ ```bash
19
+ npm i react react-dom @tanstack/react-table
20
+ ```
21
+
22
+ | Peer | Range | |
23
+ | --- | --- | --- |
24
+ | `react`, `react-dom` | `>=18.0.0 <20.0.0` | required |
25
+ | `@tanstack/react-table` | `^8.21.3` | required — you construct the `Table` instance yourself |
26
+ | `@types/react`, `@types/react-dom` | `*` | optional; your copy is used, none is installed |
27
+
28
+ ### Styles
29
+
30
+ Two stylesheets, imported once at the application entry:
31
+
32
+ ```ts
33
+ import "@undevy-org/tablewright/tokens.css"; // CSS custom properties, both themes
34
+ import "@undevy-org/tablewright/tablewright.css"; // scoped reset + component styles
35
+ ```
36
+
37
+ `@undevy-org/tablewright/styles.css` is the two of them concatenated, if you would
38
+ rather have a single import.
39
+
40
+ Then put the style scope on the element that wraps your use of the kit:
41
+
42
+ ```tsx
43
+ import { STYLE_SCOPE } from "@undevy-org/tablewright";
44
+
45
+ <div className={STYLE_SCOPE} data-theme="light">
46
+ …
47
+ </div>;
48
+ ```
49
+
50
+ The scope exists because the package must not restyle its host. Tailwind's
51
+ preflight is a global reset — it rewrites margins, list styles, heading sizes
52
+ and form-control appearance for every element on the page — so this package
53
+ ships that normalisation confined to `.tablewright` instead.
54
+
55
+ What is and is not isolated, precisely:
56
+
57
+ - **The element reset is scoped.** It never touches an element outside
58
+ `.tablewright`, and it sits at exactly one class of specificity, so it
59
+ outranks a host reset that names bare elements (`button, input { border: none }`
60
+ would otherwise erase every border in the kit) while still losing to the
61
+ package's own utilities.
62
+ - **The utility classes are global and unprefixed.** `tablewright.css` defines
63
+ `.flex`, `.rounded-md`, `.text-sm` and the rest at the top level. If your own
64
+ stylesheets define class names of the same shape, they collide, and source
65
+ order decides. CSS-module or component-scoped class names — what the first
66
+ consumer uses — cannot collide.
67
+
68
+ If your app has no reset of its own and you want the global one,
69
+ `@undevy-org/tablewright/preflight.css` is Tailwind's preflight compiled from a
70
+ stock config, so it carries Tailwind's default font stacks rather than this
71
+ package's.
72
+
73
+ Fonts are not loaded for you. Serve Inter and JetBrains Mono yourself, or
74
+ `import "@undevy-org/tablewright/fonts.css"` to pull them from Google Fonts.
75
+
76
+ You do not need Tailwind. `tablewright.css` is compiled; the package works the
77
+ same in a project that has never seen Tailwind, and adding the package to a
78
+ `content` glob is neither required nor useful.
79
+
80
+ ### Theme
81
+
82
+ Tokens resolve from the nearest ancestor carrying `data-theme="light"` or
83
+ `data-theme="dark"`. Set it on `:root` for a whole-application theme, or on any
84
+ wrapper to theme a region.
85
+
86
+ Radix portals — dropdown, popover, dialog, select — mount outside the React tree
87
+ and default to `document.body`. Their content carries the style scope itself, so
88
+ it is always laid out and typeset correctly, but `document.body` sits outside
89
+ your `data-theme` wrapper. To make portalled content follow a theme that is not
90
+ on `:root`, give it a container inside the themed subtree:
91
+
92
+ ```tsx
93
+ import { useState } from "react";
94
+ import { PortalContainerContext, STYLE_SCOPE } from "@undevy-org/tablewright";
95
+
96
+ function Page({ theme }: { theme: "light" | "dark" }) {
97
+ // Callback ref, not useRef: a ref object is still null on first render,
98
+ // and the portals would fall back to document.body.
99
+ const [portal, setPortal] = useState<HTMLDivElement | null>(null);
100
+
101
+ return (
102
+ <div ref={setPortal} className={STYLE_SCOPE} data-theme={theme}>
103
+ <PortalContainerContext.Provider value={portal}>
104
+ …
105
+ </PortalContainerContext.Provider>
106
+ </div>
107
+ );
108
+ }
109
+ ```
110
+
111
+ ### Example
112
+
113
+ ```tsx
114
+ import { getCoreRowModel, useReactTable, type ColumnDef } from "@tanstack/react-table";
115
+ import { Badge, DataTableShell, STYLE_SCOPE, type ColumnMetaDef } from "@undevy-org/tablewright";
116
+
117
+ type Row = { id: string; status: string };
118
+
119
+ const columns: ColumnDef<Row>[] = [
120
+ { id: "id", accessorKey: "id", header: "ID" },
121
+ { id: "status", accessorKey: "status", header: "Status",
122
+ cell: ({ row }) => <Badge variant="success">{row.original.status}</Badge> },
123
+ ];
124
+
125
+ const columnMeta: Record<string, ColumnMetaDef> = {
126
+ id: { minW: 140 },
127
+ status: { minW: 120 },
128
+ };
129
+
130
+ function Table({ rows }: { rows: Row[] }) {
131
+ const table = useReactTable({ data: rows, columns, getCoreRowModel: getCoreRowModel() });
132
+ return (
133
+ <div className={STYLE_SCOPE} style={{ height: 400, display: "flex" }}>
134
+ <DataTableShell
135
+ table={table}
136
+ dense={false}
137
+ emptyMessage="Nothing found"
138
+ columnMeta={columnMeta}
139
+ columnWidths={{}}
140
+ onColumnResizeStart={() => {}}
141
+ />
142
+ </div>
143
+ );
144
+ }
145
+ ```
146
+
147
+ ### Known rough edges
148
+
149
+ - **User-visible strings are hardcoded English in eleven components** —
150
+ `Sidebar`, `SidebarAccountMenu`, `TableFooter`, `DrawerShell`,
151
+ `FilterFormPanel`, `RowActionsMenu`, `GroupedColumnToggle`,
152
+ `ReportSummaryCard`, `AmountCell`, `ConfirmedAmountCell` and the dialog close
153
+ button. `ColumnHeaderMenu` is the only component that takes a `labels` prop, so
154
+ everything else needs a fork or a patch to localise.
155
+ - **`animate-in` / `fade-in-0` / `zoom-in-95` compile to nothing.** `DrawerShell`
156
+ asks for a fade-and-zoom entrance on the expanded drawer, but no
157
+ `tailwindcss-animate` plugin is configured, so those three class names produce
158
+ no CSS in any build — the modal appears instantly, in Storybook and in the
159
+ package alike. Either add the plugin or drop the classes; today the source says
160
+ one thing and the artifact does another.
161
+ - **Opacity modifiers on `var()` colours compile to nothing.** Tailwind v3 cannot
162
+ extract an alpha channel from a raw `var()`, so
163
+ `border-[var(--tag-blue-text)]/20` and `text-[var(--tag-blue-text)]/60` emit no
164
+ rule at all. The blue filter chips in `ColumnFilterPopover` and
165
+ `CompoundFilterPopover` ship without their intended 20% border and 60% glyph
166
+ colour. `color-mix(in srgb, var(--tag-blue-text) 20%, transparent)` in an
167
+ arbitrary value compiles and is the closest faithful fix.
168
+ - **Portalled surfaces re-assert the typographic base.** A font size or colour
169
+ set on your `STYLE_SCOPE` wrapper reaches the tables and toolbars but not the
170
+ dropdowns, popovers, selects or dialogs, because those carry the scope
171
+ themselves.
172
+ - **No tests.** The gates are ESLint, `tsc`, the build's own assertions, the
173
+ render check. Nothing exercises component
174
+ behaviour or interaction.
175
+
176
+ ## Development
177
+
178
+ ```bash
179
+ pnpm install
180
+ pnpm storybook # dev server
181
+ pnpm build-storybook # static build
182
+ pnpm build # dist/ — library, stylesheets, types
183
+ pnpm check:render # renders dist in headless Chrome against a hostile host stylesheet
184
+ pnpm lint
185
+ pnpm typecheck
186
+ ```
187
+
188
+ `check:render` is the one gate that resolves the CSS cascade rather than
189
+ inspecting it. It loads `dist/` — not `src` — into headless Chrome underneath a
190
+ deliberately hostile host stylesheet and asserts 27 computed styles: that the
191
+ kit's controls keep their borders and typography, that the utilities still beat
192
+ the reset, that portalled surfaces are styled while mounted on `document.body`,
193
+ and that the host's own elements are left exactly as the host styled them. Run it
194
+ after touching `src/css/`, the scope wiring, or the CSS build. It needs a Chrome
195
+ on the machine (`CHROME_PATH` overrides the search) and is deliberately not part
196
+ of `pnpm build`.
197
+
198
+ ## Structure
199
+
200
+ - `src/components/ui` — primitives (Button, Input, Dialog, Select, Tabs, ...)
201
+ - `src/components/layout` — app shell, sidebar, top bar
202
+ - `src/components/data-table` — table shell, drawers, filters, row controls
203
+ - `src/components/dashboard` — summary cards
204
+ - `src/css` — token, reset, global and font layers assembled into `dist/`
205
+ - `scripts/render-check` — the fixture and runner behind `pnpm check:render`
206
+ - `design-tokens/manifest.json` — canonical token source
207
+
208
+ ## Build
209
+
210
+ `pnpm build` runs four steps (`scripts/build.mjs`). `dist/` is cleared once at
211
+ the start, so the passes can write into the same tree:
212
+
213
+ | Step | Output |
214
+ | --- | --- |
215
+ | `TW_FORMAT=es vite build --config vite.lib.config.ts` | `dist/es/**` — one file per source module |
216
+ | `TW_FORMAT=cjs vite build --config vite.lib.config.ts` | `dist/tablewright.cjs` — one flattened file |
217
+ | `node scripts/build-css.mjs` | `dist/{tokens,tablewright,styles,preflight,fonts}.css` |
218
+ | `tsc -p tsconfig.build.json` | `dist/types/**` |
219
+
220
+ The ES output is per-module because a single flattened bundle defeats
221
+ tree-shaking here: component modules call `forwardRef(...)` and `cva(...)` at
222
+ module scope, which Rollup cannot prove side-effect free. Measured in a real
223
+ consumer with React, Radix and TanStack external — importing only `cn` costs
224
+ 25.4 kB / 5.7 kB gzip against 171 kB / 34.3 kB for the whole surface. (The
225
+ `sideEffects` field changes none of this; it is declared because a webpack
226
+ consumer needs it to keep the CSS imports, and was measured to make no difference
227
+ to what Rollup drops.)
228
+
229
+ The CJS output is `.cjs`, not `.cjs.js`. This package is `"type": "module"`, so
230
+ Node reads any `.js` inside it as ESM — a CommonJS file named `.cjs.js` returns
231
+ an empty namespace from `require()` with no error at all.
232
+
233
+ The library build is deliberately not `vite.config.ts`: Storybook's
234
+ `@storybook/react-vite` auto-loads that filename and would inherit `build.lib`.
235
+
236
+ `tsconfig.build.json` sets `noEmit: false`. The base config sets it to `true`,
237
+ and a declaration build that inherits it exits 0 having written nothing — an
238
+ empty `dist` that no error reports.
239
+
240
+ Two further passes run after those steps: relative specifiers in the emitted
241
+ declarations get an explicit `.js` extension (without it, a consumer on
242
+ `node16`/`nodenext` types every export as `any` and sees no error), and every
243
+ path `package.json` advertises is asserted to exist and be non-trivial.
244
+
245
+ ### Checks
246
+
247
+ The CSS build fails loudly on three mistakes it can otherwise make silently:
248
+
249
+ - a `content` glob that matches nothing — utilities would be missing and every
250
+ component would render unstyled;
251
+ - a rule that addresses elements directly instead of through a class — a leaked
252
+ preflight looks exactly like that and would restyle the host page;
253
+ - hand-authored CSS (`reset.css`, `scrollbar.css`) whose selector does not name
254
+ `.tablewright` outside a `:where()`. Inside `:where()` the scope contributes no
255
+ specificity, a host's plain `button, input { border: none }` beats the reset,
256
+ and every control in the kit loses its border. That one shipped, and no static
257
+ check caught it — `pnpm check:render` did, by rendering.
258
+
259
+ Each guard is verified by breaking the build on purpose and confirming it fails,
260
+ not merely by passing on a good build. The same applies to `check:render`: it was
261
+ confirmed to go red both for the specificity defect and for a portal that loses
262
+ the scope class.
@@ -0,0 +1,67 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { Badge } from "../ui/badge.js";
3
+ function ReportSummaryCard({
4
+ title,
5
+ count,
6
+ endpoint,
7
+ topActions,
8
+ filterNames,
9
+ columns,
10
+ sampleRows,
11
+ emptyStateMessage,
12
+ notes
13
+ }) {
14
+ return /* @__PURE__ */ jsxs("section", { className: "rounded-xl border border-[var(--border-subtle)] bg-[var(--bg-surface)]", children: [
15
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b border-[var(--border-subtle)] px-6 py-4", children: [
16
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
17
+ /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-semibold text-[var(--text-primary)]", children: title }),
18
+ typeof count === "number" ? /* @__PURE__ */ jsx(Badge, { variant: "neutral", children: count }) : null
19
+ ] }),
20
+ /* @__PURE__ */ jsx("code", { className: "rounded-md bg-[var(--bg-secondary)] px-2.5 py-1 font-mono text-[11px] text-[var(--text-secondary)]", children: endpoint })
21
+ ] }),
22
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 px-6 py-5 lg:grid-cols-[0.9fr_1.1fr]", children: [
23
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
24
+ /* @__PURE__ */ jsxs("div", { children: [
25
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-secondary)]", children: "Top Actions" }),
26
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: topActions.length > 0 ? topActions.map((action) => /* @__PURE__ */ jsx(Badge, { variant: "neutral", className: "rounded-full px-3 py-1", children: action }, action)) : /* @__PURE__ */ jsx("span", { className: "text-[12px] text-[var(--text-tertiary)]", children: "No visible top actions" }) })
27
+ ] }),
28
+ /* @__PURE__ */ jsxs("div", { children: [
29
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-secondary)]", children: "Filters" }),
30
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: filterNames.length > 0 ? filterNames.map((filterName) => /* @__PURE__ */ jsx(Badge, { variant: "info", className: "rounded-full px-3 py-1", children: filterName }, filterName)) : /* @__PURE__ */ jsx("span", { className: "text-[12px] text-[var(--text-tertiary)]", children: "No visible filters" }) })
31
+ ] }),
32
+ /* @__PURE__ */ jsxs("div", { children: [
33
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-secondary)]", children: "Columns" }),
34
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: columns.map((column) => /* @__PURE__ */ jsx(Badge, { variant: "warning", className: "rounded-full px-3 py-1", children: column }, column)) })
35
+ ] }),
36
+ notes && notes.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
37
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-secondary)]", children: "Notes" }),
38
+ /* @__PURE__ */ jsx("div", { className: "space-y-2 text-[12px] text-[var(--text-secondary)]", children: notes.map((note) => /* @__PURE__ */ jsx("p", { children: note }, note)) })
39
+ ] }) : null
40
+ ] }),
41
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-[var(--border-subtle)] bg-[var(--bg-hover)] p-4", children: [
42
+ /* @__PURE__ */ jsx("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-secondary)]", children: "Sample Rows" }),
43
+ sampleRows.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: sampleRows.map((row) => /* @__PURE__ */ jsxs(
44
+ "div",
45
+ {
46
+ className: "rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-3",
47
+ children: [
48
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-[13px] font-medium text-[var(--text-primary)]", children: row.title }),
49
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: row.values.map((value) => /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ className: "rounded-md bg-[var(--bg-secondary)] px-2 py-1 text-[11px] text-[var(--text-secondary)]",
53
+ children: value
54
+ },
55
+ `${row.title}-${value}`
56
+ )) })
57
+ ]
58
+ },
59
+ `${title}-${row.title}`
60
+ )) }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-[var(--border-subtle)] bg-[var(--bg-surface)] px-4 py-8 text-center text-[12px] text-[var(--text-secondary)]", children: emptyStateMessage ?? "No sample rows captured." })
61
+ ] })
62
+ ] })
63
+ ] });
64
+ }
65
+ export {
66
+ ReportSummaryCard
67
+ };
@@ -0,0 +1,113 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { X } from "lucide-react";
3
+ import { Button } from "../ui/button.js";
4
+ import { FilterChip } from "./FilterChip.js";
5
+ import { ColumnFilterPopover } from "./ColumnFilterPopover.js";
6
+ import { CompoundFilterPopover } from "./CompoundFilterPopover.js";
7
+ const DEFAULT_LABELS = {
8
+ clearAll: "Clear all",
9
+ columns: "Columns",
10
+ hiddenCount: (n) => `${n} hidden`
11
+ };
12
+ function isFilterValueEmpty(value) {
13
+ if (Array.isArray(value)) return value.length === 0;
14
+ if (typeof value === "string") return value.length === 0;
15
+ if (typeof value === "object" && value !== null) {
16
+ const keys = Object.keys(value);
17
+ if (keys.length === 0) return true;
18
+ return keys.every((k) => isFilterValueEmpty(value[k]));
19
+ }
20
+ return false;
21
+ }
22
+ function AppliedStateChips({
23
+ filters,
24
+ configs,
25
+ newestColumnId,
26
+ onRemoveFilter,
27
+ onChangeFilter,
28
+ currentSort,
29
+ columnLabels,
30
+ onRemoveSort,
31
+ hiddenColumnsCount,
32
+ onResetColumns,
33
+ onOpenColumnsMenu,
34
+ customFilterChip,
35
+ onRemoveCustomFilter,
36
+ onClearAll,
37
+ columnFilterStats,
38
+ requestOpenFilterId,
39
+ onRequestOpenHandled
40
+ }) {
41
+ const hasFilters = filters.length > 0;
42
+ const hasSort = currentSort !== null;
43
+ const hasHiddenColumns = hiddenColumnsCount > 0;
44
+ const hasCustomFilter = customFilterChip !== null;
45
+ if (!hasFilters && !hasSort && !hasHiddenColumns && !hasCustomFilter) return null;
46
+ const sortDirectionLabel = currentSort?.direction === "asc" ? "↑" : "↓";
47
+ const sortColumnLabel = currentSort ? columnLabels[currentSort.columnId] ?? currentSort.columnId : "";
48
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pb-3", children: [
49
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: onClearAll, className: "shrink-0", children: [
50
+ /* @__PURE__ */ jsx(X, { size: 14 }),
51
+ DEFAULT_LABELS.clearAll
52
+ ] }),
53
+ hasHiddenColumns && /* @__PURE__ */ jsx(
54
+ FilterChip,
55
+ {
56
+ label: DEFAULT_LABELS.columns,
57
+ value: DEFAULT_LABELS.hiddenCount(hiddenColumnsCount),
58
+ onRemove: onResetColumns,
59
+ onClick: onOpenColumnsMenu,
60
+ className: "cursor-pointer"
61
+ }
62
+ ),
63
+ hasSort && /* @__PURE__ */ jsx(FilterChip, { label: sortColumnLabel, value: sortDirectionLabel, onRemove: onRemoveSort }),
64
+ filters.map((filter) => {
65
+ const filterConfig = configs[filter.columnId];
66
+ if (!filterConfig) return null;
67
+ const isEmpty = isFilterValueEmpty(filter.value);
68
+ if (filterConfig.type === "compound") {
69
+ return /* @__PURE__ */ jsx(
70
+ CompoundFilterPopover,
71
+ {
72
+ config: filterConfig,
73
+ filter,
74
+ onChange: (value) => onChangeFilter(filter.columnId, value),
75
+ onRemove: () => onRemoveFilter(filter.columnId),
76
+ initialOpen: filter.columnId === newestColumnId,
77
+ chipClassName: isEmpty ? "border-dashed" : void 0,
78
+ columnFilterStats,
79
+ requestOpen: filter.columnId === requestOpenFilterId,
80
+ onRequestOpenHandled
81
+ },
82
+ filter.columnId
83
+ );
84
+ }
85
+ return /* @__PURE__ */ jsx(
86
+ ColumnFilterPopover,
87
+ {
88
+ config: filterConfig,
89
+ filter,
90
+ onChange: (value) => onChangeFilter(filter.columnId, value),
91
+ onRemove: () => onRemoveFilter(filter.columnId),
92
+ initialOpen: filter.columnId === newestColumnId,
93
+ chipClassName: isEmpty ? "border-dashed" : void 0,
94
+ stats: columnFilterStats?.[filter.columnId],
95
+ requestOpen: filter.columnId === requestOpenFilterId,
96
+ onRequestOpenHandled
97
+ },
98
+ filter.columnId
99
+ );
100
+ }),
101
+ hasCustomFilter && /* @__PURE__ */ jsx(
102
+ FilterChip,
103
+ {
104
+ label: customFilterChip.label,
105
+ value: customFilterChip.value,
106
+ onRemove: onRemoveCustomFilter
107
+ }
108
+ )
109
+ ] });
110
+ }
111
+ export {
112
+ AppliedStateChips
113
+ };