@inshorts/glint-ui 0.5.4 → 0.6.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 (340) hide show
  1. package/README.md +3 -1
  2. package/dist/alert.cjs +96 -0
  3. package/dist/alert.cjs.map +1 -0
  4. package/dist/alert.d.cts +29 -0
  5. package/dist/alert.d.ts +29 -0
  6. package/dist/alert.js +10 -0
  7. package/dist/alert.js.map +1 -0
  8. package/dist/autocomplete.cjs +578 -0
  9. package/dist/autocomplete.cjs.map +1 -0
  10. package/dist/autocomplete.d.cts +56 -0
  11. package/dist/autocomplete.d.ts +56 -0
  12. package/dist/autocomplete.js +11 -0
  13. package/dist/autocomplete.js.map +1 -0
  14. package/dist/badge.cjs +89 -0
  15. package/dist/badge.cjs.map +1 -0
  16. package/dist/badge.d.cts +25 -0
  17. package/dist/badge.d.ts +25 -0
  18. package/dist/badge.js +10 -0
  19. package/dist/badge.js.map +1 -0
  20. package/dist/breadcrumb.cjs +88 -0
  21. package/dist/breadcrumb.cjs.map +1 -0
  22. package/dist/breadcrumb.d.cts +14 -0
  23. package/dist/breadcrumb.d.ts +14 -0
  24. package/dist/breadcrumb.js +8 -0
  25. package/dist/breadcrumb.js.map +1 -0
  26. package/dist/button.cjs +143 -0
  27. package/dist/button.cjs.map +1 -0
  28. package/dist/button.d.cts +34 -0
  29. package/dist/button.d.ts +34 -0
  30. package/dist/button.js +9 -0
  31. package/dist/button.js.map +1 -0
  32. package/dist/card.cjs +111 -0
  33. package/dist/card.cjs.map +1 -0
  34. package/dist/card.d.cts +15 -0
  35. package/dist/card.d.ts +15 -0
  36. package/dist/card.js +18 -0
  37. package/dist/card.js.map +1 -0
  38. package/dist/checkbox.cjs +66 -0
  39. package/dist/checkbox.cjs.map +1 -0
  40. package/{src/checkbox.tsx → dist/checkbox.d.cts} +5 -22
  41. package/dist/checkbox.d.ts +25 -0
  42. package/dist/checkbox.js +10 -0
  43. package/dist/checkbox.js.map +1 -0
  44. package/dist/chunk-43ZUFY63.js +85 -0
  45. package/dist/chunk-43ZUFY63.js.map +1 -0
  46. package/dist/chunk-67ACNS42.js +29 -0
  47. package/dist/chunk-67ACNS42.js.map +1 -0
  48. package/dist/chunk-6URKHMCF.js +76 -0
  49. package/dist/chunk-6URKHMCF.js.map +1 -0
  50. package/dist/chunk-ASU6CUTZ.js +85 -0
  51. package/dist/chunk-ASU6CUTZ.js.map +1 -0
  52. package/dist/chunk-EWSBFU54.js +53 -0
  53. package/dist/chunk-EWSBFU54.js.map +1 -0
  54. package/dist/chunk-G4ZQUSAJ.js +38 -0
  55. package/dist/chunk-G4ZQUSAJ.js.map +1 -0
  56. package/dist/chunk-GMCUXEFI.js +67 -0
  57. package/dist/chunk-GMCUXEFI.js.map +1 -0
  58. package/dist/chunk-HBBOR43A.js +85 -0
  59. package/dist/chunk-HBBOR43A.js.map +1 -0
  60. package/dist/chunk-HCA5ODCD.js +11 -0
  61. package/dist/chunk-HCA5ODCD.js.map +1 -0
  62. package/dist/chunk-HYRJYUPI.js +26 -0
  63. package/dist/chunk-HYRJYUPI.js.map +1 -0
  64. package/dist/chunk-IRCTGJQB.js +26 -0
  65. package/dist/chunk-IRCTGJQB.js.map +1 -0
  66. package/dist/chunk-JDE5XYPT.js +30 -0
  67. package/dist/chunk-JDE5XYPT.js.map +1 -0
  68. package/dist/chunk-JSJEEKR3.js +59 -0
  69. package/dist/chunk-JSJEEKR3.js.map +1 -0
  70. package/dist/chunk-KR3BCFP7.js +85 -0
  71. package/dist/chunk-KR3BCFP7.js.map +1 -0
  72. package/dist/chunk-MQT5RAQV.js +202 -0
  73. package/dist/chunk-MQT5RAQV.js.map +1 -0
  74. package/dist/chunk-MUPW2TFJ.js +102 -0
  75. package/dist/chunk-MUPW2TFJ.js.map +1 -0
  76. package/dist/chunk-PM2VE7DJ.js +100 -0
  77. package/dist/chunk-PM2VE7DJ.js.map +1 -0
  78. package/dist/chunk-PPYR3O3L.js +44 -0
  79. package/dist/chunk-PPYR3O3L.js.map +1 -0
  80. package/dist/chunk-PUTC52SI.js +98 -0
  81. package/dist/chunk-PUTC52SI.js.map +1 -0
  82. package/dist/chunk-PVK4WQJZ.js +513 -0
  83. package/dist/chunk-PVK4WQJZ.js.map +1 -0
  84. package/dist/chunk-PWAKTEXF.js +18 -0
  85. package/dist/chunk-PWAKTEXF.js.map +1 -0
  86. package/dist/chunk-QTFWORA7.js +26 -0
  87. package/dist/chunk-QTFWORA7.js.map +1 -0
  88. package/dist/chunk-RUPL2H6V.js +49 -0
  89. package/dist/chunk-RUPL2H6V.js.map +1 -0
  90. package/dist/chunk-S7PVZAPL.js +28 -0
  91. package/dist/chunk-S7PVZAPL.js.map +1 -0
  92. package/dist/chunk-SQDV2AKJ.js +49 -0
  93. package/dist/chunk-SQDV2AKJ.js.map +1 -0
  94. package/dist/chunk-T2KXVH3O.js +297 -0
  95. package/dist/chunk-T2KXVH3O.js.map +1 -0
  96. package/dist/chunk-T4WCCHJQ.js +29 -0
  97. package/dist/chunk-T4WCCHJQ.js.map +1 -0
  98. package/dist/chunk-UC3ZIQPE.js +93 -0
  99. package/dist/chunk-UC3ZIQPE.js.map +1 -0
  100. package/dist/chunk-UE7JY7VR.js +28 -0
  101. package/dist/chunk-UE7JY7VR.js.map +1 -0
  102. package/dist/chunk-WVNRFO5F.js +70 -0
  103. package/dist/chunk-WVNRFO5F.js.map +1 -0
  104. package/dist/chunk-Y5WCT6XN.js +93 -0
  105. package/dist/chunk-Y5WCT6XN.js.map +1 -0
  106. package/dist/chunk-YDICVETX.js +151 -0
  107. package/dist/chunk-YDICVETX.js.map +1 -0
  108. package/dist/chunk-YSU4VWRD.js +90 -0
  109. package/dist/chunk-YSU4VWRD.js.map +1 -0
  110. package/dist/chunk-ZFSQVMLJ.js +126 -0
  111. package/dist/chunk-ZFSQVMLJ.js.map +1 -0
  112. package/dist/chunk-ZHATD73Y.js +46 -0
  113. package/dist/chunk-ZHATD73Y.js.map +1 -0
  114. package/dist/chunk-ZLAA4DGG.js +111 -0
  115. package/dist/chunk-ZLAA4DGG.js.map +1 -0
  116. package/dist/chunk-ZP252OJZ.js +56 -0
  117. package/dist/chunk-ZP252OJZ.js.map +1 -0
  118. package/dist/clipboard.cjs +125 -0
  119. package/dist/clipboard.cjs.map +1 -0
  120. package/dist/clipboard.d.cts +52 -0
  121. package/dist/clipboard.d.ts +52 -0
  122. package/dist/clipboard.js +23 -0
  123. package/dist/clipboard.js.map +1 -0
  124. package/dist/collapsible.cjs +94 -0
  125. package/dist/collapsible.cjs.map +1 -0
  126. package/dist/collapsible.d.cts +21 -0
  127. package/dist/collapsible.d.ts +21 -0
  128. package/dist/collapsible.js +9 -0
  129. package/dist/collapsible.js.map +1 -0
  130. package/dist/confirm-dialog.cjs +369 -0
  131. package/dist/confirm-dialog.cjs.map +1 -0
  132. package/dist/confirm-dialog.d.cts +16 -0
  133. package/dist/confirm-dialog.d.ts +16 -0
  134. package/dist/confirm-dialog.js +13 -0
  135. package/dist/confirm-dialog.js.map +1 -0
  136. package/dist/copy-button.cjs +102 -0
  137. package/dist/copy-button.cjs.map +1 -0
  138. package/dist/copy-button.d.cts +18 -0
  139. package/dist/copy-button.d.ts +18 -0
  140. package/dist/copy-button.js +10 -0
  141. package/dist/copy-button.js.map +1 -0
  142. package/dist/data-table.cjs +140 -0
  143. package/dist/data-table.cjs.map +1 -0
  144. package/dist/data-table.d.cts +81 -0
  145. package/dist/data-table.d.ts +81 -0
  146. package/dist/data-table.js +8 -0
  147. package/dist/data-table.js.map +1 -0
  148. package/dist/date-range-picker/dateRange.cjs +138 -0
  149. package/dist/date-range-picker/dateRange.cjs.map +1 -0
  150. package/dist/date-range-picker/dateRange.d.cts +57 -0
  151. package/dist/date-range-picker/dateRange.d.ts +57 -0
  152. package/dist/date-range-picker/dateRange.js +31 -0
  153. package/dist/date-range-picker/dateRange.js.map +1 -0
  154. package/dist/date-range-picker/index.cjs +375 -0
  155. package/dist/date-range-picker/index.cjs.map +1 -0
  156. package/dist/date-range-picker/index.d.cts +18 -0
  157. package/dist/date-range-picker/index.d.ts +18 -0
  158. package/dist/date-range-picker/index.js +10 -0
  159. package/dist/date-range-picker/index.js.map +1 -0
  160. package/dist/dialogLayer.cjs +121 -0
  161. package/dist/dialogLayer.cjs.map +1 -0
  162. package/dist/dialogLayer.d.cts +11 -0
  163. package/dist/dialogLayer.d.ts +11 -0
  164. package/dist/dialogLayer.js +8 -0
  165. package/dist/dialogLayer.js.map +1 -0
  166. package/dist/feature-access-gate.cjs +160 -0
  167. package/dist/feature-access-gate.cjs.map +1 -0
  168. package/dist/feature-access-gate.d.cts +14 -0
  169. package/dist/feature-access-gate.d.ts +14 -0
  170. package/dist/feature-access-gate.js +11 -0
  171. package/dist/feature-access-gate.js.map +1 -0
  172. package/dist/field.cjs +58 -0
  173. package/dist/field.cjs.map +1 -0
  174. package/dist/field.d.cts +23 -0
  175. package/dist/field.d.ts +23 -0
  176. package/dist/field.js +8 -0
  177. package/dist/field.js.map +1 -0
  178. package/dist/image-uploader.cjs +293 -0
  179. package/dist/image-uploader.cjs.map +1 -0
  180. package/dist/image-uploader.d.cts +34 -0
  181. package/dist/image-uploader.d.ts +34 -0
  182. package/dist/image-uploader.js +12 -0
  183. package/dist/image-uploader.js.map +1 -0
  184. package/dist/index.cjs +3012 -0
  185. package/dist/index.cjs.map +1 -0
  186. package/dist/index.d.cts +41 -0
  187. package/dist/index.d.ts +41 -0
  188. package/dist/index.js +232 -0
  189. package/dist/index.js.map +1 -0
  190. package/dist/input.cjs +58 -0
  191. package/dist/input.cjs.map +1 -0
  192. package/dist/input.d.cts +7 -0
  193. package/dist/input.d.ts +7 -0
  194. package/dist/input.js +10 -0
  195. package/dist/input.js.map +1 -0
  196. package/dist/label.cjs +67 -0
  197. package/dist/label.cjs.map +1 -0
  198. package/dist/label.d.cts +7 -0
  199. package/dist/label.d.ts +7 -0
  200. package/dist/label.js +9 -0
  201. package/dist/label.js.map +1 -0
  202. package/dist/modal.cjs +212 -0
  203. package/dist/modal.cjs.map +1 -0
  204. package/dist/modal.d.cts +39 -0
  205. package/dist/modal.d.ts +39 -0
  206. package/dist/modal.js +12 -0
  207. package/dist/modal.js.map +1 -0
  208. package/dist/page-header.cjs +78 -0
  209. package/dist/page-header.cjs.map +1 -0
  210. package/dist/page-header.d.cts +35 -0
  211. package/dist/page-header.d.ts +35 -0
  212. package/dist/page-header.js +8 -0
  213. package/dist/page-header.js.map +1 -0
  214. package/dist/pagination.cjs +237 -0
  215. package/dist/pagination.cjs.map +1 -0
  216. package/dist/pagination.d.cts +15 -0
  217. package/dist/pagination.d.ts +15 -0
  218. package/dist/pagination.js +11 -0
  219. package/dist/pagination.js.map +1 -0
  220. package/dist/panel-brand-mark.cjs +59 -0
  221. package/dist/panel-brand-mark.cjs.map +1 -0
  222. package/dist/panel-brand-mark.d.cts +13 -0
  223. package/dist/panel-brand-mark.d.ts +13 -0
  224. package/dist/panel-brand-mark.js +8 -0
  225. package/dist/panel-brand-mark.js.map +1 -0
  226. package/dist/panel-sign-in.cjs +256 -0
  227. package/dist/panel-sign-in.cjs.map +1 -0
  228. package/dist/panel-sign-in.d.cts +31 -0
  229. package/dist/panel-sign-in.d.ts +31 -0
  230. package/dist/panel-sign-in.js +14 -0
  231. package/dist/panel-sign-in.js.map +1 -0
  232. package/dist/segmented-control.cjs +126 -0
  233. package/dist/segmented-control.cjs.map +1 -0
  234. package/dist/segmented-control.d.cts +60 -0
  235. package/dist/segmented-control.d.ts +60 -0
  236. package/dist/segmented-control.js +9 -0
  237. package/dist/segmented-control.js.map +1 -0
  238. package/dist/select.cjs +648 -0
  239. package/dist/select.cjs.map +1 -0
  240. package/dist/select.d.cts +30 -0
  241. package/dist/select.d.ts +30 -0
  242. package/dist/select.js +12 -0
  243. package/dist/select.js.map +1 -0
  244. package/dist/separator.cjs +70 -0
  245. package/dist/separator.cjs.map +1 -0
  246. package/dist/separator.d.cts +13 -0
  247. package/dist/separator.d.ts +13 -0
  248. package/dist/separator.js +9 -0
  249. package/dist/separator.js.map +1 -0
  250. package/dist/side-drawer.cjs +321 -0
  251. package/dist/side-drawer.cjs.map +1 -0
  252. package/dist/side-drawer.d.cts +21 -0
  253. package/dist/side-drawer.d.ts +21 -0
  254. package/dist/side-drawer.js +13 -0
  255. package/dist/side-drawer.js.map +1 -0
  256. package/dist/spinner.cjs +57 -0
  257. package/dist/spinner.cjs.map +1 -0
  258. package/{src/spinner.tsx → dist/spinner.d.cts} +11 -22
  259. package/dist/spinner.d.ts +27 -0
  260. package/dist/spinner.js +8 -0
  261. package/dist/spinner.js.map +1 -0
  262. package/dist/spreadsheet-grid.cjs +238 -0
  263. package/dist/spreadsheet-grid.cjs.map +1 -0
  264. package/dist/spreadsheet-grid.d.cts +48 -0
  265. package/dist/spreadsheet-grid.d.ts +48 -0
  266. package/dist/spreadsheet-grid.js +8 -0
  267. package/dist/spreadsheet-grid.js.map +1 -0
  268. package/dist/state-panel.cjs +86 -0
  269. package/dist/state-panel.cjs.map +1 -0
  270. package/dist/state-panel.d.cts +23 -0
  271. package/dist/state-panel.d.ts +23 -0
  272. package/dist/state-panel.js +9 -0
  273. package/dist/state-panel.js.map +1 -0
  274. package/dist/switch.cjs +87 -0
  275. package/dist/switch.cjs.map +1 -0
  276. package/dist/switch.d.cts +13 -0
  277. package/dist/switch.d.ts +13 -0
  278. package/dist/switch.js +9 -0
  279. package/dist/switch.js.map +1 -0
  280. package/dist/table.cjs +139 -0
  281. package/dist/table.cjs.map +1 -0
  282. package/dist/table.d.cts +16 -0
  283. package/dist/table.d.ts +16 -0
  284. package/dist/table.js +22 -0
  285. package/dist/table.js.map +1 -0
  286. package/dist/tabs.cjs +168 -0
  287. package/dist/tabs.cjs.map +1 -0
  288. package/dist/tabs.d.cts +59 -0
  289. package/dist/tabs.d.ts +59 -0
  290. package/dist/tabs.js +11 -0
  291. package/dist/tabs.js.map +1 -0
  292. package/dist/textarea.cjs +66 -0
  293. package/dist/textarea.cjs.map +1 -0
  294. package/dist/textarea.d.cts +18 -0
  295. package/dist/textarea.d.ts +18 -0
  296. package/dist/textarea.js +10 -0
  297. package/dist/textarea.js.map +1 -0
  298. package/dist/utils.cjs +35 -0
  299. package/dist/utils.cjs.map +1 -0
  300. package/dist/utils.d.cts +5 -0
  301. package/dist/utils.d.ts +5 -0
  302. package/dist/utils.js +7 -0
  303. package/dist/utils.js.map +1 -0
  304. package/package.json +390 -13
  305. package/src/alert.tsx +0 -74
  306. package/src/autocomplete.tsx +0 -666
  307. package/src/badge.tsx +0 -60
  308. package/src/breadcrumb.tsx +0 -71
  309. package/src/button.tsx +0 -113
  310. package/src/card.tsx +0 -79
  311. package/src/clipboard.ts +0 -144
  312. package/src/collapsible.tsx +0 -63
  313. package/src/confirm-dialog.tsx +0 -84
  314. package/src/copy-button.tsx +0 -58
  315. package/src/data-table.tsx +0 -190
  316. package/src/date-range-picker/dateRange.ts +0 -142
  317. package/src/date-range-picker/index.tsx +0 -366
  318. package/src/dialogLayer.ts +0 -142
  319. package/src/feature-access-gate.tsx +0 -38
  320. package/src/field.tsx +0 -56
  321. package/src/image-uploader.tsx +0 -181
  322. package/src/index.ts +0 -37
  323. package/src/input.tsx +0 -16
  324. package/src/label.tsx +0 -23
  325. package/src/modal.tsx +0 -117
  326. package/src/page-header.tsx +0 -75
  327. package/src/pagination.tsx +0 -116
  328. package/src/panel-brand-mark.tsx +0 -32
  329. package/src/panel-sign-in.tsx +0 -134
  330. package/src/segmented-control.tsx +0 -155
  331. package/src/select.tsx +0 -105
  332. package/src/separator.tsx +0 -32
  333. package/src/side-drawer.tsx +0 -87
  334. package/src/spreadsheet-grid.tsx +0 -264
  335. package/src/state-panel.tsx +0 -46
  336. package/src/switch.tsx +0 -47
  337. package/src/table.tsx +0 -110
  338. package/src/tabs.tsx +0 -201
  339. package/src/textarea.tsx +0 -32
  340. package/src/utils.ts +0 -6
