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
@@ -5,15 +5,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("@chakra-ui/react");
8
+ const lucide_react_1 = require("lucide-react");
8
9
  const EmptyState_1 = __importDefault(require("../EmptyState/EmptyState"));
9
10
  const Skeletons_1 = __importDefault(require("../Skeletons/Skeletons"));
10
11
  const ErrorState_1 = __importDefault(require("./ErrorState"));
11
12
  const DefaultLoading = () => ((0, jsx_runtime_1.jsxs)(react_1.Flex, { role: "status", direction: "column", gap: 4, w: "100%", py: 2, children: [(0, jsx_runtime_1.jsx)(react_1.VisuallyHidden, { children: "Loading" }), (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 4, width: "40%" }), Array.from({ length: 4 }, (_, i) => ((0, jsx_runtime_1.jsxs)(react_1.Flex, { direction: "column", gap: 2, children: [(0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 2.5, width: "100%" }), (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 2.5, width: "70%" })] }, i)))] }));
12
13
  const hasError = (error) => error !== undefined && error !== null && error !== false;
13
- /** Renders one of loading / error / empty / children for a content region; agnostic of the fetching library. */
14
- const QueryStateBoundary = ({ isLoading = false, error, isEmpty = false, loadingFallback, emptyFallback, empty, errorTitle, errorDescription, onRetry, isRetrying, minH = "24rem", children, }) => {
15
- if (isLoading)
14
+ /** Renders one of loading / forbidden / error / empty / children for a content region; agnostic of the fetching library. */
15
+ const QueryStateBoundary = ({ isLoading = false, isForbidden = false, forbidden, hasData = false, error, isEmpty = false, loadingFallback, emptyFallback, empty, errorTitle, errorDescription, onRetry, isRetrying, minH = "24rem", children, }) => {
16
+ var _a, _b, _c;
17
+ if (isLoading && !hasData)
16
18
  return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loadingFallback !== null && loadingFallback !== void 0 ? loadingFallback : (0, jsx_runtime_1.jsx)(DefaultLoading, {}) });
