@tokis/react 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +8 -6
  2. package/dist/__tests__/Accordion.test.d.ts +2 -0
  3. package/dist/__tests__/Accordion.test.d.ts.map +1 -0
  4. package/dist/__tests__/Accordion.test.js +144 -0
  5. package/dist/__tests__/Accordion.test.js.map +1 -0
  6. package/dist/__tests__/Button.test.d.ts +2 -0
  7. package/dist/__tests__/Button.test.d.ts.map +1 -0
  8. package/dist/__tests__/Button.test.js +104 -0
  9. package/dist/__tests__/Button.test.js.map +1 -0
  10. package/dist/__tests__/Checkbox.test.d.ts +2 -0
  11. package/dist/__tests__/Checkbox.test.d.ts.map +1 -0
  12. package/dist/__tests__/Checkbox.test.js +90 -0
  13. package/dist/__tests__/Checkbox.test.js.map +1 -0
  14. package/dist/__tests__/Select.test.d.ts +2 -0
  15. package/dist/__tests__/Select.test.d.ts.map +1 -0
  16. package/dist/__tests__/Select.test.js +119 -0
  17. package/dist/__tests__/Select.test.js.map +1 -0
  18. package/dist/__tests__/datagrid-utils.test.d.ts +2 -0
  19. package/dist/__tests__/datagrid-utils.test.d.ts.map +1 -0
  20. package/dist/__tests__/datagrid-utils.test.js +190 -0
  21. package/dist/__tests__/datagrid-utils.test.js.map +1 -0
  22. package/dist/__tests__/date-utils.test.d.ts +2 -0
  23. package/dist/__tests__/date-utils.test.d.ts.map +1 -0
  24. package/dist/__tests__/date-utils.test.js +180 -0
  25. package/dist/__tests__/date-utils.test.js.map +1 -0
  26. package/dist/cjs/components/accordion/index.js +1 -1
  27. package/dist/cjs/components/checkbox/index.js +2 -1
  28. package/dist/cjs/components/datagrid/index.js +161 -0
  29. package/dist/cjs/components/datagrid/types.js +2 -0
  30. package/dist/cjs/components/datagrid/utils.js +87 -0
  31. package/dist/cjs/components/datepicker/Calendar.js +117 -0
  32. package/dist/cjs/components/datepicker/date-utils.js +121 -0
  33. package/dist/cjs/components/datepicker/index.js +134 -0
  34. package/dist/cjs/components/extended/index.js +39 -31
  35. package/dist/cjs/components/layout/index.js +5 -4
  36. package/dist/cjs/components/treeview/index.js +1 -1
  37. package/dist/cjs/hooks/useId.js +18 -5
  38. package/dist/cjs/index.js +2 -0
  39. package/dist/components/accordion/index.js +1 -1
  40. package/dist/components/accordion/index.js.map +1 -1
  41. package/dist/components/checkbox/index.js +2 -1
  42. package/dist/components/checkbox/index.js.map +1 -1
  43. package/dist/components/datagrid/index.d.ts +20 -0
  44. package/dist/components/datagrid/index.d.ts.map +1 -0
  45. package/dist/components/datagrid/index.js +159 -0
  46. package/dist/components/datagrid/index.js.map +1 -0
  47. package/dist/components/datagrid/types.d.ts +92 -0
  48. package/dist/components/datagrid/types.d.ts.map +1 -0
  49. package/dist/components/datagrid/types.js +2 -0
  50. package/dist/components/datagrid/types.js.map +1 -0
  51. package/dist/components/datagrid/utils.d.ts +14 -0
  52. package/dist/components/datagrid/utils.d.ts.map +1 -0
  53. package/dist/components/datagrid/utils.js +80 -0
  54. package/dist/components/datagrid/utils.js.map +1 -0
  55. package/dist/components/datepicker/Calendar.d.ts +30 -0
  56. package/dist/components/datepicker/Calendar.d.ts.map +1 -0
  57. package/dist/components/datepicker/Calendar.js +115 -0
  58. package/dist/components/datepicker/Calendar.js.map +1 -0
  59. package/dist/components/datepicker/date-utils.d.ts +34 -0
  60. package/dist/components/datepicker/date-utils.d.ts.map +1 -0
  61. package/dist/components/datepicker/date-utils.js +107 -0
  62. package/dist/components/datepicker/date-utils.js.map +1 -0
  63. package/dist/components/datepicker/index.d.ts +52 -0
  64. package/dist/components/datepicker/index.d.ts.map +1 -0
  65. package/dist/components/datepicker/index.js +128 -0
  66. package/dist/components/datepicker/index.js.map +1 -0
  67. package/dist/components/extended/index.d.ts +14 -29
  68. package/dist/components/extended/index.d.ts.map +1 -1
  69. package/dist/components/extended/index.js +40 -27
  70. package/dist/components/extended/index.js.map +1 -1
  71. package/dist/components/layout/index.d.ts.map +1 -1
  72. package/dist/components/layout/index.js +5 -4
  73. package/dist/components/layout/index.js.map +1 -1
  74. package/dist/components/treeview/index.js +1 -1
  75. package/dist/components/treeview/index.js.map +1 -1
  76. package/dist/hooks/useId.d.ts +13 -3
  77. package/dist/hooks/useId.d.ts.map +1 -1
  78. package/dist/hooks/useId.js +19 -6
  79. package/dist/hooks/useId.js.map +1 -1
  80. package/dist/index.d.ts +2 -0
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +2 -0
  83. package/dist/index.js.map +1 -1
  84. package/package.json +7 -7
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCellValue = getCellValue;
4
+ exports.sortRows = sortRows;
5
+ exports.filterRows = filterRows;
6
+ exports.nextSortDirection = nextSortDirection;
7
+ exports.paginateRows = paginateRows;
8
+ exports.computeColumnWidths = computeColumnWidths;
9
+ /** Extract a comparable value from a row cell. */
10
+ function getCellValue(row, col) {
11
+ return col.valueGetter ? col.valueGetter(row) : row[col.field];
12
+ }
13
+ /** Sort rows by a single column. Returns a new array. */
14
+ function sortRows(rows, sortModel, columns) {
15
+ if (!sortModel.direction)
16
+ return rows;
17
+ const col = columns.find((c) => c.field === sortModel.field);
18
+ if (!col)
19
+ return rows;
20
+ return [...rows].sort((a, b) => {
21
+ const av = getCellValue(a, col);
22
+ const bv = getCellValue(b, col);
23
+ let cmp = 0;
24
+ if (typeof av === 'number' && typeof bv === 'number') {
25
+ cmp = av - bv;
26
+ }
27
+ else {
28
+ cmp = String(av ?? '').localeCompare(String(bv ?? ''), undefined, { numeric: true, sensitivity: 'base' });
29
+ }
30
+ return sortModel.direction === 'asc' ? cmp : -cmp;
31
+ });
32
+ }
33
+ /** Filter rows based on the FilterModel. Returns a new array. */
34
+ function filterRows(rows, filterModel, columns) {
35
+ let result = rows;
36
+ // Global quick filter
37
+ const q = filterModel.quickFilter?.trim().toLowerCase();
38
+ if (q) {
39
+ const filterableCols = columns.filter((c) => c.filterable !== false);
40
+ result = result.filter((row) => filterableCols.some((col) => {
41
+ const v = getCellValue(row, col);
42
+ return String(v ?? '').toLowerCase().includes(q);
43
+ }));
44
+ }
45
+ // Per-column filters
46
+ const colFilters = filterModel.columnFilters ?? {};
47
+ const activeColFilters = Object.entries(colFilters).filter(([, v]) => v.trim() !== '');
48
+ if (activeColFilters.length > 0) {
49
+ result = result.filter((row) => activeColFilters.every(([field, filterStr]) => {
50
+ const col = columns.find((c) => c.field === field);
51
+ if (!col)
52
+ return true;
53
+ const v = getCellValue(row, col);
54
+ return String(v ?? '').toLowerCase().includes(filterStr.toLowerCase());
55
+ }));
56
+ }
57
+ return result;
58
+ }
59
+ /** Cycle sort direction: null → asc → desc → null */
60
+ function nextSortDirection(currentField, currentModel, clickedField) {
61
+ if (currentModel.field !== clickedField || currentModel.direction === null) {
62
+ return { field: clickedField, direction: 'asc' };
63
+ }
64
+ if (currentModel.direction === 'asc') {
65
+ return { field: clickedField, direction: 'desc' };
66
+ }
67
+ return { field: clickedField, direction: null };
68
+ }
69
+ /** Paginate an array. Returns the slice for the given page. */
70
+ function paginateRows(rows, page, pageSize) {
71
+ const start = page * pageSize;
72
+ return rows.slice(start, start + pageSize);
73
+ }
74
+ /** Compute column widths from fixed + flex definitions. */
75
+ function computeColumnWidths(columns, totalWidth) {
76
+ const fixed = columns.map((c) => c.width ?? 0);
77
+ const totalFixed = fixed.reduce((s, w) => s + w, 0);
78
+ const totalFlex = columns.reduce((s, c) => s + (c.flex ?? 0), 0);
79
+ const remaining = Math.max(0, totalWidth - totalFixed);
80
+ return columns.map((col, i) => {
81
+ if (col.width)
82
+ return col.width;
83
+ if (totalFlex > 0 && col.flex)
84
+ return (col.flex / totalFlex) * remaining;
85
+ return fixed[i] || 120; // fallback
86
+ });
87
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Calendar = Calendar;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * Calendar — standalone month-grid date picker widget.
7
+ *
8
+ * Implements WAI-ARIA Calendar / Date Grid pattern:
9
+ * - role="grid" on the day grid
10
+ * - role="gridcell" on each day
11
+ * - Arrow keys navigate within the grid
12
+ * - Enter/Space selects the focused date
13
+ * - Page Up/Down → prev/next month
14
+ * - Ctrl+Page Up/Down → prev/next year
15
+ * - Home/End → first/last day of current week
16
+ */
17
+ const react_1 = require("react");
18
+ const date_utils_js_1 = require("./date-utils");
19
+ const cn_js_1 = require("../../utils/cn");
20
+ function Calendar({ value, onChange, min, max, defaultMonth, className }) {
21
+ const selectedDate = (0, date_utils_js_1.parseDate)(value);
22
+ const minDate = (0, date_utils_js_1.parseDate)(min);
23
+ const maxDate = (0, date_utils_js_1.parseDate)(max);
24
+ const todayDate = (0, date_utils_js_1.today)();
25
+ const initialYear = defaultMonth?.year ?? selectedDate?.getFullYear() ?? todayDate.getFullYear();
26
+ const initialMonth = defaultMonth?.month ?? selectedDate?.getMonth() ?? todayDate.getMonth();
27
+ const [viewYear, setViewYear] = (0, react_1.useState)(initialYear);
28
+ const [viewMonth, setViewMonth] = (0, react_1.useState)(initialMonth);
29
+ const [focusedDate, setFocusedDate] = (0, react_1.useState)(selectedDate ?? todayDate);
30
+ const gridRef = (0, react_1.useRef)(null);
31
+ const grid = (0, date_utils_js_1.buildCalendarGrid)(viewYear, viewMonth);
32
+ const navigateMonth = (delta) => {
33
+ setViewMonth((m) => {
34
+ const next = m + delta;
35
+ if (next < 0) {
36
+ setViewYear((y) => y - 1);
37
+ return 11;
38
+ }
39
+ if (next > 11) {
40
+ setViewYear((y) => y + 1);
41
+ return 0;
42
+ }
43
+ return next;
44
+ });
45
+ };
46
+ const navigateYear = (delta) => {
47
+ setViewYear((y) => y + delta);
48
+ };
49
+ const selectDate = (0, react_1.useCallback)((date) => {
50
+ if (minDate && (0, date_utils_js_1.isBeforeDay)(date, minDate))
51
+ return;
52
+ if (maxDate && (0, date_utils_js_1.isAfterDay)(date, maxDate))
53
+ return;
54
+ onChange?.((0, date_utils_js_1.formatDate)(date));
55
+ }, [onChange, minDate, maxDate]);
56
+ const handleKeyDown = (e) => {
57
+ let next = new Date(focusedDate);
58
+ switch (e.key) {
59
+ case 'ArrowRight':
60
+ next.setDate(next.getDate() + 1);
61
+ break;
62
+ case 'ArrowLeft':
63
+ next.setDate(next.getDate() - 1);
64
+ break;
65
+ case 'ArrowDown':
66
+ next.setDate(next.getDate() + 7);
67
+ break;
68
+ case 'ArrowUp':
69
+ next.setDate(next.getDate() - 7);
70
+ break;
71
+ case 'Home':
72
+ next = new Date(next.getFullYear(), next.getMonth(), 1);
73
+ break;
74
+ case 'End':
75
+ next = new Date(next.getFullYear(), next.getMonth(), (0, date_utils_js_1.daysInMonth)(next.getFullYear(), next.getMonth()));
76
+ break;
77
+ case 'PageUp':
78
+ e.ctrlKey ? navigateYear(-1) : navigateMonth(-1);
79
+ return;
80
+ case 'PageDown':
81
+ e.ctrlKey ? navigateYear(1) : navigateMonth(1);
82
+ return;
83
+ case 'Enter':
84
+ case ' ':
85
+ e.preventDefault();
86
+ selectDate(focusedDate);
87
+ return;
88
+ default: return;
89
+ }
90
+ e.preventDefault();
91
+ setFocusedDate(next);
92
+ // Auto-navigate view if the focused date is outside the current view month
93
+ if (next.getFullYear() !== viewYear || next.getMonth() !== viewMonth) {
94
+ setViewYear(next.getFullYear());
95
+ setViewMonth(next.getMonth());
96
+ }
97
+ // Re-focus the cell after state update
98
+ requestAnimationFrame(() => {
99
+ const cell = gridRef.current?.querySelector('[tabindex="0"]');
100
+ cell?.focus();
101
+ });
102
+ };
103
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)('tokis-calendar', className), role: "group", "aria-label": "Calendar", children: [(0, jsx_runtime_1.jsxs)("div", { className: "tokis-calendar-nav", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "tokis-calendar-nav-btn", onClick: () => navigateYear(-1), "aria-label": "Previous year", children: "\u00AB" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tokis-calendar-nav-btn", onClick: () => navigateMonth(-1), "aria-label": "Previous month", children: "\u2039" }), (0, jsx_runtime_1.jsxs)("span", { className: "tokis-calendar-title", "aria-live": "polite", "aria-atomic": "true", children: [date_utils_js_1.MONTHS[viewMonth], " ", viewYear] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tokis-calendar-nav-btn", onClick: () => navigateMonth(1), "aria-label": "Next month", children: "\u203A" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tokis-calendar-nav-btn", onClick: () => navigateYear(1), "aria-label": "Next year", children: "\u00BB" })] }), (0, jsx_runtime_1.jsxs)("table", { ref: gridRef, className: "tokis-calendar-grid", role: "grid", "aria-label": `${date_utils_js_1.MONTHS[viewMonth]} ${viewYear}`, onKeyDown: handleKeyDown, children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsx)("tr", { role: "row", children: date_utils_js_1.DAYS_OF_WEEK.map((d) => ((0, jsx_runtime_1.jsx)("th", { role: "columnheader", abbr: d, className: "tokis-calendar-weekday", scope: "col", children: d }, d))) }) }), (0, jsx_runtime_1.jsx)("tbody", { children: grid.map((week, wi) => ((0, jsx_runtime_1.jsx)("tr", { role: "row", children: week.map((day) => {
104
+ const isCurrentMonth = day.getMonth() === viewMonth;
105
+ const isSelected = selectedDate ? (0, date_utils_js_1.isSameDay)(day, selectedDate) : false;
106
+ const isToday = (0, date_utils_js_1.isSameDay)(day, todayDate);
107
+ const isFocused = (0, date_utils_js_1.isSameDay)(day, focusedDate);
108
+ const isDisabled = Boolean((minDate && (0, date_utils_js_1.isBeforeDay)(day, minDate)) ||
109
+ (maxDate && (0, date_utils_js_1.isAfterDay)(day, maxDate)));
110
+ return ((0, jsx_runtime_1.jsx)("td", { role: "gridcell", "aria-selected": isSelected, "aria-disabled": isDisabled, "aria-label": day.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }), "aria-current": isToday ? 'date' : undefined, tabIndex: isFocused ? 0 : -1, className: (0, cn_js_1.cn)('tokis-calendar-day', !isCurrentMonth && 'tokis-calendar-day--outside', isSelected && 'tokis-calendar-day--selected', isToday && 'tokis-calendar-day--today', isDisabled && 'tokis-calendar-day--disabled', isFocused && 'tokis-calendar-day--focused'), onClick: () => !isDisabled && selectDate(day), onFocus: () => setFocusedDate(day), children: day.getDate() }, day.toISOString()));
111
+ }) }, wi))) })] }), (0, jsx_runtime_1.jsx)("div", { className: "tokis-calendar-footer", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "tokis-calendar-today-btn", onClick: () => {
112
+ setViewYear(todayDate.getFullYear());
113
+ setViewMonth(todayDate.getMonth());
114
+ setFocusedDate(todayDate);
115
+ selectDate(todayDate);
116
+ }, children: "Today" }) })] }));
117
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /**
3
+ * Zero-dependency date utilities for the DatePicker calendar.
4
+ * All functions operate on plain JS Date objects or ISO YYYY-MM-DD strings.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MONTHS = exports.DAYS_OF_WEEK = void 0;
8
+ exports.parseDate = parseDate;
9
+ exports.formatDate = formatDate;
10
+ exports.formatDisplayDate = formatDisplayDate;
11
+ exports.startOfMonth = startOfMonth;
12
+ exports.daysInMonth = daysInMonth;
13
+ exports.buildCalendarGrid = buildCalendarGrid;
14
+ exports.isSameDay = isSameDay;
15
+ exports.isBeforeDay = isBeforeDay;
16
+ exports.isAfterDay = isAfterDay;
17
+ exports.today = today;
18
+ exports.parseTime = parseTime;
19
+ exports.formatTime = formatTime;
20
+ exports.DAYS_OF_WEEK = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
21
+ exports.MONTHS = [
22
+ 'January', 'February', 'March', 'April', 'May', 'June',
23
+ 'July', 'August', 'September', 'October', 'November', 'December',
24
+ ];
25
+ /** Parse a YYYY-MM-DD string → local Date (midnight). Returns null if invalid. */
26
+ function parseDate(iso) {
27
+ if (!iso)
28
+ return null;
29
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
30
+ if (!m)
31
+ return null;
32
+ const [, y, mo, d] = m.map(Number);
33
+ const date = new Date(y, mo - 1, d);
34
+ // Guard against e.g. "2024-02-30" auto-rolling to March
35
+ if (date.getFullYear() !== y || date.getMonth() !== mo - 1 || date.getDate() !== d)
36
+ return null;
37
+ return date;
38
+ }
39
+ /** Format a Date → YYYY-MM-DD string. */
40
+ function formatDate(date) {
41
+ const y = date.getFullYear();
42
+ const m = String(date.getMonth() + 1).padStart(2, '0');
43
+ const d = String(date.getDate()).padStart(2, '0');
44
+ return `${y}-${m}-${d}`;
45
+ }
46
+ /** Format a Date for the input display label (e.g., "Jan 15, 2025"). */
47
+ function formatDisplayDate(date) {
48
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
49
+ }
50
+ /** Return the first day of the month for a given year/month (0-indexed). */
51
+ function startOfMonth(year, month) {
52
+ return new Date(year, month, 1);
53
+ }
54
+ /** Return the number of days in a month. */
55
+ function daysInMonth(year, month) {
56
+ return new Date(year, month + 1, 0).getDate();
57
+ }
58
+ /** Build the 6×7 calendar grid for a given year/month. Returns Date[][] (some days in prev/next month). */
59
+ function buildCalendarGrid(year, month) {
60
+ const firstDay = startOfMonth(year, month).getDay(); // 0=Sun
61
+ const totalDays = daysInMonth(year, month);
62
+ const cells = [];
63
+ // Pad with days from previous month
64
+ const prevMonthDays = daysInMonth(year, month - 1 < 0 ? 11 : month - 1);
65
+ const prevYear = month === 0 ? year - 1 : year;
66
+ const prevMonth = month === 0 ? 11 : month - 1;
67
+ for (let i = firstDay - 1; i >= 0; i--) {
68
+ cells.push(new Date(prevYear, prevMonth, prevMonthDays - i));
69
+ }
70
+ // Current month days
71
+ for (let d = 1; d <= totalDays; d++) {
72
+ cells.push(new Date(year, month, d));
73
+ }
74
+ // Pad to complete 6 rows (42 cells)
75
+ const nextYear = month === 11 ? year + 1 : year;
76
+ const nextMonth = month === 11 ? 0 : month + 1;
77
+ let nextDay = 1;
78
+ while (cells.length < 42) {
79
+ cells.push(new Date(nextYear, nextMonth, nextDay++));
80
+ }
81
+ // Split into 6 rows of 7
82
+ const rows = [];
83
+ for (let r = 0; r < 6; r++) {
84
+ rows.push(cells.slice(r * 7, r * 7 + 7));
85
+ }
86
+ return rows;
87
+ }
88
+ /** Returns true if two dates represent the same calendar day. */
89
+ function isSameDay(a, b) {
90
+ return a.getFullYear() === b.getFullYear()
91
+ && a.getMonth() === b.getMonth()
92
+ && a.getDate() === b.getDate();
93
+ }
94
+ /** Returns true if a date is before the given min date (by day). */
95
+ function isBeforeDay(date, min) {
96
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
97
+ const m = new Date(min.getFullYear(), min.getMonth(), min.getDate());
98
+ return d < m;
99
+ }
100
+ /** Returns true if a date is after the given max date (by day). */
101
+ function isAfterDay(date, max) {
102
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
103
+ const m = new Date(max.getFullYear(), max.getMonth(), max.getDate());
104
+ return d > m;
105
+ }
106
+ /** Returns today as a local midnight Date. */
107
+ function today() {
108
+ const t = new Date();
109
+ return new Date(t.getFullYear(), t.getMonth(), t.getDate());
110
+ }
111
+ /** Parse a HH:MM string into { hours, minutes }. */
112
+ function parseTime(t) {
113
+ if (!t)
114
+ return { hours: 0, minutes: 0 };
115
+ const [h, m] = t.split(':').map(Number);
116
+ return { hours: h ?? 0, minutes: m ?? 0 };
117
+ }
118
+ /** Format { hours, minutes } → HH:MM string. */
119
+ function formatTime(hours, minutes) {
120
+ return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
121
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Calendar = void 0;
4
+ exports.DatePicker = DatePicker;
5
+ exports.TimePicker = TimePicker;
6
+ exports.DateTimePicker = DateTimePicker;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ /**
9
+ * DatePicker, TimePicker, DateTimePicker
10
+ *
11
+ * All three share a pattern: a trigger input that opens a Portal-mounted popup.
12
+ * DatePicker uses the Calendar grid widget.
13
+ * TimePicker uses scroll-select columns for hours/minutes.
14
+ * DateTimePicker combines both.
15
+ */
16
+ const react_1 = require("react");
17
+ const index_js_1 = require("../portal/index");
18
+ const useId_js_1 = require("../../hooks/useId");
19
+ const cn_js_1 = require("../../utils/cn");
20
+ const Calendar_js_1 = require("./Calendar");
21
+ const date_utils_js_1 = require("./date-utils");
22
+ // ─── Shared floating panel hook ───────────────────────────────────────────────
23
+ function useFloatingPanel(onClose) {
24
+ const [open, setOpen] = (0, react_1.useState)(false);
25
+ const triggerRef = (0, react_1.useRef)(null);
26
+ const panelRef = (0, react_1.useRef)(null);
27
+ const [pos, setPos] = (0, react_1.useState)({ top: 0, left: 0 });
28
+ const updatePos = (0, react_1.useCallback)(() => {
29
+ const el = triggerRef.current;
30
+ if (!el)
31
+ return;
32
+ const rect = el.getBoundingClientRect();
33
+ setPos({
34
+ top: rect.bottom + window.scrollY + 4,
35
+ left: rect.left + window.scrollX,
36
+ });
37
+ }, []);
38
+ const openPanel = () => {
39
+ updatePos();
40
+ setOpen(true);
41
+ };
42
+ const closePanel = (0, react_1.useCallback)(() => {
43
+ setOpen(false);
44
+ onClose?.();
45
+ triggerRef.current?.focus();
46
+ }, [onClose]);
47
+ // Close on outside click
48
+ (0, react_1.useEffect)(() => {
49
+ if (!open)
50
+ return;
51
+ const handler = (e) => {
52
+ if (!triggerRef.current?.contains(e.target) &&
53
+ !panelRef.current?.contains(e.target)) {
54
+ closePanel();
55
+ }
56
+ };
57
+ document.addEventListener('mousedown', handler);
58
+ return () => document.removeEventListener('mousedown', handler);
59
+ }, [open, closePanel]);
60
+ // Close on Escape
61
+ (0, react_1.useEffect)(() => {
62
+ if (!open)
63
+ return;
64
+ const handler = (e) => { if (e.key === 'Escape')
65
+ closePanel(); };
66
+ document.addEventListener('keydown', handler);
67
+ return () => document.removeEventListener('keydown', handler);
68
+ }, [open, closePanel]);
69
+ return { open, openPanel, closePanel, triggerRef, panelRef, pos };
70
+ }
71
+ function DatePicker({ value: valueProp, defaultValue, onChange, label, placeholder = 'Pick a date', disabled = false, required = false, error = false, min, max, className, }) {
72
+ const [internalValue, setInternalValue] = (0, react_1.useState)(defaultValue);
73
+ const value = valueProp !== undefined ? valueProp : internalValue;
74
+ const id = (0, useId_js_1.useId)('datepicker');
75
+ const parsedDate = (0, date_utils_js_1.parseDate)(value);
76
+ const { open, openPanel, closePanel, triggerRef, panelRef, pos } = useFloatingPanel();
77
+ const handleSelect = (iso) => {
78
+ if (valueProp === undefined)
79
+ setInternalValue(iso);
80
+ onChange?.(iso);
81
+ closePanel();
82
+ };
83
+ const handleClear = (e) => {
84
+ e.stopPropagation();
85
+ if (valueProp === undefined)
86
+ setInternalValue(undefined);
87
+ onChange?.(undefined);
88
+ };
89
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)('tokis-datepicker', error && 'tokis-datepicker--error', disabled && 'tokis-datepicker--disabled', className), children: [label && ((0, jsx_runtime_1.jsxs)("label", { className: "tokis-label", htmlFor: id, children: [label, required && (0, jsx_runtime_1.jsx)("span", { className: "tokis-label-required", "aria-hidden": "true", children: " *" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "tokis-datepicker-input-wrap", children: [(0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, id: id, type: "button", className: (0, cn_js_1.cn)('tokis-datepicker-trigger', open && 'tokis-datepicker-trigger--open'), disabled: disabled, "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": label ? `${label}: ${parsedDate ? (0, date_utils_js_1.formatDisplayDate)(parsedDate) : 'not set'}` : undefined, onClick: () => (open ? closePanel() : openPanel()), children: [(0, jsx_runtime_1.jsx)("span", { className: (0, cn_js_1.cn)('tokis-datepicker-value', !parsedDate && 'tokis-datepicker-placeholder'), children: parsedDate ? (0, date_utils_js_1.formatDisplayDate)(parsedDate) : placeholder }), (0, jsx_runtime_1.jsx)("span", { className: "tokis-datepicker-icon", "aria-hidden": "true", children: "\uD83D\uDCC5" })] }), parsedDate && !disabled && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "tokis-datepicker-clear", "aria-label": "Clear date", onClick: handleClear, children: "\u2715" }))] }), open && ((0, jsx_runtime_1.jsx)(index_js_1.Portal, { children: (0, jsx_runtime_1.jsx)("div", { ref: panelRef, className: "tokis-datepicker-panel", role: "dialog", "aria-modal": "false", "aria-label": label ?? 'Date picker', style: { top: pos.top, left: pos.left }, children: (0, jsx_runtime_1.jsx)(Calendar_js_1.Calendar, { value: value, onChange: handleSelect, min: min, max: max }) }) }))] }));
90
+ }
91
+ function TimePicker({ value: valueProp, defaultValue, onChange, label, disabled = false, required = false, error = false, hourFormat = 24, minuteStep = 1, className, }) {
92
+ const [internalValue, setInternalValue] = (0, react_1.useState)(defaultValue ?? '00:00');
93
+ const value = valueProp !== undefined ? valueProp : internalValue;
94
+ const { hours, minutes } = (0, date_utils_js_1.parseTime)(value);
95
+ const id = (0, useId_js_1.useId)('timepicker');
96
+ const setTime = (h, m) => {
97
+ const next = (0, date_utils_js_1.formatTime)(h, m);
98
+ if (valueProp === undefined)
99
+ setInternalValue(next);
100
+ onChange?.(next);
101
+ };
102
+ const hourOptions = Array.from({ length: hourFormat === 12 ? 12 : 24 }, (_, i) => hourFormat === 12 ? i + 1 : i);
103
+ const minuteOptions = [];
104
+ for (let m = 0; m < 60; m += minuteStep)
105
+ minuteOptions.push(m);
106
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)('tokis-timepicker', error && 'tokis-timepicker--error', disabled && 'tokis-timepicker--disabled', className), children: [label && ((0, jsx_runtime_1.jsxs)("label", { className: "tokis-label", htmlFor: `${id}-hours`, children: [label, required && (0, jsx_runtime_1.jsx)("span", { className: "tokis-label-required", "aria-hidden": "true", children: " *" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "tokis-timepicker-inputs", children: [(0, jsx_runtime_1.jsx)("select", { id: `${id}-hours`, className: "tokis-timepicker-select", value: hours, disabled: disabled, "aria-label": "Hours", onChange: (e) => setTime(Number(e.target.value), minutes), children: hourOptions.map((h) => ((0, jsx_runtime_1.jsx)("option", { value: h, children: String(h).padStart(2, '0') }, h))) }), (0, jsx_runtime_1.jsx)("span", { className: "tokis-timepicker-sep", "aria-hidden": "true", children: ":" }), (0, jsx_runtime_1.jsx)("select", { id: `${id}-minutes`, className: "tokis-timepicker-select", value: minutes, disabled: disabled, "aria-label": "Minutes", onChange: (e) => setTime(hours, Number(e.target.value)), children: minuteOptions.map((m) => ((0, jsx_runtime_1.jsx)("option", { value: m, children: String(m).padStart(2, '0') }, m))) }), hourFormat === 12 && ((0, jsx_runtime_1.jsxs)("select", { className: "tokis-timepicker-select tokis-timepicker-ampm", value: hours < 12 ? 'AM' : 'PM', disabled: disabled, "aria-label": "AM/PM", onChange: (e) => {
107
+ const ampm = e.target.value;
108
+ let h = hours % 12;
109
+ if (ampm === 'PM')
110
+ h += 12;
111
+ setTime(h, minutes);
112
+ }, children: [(0, jsx_runtime_1.jsx)("option", { value: "AM", children: "AM" }), (0, jsx_runtime_1.jsx)("option", { value: "PM", children: "PM" })] }))] })] }));
113
+ }
114
+ function DateTimePicker({ value: valueProp, defaultValue, onChange, minuteStep = 1, hourFormat = 24, ...dateProps }) {
115
+ const split = (v) => {
116
+ if (!v)
117
+ return { date: undefined, time: '00:00' };
118
+ const [date, time] = v.split('T');
119
+ return { date, time: time ?? '00:00' };
120
+ };
121
+ const [internalValue, setInternalValue] = (0, react_1.useState)(defaultValue);
122
+ const value = valueProp !== undefined ? valueProp : internalValue;
123
+ const { date, time } = split(value);
124
+ const emit = (0, react_1.useCallback)((d, t) => {
125
+ const next = d ? `${d}T${t ?? '00:00'}` : undefined;
126
+ if (valueProp === undefined)
127
+ setInternalValue(next);
128
+ onChange?.(next);
129
+ }, [valueProp, onChange]);
130
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "tokis-datetimepicker", children: [(0, jsx_runtime_1.jsx)(DatePicker, { ...dateProps, value: date, onChange: (d) => emit(d, time) }), (0, jsx_runtime_1.jsx)(TimePicker, { value: time, disabled: dateProps.disabled, minuteStep: minuteStep, hourFormat: hourFormat, onChange: (t) => emit(date, t) })] }));
131
+ }
132
+ // Re-export Calendar for standalone use
133
+ var Calendar_js_2 = require("./Calendar");
134
+ Object.defineProperty(exports, "Calendar", { enumerable: true, get: function () { return Calendar_js_2.Calendar; } });
@@ -41,7 +41,6 @@ exports.Rating = Rating;
41
41
  exports.StarRating = Rating;