@@ -1,264 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { type CellRange, type CellRef } from "./clipboard";
6
-
7
- /**
8
- * A column knows three things: its heading, the text it contributes to a copy
9
- * or CSV export, and optionally how to draw itself.
10
- *
11
- * Keeping `value` and `render` together is what stops an exported sheet from
12
- * drifting from the visible grid.
13
- */
14
- export type GridColumn<Row> = {
15
- key: string;
16
- title: string;
17
- /** Text used for clipboard and CSV. Never JSX. */
18
- value: (row: Row) => string;
19
- /** Defaults to `value`. Use for links, tiles, truncation. */
20
- render?: (row: Row) => React.ReactNode;
21
- /** Extra classes for the <td>. */
22
- className?: string;
23
- };
24
-
25
- export interface SpreadsheetGridProps<Row> {
26
- columns: GridColumn<Row>[];
27
- rows: Row[];
28
- /** Stable React key per row. Defaults to the row index. */
29
- rowKey?: (row: Row, index: number) => string;
30
- loading?: boolean;
31
- emptyMessage?: string;
32
- loadingMessage?: string;
33
- ariaLabel?: string;
34
- /** Lifted so a toolbar can copy and describe the current selection. */
35
- range: CellRange | null;
36
- onRangeChange: (selection: {
37
- anchor: CellRef | null;
38
- focus: CellRef | null;
39
- }) => void;
40
- onCopy: () => void;
41
- }
42
-
43
- function isInRange(range: CellRange | null, r: number, c: number): boolean {
44
- if (!range) return false;
45
- return r >= range.r1 && r <= range.r2 && c >= range.c1 && c <= range.c2;
46
- }
47
-
48
- /**
49
- * Spreadsheet-like grid: click-drag a range, shift+click to extend, arrows to
50
- * move, click a heading to take the column, click the row number to take the
51
- * row, Cmd/Ctrl+A for everything, Escape to clear, Cmd/Ctrl+C to copy.
52
- *
53
- * Ported from video-shorts-panel's ExcelTable, which several panels share for
54
- * exactly this reason — a table people paste into a sheet.
55
- */
56
- export function SpreadsheetGrid<Row>({
57
- columns,
58
- rows,
59
- rowKey,
60
- loading,
61
- emptyMessage = "Nothing to show.",
62
- loadingMessage = "Loading…",
63
- ariaLabel = "Data grid",
64
- range,
65
- onRangeChange,
66
- onCopy,
67
- }: SpreadsheetGridProps<Row>) {
68
- const gridRef = React.useRef<HTMLDivElement>(null);
69
- const anchorRef = React.useRef<CellRef | null>(null);
70
- const focusRef = React.useRef<CellRef | null>(null);
71
- const [dragging, setDragging] = React.useState(false);
72
-
73
- const setSelection = React.useCallback(
74
- (anchor: CellRef | null, focus: CellRef | null) => {
75
- anchorRef.current = anchor;
76
- focusRef.current = focus;
77
- onRangeChange({ anchor, focus });
78
- },
79
- [onRangeChange],
80
- );
81
-
82
- const selectCell = React.useCallback(
83
- (r: number, c: number, extend?: boolean) => {
84
- if (extend && anchorRef.current)
85
- setSelection(anchorRef.current, { r, c });
86
- else setSelection({ r, c }, { r, c });
87
- },
88
- [setSelection],
89
- );
90
-
91
- const selectColumn = React.useCallback(
92
- (c: number, extend?: boolean) => {
93
- const lastRow = Math.max(rows.length - 1, 0);
94
- const anchorCol = extend && anchorRef.current ? anchorRef.current.c : c;
95
- setSelection({ r: 0, c: anchorCol }, { r: lastRow, c });
96
- },
97
- [rows.length, setSelection],
98
- );
99
-
100
- const selectRow = React.useCallback(
101
- (r: number, extend?: boolean) => {
102
- const lastCol = columns.length - 1;
103
- const anchorRow = extend && anchorRef.current ? anchorRef.current.r : r;
104
- setSelection({ r: anchorRow, c: 0 }, { r, c: lastCol });
105
- },
106
- [columns.length, setSelection],
107
- );
108
-
109
- const selectAll = React.useCallback(() => {
110
- if (!rows.length || !columns.length) return;
111
- setSelection({ r: 0, c: 0 }, { r: rows.length - 1, c: columns.length - 1 });
112
- }, [rows.length, columns.length, setSelection]);
113
-
114
- const scrollCellIntoView = React.useCallback((r: number, c: number) => {
115
- const cell = gridRef.current?.querySelector(`[data-cell="${r}-${c}"]`);
116
- cell?.scrollIntoView({ block: "nearest", inline: "nearest" });
117
- }, []);
118
-
119
- const moveFocus = React.useCallback(
120
- (dr: number, dc: number, extend?: boolean) => {
121
- if (!rows.length || !columns.length) return;
122
- const base = focusRef.current || { r: 0, c: 0 };
123
- const r = Math.min(Math.max(base.r + dr, 0), rows.length - 1);
124
- const c = Math.min(Math.max(base.c + dc, 0), columns.length - 1);
125
- selectCell(r, c, extend);
126
- scrollCellIntoView(r, c);
127
- },
128
- [rows.length, columns.length, selectCell, scrollCellIntoView],
129
- );
130
-
131
- // Dragging must end even if the pointer is released outside the grid.
132
- React.useEffect(() => {
133
- if (!dragging) return;
134
- const stop = () => setDragging(false);
135
- window.addEventListener("mouseup", stop);
136
- return () => window.removeEventListener("mouseup", stop);
137
- }, [dragging]);
138
-
139
- const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
140
- const meta = event.metaKey || event.ctrlKey;
141
- if (meta && event.key.toLowerCase() === "c") {
142
- event.preventDefault();
143
- onCopy();
144
- return;
145
- }
146
- if (meta && event.key.toLowerCase() === "a") {
147
- event.preventDefault();
148
- selectAll();
149
- return;
150
- }
151
- switch (event.key) {
152
- case "ArrowUp":
153
- event.preventDefault();
154
- moveFocus(-1, 0, event.shiftKey);
155
- break;
156
- case "ArrowDown":
157
- event.preventDefault();
158
- moveFocus(1, 0, event.shiftKey);
159
- break;
160
- case "ArrowLeft":
161
- event.preventDefault();
162
- moveFocus(0, -1, event.shiftKey);
163
- break;
164
- case "ArrowRight":
165
- case "Tab":
166
- event.preventDefault();
167
- moveFocus(0, 1, event.shiftKey && event.key !== "Tab");
168
- break;
169
- case "Escape":
170
- setSelection(null, null);
171
- break;
172
- default:
173
- break;
174
- }
175
- };
176
-
177
- return (
178
- <div
179
- ref={gridRef}
180
- tabIndex={0}
181
- role="grid"
182
- aria-label={ariaLabel}
183
- onKeyDown={handleKeyDown}
184
- className="border-border bg-background/80 overflow-x-auto rounded-lg border outline-none"
185
- >
186
- <table className="w-full text-sm whitespace-nowrap select-none">
187
- <thead className="text-muted-foreground border-border border-b text-left">
188
- <tr>
189
- <th
190
- scope="col"
191
- className="w-10 cursor-pointer px-2 py-2 text-center font-medium"
192
- title="Select everything"
193
- onMouseDown={() => selectAll()}
194
- >
195
- #
196
- </th>
197
- {columns.map((column, c) => (
198
- <th
199
- key={column.key}
200
- scope="col"
201
- className="cursor-pointer px-3 py-2 font-medium"
202
- title={`Select the ${column.title} column`}
203
- onMouseDown={(event) => selectColumn(c, event.shiftKey)}
204
- >
205
- {column.title}
206
- </th>
207
- ))}
208
- </tr>
209
- </thead>
210
- <tbody>
211
- {rows.length === 0 ? (
212
- <tr>
213
- <td
214
- colSpan={columns.length + 1}
215
- className="text-muted-foreground px-3 py-6 text-center"
216
- >
217
- {loading ? loadingMessage : emptyMessage}
218
- </td>
219
- </tr>
220
- ) : (
221
- rows.map((row, r) => (
222
- <tr
223
- key={rowKey ? rowKey(row, r) : String(r)}
224
- className="border-border border-b"
225
- >
226
- <td
227
- className="text-muted-foreground w-10 cursor-pointer px-2 py-2 text-center text-xs"
228
- title="Select this row"
229
- onMouseDown={(event) => selectRow(r, event.shiftKey)}
230
- >
231
- {r + 1}
232
- </td>
233
- {columns.map((column, c) => (
234
- <td
235
- key={column.key}
236
- data-cell={`${r}-${c}`}
237
- onMouseDown={(event) => {
238
- selectCell(r, c, event.shiftKey);
239
- setDragging(true);
240
- gridRef.current?.focus();
241
- }}
242
- onMouseEnter={() => {
243
- if (dragging) setSelection(anchorRef.current, { r, c });
244
- }}
245
- className={`px-3 py-2 ${column.className || ""} ${
246
- isInRange(range, r, c) ? "bg-primary/15" : ""
247
- }`}
248
- title={
249
- column.className?.includes("truncate")
250
- ? column.value(row)
251
- : undefined
252
- }
253
- >
254
- {column.render ? column.render(row) : column.value(row)}
255
- </td>
256
- ))}
257
- </tr>
258
- ))
259
- )}
260
- </tbody>
261
- </table>
262
- </div>
263
- );
264
- }
@@ -1,46 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { Spinner } from "./spinner";
4
- import { cn } from "./utils";
5
-
6
- /**
7
- * The bordered block a list shows instead of rows: loading, or nothing found.
8
- *
9
- * Fourteen copies of the same
10
- * `border-border bg-background/80 … rounded-lg border px-4 py-6 text-center`
11
- * across the panels, each with its own wording — "Loading users…", "Loading
12
- * taxonomy…", "Loading showcases…". The wording is worth keeping (it says what
13
- * is loading), the markup is not.
14
- *
15
- * `loading` adds two things the hand-rolled versions did not have: a spinner,
16
- * so a slow panel does not look like an empty one, and `role="status"` with
17
- * `aria-live="polite"`, so a screen reader is told the list arrived instead of
18
- * silently finding rows on the next tab press.
19
- */
20
- export interface StatePanelProps extends React.HTMLAttributes<HTMLDivElement> {
21
- /** Shows a spinner and announces politely. */
22
- loading?: boolean;
23
- }
24
-
25
- export function StatePanel({
26
- loading,
27
- className,
28
- children,
29
- ...props
30
- }: StatePanelProps) {
31
- return (
32
- <div
33
- role={loading ? "status" : undefined}
34
- aria-live={loading ? "polite" : undefined}
35
- className={cn(
36
- "border-border bg-background/80 text-muted-foreground rounded-lg border px-4 py-6 text-center text-sm",
37
- loading && "flex items-center justify-center gap-2",
38
- className,
39
- )}
40
- {...props}
41
- >
42
- {loading ? <Spinner size="md" /> : null}
43
- {children}
44
- </div>
45
- );
46
- }
package/src/switch.tsx DELETED
@@ -1,47 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { cn } from "./utils";
6
-
7
- export type SwitchProps = Omit<
8
- React.ButtonHTMLAttributes<HTMLButtonElement>,
9
- "onChange"
10
- > & {
11
- checked: boolean;
12
- onCheckedChange: (checked: boolean) => void;
13
- };
14
-
15
- /** Compact accessible toggle — use instead of full-width checkbox inputs. */
16
- export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
17
- ({ checked, onCheckedChange, className, disabled, id, ...props }, ref) => (
18
- <button
19
- ref={ref}
20
- id={id}
21
- type="button"
22
- role="switch"
23
- aria-checked={checked}
24
- disabled={disabled}
25
- onClick={() => onCheckedChange(!checked)}
26
- className={cn(
27
- // 24×44px track meets DESIGN.md 24px minimum target size.
28
- "relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border transition-colors",
29
- "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
30
- "disabled:cursor-not-allowed disabled:opacity-50",
31
- checked ? "border-primary bg-primary" : "border-border bg-muted",
32
- className,
33
- )}
34
- {...props}
35
- >
36
- <span
37
- aria-hidden
38
- className={cn(
39
- "bg-background pointer-events-none block size-5 rounded-full shadow-sm transition-transform",
40
- checked ? "translate-x-5" : "translate-x-0.5",
41
- )}
42
- />
43
- </button>
44
- ),
45
- );
46
-
47
- Switch.displayName = "Switch";
package/src/table.tsx DELETED
@@ -1,110 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "./utils";
4
-
5
- /**
6
- * Low-level table primitives. Prefer `DataTable` for list panels; use these
7
- * when the row markup is custom (nested actions, expandable rows, etc.).
8
- */
9
- export const Table = React.forwardRef<
10
- HTMLTableElement,
11
- React.HTMLAttributes<HTMLTableElement>
12
- >(({ className, ...props }, ref) => (
13
- <div className="relative w-full overflow-auto">
14
- <table
15
- ref={ref}
16
- className={cn("w-full caption-bottom text-sm", className)}
17
- {...props}
18
- />
19
- </div>
20
- ));
21
- Table.displayName = "Table";
22
-
23
- export const TableHeader = React.forwardRef<
24
- HTMLTableSectionElement,
25
- React.HTMLAttributes<HTMLTableSectionElement>
26
- >(({ className, ...props }, ref) => (
27
- <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
28
- ));
29
- TableHeader.displayName = "TableHeader";
30
-
31
- export const TableBody = React.forwardRef<
32
- HTMLTableSectionElement,
33
- React.HTMLAttributes<HTMLTableSectionElement>
34
- >(({ className, ...props }, ref) => (
35
- <tbody
36
- ref={ref}
37
- className={cn("[&_tr:last-child]:border-0", className)}
38
- {...props}
39
- />
40
- ));
41
- TableBody.displayName = "TableBody";
42
-
43
- export const TableFooter = React.forwardRef<
44
- HTMLTableSectionElement,
45
- React.HTMLAttributes<HTMLTableSectionElement>
46
- >(({ className, ...props }, ref) => (
47
- <tfoot
48
- ref={ref}
49
- className={cn(
50
- "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
51
- className,
52
- )}
53
- {...props}
54
- />
55
- ));
56
- TableFooter.displayName = "TableFooter";
57
-
58
- export const TableRow = React.forwardRef<
59
- HTMLTableRowElement,
60
- React.HTMLAttributes<HTMLTableRowElement>
61
- >(({ className, ...props }, ref) => (
62
- <tr
63
- ref={ref}
64
- className={cn(
65
- "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
66
- className,
67
- )}
68
- {...props}
69
- />
70
- ));
71
- TableRow.displayName = "TableRow";
72
-
73
- export const TableHead = React.forwardRef<
74
- HTMLTableCellElement,
75
- React.ThHTMLAttributes<HTMLTableCellElement>
76
- >(({ className, ...props }, ref) => (
77
- <th
78
- ref={ref}
79
- className={cn(
80
- "text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0",
81
- className,
82
- )}
83
- {...props}
84
- />
85
- ));
86
- TableHead.displayName = "TableHead";
87
-
88
- export const TableCell = React.forwardRef<
89
- HTMLTableCellElement,
90
- React.TdHTMLAttributes<HTMLTableCellElement>
91
- >(({ className, ...props }, ref) => (
92
- <td
93
- ref={ref}
94
- className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
95
- {...props}
96
- />
97
- ));
98
- TableCell.displayName = "TableCell";
99
-
100
- export const TableCaption = React.forwardRef<
101
- HTMLTableCaptionElement,
102
- React.HTMLAttributes<HTMLTableCaptionElement>
103
- >(({ className, ...props }, ref) => (
104
- <caption
105
- ref={ref}
106
- className={cn("text-muted-foreground mt-4 text-sm", className)}
107
- {...props}
108
- />
109
- ));
110
- TableCaption.displayName = "TableCaption";
package/src/tabs.tsx DELETED
@@ -1,201 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- import { cn } from "./utils";
6
-
7
- /**
8
- * The tab strip five Managers were each drawing by hand, with the same
9
- * `rounded-t-md px-3 py-1.5 text-sm font-medium` and their own idea of what an
10
- * active tab looks like.
11
- *
12
- * It is a controlled component with no state of its own: every caller already
13
- * held the active key, usually because a URL or a query depends on it.
14
- *
15
- * Roles are the reason to use this rather than styling buttons again. A hand
16
- * -rolled strip is a row of buttons to a screen reader, which says nothing
17
- * about how many tabs there are or which is selected; `tablist` / `tab` /
18
- * `aria-selected` do. Arrow-key navigation comes with that expectation, so it
19
- * is implemented here too.
20
- *
21
- * Optional `panel` on a tab (or a sibling `TabPanel`) wires APG `aria-controls`
22
- * / `role="tabpanel"`. Callers that already render content from `value` can
23
- * keep doing so and pass `panelId` for association, or switch to `panel`.
24
- */
25
- export interface TabItem<Key extends string = string> {
26
- key: Key;
27
- label: React.ReactNode;
28
- /** Rendered after the label — a count, usually. */
29
- badge?: React.ReactNode;
30
- disabled?: boolean;
31
- /**
32
- * Optional panel body. When any tab provides `panel`, panels render under
33
- * the strip with `role="tabpanel"`.
34
- */
35
- panel?: React.ReactNode;
36
- /**
37
- * External panel element id for `aria-controls` when panels live outside
38
- * this component.
39
- */
40
- panelId?: string;
41
- }
42
-
43
- export interface TabsProps<Key extends string = string> {
44
- tabs: ReadonlyArray<TabItem<Key>>;
45
- value: Key;
46
- onChange: (key: Key) => void;
47
- /** Names the strip for screen readers. */
48
- ariaLabel?: string;
49
- className?: string;
50
- }
51
-
52
- export interface TabPanelProps {
53
- /** Must match the controlling tab's generated or explicit id. */
54
- id: string;
55
- /** Id of the tab that labels this panel. */
56
- labelledBy: string;
57
- active: boolean;
58
- children: React.ReactNode;
59
- className?: string;
60
- }
61
-
62
- /** Companion panel for callers that keep content outside `Tabs`. */
63
- export function TabPanel({
64
- id,
65
- labelledBy,
66
- active,
67
- children,
68
- className,
69
- }: TabPanelProps) {
70
- return (
71
- <div
72
- role="tabpanel"
73
- id={id}
74
- aria-labelledby={labelledBy}
75
- hidden={!active}
76
- className={className}
77
- >
78
- {active ? children : null}
79
- </div>
80
- );
81
- }
82
-
83
- export function Tabs<Key extends string = string>({
84
- tabs,
85
- value,
86
- onChange,
87
- ariaLabel,
88
- className,
89
- }: TabsProps<Key>) {
90
- const refs = React.useRef(new Map<Key, HTMLButtonElement | null>());
91
- const reactId = React.useId();
92
- const hasInlinePanels = tabs.some((tab) => tab.panel != null);
93
-
94
- function tabDomId(key: Key) {
95
- return `${reactId}-tab-${key}`;
96
- }
97
-
98
- function panelDomId(tab: TabItem<Key>) {
99
- return tab.panelId ?? `${reactId}-panel-${tab.key}`;
100
- }
101
-
102
- function focusTab(key: Key) {
103
- onChange(key);
104
- // The newly selected tab has to take focus, or the next arrow press is
105
- // still measured from the old one.
106
- refs.current.get(key)?.focus();
107
- }
108
-
109
- function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
110
- const step =
111
- event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
112
- if (step === 0) return;
113
-
114
- const selectable = tabs.filter((tab) => !tab.disabled);
115
- if (selectable.length < 2) return;
116
-
117
- const current = selectable.findIndex((tab) => tab.key === value);
118
- // Wraps, which is what a tablist is expected to do.
119
- const next =
120
- selectable[(current + step + selectable.length) % selectable.length];
121
-
122
- event.preventDefault();
123
- focusTab(next.key);
124
- }
125
-
126
- const tablist = (
127
- <div
128
- role="tablist"
129
- aria-label={ariaLabel}
130
- onKeyDown={onKeyDown}
131
- className={cn(
132
- "border-border flex flex-wrap gap-1 border-b",
133
- !hasInlinePanels ? className : undefined,
134
- )}
135
- >
136
- {tabs.map((tab) => {
137
- const active = tab.key === value;
138
- const id = tabDomId(tab.key);
139
- const controls =
140
- tab.panel != null || tab.panelId ? panelDomId(tab) : undefined;
141
- return (
142
- <button
143
- key={tab.key}
144
- id={id}
145
- ref={(node) => {
146
- refs.current.set(tab.key, node);
147
- }}
148
- type="button"
149
- role="tab"
150
- aria-selected={active}
151
- aria-controls={controls}
152
- disabled={tab.disabled}
153
- // Only the active tab is reachable by Tab; arrows move within the
154
- // strip. Without this a keyboard user pages through every tab to
155
- // get past the strip.
156
- tabIndex={active ? 0 : -1}
157
- onClick={() => onChange(tab.key)}
158
- className={cn(
159
- "cursor-pointer rounded-t-md px-3 py-1.5 text-sm font-medium transition",
160
- "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
161
- "disabled:cursor-not-allowed disabled:opacity-50",
162
- // The filled-pill active state the five hand-rolled strips
163
- // already used. Adopting it keeps this a deduplication rather
164
- // than a restyle of every panel that has tabs.
165
- active
166
- ? "bg-primary text-primary-foreground"
167
- : "text-muted-foreground hover:bg-muted",
168
- )}
169
- >
170
- {tab.label}
171
- {tab.badge != null ? (
172
- <span className="text-muted-foreground ml-1.5 text-xs">
173
- {tab.badge}
174
- </span>
175
- ) : null}
176
- </button>
177
- );
178
- })}
179
- </div>
180
- );
181
-
182
- if (!hasInlinePanels) return tablist;
183
-
184
- return (
185
- <div className={cn("space-y-3", className)}>
186
- {tablist}
187
- {tabs.map((tab) =>
188
- tab.panel != null ? (
189
- <TabPanel
190
- key={tab.key}
191
- id={panelDomId(tab)}
192
- labelledBy={tabDomId(tab.key)}
193
- active={tab.key === value}
194
- >
195
- {tab.panel}
196
- </TabPanel>
197
- ) : null,
198
- )}
199
- </div>
200
- );
201
- }
package/src/textarea.tsx DELETED
@@ -1,32 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "./utils";
4
-
5
- /**
6
- * The multi-line counterpart to `Input`.
7
- *
8
- * There was no shared textarea, so sixteen of them were styled individually
9
- * and no two agreed — different borders, different focus rings, some with no
10
- * focus ring at all, which is a keyboard user losing their place.
11
- *
12
- * Deliberately not `inputClass`: that fixes `h-9`, which is a one-line
13
- * control. Everything else is shared so a textarea and an input sitting in the
14
- * same form look related.
15
- */
16
- export const textareaClass =
17
- "border-border bg-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50";
18
-
19
- export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
20
-
21
- export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
22
- ({ className, rows = 3, ...props }, ref) => (
23
- <textarea
24
- ref={ref}
25
- rows={rows}
26
- className={cn(textareaClass, className)}
27
- {...props}
28
- />
29
- ),
30
- );
31
-
32
- Textarea.displayName = "Textarea";