19
+ const forbiddenNow = typeof isForbidden === "function" ? hasError(error) && isForbidden(error) : isForbidden;
20
+ if (forbiddenNow) {
21
+ return ((0, jsx_runtime_1.jsx)(react_1.Box, { role: "status", w: "100%", children: (0, jsx_runtime_1.jsx)(EmptyState_1.default, { colorScheme: "gray", icon: (_a = forbidden === null || forbidden === void 0 ? void 0 : forbidden.icon) !== null && _a !== void 0 ? _a : lucide_react_1.Lock, title: (_b = forbidden === null || forbidden === void 0 ? void 0 : forbidden.title) !== null && _b !== void 0 ? _b : "You don't have access", description: (_c = forbidden === null || forbidden === void 0 ? void 0 : forbidden.description) !== null && _c !== void 0 ? _c : "Ask an administrator to give you access to view this.", action: forbidden === null || forbidden === void 0 ? void 0 : forbidden.action, minH: minH }) }));
22
+ }
17
23
  if (hasError(error)) {
18
24
  const description = typeof errorDescription === "function" ? errorDescription(error) : errorDescription;
19
25
  return ((0, jsx_runtime_1.jsx)(ErrorState_1.default, { title: errorTitle, description: description, onRetry: onRetry, isRetrying: isRetrying, minH: minH }));
@@ -20,10 +20,27 @@ export interface ErrorStateProps {
20
20
  size?: EmptyStateProps["size"];
21
21
  minH?: EmptyStateProps["minH"];
22
22
  }
23
- /** Picks loading → error → empty → children; `children` may be a function so data is read only when ready. */
23
+ /** Copy for the no-access panel; each field falls back to a generic default. */
24
+ export interface QueryStateForbiddenProps {
25
+ /** Defaults to "You don't have access". */
26
+ title?: string;
27
+ /** Defaults to a line asking the reader to request access. */
28
+ description?: string;
29
+ /** Defaults to a lock icon. */
30
+ icon?: ElementType;
31
+ /** Node(s) under the description, e.g. a "Request access" button. */
32
+ action?: ReactNode;
33
+ }
34
+ /** Picks loading → forbidden → error → empty → children; `children` may be a function so data is read only when ready. */
24
35
  export interface QueryStateBoundaryProps {
25
- /** Checked first — wins over error and empty. */
36
+ /** Checked first — wins over forbidden, error and empty — unless `hasData` is true. */
26
37
  isLoading?: boolean;
38
+ /** Shown after loading, before error (e.g. HTTP 403); a function is only called when `error` is set. */
39
+ isForbidden?: boolean | ((error: unknown) => boolean);
40
+ /** Copy and action for the no-access panel (`role="status"`, never error-tinted). */
41
+ forbidden?: QueryStateForbiddenProps;
42
+ /** True keeps children on screen during a refetch instead of the loading state. Default false. */
43
+ hasData?: boolean;
27
44
  /** Anything except undefined, null or false shows the error state — so 0 and "" count. */
28
45
  error?: unknown;
29
46
  /** Checked only after loading and error are ruled out. */
@@ -0,0 +1,3 @@
1
+ import type { SectionRailProps } from "./SectionRailProps";
2
+ declare const SectionRail: ({ sections, activeId, onSelect, "aria-label": ariaLabel, dirtyHint, collapseBelow, }: SectionRailProps) => import("react/jsx-runtime").JSX.Element;
3
+ export default SectionRail;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const react_2 = require("@chakra-ui/react");
9
+ const Select_1 = __importDefault(require("../Select/Select"));
10
+ const useCustomTheme_1 = require("../../Theme/useCustomTheme");
11
+ const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
12
+ const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
13
+ const motion_1 = require("../../Theme/tokens/primitives/motion");
14
+ const motion_2 = require("../../Theme/chakra/motion");
15
+ const FALLBACK_BREAKPOINTS = { sm: "30em", md: "48em", lg: "62em" };
16
+ const matchesMinWidth = (query) => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : true;
17
+ const useMinWidth = (minWidth) => {
18
+ const query = `(min-width: ${minWidth})`;
19
+ const [matches, setMatches] = (0, react_1.useState)(() => matchesMinWidth(query));
20
+ (0, react_1.useEffect)(() => {
21
+ var _a;
22
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
23
+ return undefined;
24
+ const list = window.matchMedia(query);
25
+ const update = () => setMatches(list.matches);
26
+ update();
27
+ (_a = list.addEventListener) === null || _a === void 0 ? void 0 : _a.call(list, "change", update);
28
+ return () => { var _a; return (_a = list.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(list, "change", update); };
29
+ }, [query]);
30
+ return matches;
31
+ };
32
+ const SectionRail = ({ sections, activeId, onSelect, "aria-label": ariaLabel, dirtyHint, collapseBelow = "md", }) => {
33
+ var _a;
34
+ const { colors } = (0, useCustomTheme_1.useCustomTheme)();
35
+ const { breakpoints } = (0, react_2.useTheme)();
36
+ const isRail = useMinWidth((_a = breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints[collapseBelow]) !== null && _a !== void 0 ? _a : FALLBACK_BREAKPOINTS[collapseBelow]);
37
+ const rows = (0, rowStates_1.listRowBackgrounds)(colors);
38
+ if (!isRail) {
39
+ return ((0, jsx_runtime_1.jsx)(Select_1.default, { "aria-label": ariaLabel, width: "100%", value: activeId, options: sections.map((section) => ({
40
+ id: section.id,
41
+ label: section.isDirty ? `${section.label} (${dirtyHint})` : section.label,
42
+ })), onChange: (option) => option && onSelect(String(option.id)) }));
43
+ }
44
+ return ((0, jsx_runtime_1.jsx)(react_2.Flex, { as: "nav", "aria-label": ariaLabel, direction: "column", gap: "2px", children: sections.map((section) => {
45
+ const isActive = section.id === activeId;
46
+ return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { as: "button", type: "button", align: "center", gap: 2, minH: "2rem", px: 3, py: 1.5, borderRadius: "md", fontSize: "sm", fontWeight: isActive ? 600 : 500, textAlign: "left", "aria-current": isActive ? "true" : undefined, bg: isActive ? rows.selected : "transparent", color: isActive ? colors.accentText : colors.text[800], transition: (0, motion_1.transitionFor)(["background-color", "color"]), _hover: { bg: isActive ? rows.selectedActive : rows.active }, _focusVisible: (0, focusRing_styles_1.focusVisibleRing)(colors), sx: (0, motion_2.reducedMotionSx)(), onClick: () => onSelect(section.id), children: [section.icon ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", "aria-hidden": true, display: "inline-flex", flexShrink: 0, children: section.icon })) : null, (0, jsx_runtime_1.jsx)(react_2.Text, { as: "span", noOfLines: 1, flex: "1", minW: 0, children: section.label }), section.isDirty ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", "aria-hidden": true, boxSize: "6px", borderRadius: "full", flexShrink: 0, bg: colors.semanticText.warning }), (0, jsx_runtime_1.jsx)(react_2.VisuallyHidden, { children: dirtyHint })] })) : null] }, section.id));
47
+ }) }));
48
+ };
49
+ exports.default = SectionRail;
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from "react";
2
+ /** One entry in a SectionRail. */
3
+ export interface RailSection {
4
+ id: string;
5
+ label: string;
6
+ /** Decorative leading icon, hidden from assistive tech; the label carries the name. */
7
+ icon?: ReactNode;
8
+ /** Shows a dot, and `dirtyHint` to screen readers, on a section holding unsaved edits. */
9
+ isDirty?: boolean;
10
+ }
11
+ /** Props for the section navigation of a long settings screen: a rail on wide viewports, a Select below `collapseBelow`. */
12
+ export interface SectionRailProps {
13
+ sections: RailSection[];
14
+ /** Id of the current section; marked `aria-current="true"` (and selected in the Select). */
15
+ activeId: string;
16
+ onSelect: (id: string) => void;
17
+ /** Accessible name of the nav and of the collapsed Select. */
18
+ "aria-label": string;
19
+ /** Visually hidden text announced after a dirty section's label, e.g. "Unsaved changes". */
20
+ dirtyHint: string;
21
+ /** Viewports narrower than this breakpoint render a Select instead of the rail; defaults to "md". */
22
+ collapseBelow?: "sm" | "md" | "lg";
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export { default as SectionRail } from "./SectionRail";
2
+ export type { RailSection, SectionRailProps } from "./SectionRailProps";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SectionRail = void 0;
7
+ var SectionRail_1 = require("./SectionRail");
8
+ Object.defineProperty(exports, "SectionRail", { enumerable: true, get: function () { return __importDefault(SectionRail_1).default; } });
@@ -43,18 +43,17 @@ function SegmentedControl({ options, value, onChange, size = "sm", isFullWidth =
43
43
  const segmentH = `calc(${control.height} - ${isUnderlineSlide ? UNDERLINE_WIDTH : `2 * ${TRACK_PADDING}`})`;
44
44
  // Concentric: an inset child keeping the parent's radius bulges at the corners.
45
45
  const innerRadius = isUnderlineSlide ? "none" : `calc(${radii.lg} - ${TRACK_PADDING})`;
46
- const focusShadow = (0, segmentedInk_1.segmentedFocusShadow)(colors, trackSurface);
47
46
  // Inset, never a border: a border consumes the content box and the absolute pill stops centring.
48
47
  const plateEdge = isBrandSlide
49
48
  ? `inset 0 0 0 1px ${(0, segmentedInk_1.segmentedPlateEdge)(colors, surface)}`
50
49
  : undefined;
51
- // The transparent outline replaces the UA ring and is what Windows high-contrast repaints.
52
- const segmentFocus = {
53
- outline: "2px solid transparent",
54
- outlineOffset: "2px",
55
- // underlineSlide has no track chrome to carry the indicator, so there the segment wears it.
56
- ...(isUnderlineSlide ? { boxShadow: focusShadow } : {}),
57
- };
50
+ // underlineSlide has no track chrome to carry the ring, so there the segment wears Button's own.
51
+ const noRing = { outline: "2px solid transparent", outlineOffset: "2px" };
52
+ const segmentFocus = isUnderlineSlide
53
+ ? (0, focusRing_styles_1.focusVisibleStyles)(colors)
54
+ : { _focusVisible: noRing, [focusRing_styles_1.FOCUS_VISIBLE_OVER_STATE]: noRing };
55
+ const ring = (0, focusRing_styles_1.focusRingShadow)(colors);
56
+ const trackFocusShadow = plateEdge ? `${plateEdge}, ${ring}` : ring;
58
57
  const [showFocus, setShowFocus] = (0, react_1.useState)(false);
59
58
  const selectedIndex = options.findIndex((o) => o.value === value);
60
59
  const enabled = options.reduce((acc, o, i) => (o.isDisabled ? acc : [...acc, i]), []);
@@ -96,7 +95,7 @@ function SegmentedControl({ options, value, onChange, size = "sm", isFullWidth =
96
95
  selectAt(enabled[enabled.length - 1]);
97
96
  }
98
97
  };
99
- return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, "aria-orientation": isVertical ? "vertical" : undefined, onKeyDown: handleKeyDown, onFocus: (event) => setShowFocus(event.target.matches(":focus-visible")), onBlur: () => setShowFocus(false), boxShadow: showFocus && !isUnderlineSlide ? focusShadow : plateEdge, position: "relative", display: "inline-flex", flexDirection: isVertical ? "column" : "row", align: isVertical ? "stretch" : "center", gap: isUnderlineSlide ? "0" : TRACK_PADDING, p: isUnderlineSlide ? "0" : TRACK_PADDING, h: isVertical ? "fit-content" : control.height, boxSizing: "border-box", borderRadius: isUnderlineSlide ? "none" : "lg", borderBottom: isUnderlineSlide && !isVertical ? `${UNDERLINE_WIDTH} solid ${colors.boxborder[200]}` : undefined, borderLeft: isUnderlineSlide && isVertical ? `${UNDERLINE_WIDTH} solid ${colors.boxborder[200]}` : undefined, bg: isUnderlineSlide ? "transparent" : trackSurface, w: isFullWidth ? "100%" : "fit-content", minW: "max-content", children: [isBrandSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", top: isVertical ? 0 : TRACK_PADDING, left: isVertical ? TRACK_PADDING : 0, right: isVertical ? TRACK_PADDING : undefined, h: segmentH, w: isVertical ? undefined : `${pill.width}px`, borderRadius: innerRadius, bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: isVertical ? `translateY(${pill.top}px)` : `translateX(${pill.left}px)`, transition: SLIDE, sx: (0, motion_2.reducedMotionSx)() })), isUnderlineSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", bottom: isVertical ? undefined : "-0.0625rem", left: isVertical ? "-0.0625rem" : 0, top: isVertical ? 0 : undefined, h: isVertical ? segmentH : "0.125rem", w: isVertical ? "0.125rem" : `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: isVertical ? `translateY(${pill.top}px)` : `translateX(${pill.left}px)`, transition: SLIDE, sx: (0, motion_2.reducedMotionSx)() })), options.map((option, index) => {
98
+ return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, "aria-orientation": isVertical ? "vertical" : undefined, onKeyDown: handleKeyDown, onFocus: (event) => setShowFocus(event.target.matches(":focus-visible")), onBlur: () => setShowFocus(false), boxShadow: showFocus && !isUnderlineSlide ? trackFocusShadow : plateEdge, position: "relative", display: "inline-flex", flexDirection: isVertical ? "column" : "row", align: isVertical ? "stretch" : "center", gap: isUnderlineSlide ? "0" : TRACK_PADDING, p: isUnderlineSlide ? "0" : TRACK_PADDING, h: isVertical ? "fit-content" : control.height, boxSizing: "border-box", borderRadius: isUnderlineSlide ? "none" : "lg", borderBottom: isUnderlineSlide && !isVertical ? `${UNDERLINE_WIDTH} solid ${colors.boxborder[200]}` : undefined, borderLeft: isUnderlineSlide && isVertical ? `${UNDERLINE_WIDTH} solid ${colors.boxborder[200]}` : undefined, bg: isUnderlineSlide ? "transparent" : trackSurface, w: isFullWidth ? "100%" : "fit-content", minW: "max-content", children: [isBrandSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", top: isVertical ? 0 : TRACK_PADDING, left: isVertical ? TRACK_PADDING : 0, right: isVertical ? TRACK_PADDING : undefined, h: segmentH, w: isVertical ? undefined : `${pill.width}px`, borderRadius: innerRadius, bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: isVertical ? `translateY(${pill.top}px)` : `translateX(${pill.left}px)`, transition: SLIDE, sx: (0, motion_2.reducedMotionSx)() })), isUnderlineSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", bottom: isVertical ? undefined : "-0.0625rem", left: isVertical ? "-0.0625rem" : 0, top: isVertical ? 0 : undefined, h: isVertical ? segmentH : "0.125rem", w: isVertical ? "0.125rem" : `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: isVertical ? `translateY(${pill.top}px)` : `translateX(${pill.left}px)`, transition: SLIDE, sx: (0, motion_2.reducedMotionSx)() })), options.map((option, index) => {
100
99
  const isSelected = option.value === value;
101
100
  const showShortLabel = isCompact && !!option.shortLabel;
102
101
  const displayLabel = showShortLabel ? option.shortLabel : option.label;
@@ -116,10 +115,6 @@ function SegmentedControl({ options, value, onChange, size = "sm", isFullWidth =
116
115
  : restInk
117
116
  : isSelected
118
117
  ? selectedInk
119
- : restInk, fontSize: control.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: (0, motion_1.transitionFor)(["color"]), _hover: option.isDisabled || isSelected ? undefined : { color: hoverInk }, sx: {
120
- _focusVisible: segmentFocus,
121
- [focusRing_styles_1.FOCUS_VISIBLE_OVER_STATE]: segmentFocus,
122
- ...(0, motion_2.reducedMotionSx)(),
123
- }, children: [option.icon, displayLabel, option.count != null && ((0, jsx_runtime_1.jsx)(Tag_1.default, { label: formatCount(option.count), size: COUNT_TAG_SIZE[size], variant: "subtle", colorScheme: "gray" }))] }, option.value));
118
+ : restInk, fontSize: control.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: (0, motion_1.transitionFor)(["color"]), _hover: option.isDisabled || isSelected ? undefined : { color: hoverInk }, sx: { ...segmentFocus, ...(0, motion_2.reducedMotionSx)() }, children: [option.icon, displayLabel, option.count != null && ((0, jsx_runtime_1.jsx)(Tag_1.default, { label: formatCount(option.count), size: COUNT_TAG_SIZE[size], variant: "subtle", colorScheme: "gray" }))] }, option.value));
124
119
  })] }));