42
42
  exports.TransferList = TransferList;
43
43
  exports.Icon = Icon;
44
- exports.MaterialIcon = MaterialIcon;
45
44
  exports.Backdrop = Backdrop;
46
45
  exports.Paper = Paper;
47
46
  exports.SpeedDial = SpeedDial;
@@ -58,19 +57,15 @@ exports.Popper = Popper;
58
57
  exports.TextareaAutosize = TextareaAutosize;
59
58
  exports.Fade = Fade;
60
59
  exports.useMediaQuery = useMediaQuery;
61
- exports.DataGrid = DataGrid;
62
- exports.DatePicker = DatePicker;
63
- exports.TimePicker = TimePicker;
64
- exports.DateTimePicker = DateTimePicker;
65
60
  exports.Charts = Charts;
66
61
  const jsx_runtime_1 = require("react/jsx-runtime");
67
62
  const react_1 = __importStar(require("react"));
63
+ const core_1 = require("@tokis/core");
68
64
  const cn_js_1 = require("../../utils/cn");
69
65
  const index_js_1 = require("../input/index");
70
66
  const index_js_2 = require("../button/index");
71
67
  const index_js_3 = require("../portal/index");
72
68
  const index_js_4 = require("../card/index");
73
- const index_js_5 = require("../table/index");
74
69
  function Autocomplete({ options, value, onChange, label, placeholder = 'Search...', ...props }) {
75
70
  const [query, setQuery] = (0, react_1.useState)(value ?? '');
76
71
  const [open, setOpen] = (0, react_1.useState)(false);
@@ -128,19 +123,12 @@ function TransferList({ left, right, onChange, leftTitle = 'Available', rightTit
128
123
  const renderPane = (title, items, checked, setChecked) => ((0, jsx_runtime_1.jsxs)(index_js_4.Card, { className: "tokis-transfer-pane", children: [(0, jsx_runtime_1.jsx)(index_js_4.CardHeader, { children: title }), (0, jsx_runtime_1.jsx)(index_js_4.CardBody, { children: items.map((item) => ((0, jsx_runtime_1.jsxs)("label", { className: "tokis-transfer-item", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: checked.includes(item.id), onChange: (e) => setChecked(e.target.checked ? [...checked, item.id] : checked.filter((id) => id !== item.id)) }), item.label] }, item.id))) })] }));
