pixelize-design-library 2.4.2-beta.59 → 2.4.2-beta.60

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 (138) hide show
  1. package/CHANGELOG.md +78 -9
  2. package/COMPONENT-GOTCHAS.md +118 -17
  3. package/DESIGN-GUIDE.md +17 -6
  4. package/dist/Components/AudioRecorder/AudioRecorder.d.ts +8 -0
  5. package/dist/Components/AudioRecorder/AudioRecorder.js +169 -0
  6. package/dist/Components/AudioRecorder/AudioRecorderProps.d.ts +20 -0
  7. package/dist/Components/AudioRecorder/AudioRecorderProps.js +2 -0
  8. package/dist/Components/BulkSelectionBar/BulkSelectionBar.d.ts +3 -0
  9. package/dist/Components/BulkSelectionBar/BulkSelectionBar.js +43 -0
  10. package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.d.ts +35 -0
  11. package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.js +2 -0
  12. package/dist/Components/BulkSelectionBar/index.d.ts +2 -0
  13. package/dist/Components/BulkSelectionBar/index.js +8 -0
  14. package/dist/Components/CalendarGrid/CalendarGrid.d.ts +3 -0
  15. package/dist/Components/CalendarGrid/CalendarGrid.js +173 -0
  16. package/dist/Components/CalendarGrid/CalendarGridProps.d.ts +54 -0
  17. package/dist/Components/CalendarGrid/CalendarGridProps.js +2 -0
  18. package/dist/Components/CalendarGrid/calendarMath.d.ts +49 -0
  19. package/dist/Components/CalendarGrid/calendarMath.js +178 -0
  20. package/dist/Components/CircularProgress/CircularProgress.d.ts +4 -0
  21. package/dist/Components/CircularProgress/CircularProgress.js +39 -0
  22. package/dist/Components/CircularProgress/CircularProgressProps.d.ts +22 -0
  23. package/dist/Components/CircularProgress/CircularProgressProps.js +2 -0
  24. package/dist/Components/CircularProgress/arc.d.ts +12 -0
  25. package/dist/Components/CircularProgress/arc.js +16 -0
  26. package/dist/Components/Common/fieldStyles.d.ts +1 -0
  27. package/dist/Components/Common/fieldStyles.js +2 -1
  28. package/dist/Components/Drawer/Drawer.d.ts +1 -1
  29. package/dist/Components/Drawer/Drawer.js +4 -2
  30. package/dist/Components/Drawer/DrawerProps.d.ts +3 -1
  31. package/dist/Components/EmptyState/EmptyState.js +2 -1
  32. package/dist/Components/EmptyState/EmptyStateProps.d.ts +2 -2
  33. package/dist/Components/FormDrawer/FormDrawer.d.ts +2 -0
  34. package/dist/Components/FormDrawer/FormDrawer.js +97 -0
  35. package/dist/Components/FormDrawer/FormDrawerProps.d.ts +55 -0
  36. package/dist/Components/FormDrawer/FormDrawerProps.js +2 -0
  37. package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +1 -1
  38. package/dist/Components/LauncherTrigger/LauncherTrigger.js +8 -1
  39. package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +2 -0
  40. package/dist/Components/Modal/Modal.d.ts +1 -1
  41. package/dist/Components/Modal/Modal.js +4 -2
  42. package/dist/Components/Modal/ModalProps.d.ts +3 -1
  43. package/dist/Components/Modal/warnDroppedSlotChildren.d.ts +2 -0
  44. package/dist/Components/Modal/warnDroppedSlotChildren.js +45 -0
  45. package/dist/Components/NavLauncher/NavLauncher.js +83 -26
  46. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +6 -6
  47. package/dist/Components/NavLauncher/launcherParts.d.ts +60 -1
  48. package/dist/Components/NavLauncher/launcherParts.js +93 -11
  49. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
  50. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewerProps.d.ts +1 -1
  51. package/dist/Components/QueryState/QueryStateBoundary.d.ts +2 -2
  52. package/dist/Components/QueryState/QueryStateBoundary.js +9 -3
  53. package/dist/Components/QueryState/QueryStateProps.d.ts +19 -2
  54. package/dist/Components/SectionRail/SectionRail.d.ts +3 -0
  55. package/dist/Components/SectionRail/SectionRail.js +49 -0
  56. package/dist/Components/SectionRail/SectionRailProps.d.ts +23 -0
  57. package/dist/Components/SectionRail/SectionRailProps.js +2 -0
  58. package/dist/Components/SectionRail/index.d.ts +2 -0
  59. package/dist/Components/SectionRail/index.js +8 -0
  60. package/dist/Components/SegmentedControl/SegmentedControl.js +9 -14
  61. package/dist/Components/SegmentedControl/segmentedInk.d.ts +0 -2
  62. package/dist/Components/SegmentedControl/segmentedInk.js +1 -5
  63. package/dist/Components/StatCard/StatCard.d.ts +1 -1
  64. package/dist/Components/StatCard/StatCard.js +6 -2
  65. package/dist/Components/StatCard/StatCardProps.d.ts +3 -0
  66. package/dist/Components/StatusTag/StatusTag.d.ts +4 -0
  67. package/dist/Components/StatusTag/StatusTag.js +24 -0
  68. package/dist/Components/StatusTag/StatusTagProps.d.ts +20 -0
  69. package/dist/Components/StatusTag/StatusTagProps.js +2 -0
  70. package/dist/Components/Table/Table.d.ts +1 -1
  71. package/dist/Components/Table/Table.js +6 -2
  72. package/dist/Components/Table/TableProps.d.ts +20 -0
  73. package/dist/Components/Table/components/TableBody.d.ts +1 -1
  74. package/dist/Components/Table/components/TableBody.js +43 -18
  75. package/dist/Components/Table/components/TableHeader.d.ts +1 -1
  76. package/dist/Components/Table/components/TableHeader.js +7 -5
  77. package/dist/Components/Table/components/rowDetail.d.ts +32 -0
  78. package/dist/Components/Table/components/rowDetail.js +20 -0
  79. package/dist/Components/Table/hooks/useRowExpansion.d.ts +7 -0
  80. package/dist/Components/Table/hooks/useRowExpansion.js +27 -0
  81. package/dist/Components/Tabs/Tabs.d.ts +1 -1
  82. package/dist/Components/Tabs/Tabs.js +15 -2
  83. package/dist/Components/Tabs/TabsProps.d.ts +6 -2
  84. package/dist/Components/Tabs/components/Tab.js +4 -33
  85. package/dist/Components/Tabs/components/TabLinkList.d.ts +7 -0
  86. package/dist/Components/Tabs/components/TabLinkList.js +34 -0
  87. package/dist/Components/Tabs/useTabAppearance.d.ts +17 -0
  88. package/dist/Components/Tabs/useTabAppearance.js +67 -0
  89. package/dist/Components/Tag/Tag.js +11 -1
  90. package/dist/Components/Tag/TagProps.d.ts +4 -0
  91. package/dist/Components/Tag/tagDot.d.ts +3 -0
  92. package/dist/Components/Tag/tagDot.js +21 -0
  93. package/dist/esm/Components/AudioRecorder/AudioRecorder.js +161 -0
  94. package/dist/esm/Components/AudioRecorder/AudioRecorderProps.js +1 -0
  95. package/dist/esm/Components/BulkSelectionBar/BulkSelectionBar.js +38 -0
  96. package/dist/esm/Components/BulkSelectionBar/BulkSelectionBarProps.js +1 -0
  97. package/dist/esm/Components/BulkSelectionBar/index.js +1 -0
  98. package/dist/esm/Components/CalendarGrid/CalendarGrid.js +168 -0
  99. package/dist/esm/Components/CalendarGrid/CalendarGridProps.js +1 -0
  100. package/dist/esm/Components/CalendarGrid/calendarMath.js +155 -0
  101. package/dist/esm/Components/CircularProgress/CircularProgress.js +37 -0
  102. package/dist/esm/Components/CircularProgress/CircularProgressProps.js +1 -0
  103. package/dist/esm/Components/CircularProgress/arc.js +11 -0
  104. package/dist/esm/Components/Common/fieldStyles.js +2 -1
  105. package/dist/esm/Components/Drawer/Drawer.js +4 -2
  106. package/dist/esm/Components/EmptyState/EmptyState.js +2 -1
  107. package/dist/esm/Components/FormDrawer/FormDrawer.js +58 -0
  108. package/dist/esm/Components/FormDrawer/FormDrawerProps.js +1 -0
  109. package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +5 -1
  110. package/dist/esm/Components/Modal/Modal.js +4 -2
  111. package/dist/esm/Components/Modal/warnDroppedSlotChildren.js +38 -0
  112. package/dist/esm/Components/NavLauncher/NavLauncher.js +85 -28
  113. package/dist/esm/Components/NavLauncher/launcherParts.js +88 -11
  114. package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
  115. package/dist/esm/Components/QueryState/QueryStateBoundary.js +10 -4
  116. package/dist/esm/Components/SectionRail/SectionRail.js +44 -0
  117. package/dist/esm/Components/SectionRail/SectionRailProps.js +1 -0
  118. package/dist/esm/Components/SectionRail/index.js +1 -0
  119. package/dist/esm/Components/SegmentedControl/SegmentedControl.js +11 -16
  120. package/dist/esm/Components/SegmentedControl/segmentedInk.js +0 -3
  121. package/dist/esm/Components/StatCard/StatCard.js +6 -2
  122. package/dist/esm/Components/StatusTag/StatusTag.js +19 -0
  123. package/dist/esm/Components/StatusTag/StatusTagProps.js +1 -0
  124. package/dist/esm/Components/Table/Table.js +7 -3
  125. package/dist/esm/Components/Table/components/TableBody.js +43 -18
  126. package/dist/esm/Components/Table/components/TableHeader.js +7 -5
  127. package/dist/esm/Components/Table/components/rowDetail.js +14 -0
  128. package/dist/esm/Components/Table/hooks/useRowExpansion.js +23 -0
  129. package/dist/esm/Components/Tabs/Tabs.js +15 -2
  130. package/dist/esm/Components/Tabs/components/Tab.js +4 -33
  131. package/dist/esm/Components/Tabs/components/TabLinkList.js +29 -0
  132. package/dist/esm/Components/Tabs/useTabAppearance.js +63 -0
  133. package/dist/esm/Components/Tag/Tag.js +12 -2
  134. package/dist/esm/Components/Tag/tagDot.js +17 -0
  135. package/dist/esm/index.js +7 -0
  136. package/dist/index.d.ts +16 -2
  137. package/dist/index.js +18 -4
  138. package/package.json +1 -1
