@nexub/react 6.1.0 → 6.3.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/accessibility.json +49 -1
- package/dist/components/DataTable/DataTable.d.ts +11 -2
- package/dist/components/DataTable/DataTable.js +51 -4
- package/dist/components/DateTimePicker/DateTimePicker.d.ts +20 -0
- package/dist/components/DateTimePicker/DateTimePicker.js +67 -0
- package/dist/components/DateTimePicker/index.d.ts +2 -0
- package/dist/components/DateTimePicker/index.js +4 -0
- package/dist/components/EventCalendar/EventCalendar.d.ts +38 -0
- package/dist/components/EventCalendar/EventCalendar.js +204 -0
- package/dist/components/EventCalendar/index.d.ts +2 -0
- package/dist/components/EventCalendar/index.js +4 -0
- package/dist/components/TimePicker/TimePicker.d.ts +15 -0
- package/dist/components/TimePicker/TimePicker.js +26 -0
- package/dist/components/TimePicker/index.d.ts +2 -0
- package/dist/components/TimePicker/index.js +4 -0
- package/dist/components/TreeView/TreeView.d.ts +38 -0
- package/dist/components/TreeView/TreeView.js +253 -0
- package/dist/components/TreeView/index.d.ts +2 -0
- package/dist/components/TreeView/index.js +4 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/styles.css +1 -1
- package/package.json +6 -6
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "6.
|
|
2
|
+
"version": "6.3.0",
|
|
3
3
|
"standard": "WCAG 2.2 Level AA",
|
|
4
4
|
"method": {
|
|
5
5
|
"automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark, and on open popups (menus, listboxes, dialogs), on every pull request; a new violation fails the build.",
|
|
@@ -342,6 +342,15 @@
|
|
|
342
342
|
"keyboard": [],
|
|
343
343
|
"screenReader": []
|
|
344
344
|
},
|
|
345
|
+
{
|
|
346
|
+
"slug": "date-time-picker",
|
|
347
|
+
"name": "Date Time Picker",
|
|
348
|
+
"package": "@nexub/react",
|
|
349
|
+
"automated": "pass",
|
|
350
|
+
"exceptions": [],
|
|
351
|
+
"keyboard": [],
|
|
352
|
+
"screenReader": []
|
|
353
|
+
},
|
|
345
354
|
{
|
|
346
355
|
"slug": "dialog",
|
|
347
356
|
"name": "Dialog",
|
|
@@ -401,6 +410,15 @@
|
|
|
401
410
|
"keyboard": [],
|
|
402
411
|
"screenReader": []
|
|
403
412
|
},
|
|
413
|
+
{
|
|
414
|
+
"slug": "event-calendar",
|
|
415
|
+
"name": "Event Calendar",
|
|
416
|
+
"package": "@nexub/react",
|
|
417
|
+
"automated": "pass",
|
|
418
|
+
"exceptions": [],
|
|
419
|
+
"keyboard": [],
|
|
420
|
+
"screenReader": []
|
|
421
|
+
},
|
|
404
422
|
{
|
|
405
423
|
"slug": "eyebrow-chip",
|
|
406
424
|
"name": "Eyebrow Chip",
|
|
@@ -1127,6 +1145,18 @@
|
|
|
1127
1145
|
"Render it only while waiting and remove it when tokens start, so it is not announced again."
|
|
1128
1146
|
]
|
|
1129
1147
|
},
|
|
1148
|
+
{
|
|
1149
|
+
"slug": "time-picker",
|
|
1150
|
+
"name": "Time Picker",
|
|
1151
|
+
"package": "@nexub/react",
|
|
1152
|
+
"automated": "pass",
|
|
1153
|
+
"exceptions": [],
|
|
1154
|
+
"keyboard": [
|
|
1155
|
+
"ArrowUp / ArrowDown: Changes the focused segment (hour, minute, AM/PM).",
|
|
1156
|
+
"ArrowLeft / ArrowRight: Moves between segments."
|
|
1157
|
+
],
|
|
1158
|
+
"screenReader": []
|
|
1159
|
+
},
|
|
1130
1160
|
{
|
|
1131
1161
|
"slug": "toast",
|
|
1132
1162
|
"name": "Toast",
|
|
@@ -1174,6 +1204,24 @@
|
|
|
1174
1204
|
"keyboard": [],
|
|
1175
1205
|
"screenReader": []
|
|
1176
1206
|
},
|
|
1207
|
+
{
|
|
1208
|
+
"slug": "tree-view",
|
|
1209
|
+
"name": "Tree View",
|
|
1210
|
+
"package": "@nexub/react",
|
|
1211
|
+
"automated": "pass",
|
|
1212
|
+
"exceptions": [],
|
|
1213
|
+
"keyboard": [
|
|
1214
|
+
"ArrowDown / ArrowUp: Next or previous visible item.",
|
|
1215
|
+
"ArrowRight: Opens a closed item, or moves to its first child. Mirrored in RTL.",
|
|
1216
|
+
"ArrowLeft: Closes an open item, or moves to its parent. Mirrored in RTL.",
|
|
1217
|
+
"Home / End: First or last visible item.",
|
|
1218
|
+
"Enter / Space: Selects the item, or toggles its checkbox.",
|
|
1219
|
+
"Type a letter: Moves to the next item starting with the typed text."
|
|
1220
|
+
],
|
|
1221
|
+
"screenReader": [
|
|
1222
|
+
"The tree is one tab stop (roving tabindex); it returns to the last focused or the selected item."
|
|
1223
|
+
]
|
|
1224
|
+
},
|
|
1177
1225
|
{
|
|
1178
1226
|
"slug": "activity-feed",
|
|
1179
1227
|
"name": "ActivityFeed",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type ColumnDef, type RowSelectionState, type RowData, type VisibilityState, type ColumnSizingState, type ColumnOrderState, type ColumnPinningState } from '@tanstack/react-table';
|
|
2
|
+
import { type ColumnDef, type RowSelectionState, type RowData, type VisibilityState, type ColumnSizingState, type ColumnOrderState, type ColumnPinningState, type ExpandedState } from '@tanstack/react-table';
|
|
3
3
|
declare module '@tanstack/react-table' {
|
|
4
4
|
interface ColumnMeta<TData extends RowData, TValue> {
|
|
5
5
|
/** Horizontal alignment of the header and cells. Use 'end' for numbers. @default 'start' */
|
|
@@ -75,5 +75,14 @@ export interface DataTableProps<TData> extends React.HTMLAttributes<HTMLDivEleme
|
|
|
75
75
|
pagination?: {
|
|
76
76
|
pageSize?: number;
|
|
77
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Nested rows (a tree table): return a row's children. The first column gets
|
|
80
|
+
* an expand toggle and indents each level. Sorting sorts within each level.
|
|
81
|
+
*/
|
|
82
|
+
getSubRows?: (row: TData, index: number) => TData[] | undefined;
|
|
83
|
+
/** Expanded rows by id, or `true` for all. */
|
|
84
|
+
expanded?: ExpandedState;
|
|
85
|
+
defaultExpanded?: ExpandedState;
|
|
86
|
+
onExpandedChange?: (expanded: ExpandedState) => void;
|
|
78
87
|
}
|
|
79
|
-
export declare function DataTable<TData>({ columns, data, enableSorting, enableRowSelection, selectionColumn, rowSelection: rowSelectionProp, onRowSelectionChange, getRowId, bulkActions, density: densityProp, defaultDensity, onDensityChange, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, viewOptions, toolbar, enableColumnResizing, columnSizing: columnSizingProp, onColumnSizingChange, enableColumnReordering, columnOrder: columnOrderProp, onColumnOrderChange, columnPinning: columnPinningProp, onColumnPinningChange, maxHeight, grid, rowWindow, pagination, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
|
|
88
|
+
export declare function DataTable<TData>({ columns, data, enableSorting, enableRowSelection, selectionColumn, rowSelection: rowSelectionProp, onRowSelectionChange, getRowId, bulkActions, density: densityProp, defaultDensity, onDensityChange, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, viewOptions, toolbar, enableColumnResizing, columnSizing: columnSizingProp, onColumnSizingChange, enableColumnReordering, columnOrder: columnOrderProp, onColumnOrderChange, columnPinning: columnPinningProp, onColumnPinningChange, maxHeight, grid, rowWindow, pagination, getSubRows, expanded: expandedProp, defaultExpanded, onExpandedChange, caption, captionHidden, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: DataTableProps<TData>): React.JSX.Element;
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
getSortedRowModel,
|
|
8
8
|
getFilteredRowModel,
|
|
9
9
|
getPaginationRowModel,
|
|
10
|
+
getExpandedRowModel,
|
|
10
11
|
flexRender
|
|
11
12
|
} from "@tanstack/react-table";
|
|
12
|
-
import { ArrowUp, ArrowDown, ChevronsUpDown, SlidersHorizontal, X } from "@nexub/icons";
|
|
13
|
+
import { ArrowUp, ArrowDown, ChevronDown, ChevronRight, ChevronsUpDown, SlidersHorizontal, X } from "@nexub/icons";
|
|
13
14
|
import { cn } from "../../utils/cn.js";
|
|
14
15
|
import { disabledContainer, pressable } from "../../utils/control.js";
|
|
15
16
|
import { floatingSurface } from "../../utils/surface.js";
|
|
@@ -102,6 +103,34 @@ const useAllRows = (count) => ({
|
|
|
102
103
|
before: 0,
|
|
103
104
|
after: 0
|
|
104
105
|
});
|
|
106
|
+
function TreeCell({
|
|
107
|
+
row,
|
|
108
|
+
expandLabel,
|
|
109
|
+
collapseLabel,
|
|
110
|
+
children
|
|
111
|
+
}) {
|
|
112
|
+
const open = row.getIsExpanded();
|
|
113
|
+
const ref = React.useCallback(
|
|
114
|
+
(el) => {
|
|
115
|
+
if (el) el.style.paddingInlineStart = `${row.depth * 1.5}rem`;
|
|
116
|
+
},
|
|
117
|
+
[row.depth]
|
|
118
|
+
);
|
|
119
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "flex items-center gap-1", children: [
|
|
120
|
+
row.getCanExpand() ? /* @__PURE__ */ jsx(
|
|
121
|
+
"button",
|
|
122
|
+
{
|
|
123
|
+
type: "button",
|
|
124
|
+
"aria-expanded": open,
|
|
125
|
+
"aria-label": open ? collapseLabel : expandLabel,
|
|
126
|
+
onClick: row.toggleExpanded,
|
|
127
|
+
className: "-ms-1 inline-flex size-6 shrink-0 items-center justify-center rounded-shape-min text-[var(--color-on-surface-variant)] hover:bg-[var(--color-surface-container-high)]",
|
|
128
|
+
children: open ? /* @__PURE__ */ jsx(ChevronDown, { size: "xs", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRight, { size: "xs", "aria-hidden": "true", className: "rtl:-scale-x-100" })
|
|
129
|
+
}
|
|
130
|
+
) : /* @__PURE__ */ jsx("span", { className: "-ms-1 size-6 shrink-0", "aria-hidden": "true" }),
|
|
131
|
+
children
|
|
132
|
+
] });
|
|
133
|
+
}
|
|
105
134
|
const px = (n) => typeof n === "number" ? `${n}px` : n;
|
|
106
135
|
const apply = (u, prev) => typeof u === "function" ? u(prev) : u;
|
|
107
136
|
const columnLabel = (c) => typeof c.columnDef.header === "string" ? c.columnDef.header : c.id;
|
|
@@ -134,6 +163,10 @@ function DataTable({
|
|
|
134
163
|
grid = false,
|
|
135
164
|
rowWindow,
|
|
136
165
|
pagination,
|
|
166
|
+
getSubRows,
|
|
167
|
+
expanded: expandedProp,
|
|
168
|
+
defaultExpanded,
|
|
169
|
+
onExpandedChange,
|
|
137
170
|
caption,
|
|
138
171
|
captionHidden = false,
|
|
139
172
|
className,
|
|
@@ -174,6 +207,11 @@ function DataTable({
|
|
|
174
207
|
defaultValue: {},
|
|
175
208
|
onChange: onColumnPinningChange
|
|
176
209
|
});
|
|
210
|
+
const [expanded = {}, setExpanded] = useControllableState({
|
|
211
|
+
value: expandedProp,
|
|
212
|
+
defaultValue: defaultExpanded ?? {},
|
|
213
|
+
onChange: onExpandedChange
|
|
214
|
+
});
|
|
177
215
|
const [announcement, setAnnouncement] = React.useState("");
|
|
178
216
|
const tableRef = React.useRef(null);
|
|
179
217
|
const gridNav = React.useMemo(() => createGridNavigation(dir), [dir]);
|
|
@@ -223,9 +261,12 @@ function DataTable({
|
|
|
223
261
|
columnVisibility,
|
|
224
262
|
columnSizing,
|
|
225
263
|
columnOrder,
|
|
226
|
-
columnPinning
|
|
264
|
+
columnPinning,
|
|
265
|
+
expanded: getSubRows ? expanded : void 0
|
|
227
266
|
}),
|
|
228
267
|
[
|
|
268
|
+
getSubRows,
|
|
269
|
+
expanded,
|
|
229
270
|
enableSorting,
|
|
230
271
|
sorting,
|
|
231
272
|
enableRowSelection,
|
|
@@ -257,6 +298,12 @@ function DataTable({
|
|
|
257
298
|
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
258
299
|
getFilteredRowModel: getFilteredRowModel(),
|
|
259
300
|
getPaginationRowModel: pagination ? getPaginationRowModel() : void 0,
|
|
301
|
+
getSubRows,
|
|
302
|
+
getExpandedRowModel: getSubRows ? getExpandedRowModel() : void 0,
|
|
303
|
+
onExpandedChange: getSubRows ? (u) => setExpanded(apply(u, expanded)) : void 0,
|
|
304
|
+
// With pages, count top-level rows so a parent and its children stay together
|
|
305
|
+
// (TanStack then expands inside the page model; without pages it must not).
|
|
306
|
+
paginateExpandedRows: !pagination,
|
|
260
307
|
enableRowSelection,
|
|
261
308
|
manualPagination: false
|
|
262
309
|
});
|
|
@@ -466,7 +513,7 @@ function DataTable({
|
|
|
466
513
|
"data-index": i,
|
|
467
514
|
"aria-rowindex": rowWin.virtual ? i + 2 : void 0,
|
|
468
515
|
"data-state": enableRowSelection && row.getIsSelected() ? "selected" : void 0,
|
|
469
|
-
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(
|
|
516
|
+
children: row.getVisibleCells().map((cell, _i, cells) => /* @__PURE__ */ jsx(
|
|
470
517
|
TableCell,
|
|
471
518
|
{
|
|
472
519
|
ref: pinned || enableColumnResizing ? placeCell(cell.column) : void 0,
|
|
@@ -476,7 +523,7 @@ function DataTable({
|
|
|
476
523
|
pinClass(cell.column),
|
|
477
524
|
enableColumnResizing && "truncate"
|
|
478
525
|
),
|
|
479
|
-
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
526
|
+
children: getSubRows && cell === cells.find((c) => c.column.id !== "__select") ? /* @__PURE__ */ jsx(TreeCell, { row, expandLabel: messages.expandRow, collapseLabel: messages.collapseRow, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }) : flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
480
527
|
},
|
|
481
528
|
cell.id
|
|
482
529
|
))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Locale } from 'date-fns';
|
|
3
|
+
export interface DateTimePickerProps {
|
|
4
|
+
/** Date and time in the reader's local time. */
|
|
5
|
+
value?: Date;
|
|
6
|
+
onChange?: (value: Date | undefined) => void;
|
|
7
|
+
/** Time used when a date is picked first. @default '09:00' */
|
|
8
|
+
defaultTime?: string;
|
|
9
|
+
minuteStep?: number;
|
|
10
|
+
minDate?: Date;
|
|
11
|
+
maxDate?: Date;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** IANA zone to name next to the fields (label only; the value stays local). */
|
|
14
|
+
timeZone?: string;
|
|
15
|
+
'aria-label'?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
locale?: Locale;
|
|
18
|
+
}
|
|
19
|
+
/** A DatePicker and a TimePicker that edit one `Date` together. */
|
|
20
|
+
export declare function DateTimePicker({ value, onChange, defaultTime, minuteStep, minDate, maxDate, disabled, timeZone, 'aria-label': ariaLabel, className, locale, }: DateTimePickerProps): React.JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn.js";
|
|
5
|
+
import { DatePicker } from "../DatePicker/DatePicker.js";
|
|
6
|
+
import { TimePicker } from "../TimePicker/TimePicker.js";
|
|
7
|
+
import { useIntlLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
8
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
9
|
+
const timeOf = (d) => `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
10
|
+
const withTime = (d, time) => {
|
|
11
|
+
const [h = 0, m = 0] = time.split(":").map(Number);
|
|
12
|
+
const next = new Date(d);
|
|
13
|
+
next.setHours(h, m, 0, 0);
|
|
14
|
+
return next;
|
|
15
|
+
};
|
|
16
|
+
function DateTimePicker({
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
defaultTime = "09:00",
|
|
20
|
+
minuteStep,
|
|
21
|
+
minDate,
|
|
22
|
+
maxDate,
|
|
23
|
+
disabled,
|
|
24
|
+
timeZone,
|
|
25
|
+
"aria-label": ariaLabel,
|
|
26
|
+
className,
|
|
27
|
+
locale
|
|
28
|
+
}) {
|
|
29
|
+
const messages = useNexubMessages();
|
|
30
|
+
const intlLocale = useIntlLocale();
|
|
31
|
+
const [pendingTime, setPendingTime] = React.useState(defaultTime);
|
|
32
|
+
const time = value ? timeOf(value) : pendingTime;
|
|
33
|
+
const zoneName = timeZone ? new Intl.DateTimeFormat(intlLocale, { timeZone, timeZoneName: "shortOffset" }).formatToParts(value ?? /* @__PURE__ */ new Date()).find((p) => p.type === "timeZoneName")?.value ?? timeZone : void 0;
|
|
34
|
+
return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": ariaLabel, className: cn("flex flex-wrap items-center gap-2", className), children: [
|
|
35
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-40 flex-1", children: /* @__PURE__ */ jsx(
|
|
36
|
+
DatePicker,
|
|
37
|
+
{
|
|
38
|
+
"aria-label": messages.dateLabel,
|
|
39
|
+
value,
|
|
40
|
+
onChange: (date) => onChange?.(date ? withTime(date, time) : void 0),
|
|
41
|
+
minDate,
|
|
42
|
+
maxDate,
|
|
43
|
+
disabled,
|
|
44
|
+
locale
|
|
45
|
+
}
|
|
46
|
+
) }),
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
TimePicker,
|
|
49
|
+
{
|
|
50
|
+
"aria-label": messages.timeLabel,
|
|
51
|
+
wrapperClassName: "w-36",
|
|
52
|
+
value: time,
|
|
53
|
+
minuteStep,
|
|
54
|
+
disabled,
|
|
55
|
+
onValueChange: (next) => {
|
|
56
|
+
if (!next) return;
|
|
57
|
+
if (value) onChange?.(withTime(value, next));
|
|
58
|
+
else setPendingTime(next);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
zoneName && /* @__PURE__ */ jsx("span", { className: "text-body-sm text-[var(--color-on-surface-muted)]", children: messages.timeZoneLabel(`${zoneName} (${timeZone})`) })
|
|
63
|
+
] });
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
DateTimePicker
|
|
67
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type Day } from 'date-fns';
|
|
3
|
+
export type EventCalendarView = 'month' | 'week';
|
|
4
|
+
export type EventTone = 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
5
|
+
export interface CalendarEvent {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
start: Date;
|
|
9
|
+
/** Inclusive end; events spanning days show on each of them. */
|
|
10
|
+
end?: Date;
|
|
11
|
+
/** Hides the start time. */
|
|
12
|
+
allDay?: boolean;
|
|
13
|
+
/** @default 'primary' */
|
|
14
|
+
tone?: EventTone;
|
|
15
|
+
}
|
|
16
|
+
export interface EventCalendarProps {
|
|
17
|
+
events: CalendarEvent[];
|
|
18
|
+
/** Any day inside the shown month or week. */
|
|
19
|
+
date?: Date;
|
|
20
|
+
defaultDate?: Date;
|
|
21
|
+
onDateChange?: (date: Date) => void;
|
|
22
|
+
view?: EventCalendarView;
|
|
23
|
+
defaultView?: EventCalendarView;
|
|
24
|
+
onViewChange?: (view: EventCalendarView) => void;
|
|
25
|
+
onEventClick?: (event: CalendarEvent) => void;
|
|
26
|
+
/** A day's number, or its "N more" link in month view. */
|
|
27
|
+
onDayClick?: (date: Date) => void;
|
|
28
|
+
/** Events shown per day in month view before "N more". @default 3 */
|
|
29
|
+
maxEventsPerDay?: number;
|
|
30
|
+
/** 0 = Sunday. Defaults to the locale's first day of the week. */
|
|
31
|
+
weekStartsOn?: Day;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A month or week grid of events. Display only: your app owns the events and
|
|
36
|
+
* decides what a click on an event or a day does.
|
|
37
|
+
*/
|
|
38
|
+
export declare function EventCalendar({ events, date: dateProp, defaultDate, onDateChange, view: viewProp, defaultView, onViewChange, onEventClick, onDayClick, maxEventsPerDay, weekStartsOn: weekStartsOnProp, className, }: EventCalendarProps): React.JSX.Element;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {
|
|
5
|
+
addDays,
|
|
6
|
+
addMonths,
|
|
7
|
+
addWeeks,
|
|
8
|
+
endOfDay,
|
|
9
|
+
endOfMonth,
|
|
10
|
+
endOfWeek,
|
|
11
|
+
format,
|
|
12
|
+
isSameDay,
|
|
13
|
+
isSameMonth,
|
|
14
|
+
startOfDay,
|
|
15
|
+
startOfMonth,
|
|
16
|
+
startOfWeek
|
|
17
|
+
} from "date-fns";
|
|
18
|
+
import { ChevronLeft, ChevronRight } from "@nexub/icons";
|
|
19
|
+
import { cn } from "../../utils/cn.js";
|
|
20
|
+
import { Button } from "../Button/Button.js";
|
|
21
|
+
import { IconButton } from "../IconButton/IconButton.js";
|
|
22
|
+
import { SegmentedControl } from "../SegmentedControl/SegmentedControl.js";
|
|
23
|
+
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
24
|
+
import { useIntlLocale, useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
25
|
+
const tones = {
|
|
26
|
+
primary: "bg-[var(--color-primary-container)] text-[var(--color-on-primary-container)]",
|
|
27
|
+
success: "bg-[var(--color-success-container)] text-[var(--color-on-success-container)]",
|
|
28
|
+
warning: "bg-[var(--color-warning-container)] text-[var(--color-on-warning-container)]",
|
|
29
|
+
error: "bg-[var(--color-error-container)] text-[var(--color-on-error-container)]",
|
|
30
|
+
info: "bg-[var(--color-info-container)] text-[var(--color-on-info-container)]"
|
|
31
|
+
};
|
|
32
|
+
function localeWeekStart(locale) {
|
|
33
|
+
try {
|
|
34
|
+
const loc = new Intl.Locale(locale ?? new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
35
|
+
const firstDay = loc.getWeekInfo?.().firstDay ?? loc.weekInfo?.firstDay;
|
|
36
|
+
return firstDay ? firstDay % 7 : 0;
|
|
37
|
+
} catch {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function EventCalendar({
|
|
42
|
+
events,
|
|
43
|
+
date: dateProp,
|
|
44
|
+
defaultDate,
|
|
45
|
+
onDateChange,
|
|
46
|
+
view: viewProp,
|
|
47
|
+
defaultView = "month",
|
|
48
|
+
onViewChange,
|
|
49
|
+
onEventClick,
|
|
50
|
+
onDayClick,
|
|
51
|
+
maxEventsPerDay = 3,
|
|
52
|
+
weekStartsOn: weekStartsOnProp,
|
|
53
|
+
className
|
|
54
|
+
}) {
|
|
55
|
+
const t = useNexubMessages();
|
|
56
|
+
const { dir } = useLocale();
|
|
57
|
+
const intlLocale = useIntlLocale();
|
|
58
|
+
const [date = /* @__PURE__ */ new Date(), setDate] = useControllableState({
|
|
59
|
+
value: dateProp,
|
|
60
|
+
defaultValue: defaultDate ?? /* @__PURE__ */ new Date(),
|
|
61
|
+
onChange: onDateChange
|
|
62
|
+
});
|
|
63
|
+
const [view = "month", setView] = useControllableState({
|
|
64
|
+
value: viewProp,
|
|
65
|
+
defaultValue: defaultView,
|
|
66
|
+
onChange: onViewChange
|
|
67
|
+
});
|
|
68
|
+
const weekStartsOn = weekStartsOnProp ?? localeWeekStart(intlLocale);
|
|
69
|
+
const opts = { weekStartsOn };
|
|
70
|
+
const month = view === "month";
|
|
71
|
+
const first = startOfWeek(month ? startOfMonth(date) : date, opts);
|
|
72
|
+
const last = endOfWeek(month ? endOfMonth(date) : date, opts);
|
|
73
|
+
const days = [];
|
|
74
|
+
for (let d = first; d <= last; d = addDays(d, 1)) days.push(d);
|
|
75
|
+
const weeks = [];
|
|
76
|
+
for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7));
|
|
77
|
+
const sorted = React.useMemo(
|
|
78
|
+
() => [...events].sort((a, b) => a.start.getTime() - b.start.getTime()),
|
|
79
|
+
[events]
|
|
80
|
+
);
|
|
81
|
+
const eventsOn = (day) => sorted.filter((e) => e.start <= endOfDay(day) && (e.end ?? e.start) >= startOfDay(day));
|
|
82
|
+
const fmt = (o) => new Intl.DateTimeFormat(intlLocale, o);
|
|
83
|
+
const title = month ? fmt({ month: "long", year: "numeric" }).format(date) : fmt({ month: "short", day: "numeric", year: "numeric" }).formatRange(days[0], days[6]);
|
|
84
|
+
const timeFmt = fmt({ timeStyle: "short" });
|
|
85
|
+
const fullFmt = fmt({ dateStyle: "full" });
|
|
86
|
+
const today = /* @__PURE__ */ new Date();
|
|
87
|
+
const step = (n) => setDate(month ? addMonths(date, n) : addWeeks(date, n));
|
|
88
|
+
const [Back, Forward] = dir === "rtl" ? [ChevronRight, ChevronLeft] : [ChevronLeft, ChevronRight];
|
|
89
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", className), children: [
|
|
90
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
91
|
+
/* @__PURE__ */ jsx("h2", { "aria-live": "polite", className: "me-auto text-title-lg font-semibold text-[var(--color-on-surface)]", children: title }),
|
|
92
|
+
/* @__PURE__ */ jsx(Button, { variant: "outlined", size: "sm", onClick: () => setDate(/* @__PURE__ */ new Date()), children: t.today }),
|
|
93
|
+
/* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", label: month ? t.previousMonth : t.previousWeek, onClick: () => step(-1), children: /* @__PURE__ */ jsx(Back, { size: "sm", "aria-hidden": "true" }) }),
|
|
94
|
+
/* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", label: month ? t.nextMonth : t.nextWeek, onClick: () => step(1), children: /* @__PURE__ */ jsx(Forward, { size: "sm", "aria-hidden": "true" }) }),
|
|
95
|
+
/* @__PURE__ */ jsxs(
|
|
96
|
+
SegmentedControl.Root,
|
|
97
|
+
{
|
|
98
|
+
size: "sm",
|
|
99
|
+
"aria-label": t.calendarView,
|
|
100
|
+
value: view,
|
|
101
|
+
onValueChange: (v) => v && setView(v),
|
|
102
|
+
children: [
|
|
103
|
+
/* @__PURE__ */ jsx(SegmentedControl.Item, { value: "month", children: t.monthView }),
|
|
104
|
+
/* @__PURE__ */ jsx(SegmentedControl.Item, { value: "week", children: t.weekView })
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
] }),
|
|
109
|
+
/* @__PURE__ */ jsxs("table", { className: "w-full table-fixed border-collapse text-body-sm", children: [
|
|
110
|
+
/* @__PURE__ */ jsx("caption", { className: "sr-only", children: title }),
|
|
111
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: weeks[0].map((d) => /* @__PURE__ */ jsx(
|
|
112
|
+
"th",
|
|
113
|
+
{
|
|
114
|
+
scope: "col",
|
|
115
|
+
abbr: fmt({ weekday: "long" }).format(d),
|
|
116
|
+
className: "pb-2 text-label-md font-medium text-[var(--color-on-surface-muted)]",
|
|
117
|
+
children: fmt({ weekday: "short" }).format(d)
|
|
118
|
+
},
|
|
119
|
+
d.getDay()
|
|
120
|
+
)) }) }),
|
|
121
|
+
/* @__PURE__ */ jsx("tbody", { children: weeks.map((week) => /* @__PURE__ */ jsx("tr", { children: week.map((day) => {
|
|
122
|
+
const list = eventsOn(day);
|
|
123
|
+
const shown = month ? list.slice(0, maxEventsPerDay) : list;
|
|
124
|
+
const hidden = list.length - shown.length;
|
|
125
|
+
const isToday = isSameDay(day, today);
|
|
126
|
+
const outside = month && !isSameMonth(day, date);
|
|
127
|
+
return /* @__PURE__ */ jsxs(
|
|
128
|
+
"td",
|
|
129
|
+
{
|
|
130
|
+
"aria-current": isToday ? "date" : void 0,
|
|
131
|
+
className: cn(
|
|
132
|
+
"border border-[var(--color-outline-variant)] p-1 align-top",
|
|
133
|
+
month ? "h-28" : "h-80",
|
|
134
|
+
outside && "bg-[var(--color-surface-container-low)]"
|
|
135
|
+
),
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsx(
|
|
138
|
+
DayNumber,
|
|
139
|
+
{
|
|
140
|
+
day,
|
|
141
|
+
label: fullFmt.format(day),
|
|
142
|
+
isToday,
|
|
143
|
+
muted: outside,
|
|
144
|
+
onClick: onDayClick
|
|
145
|
+
}
|
|
146
|
+
),
|
|
147
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-1 flex flex-col gap-0.5", children: shown.map((e) => {
|
|
148
|
+
const time = e.allDay || !isSameDay(e.start, day) ? "" : timeFmt.format(e.start);
|
|
149
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
+
time && /* @__PURE__ */ jsx("span", { className: "me-1 tabular-nums opacity-80", children: time }),
|
|
151
|
+
e.title
|
|
152
|
+
] });
|
|
153
|
+
const cls = cn(
|
|
154
|
+
"block w-full truncate rounded-shape-min px-1.5 py-0.5 text-start text-label-md",
|
|
155
|
+
tones[e.tone ?? "primary"]
|
|
156
|
+
);
|
|
157
|
+
return /* @__PURE__ */ jsx("li", { children: onEventClick ? /* @__PURE__ */ jsx("button", { type: "button", className: cn(cls, "hover:opacity-90"), onClick: () => onEventClick(e), children: body }) : /* @__PURE__ */ jsx("span", { className: cls, children: body }) }, e.id);
|
|
158
|
+
}) }),
|
|
159
|
+
hidden > 0 && (onDayClick ? /* @__PURE__ */ jsx(
|
|
160
|
+
"button",
|
|
161
|
+
{
|
|
162
|
+
type: "button",
|
|
163
|
+
className: "min-h-6 px-1.5 text-label-md text-[var(--color-on-surface-variant)] hover:underline",
|
|
164
|
+
onClick: () => onDayClick(day),
|
|
165
|
+
children: t.moreCount(hidden)
|
|
166
|
+
}
|
|
167
|
+
) : /* @__PURE__ */ jsx("span", { className: "mt-0.5 block px-1.5 text-label-md text-[var(--color-on-surface-variant)]", children: t.moreCount(hidden) }))
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
day.toISOString()
|
|
171
|
+
);
|
|
172
|
+
}) }, week[0].toISOString())) })
|
|
173
|
+
] })
|
|
174
|
+
] });
|
|
175
|
+
}
|
|
176
|
+
function DayNumber({
|
|
177
|
+
day,
|
|
178
|
+
label,
|
|
179
|
+
isToday,
|
|
180
|
+
muted,
|
|
181
|
+
onClick
|
|
182
|
+
}) {
|
|
183
|
+
const cls = cn(
|
|
184
|
+
"inline-flex size-7 items-center justify-center rounded-full text-label-md tabular-nums",
|
|
185
|
+
isToday ? "bg-[var(--color-primary)] text-[var(--color-on-primary)]" : muted ? "text-[var(--color-on-surface-muted)]" : "text-[var(--color-on-surface)]"
|
|
186
|
+
);
|
|
187
|
+
const n = day.getDate();
|
|
188
|
+
return onClick ? /* @__PURE__ */ jsx(
|
|
189
|
+
"button",
|
|
190
|
+
{
|
|
191
|
+
type: "button",
|
|
192
|
+
"aria-label": label,
|
|
193
|
+
className: cn(cls, !isToday && "hover:bg-[var(--color-surface-container-high)]"),
|
|
194
|
+
onClick: () => onClick(day),
|
|
195
|
+
children: n
|
|
196
|
+
}
|
|
197
|
+
) : /* @__PURE__ */ jsxs("time", { dateTime: format(day, "yyyy-MM-dd"), className: cls, children: [
|
|
198
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: n }),
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
|
|
200
|
+
] });
|
|
201
|
+
}
|
|
202
|
+
export {
|
|
203
|
+
EventCalendar
|
|
204
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type InputProps } from '../Input/Input.js';
|
|
3
|
+
export interface TimePickerProps extends Omit<InputProps, 'type' | 'value' | 'defaultValue' | 'onChange' | 'step'> {
|
|
4
|
+
/** 24-hour `"HH:mm"`; the field shows 12 or 24 hours from the reader's locale. */
|
|
5
|
+
value?: string;
|
|
6
|
+
defaultValue?: string;
|
|
7
|
+
onValueChange?: (value: string) => void;
|
|
8
|
+
/** Minutes between steps for the arrow keys and the picker. @default 1 */
|
|
9
|
+
minuteStep?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A time field built on the native `<input type="time">`: locale-aware 12/24h,
|
|
13
|
+
* keyboard segments, the platform's picker on phones, and form submission.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TimePicker: React.ForwardRefExoticComponent<TimePickerProps & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Input } from "../Input/Input.js";
|
|
5
|
+
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
6
|
+
const TimePicker = React.forwardRef(function TimePicker2({ value: valueProp, defaultValue, onValueChange, minuteStep = 1, ...props }, ref) {
|
|
7
|
+
const [value = "", setValue] = useControllableState({
|
|
8
|
+
value: valueProp,
|
|
9
|
+
defaultValue: defaultValue ?? "",
|
|
10
|
+
onChange: onValueChange
|
|
11
|
+
});
|
|
12
|
+
return /* @__PURE__ */ jsx(
|
|
13
|
+
Input,
|
|
14
|
+
{
|
|
15
|
+
ref,
|
|
16
|
+
type: "time",
|
|
17
|
+
step: minuteStep * 60,
|
|
18
|
+
value,
|
|
19
|
+
onChange: (e) => setValue(e.target.value),
|
|
20
|
+
...props
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
export {
|
|
25
|
+
TimePicker
|
|
26
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface TreeNode {
|
|
3
|
+
id: string;
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
/** Text for type-ahead when `label` isn't a string. */
|
|
6
|
+
textValue?: string;
|
|
7
|
+
icon?: React.ReactNode;
|
|
8
|
+
children?: TreeNode[];
|
|
9
|
+
/** Children exist but aren't loaded yet: expanding calls `onLoadChildren`. */
|
|
10
|
+
hasChildren?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface TreeViewProps {
|
|
14
|
+
nodes: TreeNode[];
|
|
15
|
+
'aria-label'?: string;
|
|
16
|
+
'aria-labelledby'?: string;
|
|
17
|
+
expanded?: string[];
|
|
18
|
+
defaultExpanded?: string[];
|
|
19
|
+
onExpandedChange?: (ids: string[]) => void;
|
|
20
|
+
/** Single selection (ignored when `checkable`). */
|
|
21
|
+
selected?: string | null;
|
|
22
|
+
defaultSelected?: string | null;
|
|
23
|
+
onSelectedChange?: (id: string | null) => void;
|
|
24
|
+
/** Checkboxes with tri-state parents; `checked` lists every checked id, parents included. */
|
|
25
|
+
checkable?: boolean;
|
|
26
|
+
checked?: string[];
|
|
27
|
+
defaultChecked?: string[];
|
|
28
|
+
onCheckedChange?: (ids: string[]) => void;
|
|
29
|
+
/** Load a `hasChildren` node's children, then pass them in through `nodes`. */
|
|
30
|
+
onLoadChildren?: (node: TreeNode) => Promise<unknown>;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A hierarchy people can browse with the keyboard (ARIA tree): Arrow keys move
|
|
35
|
+
* and expand, Home/End jump, typing finds an item. Optional lazy children and
|
|
36
|
+
* tri-state checkboxes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function TreeView({ nodes, expanded: expandedProp, defaultExpanded, onExpandedChange, selected: selectedProp, defaultSelected, onSelectedChange, checkable, checked: checkedProp, defaultChecked, onCheckedChange, onLoadChildren, className, ...aria }: TreeViewProps): React.JSX.Element;
|