129
124
  return ((0, jsx_runtime_1.jsxs)("div", { className: "tokis-transfer-list", children: [renderPane(leftTitle, left, leftChecked, setLeftChecked), (0, jsx_runtime_1.jsxs)("div", { className: "tokis-transfer-actions", children: [(0, jsx_runtime_1.jsx)(index_js_2.ButtonRoot, { size: "sm", onClick: moveToRight, children: '>' }), (0, jsx_runtime_1.jsx)(index_js_2.ButtonRoot, { size: "sm", onClick: moveToLeft, children: '<' })] }), renderPane(rightTitle, right, rightChecked, setRightChecked)] }));
130
125
  }
131
- const iconMap = {
132
- search: '⌕',
133
- close: '×',
134
- menu: '☰',
135
- check: '✓',
136
- star: '★',
137
- 'arrow-right': '→',
126
+ const _emojiIconMap = {
127
+ search: '⌕', close: '×', menu: '☰', check: '✓', star: '★', 'arrow-right': '→',
138
128
  };
129
+ /** @deprecated Use named SVG icons from `@tokis/icons`. */
139
130
  function Icon({ name, className, ...props }) {
140
- return (0, jsx_runtime_1.jsx)("span", { className: (0, cn_js_1.cn)('tokis-icon', className), "aria-hidden": "true", ...props, children: iconMap[name] });
141
- }
142
- function MaterialIcon({ icon, className, ...props }) {
143
- return (0, jsx_runtime_1.jsx)("span", { className: (0, cn_js_1.cn)('tokis-material-icon', className), ...props, children: icon });
131
+ return (0, jsx_runtime_1.jsx)("span", { className: (0, cn_js_1.cn)('tokis-icon', className), "aria-hidden": "true", ...props, children: _emojiIconMap[name] });
144
132
  }
145
133
  function Backdrop({ open, onClick, className, children, ...props }) {
146
134
  if (!open)
@@ -252,10 +240,42 @@ function InitColorSchemeScript() {
252
240
  })();`;
253
241
  return (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: script } });
254
242
  }
255
- function Modal({ open, onClose, children }) {
243
+ function Modal({ open, onClose, children, title, description, closeOnBackdropClick = true, closeOnEscape = true, }) {
244
+ const contentRef = (0, react_1.useRef)(null);
245
+ const previousFocusRef = (0, react_1.useRef)(null);
246
+ const titleId = (0, react_1.useId)();
247
+ const descriptionId = (0, react_1.useId)();
248
+ // Lock scroll on open
249
+ (0, react_1.useEffect)(() => {
250
+ if (!open)
251
+ return;
252
+ const prev = document.body.style.overflow;
253
+ document.body.style.overflow = 'hidden';
254
+ return () => { document.body.style.overflow = prev; };
255
+ }, [open]);
256
+ // Trap focus when open
257
+ (0, react_1.useEffect)(() => {
258
+ if (!open || !contentRef.current)
259
+ return;
260
+ previousFocusRef.current = document.activeElement;
261
+ const release = (0, core_1.trapFocus)(contentRef.current);
262
+ return () => {
263
+ release?.();
264
+ previousFocusRef.current?.focus();
265
+ };
266
+ }, [open]);
267
+ // Close on Escape
268
+ (0, react_1.useEffect)(() => {
269
+ if (!open || !closeOnEscape)
270
+ return;
271
+ const handler = (e) => { if (e.key === 'Escape')
272
+ onClose(); };
273
+ document.addEventListener('keydown', handler);
274
+ return () => document.removeEventListener('keydown', handler);
275
+ }, [open, closeOnEscape, onClose]);
256
276
  if (!open)
257
277
  return null;
258
- return ((0, jsx_runtime_1.jsx)(index_js_3.Portal, { children: (0, jsx_runtime_1.jsx)("div", { className: "tokis-modal-root", role: "presentation", onClick: onClose, children: (0, jsx_runtime_1.jsx)("div", { className: "tokis-modal-content", role: "dialog", "aria-modal": "true", onClick: (e) => e.stopPropagation(), children: children }) }) }));
278
+ return ((0, jsx_runtime_1.jsx)(index_js_3.Portal, { children: (0, jsx_runtime_1.jsx)("div", { className: "tokis-modal-root", role: "presentation", onClick: closeOnBackdropClick ? onClose : undefined, children: (0, jsx_runtime_1.jsxs)("div", { ref: contentRef, className: "tokis-modal-content", role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined, "aria-describedby": description ? descriptionId : undefined, onClick: (e) => e.stopPropagation(), children: [title && (0, jsx_runtime_1.jsx)("span", { id: titleId, className: "tokis-modal-title", children: title }), description && (0, jsx_runtime_1.jsx)("span", { id: descriptionId, className: "tokis-modal-desc", children: description }), children] }) }) }));
259
279
  }
260
280
  function NoSsr({ children, fallback = null }) {
261
281
  const [mounted, setMounted] = (0, react_1.useState)(false);
@@ -311,18 +331,6 @@ function useMediaQuery(query) {
311
331
  }, [query]);
312
332
  return matches;
313
333
  }
314
- function DataGrid({ columns, rows }) {
315
- return ((0, jsx_runtime_1.jsxs)(index_js_5.Table, { children: [(0, jsx_runtime_1.jsx)(index_js_5.TableHead, { children: (0, jsx_runtime_1.jsx)(index_js_5.TableRow, { children: columns.map((column) => ((0, jsx_runtime_1.jsx)(index_js_5.TableHeaderCell, { children: column.headerName }, column.field))) }) }), (0, jsx_runtime_1.jsx)(index_js_5.TableBody, { children: rows.map((row, rowIndex) => ((0, jsx_runtime_1.jsx)(index_js_5.TableRow, { children: columns.map((column) => ((0, jsx_runtime_1.jsx)(index_js_5.TableCell, { children: row[column.field] }, column.field))) }, rowIndex))) })] }));
316
- }
317
- function DatePicker({ label, ...props }) {
318
- return (0, jsx_runtime_1.jsx)(index_js_1.TextField, { type: "date", label: label, ...props });
319
- }
320
- function TimePicker({ label, ...props }) {
321
- return (0, jsx_runtime_1.jsx)(index_js_1.TextField, { type: "time", label: label, ...props });
322
- }
323
- function DateTimePicker({ label, ...props }) {
324
- return (0, jsx_runtime_1.jsx)(index_js_1.TextField, { type: "datetime-local", label: label, ...props });
325
- }
326
334
  function Charts({ data, labels = [] }) {
327
335
  const max = Math.max(...data, 1);
328
336
  return ((0, jsx_runtime_1.jsx)("div", { className: "tokis-chart", role: "img", "aria-label": "Bar chart", children: data.map((value, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "tokis-chart-bar-wrap", children: [(0, jsx_runtime_1.jsx)("div", { className: "tokis-chart-bar", style: { height: `${(value / max) * 100}%` }, title: `${labels[index] ?? index}: ${value}` }), (0, jsx_runtime_1.jsx)("span", { className: "tokis-chart-label", children: labels[index] ?? index + 1 })] }, index))) }));
@@ -22,10 +22,11 @@ exports.Box = (0, react_1.forwardRef)(({ as: Component = 'div', display, flex, g
22
22
  flex,
23
23
  gap: gap !== undefined ? gapVar(gap) : undefined,
24
24
  padding: p !== undefined ? gapVar(p) : undefined,
25
- paddingLeft: px !== undefined ? gapVar(px) : undefined,
26
- paddingRight: px !== undefined ? gapVar(px) : undefined,
27
- paddingTop: py !== undefined ? gapVar(py) : undefined,
28
- paddingBottom: py !== undefined ? gapVar(py) : undefined,
25
+ // Logical properties for RTL support
26
+ paddingInlineStart: px !== undefined ? gapVar(px) : undefined,
27
+ paddingInlineEnd: px !== undefined ? gapVar(px) : undefined,
28
+ paddingBlockStart: py !== undefined ? gapVar(py) : undefined,
29
+ paddingBlockEnd: py !== undefined ? gapVar(py) : undefined,
29
30
  margin: m !== undefined ? gapVar(m) : undefined,
30
31
  width,
31
32
  height,
@@ -53,7 +53,7 @@ function TreeItem({ node, level, selected, expanded, onSelect, onExpand, multiSe
53
53
  break;
54
54
  }
55
55
  };
56
- return ((0, jsx_runtime_1.jsxs)("li", { role: "none", children: [(0, jsx_runtime_1.jsxs)("div", { role: "treeitem", "aria-expanded": hasChildren ? isExpanded : undefined, "aria-selected": isSelected, "aria-disabled": node.disabled, "aria-level": level, tabIndex: node.disabled ? -1 : 0, className: (0, cn_js_1.cn)('tokis-treeview__item', isSelected && 'tokis-treeview__item--selected', node.disabled && 'tokis-treeview__item--disabled'), style: { paddingLeft: `calc(${(level - 1) * 20}px + var(--tokis-spacing-2))` }, onClick: () => {
56
+ return ((0, jsx_runtime_1.jsxs)("li", { role: "none", children: [(0, jsx_runtime_1.jsxs)("div", { role: "treeitem", "aria-expanded": hasChildren ? isExpanded : undefined, "aria-selected": isSelected, "aria-disabled": node.disabled, "aria-level": level, tabIndex: node.disabled ? -1 : 0, className: (0, cn_js_1.cn)('tokis-treeview__item', isSelected && 'tokis-treeview__item--selected', node.disabled && 'tokis-treeview__item--disabled'), style: { paddingInlineStart: `calc(${(level - 1) * 20}px + var(--tokis-spacing-2))` }, onClick: () => {
57
57
  if (node.disabled)
58
58
  return;
59
59
  if (hasChildren)
@@ -2,12 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useId = useId;
4
4
  const react_1 = require("react");
5
- const core_1 = require("@tokis/core");
6
5
  /**
7
- * Returns a stable, unique ID for the lifetime of the component.
8
- * Prefer React 18's built-in `useId` for SSR-safe IDs.
9
- * Use this hook only when a custom prefix is needed.
6
+ * Returns a stable, SSR-safe, unique ID for the lifetime of the component.
7
+ * Delegates to React 18's built-in `useId` to guarantee consistent IDs
8
+ * across server and client renders no hydration mismatch.
9
+ *
10
+ * The raw React `useId` value (e.g. `:r3:`) has colons stripped so it is safe
11
+ * for use as an HTML `id` attribute and inside class name strings.
12
+ *
13
+ * @param prefix Optional string prepended to the generated ID.
14
+ * Defaults to `'tokis'`.
15
+ *
16
+ * @example
17
+ * const id = useId(); // → 'tokis-r3'
18
+ * const id = useId('dialog'); // → 'dialog-r3'
10
19
  */
11
20
  function useId(prefix = 'tokis') {
12
- return (0, react_1.useRef)((0, core_1.generateId)(prefix)).current;
21
+ // React 18 useId is SSR-safe: server and client produce matching values.
22
+ const reactId = (0, react_1.useId)();
23
+ // Strip the leading/trailing colons React adds (e.g. ':r3:' → 'r3')
24
+ const clean = reactId.replace(/:/g, '');
25
+ return prefix ? `${prefix}-${clean}` : clean;
13
26
  }