@@ -0,0 +1,155 @@
1
+ const MS_PER_DAY = 86400000;
2
+ export const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
3
+ export const addDays = (d, n) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
4
+ export const addMonths = (d, n) => {
5
+ const target = new Date(d.getFullYear(), d.getMonth() + n, 1);
6
+ const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
7
+ return new Date(target.getFullYear(), target.getMonth(), Math.min(d.getDate(), lastDay));
8
+ };
9
+ export const isSameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
10
+ export const dayKey = (d) => `${String(d.getFullYear()).padStart(4, "0")}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
11
+ export const isWeekend = (d) => d.getDay() === 0 || d.getDay() === 6;
12
+ /** Whole calendar days from `a` to `b`, counted on date components so a DST day is still one day. */
13
+ export const diffDays = (a, b) => Math.round((Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()) - Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())) /
14
+ MS_PER_DAY);
15
+ export const startOfWeek = (d, weekStartsOn) => addDays(d, -((d.getDay() - weekStartsOn + 7) % 7));
16
+ export const weekDays = (d, weekStartsOn) => {
17
+ const first = startOfWeek(d, weekStartsOn);
18
+ return Array.from({ length: 7 }, (_, i) => addDays(first, i));
19
+ };
20
+ /** Whole weeks that cover the month of `d`, padded with the neighbouring months' days. */
21
+ export const monthWeeks = (d, weekStartsOn) => {
22
+ const first = new Date(d.getFullYear(), d.getMonth(), 1);
23
+ const last = new Date(d.getFullYear(), d.getMonth() + 1, 0);
24
+ const gridStart = startOfWeek(first, weekStartsOn);
25
+ const weekCount = Math.ceil((diffDays(gridStart, last) + 1) / 7);
26
+ return Array.from({ length: weekCount }, (_, w) => Array.from({ length: 7 }, (_, i) => addDays(gridStart, w * 7 + i)));
27
+ };
28
+ export const visibleWeeks = (view, d, weekStartsOn) => {
29
+ if (view === "month")
30
+ return monthWeeks(d, weekStartsOn);
31
+ if (view === "week")
32
+ return [weekDays(d, weekStartsOn)];
33
+ return [[startOfDay(d)]];
34
+ };
35
+ export const shiftAnchor = (view, d, direction) => {
36
+ if (view === "month")
37
+ return addMonths(d, direction);
38
+ return addDays(d, view === "week" ? 7 * direction : direction);
39
+ };
40
+ export const eventRange = (e) => {
41
+ const start = startOfDay(e.start);
42
+ const end = e.end ? startOfDay(e.end) : start;
43
+ return end < start ? { start: end, end: start } : { start, end };
44
+ };
45
+ export const eventsOnDay = (events, day) => {
46
+ const d = startOfDay(day);
47
+ return events.filter((e) => {
48
+ const r = eventRange(e);
49
+ return r.start <= d && d <= r.end;
50
+ });
51
+ };
52
+ /** Lays events out in lanes across one week row; lanes past `maxLanes` are counted per column, not drawn. */
53
+ export const layoutWeek = (days, events, maxLanes) => {
54
+ const first = days[0];
55
+ const lastCol = days.length - 1;
56
+ const candidates = events
57
+ .map((event) => {
58
+ const r = eventRange(event);
59
+ const rawStart = diffDays(first, r.start);
60
+ const rawEnd = diffDays(first, r.end);
61
+ if (rawEnd < 0 || rawStart > lastCol)
62
+ return null;
63
+ return {
64
+ event,
65
+ rawStart,
66
+ length: rawEnd - rawStart,
67
+ startCol: Math.max(0, rawStart),
68
+ endCol: Math.min(lastCol, rawEnd),
69
+ continuesBefore: rawStart < 0,
70
+ continuesAfter: rawEnd > lastCol,
71
+ };
72
+ })
73
+ .filter((c) => c !== null)
74
+ .sort((a, b) => a.rawStart - b.rawStart ||
75
+ b.length - a.length ||
76
+ a.event.label.localeCompare(b.event.label) ||
77
+ a.event.id.localeCompare(b.event.id));
78
+ const occupied = [];
79
+ const segments = [];
80
+ for (const c of candidates) {
81
+ let lane = occupied.findIndex((row) => row.slice(c.startCol, c.endCol + 1).every((taken) => !taken));
82
+ if (lane === -1) {
83
+ lane = occupied.length;
84
+ occupied.push(new Array(days.length).fill(false));
85
+ }
86
+ for (let col = c.startCol; col <= c.endCol; col += 1)
87
+ occupied[lane][col] = true;
88
+ segments.push({
89
+ event: c.event,
90
+ startCol: c.startCol,
91
+ endCol: c.endCol,
92
+ lane,
93
+ continuesBefore: c.continuesBefore,
94
+ continuesAfter: c.continuesAfter,
95
+ });
96
+ }
97
+ const hiddenByCol = new Array(days.length).fill(0);
98
+ for (const s of segments) {
99
+ if (s.lane < maxLanes)
100
+ continue;
101
+ for (let col = s.startCol; col <= s.endCol; col += 1)
102
+ hiddenByCol[col] += 1;
103
+ }
104
+ return { segments, visibleLanes: Math.min(occupied.length, maxLanes), hiddenByCol };
105
+ };
106
+ export const NAV_KEYS = [
107
+ "ArrowLeft",
108
+ "ArrowRight",
109
+ "ArrowUp",
110
+ "ArrowDown",
111
+ "Home",
112
+ "End",
113
+ "PageUp",
114
+ "PageDown",
115
+ ];
116
+ export const isNavKey = (key) => NAV_KEYS.includes(key);
117
+ export const moveFocus = (view, d, key, weekStartsOn) => {
118
+ const columns = view === "day" ? 1 : 7;
119
+ switch (key) {
120
+ case "ArrowLeft":
121
+ return addDays(d, -1);
122
+ case "ArrowRight":
123
+ return addDays(d, 1);
124
+ case "ArrowUp":
125
+ return addDays(d, -columns);
126
+ case "ArrowDown":
127
+ return addDays(d, columns);
128
+ case "Home":
129
+ return view === "day" ? d : startOfWeek(d, weekStartsOn);
130
+ case "End":
131
+ return view === "day" ? d : addDays(startOfWeek(d, weekStartsOn), 6);
132
+ case "PageUp":
133
+ return shiftAnchor(view, d, -1);
134
+ default:
135
+ return shiftAnchor(view, d, 1);
136
+ }
137
+ };
138
+ const probe = (weekday) => new Date(2024, 0, 7 + weekday);
139
+ export const weekdayLabels = (locale, weekStartsOn, style = "short") => {
140
+ const fmt = new Intl.DateTimeFormat(locale, { weekday: style });
141
+ return Array.from({ length: 7 }, (_, i) => fmt.format(probe((i + weekStartsOn) % 7)));
142
+ };
143
+ export const formatTitle = (view, d, locale, weekStartsOn) => {
144
+ if (view === "month")
145
+ return new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(d);
146
+ if (view === "day")
147
+ return new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(d);
148
+ const days = weekDays(d, weekStartsOn);
149
+ const opts = { day: "numeric", month: "short", year: "numeric" };
150
+ const fmt = new Intl.DateTimeFormat(locale, opts);
151
+ return typeof fmt.formatRange === "function"
152
+ ? fmt.formatRange(days[0], days[6])
153
+ : `${fmt.format(days[0])} - ${fmt.format(days[6])}`;
154
+ };
155
+ export const formatFullDate = (d, locale) => new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(d);
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Flex, chakra } from "@chakra-ui/react";
3
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
+ import { resolveSoftTone, toneScale } from "../../Theme/tokens/builders/tone.js";
5
+ import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
6
+ import { reducedMotionSx } from "../../Theme/chakra/motion.js";
7
+ import { arcGeometry, SIZE_PX, THICKNESS_PX } from "./arc.js";
8
+ const Circle = chakra("circle");
9
+ const LABEL_FONT = {
10
+ sm: "0.625rem",
11
+ md: "0.75rem",
12
+ lg: "1rem",
13
+ };
14
+ const scaleFor = (colors, tone) => tone === "info" ? colors.primary : toneScale(colors, tone);
15
+ /** Ring-shaped determinate progress with a centred label — for a linear bar use ProgressBar. */
16
+ const CircularProgress = ({ value, max = 100, size = "md", thickness, tone = "info", label, "aria-label": ariaLabel, "data-testid": testId, }) => {
17
+ const { colors } = useCustomTheme();
18
+ const diameter = SIZE_PX[size];
19
+ const stroke = thickness !== null && thickness !== void 0 ? thickness : THICKNESS_PX[size];
20
+ const { radius, circumference, fraction, clamped, safeMax, dashOffset } = arcGeometry(diameter, stroke, value, max);
21
+ const ink = resolveSoftTone(colors, scaleFor(colors, tone), colors.background[50]).fg;
22
+ const center = diameter / 2;
23
+ const valueText = typeof label === "string" || typeof label === "number" ? String(label) : undefined;
24
+ return (_jsxs(Box, { role: "progressbar", "aria-label": ariaLabel, "aria-valuenow": clamped, "aria-valuemin": 0, "aria-valuemax": safeMax, "aria-valuetext": valueText, "data-testid": testId, position: "relative", display: "inline-flex", flexShrink: 0, boxSize: `${diameter}px`, children: [_jsxs("svg", { width: diameter, height: diameter, viewBox: `0 0 ${diameter} ${diameter}`, "aria-hidden": true, focusable: "false", children: [_jsx(Circle, { cx: center, cy: center, r: radius, fill: "none", sx: { stroke: colors.backgroundColor.subtle, strokeWidth: stroke } }), _jsx(Circle, { cx: center, cy: center, r: radius, fill: "none", "data-testid": testId ? `${testId}-arc` : undefined, sx: {
25
+ stroke: ink,
26
+ strokeWidth: stroke,
27
+ strokeLinecap: "round",
28
+ strokeDasharray: circumference,
29
+ strokeDashoffset: dashOffset,
30
+ strokeOpacity: fraction > 0 ? 1 : 0,
31
+ transform: "rotate(-90deg)",
32
+ transformOrigin: "50% 50%",
33
+ transition: transitionFor(["stroke-dashoffset"]),
34
+ ...reducedMotionSx(),
35
+ } })] }), label !== undefined && label !== null && (_jsx(Flex, { position: "absolute", inset: 0, align: "center", justify: "center", fontSize: LABEL_FONT[size], fontWeight: 600, lineHeight: 1, color: colors.header[900], sx: { fontVariantNumeric: "tabular-nums" }, children: label }))] }));
36
+ };
37
+ export default CircularProgress;
@@ -0,0 +1,11 @@
1
+ export const SIZE_PX = { sm: 32, md: 48, lg: 72 };
2
+ export const THICKNESS_PX = { sm: 4, md: 5, lg: 7 };
3
+ export const clampValue = (value, max) => Number.isFinite(value) ? Math.min(Math.max(value, 0), max) : 0;
4
+ export const arcGeometry = (diameter, thickness, value, max) => {
5
+ const safeMax = max > 0 ? max : 100;
6
+ const clamped = clampValue(value, safeMax);
7
+ const radius = Math.max((diameter - thickness) / 2, 0);
8
+ const circumference = 2 * Math.PI * radius;
9
+ const fraction = clamped / safeMax;
10
+ return { radius, circumference, fraction, clamped, safeMax, dashOffset: circumference * (1 - fraction) };
11
+ };
@@ -5,7 +5,8 @@ import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outline
5
5
  import { fieldFocusVisible, focusGlowShadow } from "../../Theme/chakra/focusRing.styles.js";
6
6
  export const FIELD_RADIUS = "lg";
7
7
  export const FIELD_BORDER_WIDTH = "1px";
8
- export const FIELD_TRANSITION = transitionFor(["border-color", "box-shadow", "background-color"]);
8
+ /** No box-shadow: the focus ring must be present the frame focus lands, not fade in. */
9
+ export const FIELD_TRANSITION = transitionFor(["border-color", "background-color"]);
9
10
  /** Chakra `h`/`fontSize`/`px` for a field or select of this size. */
10
11
  export const controlSizeStyles = (size) => {
11
12
  const spec = CONTROL_SIZES[size];
@@ -1,6 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from "react";
3
3
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
+ import { warnDroppedSlotChildren } from "../Modal/warnDroppedSlotChildren.js";
4
5
  import { Drawer as ChakraDrawer, DrawerBody as ChakraDrawerBody, DrawerFooter as ChakraDrawerFooter, DrawerHeader as ChakraDrawerHeader, DrawerOverlay, DrawerContent, DrawerCloseButton, } from "@chakra-ui/react";
5
6
  export const DrawerHeader = ({ children }) => {
6
7
  return _jsx(_Fragment, { children: children });
@@ -27,11 +28,12 @@ const findSlot = (children, slot) => {
27
28
  }
28
29
  return undefined;
29
30
  };
30
- export default function Drawer({ isOpen, onClose, placement = "right", size = "xl", id, variant, vaiant, initialFocusRef, finalFocusRef, closeOnOverlayClick, closeOnEsc, contentProps, hideCloseButton, children, }) {
31
+ export default function Drawer({ isOpen, onClose, placement = "right", size = "xl", id, variant, vaiant, initialFocusRef, finalFocusRef, closeOnOverlayClick, closeOnEsc, contentProps, hideCloseButton, portalChildren, children, }) {
31
32
  var _a, _b;
32
33
  const { colors } = useCustomTheme();
33
34
  const headerSlot = findSlot(children, DrawerHeader);
34
35
  const bodySlot = findSlot(children, DrawerBody);
35
36
  const footerSlot = findSlot(children, DrawerFooter);
36
- return (_jsxs(ChakraDrawer, { isOpen: isOpen, placement: placement, onClose: onClose, size: size, variant: (_a = variant !== null && variant !== void 0 ? variant : vaiant) !== null && _a !== void 0 ? _a : "solid", id: id, initialFocusRef: initialFocusRef, finalFocusRef: finalFocusRef, closeOnOverlayClick: closeOnOverlayClick, closeOnEsc: closeOnEsc, children: [_jsx(DrawerOverlay, {}), _jsxs(DrawerContent, { ...contentProps, children: [!hideCloseButton && _jsx(DrawerCloseButton, {}), headerSlot && (_jsx(ChakraDrawerHeader, { borderBottomWidth: "1px", borderColor: colors.boxborder[200], ...headerSlot.props, children: headerSlot })), _jsx(ChakraDrawerBody, { ...((_b = bodySlot === null || bodySlot === void 0 ? void 0 : bodySlot.props) !== null && _b !== void 0 ? _b : {}), children: bodySlot }), footerSlot && (_jsx(ChakraDrawerFooter, { borderTopWidth: "1px", borderColor: colors.boxborder[200], ...footerSlot.props, children: footerSlot }))] })] }));
37
+ warnDroppedSlotChildren("Drawer", children, [DrawerHeader, DrawerBody, DrawerFooter]);
38
+ return (_jsxs(ChakraDrawer, { isOpen: isOpen, placement: placement, onClose: onClose, size: size, variant: (_a = variant !== null && variant !== void 0 ? variant : vaiant) !== null && _a !== void 0 ? _a : "solid", id: id, initialFocusRef: initialFocusRef, finalFocusRef: finalFocusRef, closeOnOverlayClick: closeOnOverlayClick, closeOnEsc: closeOnEsc, children: [_jsx(DrawerOverlay, {}), _jsxs(DrawerContent, { ...contentProps, children: [!hideCloseButton && _jsx(DrawerCloseButton, {}), headerSlot && (_jsx(ChakraDrawerHeader, { borderBottomWidth: "1px", borderColor: colors.boxborder[200], ...headerSlot.props, children: headerSlot })), _jsx(ChakraDrawerBody, { ...((_b = bodySlot === null || bodySlot === void 0 ? void 0 : bodySlot.props) !== null && _b !== void 0 ? _b : {}), children: bodySlot }), footerSlot && (_jsx(ChakraDrawerFooter, { borderTopWidth: "1px", borderColor: colors.boxborder[200], ...footerSlot.props, children: footerSlot })), portalChildren] })] }));
37
39
  }
@@ -24,6 +24,7 @@ const scaleFor = (colors, scheme) => scheme === "primary" || scheme === "seconda
24
24
  const EmptyState = ({ title = "No data found", titleAs = "h3", description, icon: IconComp = Inbox, image, imageAlt = "", size = "md", colorScheme = "primary", variant = "default", action, animate = false, minH = "15rem", sx, }) => {
25
25
  const theme = useCustomTheme();
26
26
  const s = sizeMap[size];
27
+ const hasDescription = description !== undefined && description !== null && description !== false && description !== "";
27
28
  const palette = scaleFor(theme.colors, colorScheme);
28
29
  const tone = resolveSoftTone(theme.colors, palette, theme.colors.background[50]);
29
30
  const accentSoft = tone.bg;
@@ -46,6 +47,6 @@ const EmptyState = ({ title = "No data found", titleAs = "h3", description, icon
46
47
  bg: accentSoft,
47
48
  opacity: HALO_OPACITY,
48
49
  zIndex: -1,
49
- }, sx: reducedMotionSx(), children: _jsx(IconComp, { size: s.icon, strokeWidth: 1.75, "aria-hidden": true }) })), _jsxs(Box, { children: [_jsx(Text, { as: titleAs, fontSize: s.title, fontWeight: 600, color: theme.colors.header[900], mt: 1, children: title }), description && (_jsx(Text, { fontSize: s.desc, color: theme.colors.textMuted, mt: 1, maxW: "23.75rem", children: description }))] }), action && (_jsx(Flex, { gap: 3, mt: 2, wrap: "wrap", justify: "center", children: action }))] }));
50
+ }, sx: reducedMotionSx(), children: _jsx(IconComp, { size: s.icon, strokeWidth: 1.75, "aria-hidden": true }) })), _jsxs(Box, { children: [_jsx(Text, { as: titleAs, fontSize: s.title, fontWeight: 600, color: theme.colors.header[900], mt: 1, children: title }), hasDescription && (_jsx(Text, { as: typeof description === "string" ? undefined : "div", fontSize: s.desc, color: theme.colors.textMuted, mt: 1, maxW: "23.75rem", children: description }))] }), action && (_jsx(Flex, { gap: 3, mt: 2, wrap: "wrap", justify: "center", children: action }))] }));
50
51
  };
51
52
  export default EmptyState;
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment, useEffect, useId, useRef, useState } from "react";
3
+ import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Divider, Flex, Heading, ListItem, Text, UnorderedList, } from "@chakra-ui/react";
4
+ import Drawer, { DrawerBody, DrawerFooter, DrawerHeader } from "../Drawer/Drawer.js";
5
+ import AlertDialog from "../AlertDialog/AlertDialog.js";
6
+ import Button from "../Button/Button.js";
7
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
8
+ const DEFAULT_LABELS = {
9
+ fixErrors: "Please fix the following before saving",
10
+ discardTitle: "Discard changes?",
11
+ discardBody: "You have unsaved changes. If you close now, they will be lost.",
12
+ discard: "Discard",
13
+ keepEditing: "Keep editing",
14
+ };
15
+ export default function FormDrawer({ isOpen, onClose, title, description, sections, errors = [], isDirty, onSubmit, isSubmitting = false, submitLabel = "Save", cancelLabel = "Cancel", secondaryAction, size = "md", labels, "data-testid": testId = "form-drawer", }) {
16
+ const { colors } = useCustomTheme();
17
+ const uid = useId();
18
+ const formId = `${uid}-form`;
19
+ const titleId = `${uid}-title`;
20
+ const text = { ...DEFAULT_LABELS, ...labels };
21
+ const [confirmDiscard, setConfirmDiscard] = useState(false);
22
+ const [attempt, setAttempt] = useState(0);
23
+ const keepEditingRef = useRef(null);
24
+ const summaryRef = useRef(null);
25
+ const focusPending = useRef(false);
26
+ useEffect(() => {
27
+ if (!isOpen)
28
+ setConfirmDiscard(false);
29
+ }, [isOpen]);
30
+ useEffect(() => {
31
+ var _a;
32
+ if (!focusPending.current || isSubmitting)
33
+ return;
34
+ focusPending.current = false;
35
+ if (errors.length)
36
+ (_a = summaryRef.current) === null || _a === void 0 ? void 0 : _a.focus();
37
+ }, [attempt, errors, isSubmitting]);
38
+ const requestClose = () => {
39
+ if (isSubmitting)
40
+ return;
41
+ if (isDirty)
42
+ setConfirmDiscard(true);
43
+ else
44
+ onClose();
45
+ };
46
+ const submit = (event) => {
47
+ event.preventDefault();
48
+ if (isSubmitting)
49
+ return;
50
+ focusPending.current = true;
51
+ setAttempt((n) => n + 1);
52
+ onSubmit();
53
+ };
54
+ return (_jsxs(Drawer, { isOpen: isOpen, onClose: requestClose, size: size, closeOnEsc: !isSubmitting, closeOnOverlayClick: !isSubmitting, portalChildren: _jsx(AlertDialog, { isOpen: confirmDiscard, onClose: () => setConfirmDiscard(false), leastDestructiveRef: keepEditingRef, isCentered: true, tone: "danger", title: text.discardTitle, content: _jsx(Text, { fontSize: "sm", color: colors.text[800], children: text.discardBody }), cancelButtonText: text.keepEditing, confirmButtonText: text.discard, onConfirm: () => {
55
+ setConfirmDiscard(false);
56
+ onClose();
57
+ } }), children: [_jsxs(DrawerHeader, { children: [_jsx(Heading, { id: titleId, as: "h2", fontSize: "md", fontWeight: 600, color: colors.header[900], children: title }), description ? (_jsx(Text, { mt: 0.5, fontSize: "sm", fontWeight: 400, color: colors.textMuted, children: description })) : null] }), _jsx(DrawerBody, { children: _jsx(Box, { as: "form", id: formId, "aria-labelledby": titleId, onSubmit: submit, noValidate: true, "data-testid": testId, children: _jsxs(Flex, { direction: "column", gap: 5, pt: 2, children: [errors.length ? (_jsxs(Alert, { ref: summaryRef, tabIndex: -1, status: "error", variant: "subtle", borderRadius: "md", alignItems: "flex-start", "data-testid": `${testId}-errors`, children: [_jsx(AlertIcon, {}), _jsxs(Box, { children: [_jsx(AlertTitle, { fontSize: "sm", children: text.fixErrors }), _jsx(AlertDescription, { fontSize: "sm", children: _jsx(UnorderedList, { mt: 1, children: errors.map((message) => (_jsx(ListItem, { children: message }, message))) }) })] })] })) : null, sections.map((section, index) => (_jsxs(Fragment, { children: [index > 0 ? _jsx(Divider, { borderColor: colors.boxborder[200] }) : null, _jsxs(Flex, { as: "section", direction: "column", gap: 4, "aria-label": section.title, children: [section.title ? (_jsxs(Box, { children: [_jsx(Heading, { as: "h3", fontSize: "sm", fontWeight: 600, color: colors.header[900], children: section.title }), section.description ? (_jsx(Text, { mt: 0.5, fontSize: "xs", color: colors.textMuted, children: section.description })) : null] })) : null, section.content] })] }, section.id)))] }) }) }), _jsx(DrawerFooter, { children: _jsxs(Flex, { gap: 2, w: "100%", justify: "flex-end", wrap: "wrap", children: [secondaryAction, _jsx(Button, { variant: "ghost", colorScheme: "gray", onClick: requestClose, isDisabled: isSubmitting, label: cancelLabel }), _jsx(Button, { variant: "solid", colorScheme: "primary", type: "submit", form: formId, isLoading: isSubmitting, loadingText: submitLabel, label: submitLabel, "data-testid": `${testId}-submit` })] }) })] }));
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -8,6 +8,7 @@ import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
8
8
  import { useMatchMedia } from "../../Hooks/useMatchMedia.js";
9
9
  import { detectApple } from "../Kbd/Kbd.js";
10
10
  import { formatShortcut } from "../AppSwitcher/AppSwitcherProps.js";
11
+ import ToolTip from "../ToolTip/ToolTip.js";
11
12
  const MD_QUERY = "(min-width: 48em)";
12
13
  const KEY_NAMES = {
13
14
  mod: ["Meta", "Control"],
@@ -27,11 +28,14 @@ export const keyShortcutsFor = (shortcut) => {
27
28
  const chords = Array.from({ length: variants }, (_, v) => options.map((o) => o[Math.min(v, o.length - 1)]).join("+"));
28
29
  return Array.from(new Set(chords)).join(" ");
29
30
  };
30
- const LauncherTrigger = ({ onClick, isOpen, label = "Menu", shortcut = "mod+k", icon: Icon = Compass, "aria-label": ariaLabel, "data-testid": testId, }) => {
31
+ const LauncherTrigger = ({ onClick, isOpen, label = "Menu", shortcut = "mod+k", icon: Icon = Compass, display = "chip", "aria-label": ariaLabel, "data-testid": testId, }) => {
31
32
  const { colors } = useCustomTheme();
32
33
  const showHint = useMatchMedia(MD_QUERY, true);
33
34
  const ring = focusVisibleRing(colors);
34
35
  const hint = shortcut ? formatShortcut(shortcut, detectApple()) : "";
36
+ if (display === "icon") {
37
+ return (_jsx(ToolTip, { label: hint ? `${label} (${hint})` : label, placement: "bottom", children: _jsx(Box, { as: "button", type: "button", "data-testid": testId, onClick: onClick, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : label, "aria-keyshortcuts": shortcut ? keyShortcutsFor(shortcut) : undefined, display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, w: "36px", h: "36px", borderRadius: "10px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.primary[50], color: colors.accentText, cursor: "pointer", transition: transitionFor(["background-color", "border-color", "box-shadow"]), _hover: { borderColor: colors.primary[300] }, sx: { _focusVisible: ring, [FOCUS_VISIBLE_OVER_STATE]: ring, ...reducedMotionSx() }, children: _jsx(Icon, { size: 17, "aria-hidden": "true" }) }) }));
38
+ }
35
39
  return (_jsxs(Box, { as: "button", type: "button", "data-testid": testId, onClick: onClick, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-label": ariaLabel, "aria-keyshortcuts": shortcut ? keyShortcutsFor(shortcut) : undefined, display: "inline-flex", alignItems: "center", gap: 2, flexShrink: 0, h: "2.25rem", pl: "0.625rem", pr: "0.375rem", borderRadius: "10px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.primary[50], color: colors.accentText, fontSize: "0.84375rem", fontWeight: 600, lineHeight: 1, cursor: "pointer", transition: transitionFor(["background-color", "border-color", "box-shadow"]), _hover: { borderColor: colors.primary[300] }, sx: { _focusVisible: ring, [FOCUS_VISIBLE_OVER_STATE]: ring, ...reducedMotionSx() }, children: [_jsxs(Box, { as: "span", display: "inline-flex", alignItems: "center", gap: 2, "aria-hidden": ariaLabel ? true : undefined, children: [_jsx(Icon, { size: 17, "aria-hidden": "true" }), _jsx("span", { children: label })] }), hint && showHint && (_jsx(Box, { as: "kbd", "aria-hidden": "true", fontFamily: "inherit", fontSize: "0.6875rem", fontWeight: 600, lineHeight: "normal", px: "6px", py: "2px", borderRadius: "6px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.surfaceRaised, children: hint }))] }));
36
40
  };
37
41
  export default LauncherTrigger;
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import React from "react";
3
3
  import { Modal as ChakraModal, ModalOverlay, ModalContent, ModalHeader as ChakraModalHeader, ModalCloseButton, ModalBody as ChakraModalBody, ModalFooter as ChakraModalFooter, Flex, Spinner, Text, } from "@chakra-ui/react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
+ import { warnDroppedSlotChildren } from "./warnDroppedSlotChildren.js";
5
6
  export const ModalHeader = ({ children }) => {
6
7
  return _jsx(_Fragment, { children: children });
7
8
  };
@@ -27,12 +28,13 @@ const findSlot = (children, slot) => {
27
28
  }
28
29
  return undefined;
29
30
  };
30
- export default function Modal({ size, isOpen = false, onClose, overlaybg, overlaybackdropFilter, overlaybackdropInvert, overlaybackdropBlur, isCentered, finalFocusRef, blockScrollOnMount = true, initialFocusRef, closeOnOverlayClick = true, closeOnEsc = true, trapFocus = true, motionPreset = "scale", scrollBehavior = "outside", children, isLoading, title, subtitle, hideCloseButton, contentProps, }) {
31
+ export default function Modal({ size, isOpen = false, onClose, overlaybg, overlaybackdropFilter, overlaybackdropInvert, overlaybackdropBlur, isCentered, finalFocusRef, blockScrollOnMount = true, initialFocusRef, closeOnOverlayClick = true, closeOnEsc = true, trapFocus = true, motionPreset = "scale", scrollBehavior = "outside", children, isLoading, title, subtitle, hideCloseButton, contentProps, portalChildren, }) {
31
32
  var _a, _b;
32
33
  const { colors } = useCustomTheme();
33
34
  const headerSlot = findSlot(children, ModalHeader);
34
35
  const bodySlot = findSlot(children, ModalBody);
35
36
  const footerSlot = findSlot(children, ModalFooter);
37
+ warnDroppedSlotChildren("Modal", children, [ModalHeader, ModalBody, ModalFooter]);
36
38
  const headerContent = headerSlot !== null && headerSlot !== void 0 ? headerSlot : title;
37
- return (_jsxs(ChakraModal, { isOpen: isOpen, onClose: onClose, size: size, isCentered: isCentered, finalFocusRef: finalFocusRef, blockScrollOnMount: blockScrollOnMount, initialFocusRef: initialFocusRef, closeOnOverlayClick: closeOnOverlayClick, closeOnEsc: closeOnEsc, trapFocus: trapFocus, motionPreset: motionPreset, scrollBehavior: scrollBehavior, children: [_jsx(ModalOverlay, { bg: overlaybg, backdropFilter: overlaybackdropFilter, backdropInvert: overlaybackdropInvert, backdropBlur: overlaybackdropBlur }), _jsxs(ModalContent, { "aria-busy": isLoading || undefined, ...contentProps, children: [(headerContent || subtitle) && (_jsxs(ChakraModalHeader, { ...((_a = headerSlot === null || headerSlot === void 0 ? void 0 : headerSlot.props) !== null && _a !== void 0 ? _a : {}), children: [headerContent, subtitle && (_jsx(Text, { as: "div", mt: 0.5, fontSize: "0.875rem", fontWeight: 400, color: colors.textMuted, children: subtitle }))] })), !hideCloseButton && _jsx(ModalCloseButton, {}), isLoading ? (_jsx(Flex, { role: "status", "aria-label": "Loading", justify: "center", align: "center", py: 16, children: _jsx(Spinner, { thickness: "4px", speed: "0.65s", emptyColor: colors.boxborder[200], color: colors.accentText, size: "xl" }) })) : (_jsxs(_Fragment, { children: [_jsx(ChakraModalBody, { ...((_b = bodySlot === null || bodySlot === void 0 ? void 0 : bodySlot.props) !== null && _b !== void 0 ? _b : {}), children: bodySlot }), footerSlot && (_jsx(ChakraModalFooter, { ...footerSlot.props, children: footerSlot }))] }))] })] }));
39
+ return (_jsxs(ChakraModal, { isOpen: isOpen, onClose: onClose, size: size, isCentered: isCentered, finalFocusRef: finalFocusRef, blockScrollOnMount: blockScrollOnMount, initialFocusRef: initialFocusRef, closeOnOverlayClick: closeOnOverlayClick, closeOnEsc: closeOnEsc, trapFocus: trapFocus, motionPreset: motionPreset, scrollBehavior: scrollBehavior, children: [_jsx(ModalOverlay, { bg: overlaybg, backdropFilter: overlaybackdropFilter, backdropInvert: overlaybackdropInvert, backdropBlur: overlaybackdropBlur }), _jsxs(ModalContent, { "aria-busy": isLoading || undefined, ...contentProps, children: [(headerContent || subtitle) && (_jsxs(ChakraModalHeader, { ...((_a = headerSlot === null || headerSlot === void 0 ? void 0 : headerSlot.props) !== null && _a !== void 0 ? _a : {}), children: [headerContent, subtitle && (_jsx(Text, { as: "div", mt: 0.5, fontSize: "0.875rem", fontWeight: 400, color: colors.textMuted, children: subtitle }))] })), !hideCloseButton && _jsx(ModalCloseButton, {}), isLoading ? (_jsx(Flex, { role: "status", "aria-label": "Loading", justify: "center", align: "center", py: 16, children: _jsx(Spinner, { thickness: "4px", speed: "0.65s", emptyColor: colors.boxborder[200], color: colors.accentText, size: "xl" }) })) : (_jsxs(_Fragment, { children: [_jsx(ChakraModalBody, { ...((_b = bodySlot === null || bodySlot === void 0 ? void 0 : bodySlot.props) !== null && _b !== void 0 ? _b : {}), children: bodySlot }), footerSlot && (_jsx(ChakraModalFooter, { ...footerSlot.props, children: footerSlot }))] })), portalChildren] })] }));
38
40
  }
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ const warned = new Set();
3
+ const nameOf = (type) => {
4
+ if (typeof type === "string")
5
+ return type;
6
+ const fn = type;
7
+ return (fn === null || fn === void 0 ? void 0 : fn.displayName) || (fn === null || fn === void 0 ? void 0 : fn.name) || "Component";
8
+ };
9
+ const collect = (children, slots, dropped) => {
10
+ for (const child of React.Children.toArray(children)) {
11
+ if (typeof child === "string" || typeof child === "number") {
12
+ if (String(child).trim())
13
+ dropped.set("#text", "text");
14
+ continue;
15
+ }
16
+ if (!React.isValidElement(child))
17
+ continue;
18
+ if (slots.includes(child.type))
19
+ continue;
20
+ if (child.type === React.Fragment) {
21
+ collect(child.props.children, slots, dropped);
22
+ continue;
23
+ }
24
+ dropped.set(child.type, nameOf(child.type));
25
+ }
26
+ };
27
+ export const warnDroppedSlotChildren = (owner, children, slots) => {
28
+ if (process.env.NODE_ENV === "production")
29
+ return;
30
+ const dropped = new Map();
31
+ collect(children, slots, dropped);
32
+ dropped.forEach((name, type) => {
33
+ if (warned.has(type))
34
+ return;
35
+ warned.add(type);
36
+ console.warn(`[pixelize-design-library] ${owner}: <${name}> is not a ${owner}Header/${owner}Body/${owner}Footer slot and was dropped. Pass nested dialogs and live regions through the portalChildren prop.`);
37
+ });
38
+ };