@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.
- package/README.md +3 -1
- package/dist/alert.cjs +96 -0
- package/dist/alert.cjs.map +1 -0
- package/dist/alert.d.cts +29 -0
- package/dist/alert.d.ts +29 -0
- package/dist/alert.js +10 -0
- package/dist/alert.js.map +1 -0
- package/dist/autocomplete.cjs +578 -0
- package/dist/autocomplete.cjs.map +1 -0
- package/dist/autocomplete.d.cts +56 -0
- package/dist/autocomplete.d.ts +56 -0
- package/dist/autocomplete.js +11 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/badge.cjs +89 -0
- package/dist/badge.cjs.map +1 -0
- package/dist/badge.d.cts +25 -0
- package/dist/badge.d.ts +25 -0
- package/dist/badge.js +10 -0
- package/dist/badge.js.map +1 -0
- package/dist/breadcrumb.cjs +88 -0
- package/dist/breadcrumb.cjs.map +1 -0
- package/dist/breadcrumb.d.cts +14 -0
- package/dist/breadcrumb.d.ts +14 -0
- package/dist/breadcrumb.js +8 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/button.cjs +143 -0
- package/dist/button.cjs.map +1 -0
- package/dist/button.d.cts +34 -0
- package/dist/button.d.ts +34 -0
- package/dist/button.js +9 -0
- package/dist/button.js.map +1 -0
- package/dist/card.cjs +111 -0
- package/dist/card.cjs.map +1 -0
- package/dist/card.d.cts +15 -0
- package/dist/card.d.ts +15 -0
- package/dist/card.js +18 -0
- package/dist/card.js.map +1 -0
- package/dist/checkbox.cjs +66 -0
- package/dist/checkbox.cjs.map +1 -0
- package/{src/checkbox.tsx → dist/checkbox.d.cts} +5 -22
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +10 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-43ZUFY63.js +85 -0
- package/dist/chunk-43ZUFY63.js.map +1 -0
- package/dist/chunk-67ACNS42.js +29 -0
- package/dist/chunk-67ACNS42.js.map +1 -0
- package/dist/chunk-6URKHMCF.js +76 -0
- package/dist/chunk-6URKHMCF.js.map +1 -0
- package/dist/chunk-ASU6CUTZ.js +85 -0
- package/dist/chunk-ASU6CUTZ.js.map +1 -0
- package/dist/chunk-EWSBFU54.js +53 -0
- package/dist/chunk-EWSBFU54.js.map +1 -0
- package/dist/chunk-G4ZQUSAJ.js +38 -0
- package/dist/chunk-G4ZQUSAJ.js.map +1 -0
- package/dist/chunk-GMCUXEFI.js +67 -0
- package/dist/chunk-GMCUXEFI.js.map +1 -0
- package/dist/chunk-HBBOR43A.js +85 -0
- package/dist/chunk-HBBOR43A.js.map +1 -0
- package/dist/chunk-HCA5ODCD.js +11 -0
- package/dist/chunk-HCA5ODCD.js.map +1 -0
- package/dist/chunk-HYRJYUPI.js +26 -0
- package/dist/chunk-HYRJYUPI.js.map +1 -0
- package/dist/chunk-IRCTGJQB.js +26 -0
- package/dist/chunk-IRCTGJQB.js.map +1 -0
- package/dist/chunk-JDE5XYPT.js +30 -0
- package/dist/chunk-JDE5XYPT.js.map +1 -0
- package/dist/chunk-JSJEEKR3.js +59 -0
- package/dist/chunk-JSJEEKR3.js.map +1 -0
- package/dist/chunk-KR3BCFP7.js +85 -0
- package/dist/chunk-KR3BCFP7.js.map +1 -0
- package/dist/chunk-MQT5RAQV.js +202 -0
- package/dist/chunk-MQT5RAQV.js.map +1 -0
- package/dist/chunk-MUPW2TFJ.js +102 -0
- package/dist/chunk-MUPW2TFJ.js.map +1 -0
- package/dist/chunk-PM2VE7DJ.js +100 -0
- package/dist/chunk-PM2VE7DJ.js.map +1 -0
- package/dist/chunk-PPYR3O3L.js +44 -0
- package/dist/chunk-PPYR3O3L.js.map +1 -0
- package/dist/chunk-PUTC52SI.js +98 -0
- package/dist/chunk-PUTC52SI.js.map +1 -0
- package/dist/chunk-PVK4WQJZ.js +513 -0
- package/dist/chunk-PVK4WQJZ.js.map +1 -0
- package/dist/chunk-PWAKTEXF.js +18 -0
- package/dist/chunk-PWAKTEXF.js.map +1 -0
- package/dist/chunk-QTFWORA7.js +26 -0
- package/dist/chunk-QTFWORA7.js.map +1 -0
- package/dist/chunk-RUPL2H6V.js +49 -0
- package/dist/chunk-RUPL2H6V.js.map +1 -0
- package/dist/chunk-S7PVZAPL.js +28 -0
- package/dist/chunk-S7PVZAPL.js.map +1 -0
- package/dist/chunk-SQDV2AKJ.js +49 -0
- package/dist/chunk-SQDV2AKJ.js.map +1 -0
- package/dist/chunk-T2KXVH3O.js +297 -0
- package/dist/chunk-T2KXVH3O.js.map +1 -0
- package/dist/chunk-T4WCCHJQ.js +29 -0
- package/dist/chunk-T4WCCHJQ.js.map +1 -0
- package/dist/chunk-UC3ZIQPE.js +93 -0
- package/dist/chunk-UC3ZIQPE.js.map +1 -0
- package/dist/chunk-UE7JY7VR.js +28 -0
- package/dist/chunk-UE7JY7VR.js.map +1 -0
- package/dist/chunk-WVNRFO5F.js +70 -0
- package/dist/chunk-WVNRFO5F.js.map +1 -0
- package/dist/chunk-Y5WCT6XN.js +93 -0
- package/dist/chunk-Y5WCT6XN.js.map +1 -0
- package/dist/chunk-YDICVETX.js +151 -0
- package/dist/chunk-YDICVETX.js.map +1 -0
- package/dist/chunk-YSU4VWRD.js +90 -0
- package/dist/chunk-YSU4VWRD.js.map +1 -0
- package/dist/chunk-ZFSQVMLJ.js +126 -0
- package/dist/chunk-ZFSQVMLJ.js.map +1 -0
- package/dist/chunk-ZHATD73Y.js +46 -0
- package/dist/chunk-ZHATD73Y.js.map +1 -0
- package/dist/chunk-ZLAA4DGG.js +111 -0
- package/dist/chunk-ZLAA4DGG.js.map +1 -0
- package/dist/chunk-ZP252OJZ.js +56 -0
- package/dist/chunk-ZP252OJZ.js.map +1 -0
- package/dist/clipboard.cjs +125 -0
- package/dist/clipboard.cjs.map +1 -0
- package/dist/clipboard.d.cts +52 -0
- package/dist/clipboard.d.ts +52 -0
- package/dist/clipboard.js +23 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/collapsible.cjs +94 -0
- package/dist/collapsible.cjs.map +1 -0
- package/dist/collapsible.d.cts +21 -0
- package/dist/collapsible.d.ts +21 -0
- package/dist/collapsible.js +9 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/confirm-dialog.cjs +369 -0
- package/dist/confirm-dialog.cjs.map +1 -0
- package/dist/confirm-dialog.d.cts +16 -0
- package/dist/confirm-dialog.d.ts +16 -0
- package/dist/confirm-dialog.js +13 -0
- package/dist/confirm-dialog.js.map +1 -0
- package/dist/copy-button.cjs +102 -0
- package/dist/copy-button.cjs.map +1 -0
- package/dist/copy-button.d.cts +18 -0
- package/dist/copy-button.d.ts +18 -0
- package/dist/copy-button.js +10 -0
- package/dist/copy-button.js.map +1 -0
- package/dist/data-table.cjs +140 -0
- package/dist/data-table.cjs.map +1 -0
- package/dist/data-table.d.cts +81 -0
- package/dist/data-table.d.ts +81 -0
- package/dist/data-table.js +8 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-range-picker/dateRange.cjs +138 -0
- package/dist/date-range-picker/dateRange.cjs.map +1 -0
- package/dist/date-range-picker/dateRange.d.cts +57 -0
- package/dist/date-range-picker/dateRange.d.ts +57 -0
- package/dist/date-range-picker/dateRange.js +31 -0
- package/dist/date-range-picker/dateRange.js.map +1 -0
- package/dist/date-range-picker/index.cjs +375 -0
- package/dist/date-range-picker/index.cjs.map +1 -0
- package/dist/date-range-picker/index.d.cts +18 -0
- package/dist/date-range-picker/index.d.ts +18 -0
- package/dist/date-range-picker/index.js +10 -0
- package/dist/date-range-picker/index.js.map +1 -0
- package/dist/dialogLayer.cjs +121 -0
- package/dist/dialogLayer.cjs.map +1 -0
- package/dist/dialogLayer.d.cts +11 -0
- package/dist/dialogLayer.d.ts +11 -0
- package/dist/dialogLayer.js +8 -0
- package/dist/dialogLayer.js.map +1 -0
- package/dist/feature-access-gate.cjs +160 -0
- package/dist/feature-access-gate.cjs.map +1 -0
- package/dist/feature-access-gate.d.cts +14 -0
- package/dist/feature-access-gate.d.ts +14 -0
- package/dist/feature-access-gate.js +11 -0
- package/dist/feature-access-gate.js.map +1 -0
- package/dist/field.cjs +58 -0
- package/dist/field.cjs.map +1 -0
- package/dist/field.d.cts +23 -0
- package/dist/field.d.ts +23 -0
- package/dist/field.js +8 -0
- package/dist/field.js.map +1 -0
- package/dist/image-uploader.cjs +293 -0
- package/dist/image-uploader.cjs.map +1 -0
- package/dist/image-uploader.d.cts +34 -0
- package/dist/image-uploader.d.ts +34 -0
- package/dist/image-uploader.js +12 -0
- package/dist/image-uploader.js.map +1 -0
- package/dist/index.cjs +3012 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/input.cjs +58 -0
- package/dist/input.cjs.map +1 -0
- package/dist/input.d.cts +7 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.js +10 -0
- package/dist/input.js.map +1 -0
- package/dist/label.cjs +67 -0
- package/dist/label.cjs.map +1 -0
- package/dist/label.d.cts +7 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.js +9 -0
- package/dist/label.js.map +1 -0
- package/dist/modal.cjs +212 -0
- package/dist/modal.cjs.map +1 -0
- package/dist/modal.d.cts +39 -0
- package/dist/modal.d.ts +39 -0
- package/dist/modal.js +12 -0
- package/dist/modal.js.map +1 -0
- package/dist/page-header.cjs +78 -0
- package/dist/page-header.cjs.map +1 -0
- package/dist/page-header.d.cts +35 -0
- package/dist/page-header.d.ts +35 -0
- package/dist/page-header.js +8 -0
- package/dist/page-header.js.map +1 -0
- package/dist/pagination.cjs +237 -0
- package/dist/pagination.cjs.map +1 -0
- package/dist/pagination.d.cts +15 -0
- package/dist/pagination.d.ts +15 -0
- package/dist/pagination.js +11 -0
- package/dist/pagination.js.map +1 -0
- package/dist/panel-brand-mark.cjs +59 -0
- package/dist/panel-brand-mark.cjs.map +1 -0
- package/dist/panel-brand-mark.d.cts +13 -0
- package/dist/panel-brand-mark.d.ts +13 -0
- package/dist/panel-brand-mark.js +8 -0
- package/dist/panel-brand-mark.js.map +1 -0
- package/dist/panel-sign-in.cjs +256 -0
- package/dist/panel-sign-in.cjs.map +1 -0
- package/dist/panel-sign-in.d.cts +31 -0
- package/dist/panel-sign-in.d.ts +31 -0
- package/dist/panel-sign-in.js +14 -0
- package/dist/panel-sign-in.js.map +1 -0
- package/dist/segmented-control.cjs +126 -0
- package/dist/segmented-control.cjs.map +1 -0
- package/dist/segmented-control.d.cts +60 -0
- package/dist/segmented-control.d.ts +60 -0
- package/dist/segmented-control.js +9 -0
- package/dist/segmented-control.js.map +1 -0
- package/dist/select.cjs +648 -0
- package/dist/select.cjs.map +1 -0
- package/dist/select.d.cts +30 -0
- package/dist/select.d.ts +30 -0
- package/dist/select.js +12 -0
- package/dist/select.js.map +1 -0
- package/dist/separator.cjs +70 -0
- package/dist/separator.cjs.map +1 -0
- package/dist/separator.d.cts +13 -0
- package/dist/separator.d.ts +13 -0
- package/dist/separator.js +9 -0
- package/dist/separator.js.map +1 -0
- package/dist/side-drawer.cjs +321 -0
- package/dist/side-drawer.cjs.map +1 -0
- package/dist/side-drawer.d.cts +21 -0
- package/dist/side-drawer.d.ts +21 -0
- package/dist/side-drawer.js +13 -0
- package/dist/side-drawer.js.map +1 -0
- package/dist/spinner.cjs +57 -0
- package/dist/spinner.cjs.map +1 -0
- package/{src/spinner.tsx → dist/spinner.d.cts} +11 -22
- package/dist/spinner.d.ts +27 -0
- package/dist/spinner.js +8 -0
- package/dist/spinner.js.map +1 -0
- package/dist/spreadsheet-grid.cjs +238 -0
- package/dist/spreadsheet-grid.cjs.map +1 -0
- package/dist/spreadsheet-grid.d.cts +48 -0
- package/dist/spreadsheet-grid.d.ts +48 -0
- package/dist/spreadsheet-grid.js +8 -0
- package/dist/spreadsheet-grid.js.map +1 -0
- package/dist/state-panel.cjs +86 -0
- package/dist/state-panel.cjs.map +1 -0
- package/dist/state-panel.d.cts +23 -0
- package/dist/state-panel.d.ts +23 -0
- package/dist/state-panel.js +9 -0
- package/dist/state-panel.js.map +1 -0
- package/dist/switch.cjs +87 -0
- package/dist/switch.cjs.map +1 -0
- package/dist/switch.d.cts +13 -0
- package/dist/switch.d.ts +13 -0
- package/dist/switch.js +9 -0
- package/dist/switch.js.map +1 -0
- package/dist/table.cjs +139 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +16 -0
- package/dist/table.d.ts +16 -0
- package/dist/table.js +22 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.cjs +168 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.d.cts +59 -0
- package/dist/tabs.d.ts +59 -0
- package/dist/tabs.js +11 -0
- package/dist/tabs.js.map +1 -0
- package/dist/textarea.cjs +66 -0
- package/dist/textarea.cjs.map +1 -0
- package/dist/textarea.d.cts +18 -0
- package/dist/textarea.d.ts +18 -0
- package/dist/textarea.js +10 -0
- package/dist/textarea.js.map +1 -0
- package/dist/utils.cjs +35 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +5 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/package.json +390 -13
- package/src/alert.tsx +0 -74
- package/src/autocomplete.tsx +0 -666
- package/src/badge.tsx +0 -60
- package/src/breadcrumb.tsx +0 -71
- package/src/button.tsx +0 -113
- package/src/card.tsx +0 -79
- package/src/clipboard.ts +0 -144
- package/src/collapsible.tsx +0 -63
- package/src/confirm-dialog.tsx +0 -84
- package/src/copy-button.tsx +0 -58
- package/src/data-table.tsx +0 -190
- package/src/date-range-picker/dateRange.ts +0 -142
- package/src/date-range-picker/index.tsx +0 -366
- package/src/dialogLayer.ts +0 -142
- package/src/feature-access-gate.tsx +0 -38
- package/src/field.tsx +0 -56
- package/src/image-uploader.tsx +0 -181
- package/src/index.ts +0 -37
- package/src/input.tsx +0 -16
- package/src/label.tsx +0 -23
- package/src/modal.tsx +0 -117
- package/src/page-header.tsx +0 -75
- package/src/pagination.tsx +0 -116
- package/src/panel-brand-mark.tsx +0 -32
- package/src/panel-sign-in.tsx +0 -134
- package/src/segmented-control.tsx +0 -155
- package/src/select.tsx +0 -105
- package/src/separator.tsx +0 -32
- package/src/side-drawer.tsx +0 -87
- package/src/spreadsheet-grid.tsx +0 -264
- package/src/state-panel.tsx +0 -46
- package/src/switch.tsx +0 -47
- package/src/table.tsx +0 -110
- package/src/tabs.tsx +0 -201
- package/src/textarea.tsx +0 -32
- package/src/utils.ts +0 -6
package/src/spreadsheet-grid.tsx
DELETED
|
@@ -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
|
-
}
|
package/src/state-panel.tsx
DELETED
|
@@ -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";
|