@inshorts/glint-ui 0.5.3 → 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/LICENSE +13 -0
- package/README.md +3 -9
- 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 +395 -19
- 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 -46
- 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/data-table.tsx
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "./utils";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The list table every feature panel was hand-rolling.
|
|
7
|
-
*
|
|
8
|
-
* Seventeen files repeated the same wrapper string
|
|
9
|
-
* (`border-border bg-background/80 overflow-x-auto rounded-lg border`) and the
|
|
10
|
-
* header had already drifted into two incompatible looks, so Campaigns and
|
|
11
|
-
* Targets did not resemble each other. Both are kept as `variant` rather than
|
|
12
|
-
* unified here: collapsing them is a visual change to ten panels and belongs in
|
|
13
|
-
* its own reviewable diff, not smuggled in with a refactor.
|
|
14
|
-
*
|
|
15
|
-
* The column contract deliberately echoes `GridColumn` in
|
|
16
|
-
* `spreadsheet-grid.tsx` — `key`, `title`, a renderer, an optional class — so
|
|
17
|
-
* the repo has one shape to learn. It drops that type's `value: (row) => string`
|
|
18
|
-
* because there is no clipboard or CSV here; nothing would keep it honest, and
|
|
19
|
-
* a required field nobody reads gets filled in with lies.
|
|
20
|
-
*
|
|
21
|
-
* **Not for every table.** `GroupTable` renders parent and child rows with
|
|
22
|
-
* different column counts, and `BusinessTable`'s rows are dnd-kit sortables that
|
|
23
|
-
* own their own refs and transforms. `PublishModal`, `BulkCreateModal` and the
|
|
24
|
-
* clone queue in `MetaAdSetsPanel` are grids of inputs rather than lists of
|
|
25
|
-
* values — every cell is an editable control, and they size their header and
|
|
26
|
-
* body rows differently on purpose. All five are legitimately tables of a
|
|
27
|
-
* different kind; bending this component around them would cost more than it
|
|
28
|
-
* saves.
|
|
29
|
-
*/
|
|
30
|
-
export type TableColumn<Row> = {
|
|
31
|
-
key: string;
|
|
32
|
-
/** Node, not string: a header can carry a select-all box or a sort control. */
|
|
33
|
-
title: React.ReactNode;
|
|
34
|
-
cell: (row: Row, index: number) => React.ReactNode;
|
|
35
|
-
align?: "left" | "right" | "center";
|
|
36
|
-
/**
|
|
37
|
-
* Set on a sortable column so a screen reader announces the current sort.
|
|
38
|
-
* Only the sorted column says `ascending`/`descending`; the rest say `none`,
|
|
39
|
-
* which is what marks them as sortable at all.
|
|
40
|
-
*/
|
|
41
|
-
ariaSort?: "ascending" | "descending" | "none";
|
|
42
|
-
/** Extra classes for this column's `<td>`. */
|
|
43
|
-
className?: string;
|
|
44
|
-
/** Extra classes for this column's `<th>`. */
|
|
45
|
-
headerClassName?: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export interface DataTableProps<Row> {
|
|
49
|
-
columns: TableColumn<Row>[];
|
|
50
|
-
rows: Row[];
|
|
51
|
-
/** Stable React key per row. */
|
|
52
|
-
rowKey: (row: Row, index: number) => string;
|
|
53
|
-
/**
|
|
54
|
-
* `uppercase` — the tinted small-caps header (campaigns, showcase, filters).
|
|
55
|
-
* `plain` — the quieter left-aligned header (ads, targets, creatives).
|
|
56
|
-
*/
|
|
57
|
-
variant?: "uppercase" | "plain";
|
|
58
|
-
/**
|
|
59
|
-
* `comfortable` — the roomy default, for tables of a handful of columns.
|
|
60
|
-
* `compact` — what every table of eight columns or more had already chosen by
|
|
61
|
-
* hand, because the roomy padding pushes them into a horizontal scroll.
|
|
62
|
-
*/
|
|
63
|
-
density?: "comfortable" | "compact";
|
|
64
|
-
/** Pins the header for a table inside its own scroll container. */
|
|
65
|
-
stickyHeader?: boolean;
|
|
66
|
-
rowClassName?: (row: Row, index: number) => string | undefined;
|
|
67
|
-
/** Rendered in place of `<tbody>` rows when there are none. */
|
|
68
|
-
emptyMessage?: React.ReactNode;
|
|
69
|
-
/**
|
|
70
|
-
* Rows for a `<tfoot>` — a totals line, typically. The caller supplies the
|
|
71
|
-
* `<tr>` itself because a summary row rarely lines up with the columns above
|
|
72
|
-
* it; designer-counts spans its first two.
|
|
73
|
-
*/
|
|
74
|
-
footer?: React.ReactNode;
|
|
75
|
-
/** Names the table for screen readers when no visible caption fits. */
|
|
76
|
-
ariaLabel?: string;
|
|
77
|
-
/** Classes for the scroll wrapper. */
|
|
78
|
-
className?: string;
|
|
79
|
-
/** Classes for the `<table>`. */
|
|
80
|
-
tableClassName?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const HEADER_VARIANTS = {
|
|
84
|
-
uppercase:
|
|
85
|
-
"border-border bg-muted/40 text-muted-foreground border-b text-xs tracking-wide uppercase",
|
|
86
|
-
plain: "text-muted-foreground border-border border-b text-left",
|
|
87
|
-
} as const;
|
|
88
|
-
|
|
89
|
-
const DENSITY = {
|
|
90
|
-
comfortable: "px-4 py-3",
|
|
91
|
-
compact: "px-3 py-2",
|
|
92
|
-
} as const;
|
|
93
|
-
|
|
94
|
-
const ALIGN = {
|
|
95
|
-
left: "text-left",
|
|
96
|
-
right: "text-right",
|
|
97
|
-
center: "text-center",
|
|
98
|
-
} as const;
|
|
99
|
-
|
|
100
|
-
export function DataTable<Row>({
|
|
101
|
-
columns,
|
|
102
|
-
rows,
|
|
103
|
-
rowKey,
|
|
104
|
-
variant = "uppercase",
|
|
105
|
-
density = "comfortable",
|
|
106
|
-
stickyHeader = false,
|
|
107
|
-
rowClassName,
|
|
108
|
-
emptyMessage,
|
|
109
|
-
footer,
|
|
110
|
-
ariaLabel,
|
|
111
|
-
className,
|
|
112
|
-
tableClassName,
|
|
113
|
-
}: DataTableProps<Row>) {
|
|
114
|
-
return (
|
|
115
|
-
<div
|
|
116
|
-
className={cn(
|
|
117
|
-
"border-border bg-background/80 overflow-x-auto rounded-lg border",
|
|
118
|
-
className,
|
|
119
|
-
)}
|
|
120
|
-
>
|
|
121
|
-
<table
|
|
122
|
-
aria-label={ariaLabel}
|
|
123
|
-
className={cn("min-w-full text-left text-sm", tableClassName)}
|
|
124
|
-
>
|
|
125
|
-
<thead
|
|
126
|
-
className={cn(
|
|
127
|
-
HEADER_VARIANTS[variant],
|
|
128
|
-
stickyHeader && "bg-muted sticky top-0 z-10",
|
|
129
|
-
)}
|
|
130
|
-
>
|
|
131
|
-
<tr>
|
|
132
|
-
{columns.map((column) => (
|
|
133
|
-
<th
|
|
134
|
-
key={column.key}
|
|
135
|
-
scope="col"
|
|
136
|
-
aria-sort={column.ariaSort}
|
|
137
|
-
className={cn(
|
|
138
|
-
DENSITY[density],
|
|
139
|
-
"font-medium",
|
|
140
|
-
ALIGN[column.align ?? "left"],
|
|
141
|
-
column.headerClassName,
|
|
142
|
-
)}
|
|
143
|
-
>
|
|
144
|
-
{column.title}
|
|
145
|
-
</th>
|
|
146
|
-
))}
|
|
147
|
-
</tr>
|
|
148
|
-
</thead>
|
|
149
|
-
<tbody>
|
|
150
|
-
{rows.length === 0 && emptyMessage != null ? (
|
|
151
|
-
<tr>
|
|
152
|
-
{/* One cell across the whole width — a short row would otherwise
|
|
153
|
-
leave the message sitting under the first column. */}
|
|
154
|
-
<td
|
|
155
|
-
colSpan={columns.length}
|
|
156
|
-
className="text-muted-foreground px-4 py-6 text-center text-sm"
|
|
157
|
-
>
|
|
158
|
-
{emptyMessage}
|
|
159
|
-
</td>
|
|
160
|
-
</tr>
|
|
161
|
-
) : (
|
|
162
|
-
rows.map((row, index) => (
|
|
163
|
-
<tr
|
|
164
|
-
key={rowKey(row, index)}
|
|
165
|
-
className={cn(
|
|
166
|
-
"border-border/70 border-b last:border-0",
|
|
167
|
-
rowClassName?.(row, index),
|
|
168
|
-
)}
|
|
169
|
-
>
|
|
170
|
-
{columns.map((column) => (
|
|
171
|
-
<td
|
|
172
|
-
key={column.key}
|
|
173
|
-
className={cn(
|
|
174
|
-
DENSITY[density],
|
|
175
|
-
ALIGN[column.align ?? "left"],
|
|
176
|
-
column.className,
|
|
177
|
-
)}
|
|
178
|
-
>
|
|
179
|
-
{column.cell(row, index)}
|
|
180
|
-
</td>
|
|
181
|
-
))}
|
|
182
|
-
</tr>
|
|
183
|
-
))
|
|
184
|
-
)}
|
|
185
|
-
</tbody>
|
|
186
|
-
{footer != null ? <tfoot>{footer}</tfoot> : null}
|
|
187
|
-
</table>
|
|
188
|
-
</div>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Date helpers for the range picker, all in local time.
|
|
3
|
-
*
|
|
4
|
-
* Deliberately not `toISOString()`: that converts to UTC first, so on any
|
|
5
|
-
* negative-offset day it returns yesterday's date and the picker highlights the
|
|
6
|
-
* wrong cell. Every value here is built and read as the local calendar day the
|
|
7
|
-
* user is actually looking at.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** `YYYY-MM-DD`, the value shape the filters store. */
|
|
11
|
-
export type IsoDate = string;
|
|
12
|
-
|
|
13
|
-
const pad = (n: number) => String(n).padStart(2, "0");
|
|
14
|
-
|
|
15
|
-
export function toIsoDate(date: Date): IsoDate {
|
|
16
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Parses `YYYY-MM-DD` into a local midnight Date; null for anything else. */
|
|
20
|
-
export function fromIsoDate(value: string | null | undefined): Date | null {
|
|
21
|
-
if (!value) return null;
|
|
22
|
-
const trimmed = value.trim();
|
|
23
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
|
|
24
|
-
if (!match) return null;
|
|
25
|
-
const date = new Date(
|
|
26
|
-
Number(match[1]),
|
|
27
|
-
Number(match[2]) - 1,
|
|
28
|
-
Number(match[3]),
|
|
29
|
-
);
|
|
30
|
-
// Rejects 2026-02-31, which would otherwise roll silently into March.
|
|
31
|
-
return toIsoDate(date) === trimmed ? date : null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function startOfMonth(date: Date): Date {
|
|
35
|
-
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function addMonths(date: Date, count: number): Date {
|
|
39
|
-
return new Date(date.getFullYear(), date.getMonth() + count, 1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function addDays(date: Date, count: number): Date {
|
|
43
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + count);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function isSameDay(a: Date | null, b: Date | null): boolean {
|
|
47
|
-
if (!a || !b) return false;
|
|
48
|
-
return toIsoDate(a) === toIsoDate(b);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The six-week grid for a month, Sunday-first, padded with the neighbouring
|
|
53
|
-
* months so every row has seven cells.
|
|
54
|
-
*
|
|
55
|
-
* Always six rows, never five: a grid that changes height as you page through
|
|
56
|
-
* months makes everything below it jump.
|
|
57
|
-
*/
|
|
58
|
-
export function monthGrid(month: Date): Date[] {
|
|
59
|
-
const first = startOfMonth(month);
|
|
60
|
-
const start = addDays(first, -first.getDay());
|
|
61
|
-
return Array.from({ length: 42 }, (_, index) => addDays(start, index));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function monthLabel(month: Date, locale = "en-IN"): string {
|
|
65
|
-
return month.toLocaleDateString(locale, { month: "long", year: "numeric" });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Human form for the trigger — "22 Aug 2026". */
|
|
69
|
-
export function formatDisplay(value: string, locale = "en-IN"): string {
|
|
70
|
-
const date = fromIsoDate(value);
|
|
71
|
-
if (!date) return "";
|
|
72
|
-
return date.toLocaleDateString(locale, {
|
|
73
|
-
day: "numeric",
|
|
74
|
-
month: "short",
|
|
75
|
-
year: "numeric",
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function startOfDayMs(value: string): number {
|
|
80
|
-
const date = fromIsoDate(value);
|
|
81
|
-
return date ? date.getTime() : 0;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The end of a day as milliseconds.
|
|
86
|
-
*
|
|
87
|
-
* A range picked as whole days has to include everything created on the last
|
|
88
|
-
* day, so "to" means 23:59:59.999 rather than that morning's midnight — which
|
|
89
|
-
* would drop a whole day of cards without saying so.
|
|
90
|
-
*/
|
|
91
|
-
export function endOfDayMs(value: string): number {
|
|
92
|
-
const date = fromIsoDate(value);
|
|
93
|
-
if (!date) return 0;
|
|
94
|
-
return new Date(
|
|
95
|
-
date.getFullYear(),
|
|
96
|
-
date.getMonth(),
|
|
97
|
-
date.getDate(),
|
|
98
|
-
23,
|
|
99
|
-
59,
|
|
100
|
-
59,
|
|
101
|
-
999,
|
|
102
|
-
).getTime();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 11:59 PM on that local day, as 23:59:59.
|
|
107
|
-
*
|
|
108
|
-
* The next midnight is already the following date, and the feed includes that
|
|
109
|
-
* whole day — so the 28th through the 28th came back with the 29th. Stopping
|
|
110
|
-
* at 11:59 PM keeps the end on the day that was picked.
|
|
111
|
-
*/
|
|
112
|
-
export function endAt2359Ms(value: string): number {
|
|
113
|
-
const date = fromIsoDate(value);
|
|
114
|
-
if (!date) return 0;
|
|
115
|
-
return new Date(
|
|
116
|
-
date.getFullYear(),
|
|
117
|
-
date.getMonth(),
|
|
118
|
-
date.getDate(),
|
|
119
|
-
23,
|
|
120
|
-
59,
|
|
121
|
-
59,
|
|
122
|
-
0,
|
|
123
|
-
).getTime();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Created-date window for the feed.
|
|
128
|
-
*
|
|
129
|
-
* A blank end means the start day, so one selected date is that whole day.
|
|
130
|
-
* `end_time` is 11:59 PM on the end date.
|
|
131
|
-
*/
|
|
132
|
-
export function createdRangeMs(
|
|
133
|
-
from: string,
|
|
134
|
-
to: string,
|
|
135
|
-
): { start_time: number; end_time: number } {
|
|
136
|
-
const startDate = from.trim() || to.trim();
|
|
137
|
-
const endDate = to.trim() || from.trim();
|
|
138
|
-
return {
|
|
139
|
-
start_time: startOfDayMs(startDate),
|
|
140
|
-
end_time: endAt2359Ms(endDate),
|
|
141
|
-
};
|
|
142
|
-
}
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { CalendarDays, ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
import { cn } from "../utils";
|
|
7
|
-
import {
|
|
8
|
-
addDays,
|
|
9
|
-
addMonths,
|
|
10
|
-
formatDisplay,
|
|
11
|
-
fromIsoDate,
|
|
12
|
-
isSameDay,
|
|
13
|
-
monthGrid,
|
|
14
|
-
monthLabel,
|
|
15
|
-
startOfMonth,
|
|
16
|
-
toIsoDate,
|
|
17
|
-
} from "./dateRange";
|
|
18
|
-
|
|
19
|
-
export type DateRangeValue = { from: string; to: string };
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A two-month range calendar.
|
|
23
|
-
*
|
|
24
|
-
* Two separate date fields cost two columns and read as two unrelated
|
|
25
|
-
* questions when they are one: which window. They also let the pair be set
|
|
26
|
-
* backwards, which returns nothing and looks like a broken filter.
|
|
27
|
-
*
|
|
28
|
-
* Two months rather than one because almost every range anyone picks here
|
|
29
|
-
* crosses a month boundary — "last week of July into the first of August" is a
|
|
30
|
-
* single gesture with two months visible and a paging exercise with one.
|
|
31
|
-
*
|
|
32
|
-
* Presets are the useful half. The common ranges are the same handful every
|
|
33
|
-
* time, and picking "Last 7 days" out of a list beats counting backwards on a
|
|
34
|
-
* grid.
|
|
35
|
-
*/
|
|
36
|
-
const PRESETS: { label: string; range: () => DateRangeValue }[] = [
|
|
37
|
-
{
|
|
38
|
-
label: "Today",
|
|
39
|
-
range: () => {
|
|
40
|
-
const today = toIsoDate(new Date());
|
|
41
|
-
return { from: today, to: today };
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
label: "Yesterday",
|
|
46
|
-
range: () => {
|
|
47
|
-
const day = toIsoDate(addDays(new Date(), -1));
|
|
48
|
-
return { from: day, to: day };
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
label: "Last 7 days",
|
|
53
|
-
range: () => ({
|
|
54
|
-
from: toIsoDate(addDays(new Date(), -6)),
|
|
55
|
-
to: toIsoDate(new Date()),
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
label: "Last 30 days",
|
|
60
|
-
range: () => ({
|
|
61
|
-
from: toIsoDate(addDays(new Date(), -29)),
|
|
62
|
-
to: toIsoDate(new Date()),
|
|
63
|
-
}),
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
label: "This month",
|
|
67
|
-
range: () => ({
|
|
68
|
-
from: toIsoDate(startOfMonth(new Date())),
|
|
69
|
-
to: toIsoDate(new Date()),
|
|
70
|
-
}),
|
|
71
|
-
},
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
const WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
75
|
-
|
|
76
|
-
export interface DateRangePickerProps {
|
|
77
|
-
value: DateRangeValue;
|
|
78
|
-
onChange: (next: DateRangeValue) => void;
|
|
79
|
-
disabled?: boolean;
|
|
80
|
-
placeholder?: string;
|
|
81
|
-
className?: string;
|
|
82
|
-
/** Names the trigger for screen readers when there is no visible label. */
|
|
83
|
-
ariaLabel?: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function DateRangePicker({
|
|
87
|
-
value,
|
|
88
|
-
onChange,
|
|
89
|
-
disabled,
|
|
90
|
-
placeholder = "Any date",
|
|
91
|
-
className,
|
|
92
|
-
ariaLabel = "Date range",
|
|
93
|
-
}: DateRangePickerProps) {
|
|
94
|
-
const [open, setOpen] = React.useState(false);
|
|
95
|
-
const [hovered, setHovered] = React.useState<Date | null>(null);
|
|
96
|
-
/** Set while choosing the second end, so the range can preview under the cursor. */
|
|
97
|
-
const [pendingFrom, setPendingFrom] = React.useState<Date | null>(null);
|
|
98
|
-
const rootRef = React.useRef<HTMLDivElement>(null);
|
|
99
|
-
|
|
100
|
-
const from = fromIsoDate(value.from);
|
|
101
|
-
const to = fromIsoDate(value.to);
|
|
102
|
-
|
|
103
|
-
const [viewMonth, setViewMonth] = React.useState<Date>(() =>
|
|
104
|
-
startOfMonth(from ?? new Date()),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
React.useEffect(() => {
|
|
108
|
-
if (!open) return undefined;
|
|
109
|
-
const onPointerDown = (event: MouseEvent) => {
|
|
110
|
-
if (!rootRef.current?.contains(event.target as Node)) setOpen(false);
|
|
111
|
-
};
|
|
112
|
-
const onKeyDown = (event: KeyboardEvent) => {
|
|
113
|
-
if (event.key === "Escape") setOpen(false);
|
|
114
|
-
};
|
|
115
|
-
document.addEventListener("mousedown", onPointerDown);
|
|
116
|
-
document.addEventListener("keydown", onKeyDown);
|
|
117
|
-
return () => {
|
|
118
|
-
document.removeEventListener("mousedown", onPointerDown);
|
|
119
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
120
|
-
};
|
|
121
|
-
}, [open]);
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Reopening lands on the current selection rather than wherever the view was
|
|
125
|
-
* left. Done here rather than in an effect: it is a response to an event, and
|
|
126
|
-
* setting state from an effect is what `react-hooks/set-state-in-effect`
|
|
127
|
-
* rejects — correctly, since it costs an extra render every open.
|
|
128
|
-
*/
|
|
129
|
-
function openPicker() {
|
|
130
|
-
setViewMonth(startOfMonth(fromIsoDate(value.from) ?? new Date()));
|
|
131
|
-
setPendingFrom(null);
|
|
132
|
-
setHovered(null);
|
|
133
|
-
setOpen(true);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const anchor = pendingFrom ?? (to ? null : from);
|
|
137
|
-
const previewEnd = pendingFrom ? hovered : null;
|
|
138
|
-
|
|
139
|
-
function rangeBounds(): [Date | null, Date | null] {
|
|
140
|
-
if (pendingFrom) {
|
|
141
|
-
if (!previewEnd) return [pendingFrom, pendingFrom];
|
|
142
|
-
return previewEnd.getTime() < pendingFrom.getTime()
|
|
143
|
-
? [previewEnd, pendingFrom]
|
|
144
|
-
: [pendingFrom, previewEnd];
|
|
145
|
-
}
|
|
146
|
-
return [from, to];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const [rangeStart, rangeEnd] = rangeBounds();
|
|
150
|
-
|
|
151
|
-
function selectDay(day: Date) {
|
|
152
|
-
if (!anchor) {
|
|
153
|
-
// Starting a new range. Keeping only `from` means the next click closes
|
|
154
|
-
// it, whichever side of the anchor it lands on.
|
|
155
|
-
setPendingFrom(day);
|
|
156
|
-
onChange({ from: toIsoDate(day), to: "" });
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const [start, end] =
|
|
160
|
-
day.getTime() < anchor.getTime() ? [day, anchor] : [anchor, day];
|
|
161
|
-
onChange({ from: toIsoDate(start), to: toIsoDate(end) });
|
|
162
|
-
setPendingFrom(null);
|
|
163
|
-
setHovered(null);
|
|
164
|
-
setOpen(false);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** Arrow keys move by day/week across both months, as a calendar should. */
|
|
168
|
-
function onGridKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
|
169
|
-
const offsets: Record<string, number> = {
|
|
170
|
-
ArrowLeft: -1,
|
|
171
|
-
ArrowRight: 1,
|
|
172
|
-
ArrowUp: -7,
|
|
173
|
-
ArrowDown: 7,
|
|
174
|
-
};
|
|
175
|
-
const offset = offsets[event.key];
|
|
176
|
-
if (offset === undefined) return;
|
|
177
|
-
const focused = document.activeElement as HTMLElement | null;
|
|
178
|
-
const iso = focused?.dataset?.day;
|
|
179
|
-
const current = fromIsoDate(iso);
|
|
180
|
-
if (!current) return;
|
|
181
|
-
event.preventDefault();
|
|
182
|
-
const next = addDays(current, offset);
|
|
183
|
-
const nextIso = toIsoDate(next);
|
|
184
|
-
// Page the view when the move lands outside what is rendered.
|
|
185
|
-
if (!rootRef.current?.querySelector(`[data-day="${nextIso}"]`)) {
|
|
186
|
-
setViewMonth(startOfMonth(offset < 0 ? next : addMonths(next, -1)));
|
|
187
|
-
}
|
|
188
|
-
window.requestAnimationFrame(() => {
|
|
189
|
-
rootRef.current
|
|
190
|
-
?.querySelector<HTMLElement>(`[data-day="${nextIso}"]`)
|
|
191
|
-
?.focus();
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const label =
|
|
196
|
-
value.from || value.to
|
|
197
|
-
? `${formatDisplay(value.from) || "Any"} → ${formatDisplay(value.to) || "Any"}`
|
|
198
|
-
: placeholder;
|
|
199
|
-
|
|
200
|
-
const today = new Date();
|
|
201
|
-
|
|
202
|
-
function renderMonth(month: Date) {
|
|
203
|
-
return (
|
|
204
|
-
<div key={monthLabel(month)} className="w-[15.75rem] shrink-0">
|
|
205
|
-
<div className="text-foreground mb-2 text-center text-sm font-medium whitespace-nowrap">
|
|
206
|
-
{monthLabel(month)}
|
|
207
|
-
</div>
|
|
208
|
-
<div className="text-muted-foreground grid grid-cols-7 text-center text-[11px]">
|
|
209
|
-
{WEEKDAYS.map((day) => (
|
|
210
|
-
<div key={day} className="grid size-9 place-items-center">
|
|
211
|
-
{day}
|
|
212
|
-
</div>
|
|
213
|
-
))}
|
|
214
|
-
</div>
|
|
215
|
-
<div className="grid grid-cols-7">
|
|
216
|
-
{monthGrid(month).map((day) => {
|
|
217
|
-
const iso = toIsoDate(day);
|
|
218
|
-
const outside = day.getMonth() !== month.getMonth();
|
|
219
|
-
const isStart = isSameDay(day, rangeStart);
|
|
220
|
-
const isEnd = isSameDay(day, rangeEnd);
|
|
221
|
-
const inRange =
|
|
222
|
-
rangeStart != null &&
|
|
223
|
-
rangeEnd != null &&
|
|
224
|
-
day.getTime() > rangeStart.getTime() &&
|
|
225
|
-
day.getTime() < rangeEnd.getTime();
|
|
226
|
-
const isToday = isSameDay(day, today);
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<button
|
|
230
|
-
key={iso}
|
|
231
|
-
type="button"
|
|
232
|
-
data-day={iso}
|
|
233
|
-
// Outside days stay clickable — clicking one is how you reach
|
|
234
|
-
// the neighbouring month without paging.
|
|
235
|
-
onClick={() => selectDay(day)}
|
|
236
|
-
onMouseEnter={() => setHovered(day)}
|
|
237
|
-
aria-label={day.toDateString()}
|
|
238
|
-
aria-pressed={isStart || isEnd}
|
|
239
|
-
className={cn(
|
|
240
|
-
"focus-visible:ring-ring relative grid size-9 place-items-center rounded-md text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
241
|
-
outside ? "text-muted-foreground/50" : "text-foreground",
|
|
242
|
-
inRange && "bg-primary/10 rounded-none",
|
|
243
|
-
(isStart || isEnd) &&
|
|
244
|
-
"bg-primary text-primary-foreground font-medium",
|
|
245
|
-
// Square off the inner edge so a range reads as one bar
|
|
246
|
-
// rather than two pills with a gap.
|
|
247
|
-
isStart && rangeEnd && !isEnd && "rounded-r-none",
|
|
248
|
-
isEnd && rangeStart && !isStart && "rounded-l-none",
|
|
249
|
-
!isStart && !isEnd && "hover:bg-muted",
|
|
250
|
-
isToday &&
|
|
251
|
-
!isStart &&
|
|
252
|
-
!isEnd &&
|
|
253
|
-
"ring-border font-semibold ring-1",
|
|
254
|
-
)}
|
|
255
|
-
>
|
|
256
|
-
{day.getDate()}
|
|
257
|
-
</button>
|
|
258
|
-
);
|
|
259
|
-
})}
|
|
260
|
-
</div>
|
|
261
|
-
</div>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return (
|
|
266
|
-
<div ref={rootRef} className={cn("relative", className)}>
|
|
267
|
-
<div
|
|
268
|
-
className={cn(
|
|
269
|
-
"border-border bg-background flex items-center rounded-md border",
|
|
270
|
-
"focus-within:ring-ring/50 focus-within:border-ring focus-within:ring-2",
|
|
271
|
-
disabled && "opacity-50",
|
|
272
|
-
)}
|
|
273
|
-
>
|
|
274
|
-
<button
|
|
275
|
-
type="button"
|
|
276
|
-
disabled={disabled}
|
|
277
|
-
aria-haspopup="dialog"
|
|
278
|
-
aria-expanded={open}
|
|
279
|
-
aria-label={ariaLabel}
|
|
280
|
-
onClick={() => (open ? setOpen(false) : openPicker())}
|
|
281
|
-
className="flex min-w-0 flex-1 items-center gap-2 px-2 py-1.5 text-left text-sm"
|
|
282
|
-
>
|
|
283
|
-
<CalendarDays
|
|
284
|
-
className="text-muted-foreground size-4 shrink-0"
|
|
285
|
-
aria-hidden
|
|
286
|
-
/>
|
|
287
|
-
<span
|
|
288
|
-
className={cn(
|
|
289
|
-
"truncate",
|
|
290
|
-
!value.from && !value.to && "text-muted-foreground",
|
|
291
|
-
)}
|
|
292
|
-
>
|
|
293
|
-
{label}
|
|
294
|
-
</span>
|
|
295
|
-
</button>
|
|
296
|
-
{(value.from || value.to) && !disabled ? (
|
|
297
|
-
<button
|
|
298
|
-
type="button"
|
|
299
|
-
aria-label="Clear date range"
|
|
300
|
-
onClick={() => {
|
|
301
|
-
onChange({ from: "", to: "" });
|
|
302
|
-
setPendingFrom(null);
|
|
303
|
-
}}
|
|
304
|
-
className="text-muted-foreground hover:text-foreground shrink-0 px-2"
|
|
305
|
-
>
|
|
306
|
-
<X className="size-3.5" aria-hidden />
|
|
307
|
-
</button>
|
|
308
|
-
) : null}
|
|
309
|
-
</div>
|
|
310
|
-
|
|
311
|
-
{open ? (
|
|
312
|
-
<div
|
|
313
|
-
role="dialog"
|
|
314
|
-
aria-label={ariaLabel}
|
|
315
|
-
// `w-max` so the calendar sets its own width instead of inheriting
|
|
316
|
-
// the filter column, which squeezed the months until the numbers ran
|
|
317
|
-
// together. Anchored right because this sits in the last column and
|
|
318
|
-
// would otherwise open off the edge of the viewport.
|
|
319
|
-
className="border-border bg-background absolute top-full right-0 z-50 mt-1 flex w-max rounded-lg border p-3 shadow-lg"
|
|
320
|
-
>
|
|
321
|
-
<div className="border-border mr-3 flex w-32 shrink-0 flex-col gap-0.5 border-r pr-3">
|
|
322
|
-
{PRESETS.map((preset) => (
|
|
323
|
-
<button
|
|
324
|
-
key={preset.label}
|
|
325
|
-
type="button"
|
|
326
|
-
className="hover:bg-muted text-foreground rounded-md px-2 py-1.5 text-left text-xs"
|
|
327
|
-
onClick={() => {
|
|
328
|
-
onChange(preset.range());
|
|
329
|
-
setPendingFrom(null);
|
|
330
|
-
setOpen(false);
|
|
331
|
-
}}
|
|
332
|
-
>
|
|
333
|
-
{preset.label}
|
|
334
|
-
</button>
|
|
335
|
-
))}
|
|
336
|
-
</div>
|
|
337
|
-
|
|
338
|
-
<div onKeyDown={onGridKeyDown}>
|
|
339
|
-
<div className="mb-2 flex items-center justify-between">
|
|
340
|
-
<button
|
|
341
|
-
type="button"
|
|
342
|
-
aria-label="Previous month"
|
|
343
|
-
className="hover:bg-muted text-muted-foreground hover:text-foreground rounded-md p-1.5"
|
|
344
|
-
onClick={() => setViewMonth(addMonths(viewMonth, -1))}
|
|
345
|
-
>
|
|
346
|
-
<ChevronLeft className="size-4" aria-hidden />
|
|
347
|
-
</button>
|
|
348
|
-
<button
|
|
349
|
-
type="button"
|
|
350
|
-
aria-label="Next month"
|
|
351
|
-
className="hover:bg-muted text-muted-foreground hover:text-foreground rounded-md p-1.5"
|
|
352
|
-
onClick={() => setViewMonth(addMonths(viewMonth, 1))}
|
|
353
|
-
>
|
|
354
|
-
<ChevronRight className="size-4" aria-hidden />
|
|
355
|
-
</button>
|
|
356
|
-
</div>
|
|
357
|
-
<div className="flex gap-5" onMouseLeave={() => setHovered(null)}>
|
|
358
|
-
{renderMonth(viewMonth)}
|
|
359
|
-
{renderMonth(addMonths(viewMonth, 1))}
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
</div>
|
|
363
|
-
) : null}
|
|
364
|
-
</div>
|
|
365
|
-
);
|
|
366
|
-
}
|