125
120
  }
@@ -6,8 +6,6 @@ export declare const segmentedTrackSurface: (variant: SegmentedControlVariant, c
6
6
  export declare const segmentedSelectedSurface: (variant: SegmentedControlVariant, colors: PaletteProps, trackSurface: string) => string;
7
7
  /** Unselected label ink, lifted to AA against the track it is drawn on. */
8
8
  export declare const segmentedRestInk: (colors: PaletteProps, trackSurface: string) => string;
9
- /** A radiogroup is one tab stop, so the group wears the ring: a brand hairline plus the field bloom. */
10
- export declare const segmentedFocusShadow: (colors: PaletteProps, trackSurface: string) => string;
11
9
  /** The plate can match its backdrop's lightness, so its edge carries the boundary.
12
10
  * Drawn inset by the caller: a real border consumes the content box and decentres the sliding pill. */
13
11
  export declare const segmentedPlateEdge: (colors: PaletteProps, surface?: string) => string;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.segmentedPlateEdge = exports.segmentedFocusShadow = exports.segmentedRestInk = exports.segmentedSelectedSurface = exports.segmentedTrackSurface = void 0;
4
- const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
3
+ exports.segmentedPlateEdge = exports.segmentedRestInk = exports.segmentedSelectedSurface = exports.segmentedTrackSurface = void 0;
5
4
  const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
6
5
  const accentText_1 = require("../../Theme/tokens/builders/accentText");
7
6
  /** The surface a segment's label really sits on. */
@@ -21,9 +20,6 @@ exports.segmentedSelectedSurface = segmentedSelectedSurface;
21
20
  /** Unselected label ink, lifted to AA against the track it is drawn on. */
22
21
  const segmentedRestInk = (colors, trackSurface) => (0, outlineRung_1.liftToContrast)(colors.text[600], trackSurface, accentText_1.AA_NORMAL_TEXT);
23
22
  exports.segmentedRestInk = segmentedRestInk;
24
- /** A radiogroup is one tab stop, so the group wears the ring: a brand hairline plus the field bloom. */
25
- const segmentedFocusShadow = (colors, trackSurface) => `inset 0 0 0 1px ${(0, outlineRung_1.liftToContrast)(colors.primary[500], trackSurface, outlineRung_1.AA_NON_TEXT)}, ${(0, focusRing_styles_1.focusGlowShadow)(colors)}`;
26
- exports.segmentedFocusShadow = segmentedFocusShadow;
27
23
  /** The plate can match its backdrop's lightness, so its edge carries the boundary.
28
24
  * Drawn inset by the caller: a real border consumes the content box and decentres the sliding pill. */
29
25
  const segmentedPlateEdge = (colors, surface) => (0, outlineRung_1.liftToContrast)(colors.boxborder[500], surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main, outlineRung_1.AA_NON_TEXT);
@@ -1,4 +1,4 @@
1
1
  import type { StatCardProps } from "./StatCardProps";
2
2
  /** KPI tile: tinted icon chip, label, 2xl tabular value, delta coloured by whether the change is good. */
3
- declare const StatCard: ({ label, value, delta, trend, goodDirection, hint, sparkline, footer, icon: Icon, isLoading, onClick, "aria-label": ariaLabel, "data-testid": testId, }: StatCardProps) => import("react/jsx-runtime").JSX.Element;
3
+ declare const StatCard: ({ label, value, delta, trend, goodDirection, hint, sparkline, footer, icon: Icon, iconTone, isLoading, onClick, "aria-label": ariaLabel, "data-testid": testId, }: StatCardProps) => import("react/jsx-runtime").JSX.Element;
4
4
  export default StatCard;
@@ -7,13 +7,14 @@ const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("@chakra-ui/react");
8
8
  const lucide_react_1 = require("lucide-react");
9
9
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
10
+ const tone_1 = require("../../Theme/tokens/builders/tone");
10
11
  const motion_1 = require("../../Theme/tokens/primitives/motion");
11
12
  const motion_2 = require("../../Theme/chakra/motion");
12
13
  const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
13
14
  const Skeletons_1 = __importDefault(require("../Skeletons/Skeletons"));
14
15
  const StatCardSkeleton = ({ hasIcon }) => ((0, jsx_runtime_1.jsxs)(react_1.Flex, { direction: "column", gap: 3, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { align: "center", gap: 2, children: [hasIcon && (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 8, width: 8 }), (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 3, width: "40%" })] }), (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 7, width: "55%" }), (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: 3, width: "45%" })] }));
15
16
  /** KPI tile: tinted icon chip, label, 2xl tabular value, delta coloured by whether the change is good. */
16
- const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, sparkline, footer, icon: Icon, isLoading = false, onClick, "aria-label": ariaLabel, "data-testid": testId, }) => {
17
+ const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, sparkline, footer, icon: Icon, iconTone, isLoading = false, onClick, "aria-label": ariaLabel, "data-testid": testId, }) => {
17
18
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
18
19
  const isInteractive = !!onClick && !isLoading;
19
20
  const deltaColor = trend
@@ -22,6 +23,9 @@ const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, spar
22
23
  : colors.semanticText.error
23
24
  : colors.textMuted;
24
25
  const DeltaIcon = trend === "down" ? lucide_react_1.ArrowDownRight : lucide_react_1.ArrowUpRight;
26
+ const chip = iconTone
27
+ ? (0, tone_1.resolveSoftTone)(colors, (0, tone_1.toneScale)(colors, iconTone), colors.background[50])
28
+ : { bg: colors.primary.opacity[8], fg: colors.accentText };
25
29
  const surface = {
26
30
  display: "block",
27
31
  w: "100%",
@@ -33,7 +37,7 @@ const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, spar
33
37
  px: 5,
34
38
  py: 4,
35
39
  };
36
- const content = isLoading ? ((0, jsx_runtime_1.jsx)(StatCardSkeleton, { hasIcon: !!Icon })) : ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", direction: "column", gap: 3, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: 2, children: [Icon && ((0, jsx_runtime_1.jsx)(react_1.Flex, { as: "span", boxSize: 8, flexShrink: 0, align: "center", justify: "center", borderRadius: "lg", bg: colors.primary.opacity[8], color: colors.accentText, children: (0, jsx_runtime_1.jsx)(Icon, { size: 16, "aria-hidden": true }) })), (0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: label })] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", justify: "space-between", gap: 3, children: [(0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", display: "block", fontSize: "2xl", fontWeight: 700, letterSpacing: "-0.02em", lineHeight: "short", color: colors.header[900], sx: { fontVariantNumeric: "tabular-nums" }, children: value }), sparkline && ((0, jsx_runtime_1.jsx)(react_1.Flex, { as: "span", align: "center", justify: "center", flexShrink: 0, h: 8, minW: 16, maxW: 24, overflow: "hidden", "data-testid": "stat-sparkline", children: sparkline }))] }), (delta !== undefined || hint) && ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: 1.5, wrap: "wrap", children: [delta !== undefined && ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: 0.5, color: deltaColor, "data-testid": "stat-delta", children: [trend && (0, jsx_runtime_1.jsx)(DeltaIcon, { size: 14, "aria-hidden": true }), trend && (0, jsx_runtime_1.jsx)(react_1.VisuallyHidden, { children: trend === "up" ? "Up" : "Down" }), (0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", fontSize: "xs", fontWeight: 600, sx: { fontVariantNumeric: "tabular-nums" }, children: delta })] })), hint && ((0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", fontSize: "xs", color: colors.textMuted, children: hint }))] })), footer && ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", pt: 3, borderTopWidth: "1px", borderColor: colors.boxborder[200], "data-testid": "stat-footer", children: footer }))] }));
40
+ const content = isLoading ? ((0, jsx_runtime_1.jsx)(StatCardSkeleton, { hasIcon: !!Icon })) : ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", direction: "column", gap: 3, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: 2, children: [Icon && ((0, jsx_runtime_1.jsx)(react_1.Flex, { as: "span", boxSize: 8, flexShrink: 0, align: "center", justify: "center", borderRadius: "lg", bg: chip.bg, color: chip.fg, "data-icon-tone": iconTone, children: (0, jsx_runtime_1.jsx)(Icon, { size: 16, "aria-hidden": true }) })), (0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: label })] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", justify: "space-between", gap: 3, children: [(0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", display: "block", fontSize: "2xl", fontWeight: 700, letterSpacing: "-0.02em", lineHeight: "short", color: colors.header[900], sx: { fontVariantNumeric: "tabular-nums" }, children: value }), sparkline && ((0, jsx_runtime_1.jsx)(react_1.Flex, { as: "span", align: "center", justify: "center", flexShrink: 0, h: 8, minW: 16, maxW: 24, overflow: "hidden", "data-testid": "stat-sparkline", children: sparkline }))] }), (delta !== undefined || hint) && ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: 1.5, wrap: "wrap", children: [delta !== undefined && ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: 0.5, color: deltaColor, "data-testid": "stat-delta", children: [trend && (0, jsx_runtime_1.jsx)(DeltaIcon, { size: 14, "aria-hidden": true }), trend && (0, jsx_runtime_1.jsx)(react_1.VisuallyHidden, { children: trend === "up" ? "Up" : "Down" }), (0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", fontSize: "xs", fontWeight: 600, sx: { fontVariantNumeric: "tabular-nums" }, children: delta })] })), hint && ((0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", fontSize: "xs", color: colors.textMuted, children: hint }))] })), footer && ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", pt: 3, borderTopWidth: "1px", borderColor: colors.boxborder[200], "data-testid": "stat-footer", children: footer }))] }));
37
41
  if (!isInteractive) {
38
42
  return ((0, jsx_runtime_1.jsx)(react_1.Box, { ...surface, "aria-busy": isLoading || undefined, "data-testid": testId, children: content }));
39
43
  }
@@ -1,4 +1,5 @@
1
1
  import type { ElementType, MouseEventHandler, ReactNode } from "react";
2
+ import type { Tone } from "../../Theme/tokens/builders/tone";
2
3
  type StatTrend = "up" | "down";
3
4
  /** KPI tile; delta is green when `trend` matches `goodDirection` (default "up"), else red. */
4
5
  export interface StatCardProps {
@@ -19,6 +20,8 @@ export interface StatCardProps {
19
20
  footer?: ReactNode;
20
21
  /** Icon component (not an element) shown in a tinted chip before the label. */
21
22
  icon?: ElementType;
23
+ /** Paints the icon chip with a soft status tone; unset keeps the brand-primary chip, "neutral" is grey. */
24
+ iconTone?: Tone;
22
25
  /** Shows a skeleton and disables the click target. */
23
26
  isLoading?: boolean;
24
27
  /** Turns the tile into a full-size button with hover and focus styles. */
@@ -0,0 +1,4 @@
1
+ import type { StatusTagProps } from "./StatusTagProps";
2
+ /** Typed status-to-Tag wrapper: map your vocabulary to tones once, render chips or dots everywhere. */
3
+ declare function StatusTag<S extends string>({ status, label, map, display, size, "data-testid": testId, }: StatusTagProps<S>): import("react/jsx-runtime").JSX.Element;
4
+ export default StatusTag;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const Tag_1 = __importDefault(require("../Tag/Tag"));
8
+ const NEUTRAL = { tone: "neutral" };
9
+ const warned = new Set();
10
+ const warnUnknown = (status) => {
11
+ if (process.env.NODE_ENV === "production" || warned.has(status))
12
+ return;
13
+ warned.add(status);
14
+ console.warn(`StatusTag: no appearance mapped for status "${status}"; rendering neutral.`);
15
+ };
16
+ /** Typed status-to-Tag wrapper: map your vocabulary to tones once, render chips or dots everywhere. */
17
+ function StatusTag({ status, label, map, display = "chip", size = "sm", "data-testid": testId, }) {
18
+ const known = Object.prototype.hasOwnProperty.call(map, status);
19
+ if (!known)
20
+ warnUnknown(status);
21
+ const { tone, variant } = known ? map[status] : NEUTRAL;
22
+ return (0, jsx_runtime_1.jsx)(Tag_1.default, { label: label, tone: tone, variant: variant, display: display, size: size, "data-testid": testId });
23
+ }
24
+ exports.default = StatusTag;
@@ -0,0 +1,20 @@
1
+ import type { Tone } from "../../Theme/tokens/builders/tone";
2
+ import type { TagDisplay, TagVariant } from "../Tag/TagProps";
3
+ /** How one status looks: a tone, plus the chip variant (ignored in dot display). */
4
+ export type StatusAppearance = {
5
+ tone: Tone;
6
+ variant?: TagVariant;
7
+ };
8
+ /** Props for StatusTag; `S` is the app's status union so `map` must cover every status. */
9
+ export interface StatusTagProps<S extends string = string> {
10
+ /** The raw status value; one missing from `map` renders neutral and warns once in development. */
11
+ status: S;
12
+ /** Visible text; the app owns translation, StatusTag never derives it from `status`. */
13
+ label: string;
14
+ /** Status vocabulary to appearance, declared once per app. */
15
+ map: Record<S, StatusAppearance>;
16
+ /** "dot" gives a quiet dot plus label; default "chip". */
17
+ display?: TagDisplay;
18
+ size?: "sm" | "md";
19
+ "data-testid"?: string;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,3 @@
1
1
  import { TableProps } from "./TableProps";
2
2
  /** Data table: virtualized-height rows, pagination, filters, selection, and an optional Monday-style grouped board. */
3
- export default function Table({ data, columns, onSelection, isLoading, isCheckbox, freezedBgColor, freezedTextColor, noBorders, isPagination, onRowClick, selections, isActionFreeze, preferences, paginationMode, infiniteScroll, hasMore, isLoadingMore, groupLoadMore, loadMoreText, loadMorePosition, loadMoreChunkSize, noOfRowsPerPage, totalRecords, onPagination, isTableSettings, headerActions, onGlobalSearch, onNoOfRowsPerPageChange, paginationSelectOptions, tableMaxHeight, minVisibleRows, maxVisibleRows, autoFitViewport, tableSettings, filterSidebar, loadingSkeletonRows, defaultVisibleColumns, density, variant, stripe, groupColors, onAddItem, emptyState, onRefresh, isGlobalSearch, }: TableProps): import("react/jsx-runtime").JSX.Element;
3
+ export default function Table({ data, columns, onSelection, isLoading, isCheckbox, freezedBgColor, freezedTextColor, noBorders, isPagination, onRowClick, selections, isActionFreeze, preferences, paginationMode, infiniteScroll, hasMore, isLoadingMore, groupLoadMore, loadMoreText, loadMorePosition, loadMoreChunkSize, noOfRowsPerPage, totalRecords, onPagination, isTableSettings, headerActions, onGlobalSearch, onNoOfRowsPerPageChange, paginationSelectOptions, tableMaxHeight, minVisibleRows, maxVisibleRows, autoFitViewport, tableSettings, filterSidebar, loadingSkeletonRows, defaultVisibleColumns, density, variant, stripe, renderRowDetail, expandedRows, onExpandedRowsChange, groupColors, onAddItem, emptyState, onRefresh, isGlobalSearch, }: TableProps): import("react/jsx-runtime").JSX.Element;
@@ -25,6 +25,7 @@ const rowMetrics_1 = require("./components/rowMetrics");
25
25
  const scrollbar_1 = require("../../Theme/tokens/builders/scrollbar");
26
26
  const scrollShadow_1 = require("../../Theme/tokens/builders/scrollShadow");
27
27
  const useTable_1 = __importDefault(require("./hooks/useTable"));
28
+ const useRowExpansion_1 = require("./hooks/useRowExpansion");
28
29
  const columnLabel_1 = require("./columnLabel");
29
30
  const table_1 = require("../../Utils/table");
30
31
  const usePreferences_1 = require("../../Hooks/usePreferences");
@@ -45,12 +46,15 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
45
46
  name: "",
46
47
  page: "",
47
48
  orgId: "",
48
- }, paginationMode = "client", infiniteScroll = false, hasMore, isLoadingMore = false, groupLoadMore = false, loadMoreText = "Load more", loadMorePosition = "top", loadMoreChunkSize, noOfRowsPerPage = 50, totalRecords = 0, onPagination, isTableSettings = false, headerActions, onGlobalSearch, onNoOfRowsPerPageChange, paginationSelectOptions, tableMaxHeight, minVisibleRows, maxVisibleRows, autoFitViewport, tableSettings, filterSidebar, loadingSkeletonRows, defaultVisibleColumns, density = "normal", variant = "classic", stripe = false, groupColors, onAddItem, emptyState, onRefresh, isGlobalSearch = true, }) {
49
+ }, paginationMode = "client", infiniteScroll = false, hasMore, isLoadingMore = false, groupLoadMore = false, loadMoreText = "Load more", loadMorePosition = "top", loadMoreChunkSize, noOfRowsPerPage = 50, totalRecords = 0, onPagination, isTableSettings = false, headerActions, onGlobalSearch, onNoOfRowsPerPageChange, paginationSelectOptions, tableMaxHeight, minVisibleRows, maxVisibleRows, autoFitViewport, tableSettings, filterSidebar, loadingSkeletonRows, defaultVisibleColumns, density = "normal", variant = "classic", stripe = false, renderRowDetail, expandedRows, onExpandedRowsChange, groupColors, onAddItem, emptyState, onRefresh, isGlobalSearch = true, }) {
49
50
  var _a, _b, _c, _d, _e;
50
51
  const theme = (0, useCustomTheme_1.useCustomTheme)();
51
52
  const clearFiltersTone = (0, react_1.useMemo)(() => (0, filledSurface_1.resolveFilledSurface)(theme.colors.semantic.error, theme.colors), [theme.colors]);
52
53
  const variantTokens = (0, react_1.useMemo)(() => variants_1.TABLE_VARIANTS[variant](theme.colors, (0, accentText_1.isDarkCanvas)(theme.colors.backgroundColor.main)), [variant, theme.colors]);
53
54
  const [columnsSearch, setColumnsSearch] = (0, react_1.useState)({});
55
+ const detailIdPrefix = (0, react_1.useId)();
56
+ const { expandedIds, toggle: toggleDetail } = (0, useRowExpansion_1.useRowExpansion)(expandedRows, onExpandedRowsChange);
57
+ const rowDetail = (0, react_1.useMemo)(() => (renderRowDetail ? { render: renderRowDetail, toggle: toggleDetail, idPrefix: detailIdPrefix } : undefined), [renderRowDetail, toggleDetail, detailIdPrefix]);
54
58
  const tableContainerRef = (0, react_1.useRef)(null);
55
59
  const { isOpen: isFilterModalOpen, onOpen: onFilterModalOpen, onClose: onFilterModalClose } = (0, react_2.useDisclosure)();
56
60
  // Below `md` the sidebar has no room — default to the modal, unless the consumer picked a mode.
@@ -448,5 +452,5 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
448
452
  backgroundColor: "transparent",
449
453
  zIndex: 999,
450
454
  },
451
- }, children: [(0, jsx_runtime_1.jsx)(react_3.Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: (0, jsx_runtime_1.jsx)(TableHeader_1.default, { columns: columnsList, isCheckbox: isCheckbox, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleSort: handleSort, headerRefs: headerRefs, columnWidths: columnWidths, slotRef: slotRef, slotWidth: slotWidth, columnsSort: columnsSort, noBorders: noBorders, handleCheckbox: handleCheckbox, isLoading: isTableLoading, checked: headerChecked, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, setColumnsSearch: setColumnsSearch, columnsSearch: columnsSearch, isSelecting: isSelecting }) }), (0, jsx_runtime_1.jsx)(react_3.Tbody, { children: (0, jsx_runtime_1.jsx)(TableBody_1.default, { data: isGrouped ? groupedSource : _filteredData, groups: renderGroups, onAddItem: onAddItem, columns: columnsList, startRow: isGrouped ? 0 : startRow, rowsPerPage: rowsPerPage, scrollContainerRef: tableContainerRef, isCheckbox: isCheckbox, columnWidths: columnWidths, slotWidth: slotWidth, noBorders: noBorders, freezedBgColor: freezedBgColor !== null && freezedBgColor !== void 0 ? freezedBgColor : theme.colors.backgroundColor.secondary, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleCheckbox: handleCheckbox, selections: selection, isLoading: isTableLoading, loadingSkeletonRows: loadingSkeletonRows, onRowClick: onRowClick, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, density: densityState, stripe: stripe, emptyState: emptyState }) })] }), canInfinite && isLoadingMore && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "sm", color: theme.colors.accentText }), (0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
455
+ }, children: [(0, jsx_runtime_1.jsx)(react_3.Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: (0, jsx_runtime_1.jsx)(TableHeader_1.default, { columns: columnsList, isCheckbox: isCheckbox, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleSort: handleSort, headerRefs: headerRefs, columnWidths: columnWidths, slotRef: slotRef, slotWidth: slotWidth, columnsSort: columnsSort, noBorders: noBorders, handleCheckbox: handleCheckbox, isLoading: isTableLoading, checked: headerChecked, isContent: isContent, isLink: isLink, hasDetail: !!rowDetail, isActionFreeze: isActionFreeze, setColumnsSearch: setColumnsSearch, columnsSearch: columnsSearch, isSelecting: isSelecting }) }), (0, jsx_runtime_1.jsx)(react_3.Tbody, { children: (0, jsx_runtime_1.jsx)(TableBody_1.default, { data: isGrouped ? groupedSource : _filteredData, groups: renderGroups, onAddItem: onAddItem, columns: columnsList, startRow: isGrouped ? 0 : startRow, rowsPerPage: rowsPerPage, scrollContainerRef: tableContainerRef, isCheckbox: isCheckbox, columnWidths: columnWidths, slotWidth: slotWidth, noBorders: noBorders, freezedBgColor: freezedBgColor !== null && freezedBgColor !== void 0 ? freezedBgColor : theme.colors.backgroundColor.secondary, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleCheckbox: handleCheckbox, selections: selection, isLoading: isTableLoading, loadingSkeletonRows: loadingSkeletonRows, onRowClick: onRowClick, isContent: isContent, isLink: isLink, rowDetail: rowDetail, expandedIds: expandedIds, isActionFreeze: isActionFreeze, density: densityState, stripe: stripe, emptyState: emptyState }) })] }), canInfinite && isLoadingMore && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "sm", color: theme.colors.accentText }), (0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
452
456
  }
@@ -28,6 +28,12 @@ export type TableProps = {
28
28
  density?: TableDensity;
29
29
  /** Visual preset. Defaults to `classic` — the table exactly as it shipped. */
30
30
  variant?: TableVariant;
31
+ /** Content shown under a row when expanded; return null for rows with no detail and they get no chevron. Memoize it: an inline function re-renders every row. */
32
+ renderRowDetail?: (row: DataObject) => ReactNode | null;
33
+ /** Controlled expanded row ids; omit for uncontrolled. */
34
+ expandedRows?: (string | number)[];
35
+ /** Fires with the full expanded id list on every chevron toggle, controlled or not. */
36
+ onExpandedRowsChange?: (ids: (string | number)[]) => void;
31
37
  /** Zebra rows: every second row gets the variant's `row.stripeBg` fill. Frozen and selected cells keep their own fill. */
32
38
  stripe?: boolean;
33
39
  /** Shown when there are no rows. An object feeds the library `EmptyState`; any other node renders as-is. */
@@ -207,6 +213,12 @@ export type ColumnSortingProps = {
207
213
  column: string | number;
208
214
  direction: "asc" | "desc" | "none";
209
215
  };
216
+ /** Internal wiring for the row-detail expander, built once by `Table`. */
217
+ export type TableRowDetail = {
218
+ render: (row: DataObject) => ReactNode | null;
219
+ toggle: (id: string | number) => void;
220
+ idPrefix: string;
221
+ };
210
222
  /** Props for the TableHeader row subcomponent: all columns, sorting, search, freeze, selection. */
211
223
  export type TableHeaderPageProps = Pick<TableProps, "columns" | "isCheckbox" | "freezedTextColor" | "noBorders" | "isLoading" | "isActionFreeze"> & {
212
224
  handleSort: (label: string | number, type: "asc" | "desc" | "none") => void;
@@ -225,6 +237,8 @@ export type TableHeaderPageProps = Pick<TableProps, "columns" | "isCheckbox" | "
225
237
  checked: boolean | "indeterminate";
226
238
  isContent: boolean;
227
239
  isLink: boolean;
240
+ /** Adds the empty chevron column header after the checkbox. */
241
+ hasDetail?: boolean;
228
242
  setColumnsSearch: React.Dispatch<React.SetStateAction<Record<string, string>>>;
229
243
  columnsSearch: Record<string, string>;
230
244
  isSelecting?: boolean;
@@ -241,6 +255,10 @@ export type TableBodyPageProps = Pick<TableProps, "columns" | "isCheckbox" | "da
241
255
  selections: (string | number)[];
242
256
  isContent: boolean;
243
257
  isLink: boolean;
258
+ /** Row-detail expander wiring; omitted when `renderRowDetail` is not used. */
259
+ rowDetail?: TableRowDetail;
260
+ /** Ids of the rows whose detail is open. */
261
+ expandedIds?: Set<string | number>;
244
262
  scrollContainerRef?: MutableRefObject<HTMLDivElement | null>;
245
263
  /** Optional per-row id → accent color (Monday group stripe). */
246
264
  accentColors?: Record<string | number, string>;
@@ -273,6 +291,8 @@ export type ColumnHeaderProps = Pick<TableProps, "isCheckbox" | "noBorders"> & P
273
291
  index: number;
274
292
  columnWidths: number[];
275
293
  slotWidth: number;
294
+ /** Width of the detail chevron column to the left of frozen columns; 0 when absent. */
295
+ detailWidth?: number;
276
296
  headerRefs: MutableRefObject<(HTMLTableCellElement | null)[]>;
277
297
  columnsSearch: Record<string, string>;
278
298
  setColumnsSearch: Dispatch<SetStateAction<Record<string, string>>>;
@@ -1,4 +1,4 @@
1
1
  import { TableBodyPageProps } from "../TableProps";
2
2
  export declare const findFirstVisibleIndex: (offsets: number[], total: number, target: number) => number;
3
- declare const TableBody: ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWidths, slotWidth, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe, accentColors, groups, onAddItem, emptyState, }: TableBodyPageProps) => import("react/jsx-runtime").JSX.Element;
3
+ declare const TableBody: ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWidths, slotWidth, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe, accentColors, groups, onAddItem, emptyState, rowDetail, expandedIds, }: TableBodyPageProps) => import("react/jsx-runtime").JSX.Element;
4
4
  export default TableBody;