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,13 +5,13 @@ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
5
  import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
6
6
  import { BUTTON_SIZES } from "../../Theme/tokens/primitives/controlSizes.js";
7
7
  import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
8
- import { FOCUS_VISIBLE_OVER_STATE } from "../../Theme/chakra/focusRing.styles.js";
8
+ import { FOCUS_VISIBLE_OVER_STATE, focusRingShadow, focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
9
9
  import { reducedMotionSx } from "../../Theme/chakra/motion.js";
10
10
  import { AA_NORMAL_TEXT } from "../../Theme/tokens/builders/accentText.js";
11
11
  import { liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
12
12
  import Tag from "../Tag/Tag.js";
13
13
  import { useSlidingPill } from "./useSlidingPill.js";
14
- import { segmentedFocusShadow, segmentedPlateEdge, segmentedRestInk, segmentedSelectedSurface, segmentedTrackSurface, } from "./segmentedInk.js";
14
+ import { segmentedPlateEdge, segmentedRestInk, segmentedSelectedSurface, segmentedTrackSurface, } from "./segmentedInk.js";
15
15
  const TRACK_PADDING = "0.125rem";
16
16
  const UNDERLINE_WIDTH = "0.0625rem";
17
17
  const SLIDE = transitionFor(["transform", "width"], "base");
@@ -37,18 +37,17 @@ export default function SegmentedControl({ options, value, onChange, size = "sm"
37
37
  const segmentH = `calc(${control.height} - ${isUnderlineSlide ? UNDERLINE_WIDTH : `2 * ${TRACK_PADDING}`})`;
38
38
  // Concentric: an inset child keeping the parent's radius bulges at the corners.
39
39
  const innerRadius = isUnderlineSlide ? "none" : `calc(${radii.lg} - ${TRACK_PADDING})`;
40
- const focusShadow = segmentedFocusShadow(colors, trackSurface);
41
40
  // Inset, never a border: a border consumes the content box and the absolute pill stops centring.
42
41
  const plateEdge = isBrandSlide
43
42
  ? `inset 0 0 0 1px ${segmentedPlateEdge(colors, surface)}`
44
43
  : undefined;
45
- // The transparent outline replaces the UA ring and is what Windows high-contrast repaints.
46
- const segmentFocus = {
47
- outline: "2px solid transparent",
48
- outlineOffset: "2px",
49
- // underlineSlide has no track chrome to carry the indicator, so there the segment wears it.
50
- ...(isUnderlineSlide ? { boxShadow: focusShadow } : {}),
51
- };
44
+ // underlineSlide has no track chrome to carry the ring, so there the segment wears Button's own.
45
+ const noRing = { outline: "2px solid transparent", outlineOffset: "2px" };
46
+ const segmentFocus = isUnderlineSlide
47
+ ? focusVisibleStyles(colors)
48
+ : { _focusVisible: noRing, [FOCUS_VISIBLE_OVER_STATE]: noRing };
49
+ const ring = focusRingShadow(colors);
50
+ const trackFocusShadow = plateEdge ? `${plateEdge}, ${ring}` : ring;
52
51
  const [showFocus, setShowFocus] = useState(false);
53
52
  const selectedIndex = options.findIndex((o) => o.value === value);
54
53
  const enabled = options.reduce((acc, o, i) => (o.isDisabled ? acc : [...acc, i]), []);
@@ -90,7 +89,7 @@ export default function SegmentedControl({ options, value, onChange, size = "sm"
90
89
  selectAt(enabled[enabled.length - 1]);
91
90
  }
92
91
  };
93
- return (_jsxs(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 && (_jsx(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: reducedMotionSx() })), isUnderlineSlide && pill && (_jsx(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: reducedMotionSx() })), options.map((option, index) => {
92
+ return (_jsxs(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 && (_jsx(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: reducedMotionSx() })), isUnderlineSlide && pill && (_jsx(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: reducedMotionSx() })), options.map((option, index) => {
94
93
  const isSelected = option.value === value;
95
94
  const showShortLabel = isCompact && !!option.shortLabel;
96
95
  const displayLabel = showShortLabel ? option.shortLabel : option.label;
@@ -110,10 +109,6 @@ export default function SegmentedControl({ options, value, onChange, size = "sm"
110
109
  : restInk
111
110
  : isSelected
112
111
  ? selectedInk
113
- : restInk, fontSize: control.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: transitionFor(["color"]), _hover: option.isDisabled || isSelected ? undefined : { color: hoverInk }, sx: {
114
- _focusVisible: segmentFocus,
115
- [FOCUS_VISIBLE_OVER_STATE]: segmentFocus,
116
- ...reducedMotionSx(),
117
- }, children: [option.icon, displayLabel, option.count != null && (_jsx(Tag, { label: formatCount(option.count), size: COUNT_TAG_SIZE[size], variant: "subtle", colorScheme: "gray" }))] }, option.value));
112
+ : restInk, fontSize: control.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: transitionFor(["color"]), _hover: option.isDisabled || isSelected ? undefined : { color: hoverInk }, sx: { ...segmentFocus, ...reducedMotionSx() }, children: [option.icon, displayLabel, option.count != null && (_jsx(Tag, { label: formatCount(option.count), size: COUNT_TAG_SIZE[size], variant: "subtle", colorScheme: "gray" }))] }, option.value));
118
113
  })] }));
119
114
  }
@@ -1,4 +1,3 @@
1
- import { focusGlowShadow } from "../../Theme/chakra/focusRing.styles.js";
2
1
  import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
3
2
  import { AA_NORMAL_TEXT } from "../../Theme/tokens/builders/accentText.js";
4
3
  /** The surface a segment's label really sits on. */
@@ -15,8 +14,6 @@ export const segmentedTrackSurface = (variant, colors, surface) => {
15
14
  export const segmentedSelectedSurface = (variant, colors, trackSurface) => (variant === "default" ? colors.backgroundColor.light : trackSurface);
16
15
  /** Unselected label ink, lifted to AA against the track it is drawn on. */
17
16
  export const segmentedRestInk = (colors, trackSurface) => liftToContrast(colors.text[600], trackSurface, AA_NORMAL_TEXT);
18
- /** A radiogroup is one tab stop, so the group wears the ring: a brand hairline plus the field bloom. */
19
- export const segmentedFocusShadow = (colors, trackSurface) => `inset 0 0 0 1px ${liftToContrast(colors.primary[500], trackSurface, AA_NON_TEXT)}, ${focusGlowShadow(colors)}`;
20
17
  /** The plate can match its backdrop's lightness, so its edge carries the boundary.
21
18
  * Drawn inset by the caller: a real border consumes the content box and decentres the sliding pill. */
22
19
  export const segmentedPlateEdge = (colors, surface) => liftToContrast(colors.boxborder[500], surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main, AA_NON_TEXT);
@@ -2,13 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Flex, Text, VisuallyHidden } from "@chakra-ui/react";
3
3
  import { ArrowDownRight, ArrowUpRight } from "lucide-react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
+ import { resolveSoftTone, toneScale } from "../../Theme/tokens/builders/tone.js";
5
6
  import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
6
7
  import { reducedMotionSx } from "../../Theme/chakra/motion.js";
7
8
  import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
8
9
  import Skeletons from "../Skeletons/Skeletons.js";
9
10
  const StatCardSkeleton = ({ hasIcon }) => (_jsxs(Flex, { direction: "column", gap: 3, children: [_jsxs(Flex, { align: "center", gap: 2, children: [hasIcon && _jsx(Skeletons, { type: "skeleton", height: 8, width: 8 }), _jsx(Skeletons, { type: "skeleton", height: 3, width: "40%" })] }), _jsx(Skeletons, { type: "skeleton", height: 7, width: "55%" }), _jsx(Skeletons, { type: "skeleton", height: 3, width: "45%" })] }));
10
11
  /** KPI tile: tinted icon chip, label, 2xl tabular value, delta coloured by whether the change is good. */
11
- const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, sparkline, footer, icon: Icon, isLoading = false, onClick, "aria-label": ariaLabel, "data-testid": testId, }) => {
12
+ const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, sparkline, footer, icon: Icon, iconTone, isLoading = false, onClick, "aria-label": ariaLabel, "data-testid": testId, }) => {
12
13
  const { colors } = useCustomTheme();
13
14
  const isInteractive = !!onClick && !isLoading;
14
15
  const deltaColor = trend
@@ -17,6 +18,9 @@ const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, spar
17
18
  : colors.semanticText.error
18
19
  : colors.textMuted;
19
20
  const DeltaIcon = trend === "down" ? ArrowDownRight : ArrowUpRight;
21
+ const chip = iconTone
22
+ ? resolveSoftTone(colors, toneScale(colors, iconTone), colors.background[50])
23
+ : { bg: colors.primary.opacity[8], fg: colors.accentText };
20
24
  const surface = {
21
25
  display: "block",
22
26
  w: "100%",
@@ -28,7 +32,7 @@ const StatCard = ({ label, value, delta, trend, goodDirection = "up", hint, spar
28
32
  px: 5,
29
33
  py: 4,
30
34
  };
31
- const content = isLoading ? (_jsx(StatCardSkeleton, { hasIcon: !!Icon })) : (_jsxs(Flex, { as: "span", direction: "column", gap: 3, children: [_jsxs(Flex, { as: "span", align: "center", gap: 2, children: [Icon && (_jsx(Flex, { as: "span", boxSize: 8, flexShrink: 0, align: "center", justify: "center", borderRadius: "lg", bg: colors.primary.opacity[8], color: colors.accentText, children: _jsx(Icon, { size: 16, "aria-hidden": true }) })), _jsx(Text, { as: "span", fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: label })] }), _jsxs(Flex, { as: "span", align: "center", justify: "space-between", gap: 3, children: [_jsx(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 && (_jsx(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) && (_jsxs(Flex, { as: "span", align: "center", gap: 1.5, wrap: "wrap", children: [delta !== undefined && (_jsxs(Flex, { as: "span", align: "center", gap: 0.5, color: deltaColor, "data-testid": "stat-delta", children: [trend && _jsx(DeltaIcon, { size: 14, "aria-hidden": true }), trend && _jsx(VisuallyHidden, { children: trend === "up" ? "Up" : "Down" }), _jsx(Text, { as: "span", fontSize: "xs", fontWeight: 600, sx: { fontVariantNumeric: "tabular-nums" }, children: delta })] })), hint && (_jsx(Text, { as: "span", fontSize: "xs", color: colors.textMuted, children: hint }))] })), footer && (_jsx(Box, { as: "span", pt: 3, borderTopWidth: "1px", borderColor: colors.boxborder[200], "data-testid": "stat-footer", children: footer }))] }));
35
+ const content = isLoading ? (_jsx(StatCardSkeleton, { hasIcon: !!Icon })) : (_jsxs(Flex, { as: "span", direction: "column", gap: 3, children: [_jsxs(Flex, { as: "span", align: "center", gap: 2, children: [Icon && (_jsx(Flex, { as: "span", boxSize: 8, flexShrink: 0, align: "center", justify: "center", borderRadius: "lg", bg: chip.bg, color: chip.fg, "data-icon-tone": iconTone, children: _jsx(Icon, { size: 16, "aria-hidden": true }) })), _jsx(Text, { as: "span", fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: label })] }), _jsxs(Flex, { as: "span", align: "center", justify: "space-between", gap: 3, children: [_jsx(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 && (_jsx(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) && (_jsxs(Flex, { as: "span", align: "center", gap: 1.5, wrap: "wrap", children: [delta !== undefined && (_jsxs(Flex, { as: "span", align: "center", gap: 0.5, color: deltaColor, "data-testid": "stat-delta", children: [trend && _jsx(DeltaIcon, { size: 14, "aria-hidden": true }), trend && _jsx(VisuallyHidden, { children: trend === "up" ? "Up" : "Down" }), _jsx(Text, { as: "span", fontSize: "xs", fontWeight: 600, sx: { fontVariantNumeric: "tabular-nums" }, children: delta })] })), hint && (_jsx(Text, { as: "span", fontSize: "xs", color: colors.textMuted, children: hint }))] })), footer && (_jsx(Box, { as: "span", pt: 3, borderTopWidth: "1px", borderColor: colors.boxborder[200], "data-testid": "stat-footer", children: footer }))] }));
32
36
  if (!isInteractive) {
33
37
  return (_jsx(Box, { ...surface, "aria-busy": isLoading || undefined, "data-testid": testId, children: content }));
34
38
  }
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Tag from "../Tag/Tag.js";
3
+ const NEUTRAL = { tone: "neutral" };
4
+ const warned = new Set();
5
+ const warnUnknown = (status) => {
6
+ if (process.env.NODE_ENV === "production" || warned.has(status))
7
+ return;
8
+ warned.add(status);
9
+ console.warn(`StatusTag: no appearance mapped for status "${status}"; rendering neutral.`);
10
+ };
11
+ /** Typed status-to-Tag wrapper: map your vocabulary to tones once, render chips or dots everywhere. */
12
+ function StatusTag({ status, label, map, display = "chip", size = "sm", "data-testid": testId, }) {
13
+ const known = Object.prototype.hasOwnProperty.call(map, status);
14
+ if (!known)
15
+ warnUnknown(status);
16
+ const { tone, variant } = known ? map[status] : NEUTRAL;
17
+ return _jsx(Tag, { label: label, tone: tone, variant: variant, display: display, size: size, "data-testid": testId });
18
+ }
19
+ export default StatusTag;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
3
3
  import { Box, TableContainer, Flex, Spinner, Popover, PopoverTrigger, PopoverContent, PopoverBody, Portal, useBreakpointValue, IconButton, Modal, ModalOverlay, ModalContent, ModalBody, ModalCloseButton, useDisclosure } from "@chakra-ui/react";
4
4
  import { Table as ChakraTable, Tbody, Thead, Icon } from "@chakra-ui/react";
5
5
  import { motion } from "framer-motion";
@@ -19,6 +19,7 @@ import { DENSITY_ROWS, EMPTY_STATE_HEIGHT, GROUP_HEADER_HEIGHT, MAX_TABLE_HEIGHT
19
19
  import { scrollbarStyles, SCROLLBAR_WIDTH } from "../../Theme/tokens/builders/scrollbar.js";
20
20
  import { scrollShadowStyles } from "../../Theme/tokens/builders/scrollShadow.js";
21
21
  import useTable from "./hooks/useTable.js";
22
+ import { useRowExpansion } from "./hooks/useRowExpansion.js";
22
23
  import { toSavedColumns } from "./columnLabel.js";
23
24
  import { searchAndSortData, SortMultiColumnData, groupRows, buildTablePalette, pickTableColor } from "../../Utils/table.js";
24
25
  import { useGetPreferences, useSavePreferences, } from "../../Hooks/usePreferences.js";
@@ -39,12 +40,15 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
39
40
  name: "",
40
41
  page: "",
41
42
  orgId: "",
42
- }, 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, }) {
43
+ }, 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, }) {
43
44
  var _a, _b, _c, _d, _e;
44
45
  const theme = useCustomTheme();
45
46
  const clearFiltersTone = useMemo(() => resolveFilledSurface(theme.colors.semantic.error, theme.colors), [theme.colors]);
46
47
  const variantTokens = useMemo(() => TABLE_VARIANTS[variant](theme.colors, isDarkCanvas(theme.colors.backgroundColor.main)), [variant, theme.colors]);
47
48
  const [columnsSearch, setColumnsSearch] = useState({});
49
+ const detailIdPrefix = useId();
50
+ const { expandedIds, toggle: toggleDetail } = useRowExpansion(expandedRows, onExpandedRowsChange);
51
+ const rowDetail = useMemo(() => (renderRowDetail ? { render: renderRowDetail, toggle: toggleDetail, idPrefix: detailIdPrefix } : undefined), [renderRowDetail, toggleDetail, detailIdPrefix]);
48
52
  const tableContainerRef = useRef(null);
49
53
  const { isOpen: isFilterModalOpen, onOpen: onFilterModalOpen, onClose: onFilterModalClose } = useDisclosure();
50
54
  // Below `md` the sidebar has no room — default to the modal, unless the consumer picked a mode.
@@ -442,5 +446,5 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
442
446
  backgroundColor: "transparent",
443
447
  zIndex: 999,
444
448
  },
445
- }, children: [_jsx(Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: _jsx(TableHeader, { 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 }) }), _jsx(Tbody, { children: _jsx(TableBody, { 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 && (_jsxs(Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [_jsx(Spinner, { size: "sm", color: theme.colors.accentText }), _jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && (_jsxs(Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && (_jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
449
+ }, children: [_jsx(Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: _jsx(TableHeader, { 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 }) }), _jsx(Tbody, { children: _jsx(TableBody, { 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 && (_jsxs(Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [_jsx(Spinner, { size: "sm", color: theme.colors.accentText }), _jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && (_jsxs(Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && (_jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
446
450
  }
@@ -15,6 +15,7 @@ import { DENSITY_ROWS, EMPTY_STATE_HEIGHT, GROUP_HEADER_HEIGHT } from "./rowMetr
15
15
  import { columnLabelText, isPrimitiveLabel } from "../columnLabel.js";
16
16
  import { transitionFor } from "../../../Theme/tokens/primitives/motion.js";
17
17
  import { reducedMotionSx } from "../../../Theme/chakra/motion.js";
18
+ import { DETAIL_SLOT_PX, RowDetailRow, RowDetailToggle, detailDomIds } from "./rowDetail.js";
18
19
  const getRowId = (row) => row.id;
19
20
  const getRowContent = (row) => typeof row.content === "function"
20
21
  ? row.content
@@ -47,20 +48,26 @@ const StatusCell = ({ value, onSolid, align, }) => (_jsx(Box, { display: "flex",
47
48
  // the group header already uses, so an override hex behaves identically in both.
48
49
  const StatusPill = ({ value, soft, ink, radius, }) => (_jsx(Box, { display: "inline-flex", alignItems: "center", maxW: "100%", px: "0.5rem", py: "0.125rem", borderRadius: radius, bg: soft, color: ink, fontSize: "0.75rem", fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: value }));
49
50
  const EMPTY_HOVER = {};
50
- const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent, isLink, isCheckbox, isLoading, isActionFreeze, columns, leftOffsets, noBorders, freezedBgColor, freezedTextColor, theme, borderStyle, selectedBg, selectedFrozenBg, hoverStyle, rowHeight, cellPy, palette, accentColor, isStriped, ariaRowIndex, handleCheckbox, onRowClick, toggleRowExpansion, onMeasure, }) => {
51
+ const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent, isLink, isCheckbox, isLoading, isActionFreeze, columns, leftOffsets, noBorders, freezedBgColor, freezedTextColor, theme, borderStyle, selectedBg, selectedFrozenBg, hoverStyle, rowHeight, cellPy, palette, accentColor, isStriped, rowDetail, isDetailOpen = false, detailLeft = 0, detailColSpan = 0, ariaRowIndex, handleCheckbox, onRowClick, toggleRowExpansion, onMeasure, }) => {
51
52
  // Measured-height virtualization: report this row's real height (main + any
52
53
  // expanded panel) so the windower can place variable/expandable rows exactly.
53
54
  const mainRowRef = useRef(null);
54
55
  const expandedRowRef = useRef(null);
56
+ const detailRowRef = useRef(null);
55
57
  const hasContent = !!getRowContent(row);
58
+ const detailNode = rowDetail ? rowDetail.render(row) : null;
59
+ const hasDetail = detailNode !== null && detailNode !== undefined;
60
+ const detailOpen = isDetailOpen && hasDetail;
61
+ const detailIds = rowDetail ? detailDomIds(rowDetail.idPrefix, getRowId(row)) : null;
56
62
  useLayoutEffect(() => {
57
63
  if (!onMeasure)
58
64
  return;
59
65
  const measure = () => {
60
- var _a, _b, _c, _d;
66
+ var _a, _b, _c, _d, _e, _f;
61
67
  const main = (_b = (_a = mainRowRef.current) === null || _a === void 0 ? void 0 : _a.offsetHeight) !== null && _b !== void 0 ? _b : 0;
62
68
  const exp = isExpanded && hasContent ? (_d = (_c = expandedRowRef.current) === null || _c === void 0 ? void 0 : _c.offsetHeight) !== null && _d !== void 0 ? _d : 0 : 0;
63
- const h = main + exp;
69
+ const det = detailOpen ? (_f = (_e = detailRowRef.current) === null || _e === void 0 ? void 0 : _e.offsetHeight) !== null && _f !== void 0 ? _f : 0 : 0;
70
+ const h = main + exp + det;
64
71
  if (h > 0)
65
72
  onMeasure(getRowId(row), h);
66
73
  };
@@ -73,12 +80,14 @@ const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent,
73
80
  ro.observe(mainRowRef.current);
74
81
  if (expandedRowRef.current)
75
82
  ro.observe(expandedRowRef.current);
83
+ if (detailRowRef.current)
84
+ ro.observe(detailRowRef.current);
76
85
  }
77
86
  return () => {
78
87
  cancelAnimationFrame(raf);
79
88
  ro === null || ro === void 0 ? void 0 : ro.disconnect();
80
89
  };
81
- }, [onMeasure, isExpanded, hasContent, row]);
90
+ }, [onMeasure, isExpanded, hasContent, detailOpen, row]);
82
91
  const handleCellClick = useCallback((header) => {
83
92
  if (!header.node && onRowClick) {
84
93
  onRowClick(row, {
@@ -108,6 +117,10 @@ const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent,
108
117
  "& .table-slot-index": { opacity: isChecked ? 0 : 1 },
109
118
  "&:hover .table-slot-box, &:focus-within .table-slot-box": { opacity: 1 },
110
119
  "&:hover .table-slot-index, &:focus-within .table-slot-index": { opacity: 0 },
120
+ "@media (pointer: coarse)": {
121
+ "& .table-slot-box": { opacity: 1 },
122
+ "& .table-slot-index": { opacity: 0 },
123
+ },
111
124
  }
112
125
  : {}),
113
126
  };
@@ -119,12 +132,12 @@ const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent,
119
132
  const chromeHoverable = rowStyle ? "false" : "true";
120
133
  const rowContent = getRowContent(row);
121
134
  const restBg = isStriped ? variant.row.stripeBg : theme.colors.background[50];
122
- return (_jsxs(React.Fragment, { children: [_jsxs(Tr, { ref: mainRowRef, "aria-rowindex": ariaRowIndex, opacity: isLoading ? 0.4 : 1, pointerEvents: isLoading ? "none" : "auto", transition: transitionFor(["opacity"], "base"), sx: { ...rowSx, ...reducedMotionSx() }, children: [isContent && (_jsx(Td, { w: "6", p: 0, fontSize: 14, background: isChecked ? selectedFrozenBg : frozenCellBg, color: freezedTextColor, position: "sticky", borderBottom: borderStyle, boxShadow: leadSx, boxSizing: "border-box", "data-hoverable": chromeHoverable, left: "0px", zIndex: 1, className: "columns sticky-columns", children: hasContent && (_jsx(IconButton, { "aria-label": isExpanded ? "Collapse row" : "Expand row", color: theme.colors.textMuted, icon: isExpanded ? (_jsx(ChevronDown, { fontSize: 16 })) : (_jsx(ChevronRight, { fontSize: 16 })), _hover: { transform: "scale(1.1)" }, size: "sm", onClick: () => toggleRowExpansion(rowIndex), variant: "ghost" })) })), isCheckbox && (_jsx(Td, { w: variant.slot.width, minW: variant.slot.width, px: variant.slot.centered ? 0 : undefined, textAlign: variant.slot.centered ? "center" : undefined, fontSize: 14, fontWeight: 600, color: theme.colors.background[50], textTransform: "capitalize", background: isChecked ? selectedFrozenBg : frozenCellBg, position: "sticky", borderBottom: borderStyle, boxShadow: !isContent ? leadSx : undefined, "data-hoverable": chromeHoverable, boxSizing: "border-box", left: "0px", zIndex: 1, className: "columns sticky-columns", children: _jsxs(Box, { display: restsOnIndex ? "grid" : undefined, placeItems: "center", children: [restsOnIndex && (_jsx(Box, { as: "span", className: "table-slot-index", gridArea: "1 / 1", color: variant.slot.indexInk, fontSize: "0.6875rem", fontWeight: 500, transition: transitionFor(["opacity"]), sx: { fontVariantNumeric: "tabular-nums", ...reducedMotionSx() }, children: rowIndex + 1 })), _jsx(Box, { as: "span", className: restsOnIndex ? "table-slot-box" : undefined, gridArea: restsOnIndex ? "1 / 1" : undefined, display: "flex", alignItems: "center", justifyContent: "center", transition: transitionFor(["opacity"]), sx: reducedMotionSx(), children: _jsx(CheckBox, { "aria-label": `Select row ${rowIndex + 1}`, onChange: () => handleCheckbox(getRowId(row)), isChecked: isChecked }) })] }) })), columns.map((header, headerIndex) => {
135
+ return (_jsxs(React.Fragment, { children: [_jsxs(Tr, { ref: mainRowRef, "aria-rowindex": ariaRowIndex, opacity: isLoading ? 0.4 : 1, pointerEvents: isLoading ? "none" : "auto", transition: transitionFor(["opacity"], "base"), sx: { ...rowSx, ...reducedMotionSx() }, children: [isContent && (_jsx(Td, { w: "6", p: 0, fontSize: 14, background: isChecked ? selectedFrozenBg : frozenCellBg, color: freezedTextColor, position: "sticky", borderBottom: borderStyle, boxShadow: leadSx, boxSizing: "border-box", "data-hoverable": chromeHoverable, left: "0px", zIndex: 1, className: "columns sticky-columns", children: hasContent && (_jsx(IconButton, { "aria-label": isExpanded ? "Collapse row" : "Expand row", color: theme.colors.textMuted, icon: isExpanded ? (_jsx(ChevronDown, { fontSize: 16 })) : (_jsx(ChevronRight, { fontSize: 16 })), _hover: { transform: "scale(1.1)" }, size: "sm", onClick: () => toggleRowExpansion(rowIndex), variant: "ghost" })) })), isCheckbox && (_jsx(Td, { w: variant.slot.width, minW: variant.slot.width, px: variant.slot.centered ? 0 : undefined, textAlign: variant.slot.centered ? "center" : undefined, fontSize: 14, fontWeight: 600, color: theme.colors.background[50], textTransform: "capitalize", background: isChecked ? selectedFrozenBg : frozenCellBg, position: "sticky", borderBottom: borderStyle, boxShadow: !isContent ? leadSx : undefined, "data-hoverable": chromeHoverable, boxSizing: "border-box", left: "0px", zIndex: 1, className: "columns sticky-columns", children: _jsxs(Box, { display: restsOnIndex ? "grid" : undefined, placeItems: "center", children: [restsOnIndex && (_jsx(Box, { as: "span", className: "table-slot-index", gridArea: "1 / 1", color: variant.slot.indexInk, fontSize: "0.6875rem", fontWeight: 500, transition: transitionFor(["opacity"]), sx: { fontVariantNumeric: "tabular-nums", ...reducedMotionSx() }, children: rowIndex + 1 })), _jsx(Box, { as: "span", className: restsOnIndex ? "table-slot-box" : undefined, gridArea: restsOnIndex ? "1 / 1" : undefined, display: "flex", alignItems: "center", justifyContent: "center", transition: transitionFor(["opacity"]), sx: reducedMotionSx(), children: _jsx(CheckBox, { "aria-label": `Select row ${rowIndex + 1}`, onChange: () => handleCheckbox(getRowId(row)), isChecked: isChecked }) })] }) })), rowDetail && detailIds && (_jsx(Td, { w: `${DETAIL_SLOT_PX}px`, minW: `${DETAIL_SLOT_PX}px`, maxW: `${DETAIL_SLOT_PX}px`, p: 0, textAlign: "center", background: isChecked ? selectedFrozenBg : frozenCellBg, position: "sticky", borderBottom: borderStyle, boxShadow: !isContent && !isCheckbox ? leadSx : undefined, "data-hoverable": chromeHoverable, boxSizing: "border-box", left: `${detailLeft}px`, zIndex: 1, className: "columns sticky-columns", children: hasDetail && (_jsx(RowDetailToggle, { isOpen: detailOpen, detailId: detailIds.detailId, color: theme.colors.textMuted, theme: theme, onToggle: () => rowDetail.toggle(getRowId(row)) })) })), columns.map((header, headerIndex) => {
123
136
  var _a, _b, _c, _d;
124
137
  if (header.isHidden)
125
138
  return null;
126
139
  const isFrozen = header.isFreeze;
127
- const isFirstDataCell = !isContent && !isCheckbox && headerIndex === firstDataColIndex;
140
+ const isFirstDataCell = !isContent && !isCheckbox && !rowDetail && headerIndex === firstDataColIndex;
128
141
  const isStatus = header.type === "status" && !header.node;
129
142
  const rawStatus = String((_a = row[header.id]) !== null && _a !== void 0 ? _a : "");
130
143
  const statusColor = isStatus
@@ -134,7 +147,7 @@ const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent,
134
147
  const bleedBg = statusColor === null || statusColor === void 0 ? void 0 : statusColor.soft;
135
148
  const bleedInk = statusColor === null || statusColor === void 0 ? void 0 : statusColor.text;
136
149
  const canHover = !isBleed && !((cellStyles === null || cellStyles === void 0 ? void 0 : cellStyles[header.id]) || rowStyle);
137
- return (_jsx(Td, { maxWidth: 500, minWidth: 120, height: `${rowHeight}px`, py: isBleed ? 0 : cellPy, px: isBleed ? 0 : undefined, onClick: () => handleCellClick(header), fontSize: 14, fontWeight: 400, position: isFrozen ? "sticky" : undefined, left: isFrozen ? `${leftOffsets[headerIndex]}px` : undefined, zIndex: isFrozen ? 1 : 0, background: isBleed ? bleedBg : isChecked ? (isFrozen ? selectedFrozenBg : selectedBg) : isFrozen ? frozenCellBg : restBg, textAlign: isBleed ? undefined : header.align, textOverflow: "ellipsis", boxShadow: isFirstDataCell ? leadSx : undefined, borderBottom: noBorders
150
+ return (_jsx(Td, { maxWidth: 500, minWidth: 120, height: `${rowHeight}px`, py: isBleed ? 0 : cellPy, px: isBleed ? 0 : undefined, id: hasDetail && headerIndex === firstDataColIndex ? detailIds === null || detailIds === void 0 ? void 0 : detailIds.cellId : undefined, onClick: () => handleCellClick(header), fontSize: 14, fontWeight: 400, position: isFrozen ? "sticky" : undefined, left: isFrozen ? `${leftOffsets[headerIndex]}px` : undefined, zIndex: isFrozen ? 1 : 0, background: isBleed ? bleedBg : isChecked ? (isFrozen ? selectedFrozenBg : selectedBg) : isFrozen ? frozenCellBg : restBg, textAlign: isBleed ? undefined : header.align, textOverflow: "ellipsis", boxShadow: isFirstDataCell ? leadSx : undefined, borderBottom: noBorders
138
151
  ? "none"
139
152
  : `0.063rem solid ${isFrozen || isChecked
140
153
  ? variant.row.frozenRuleColor
@@ -144,7 +157,7 @@ const TableRow = React.memo(({ row, rowIndex, isChecked, isExpanded, isContent,
144
157
  }), isLink && (_jsx(Td, { w: 2, p: 0, fontSize: 14, background: isChecked ? (isActionFreeze ? selectedFrozenBg : selectedBg) : isActionFreeze ? frozenCellBg : restBg, color: freezedTextColor, position: isActionFreeze ? "sticky" : "relative", borderBottom: borderStyle, boxSizing: "border-box", right: 0, zIndex: 1, className: `columns sticky-columns${isActionFreeze ? "-right" : ""}`, children: (row.onLink || row.onEdit || row.onDelete) ? (_jsx(TableActions, { row: row })) : null }))] }), rowContent && isExpanded && (_jsx(Tr, { ref: expandedRowRef, children: _jsx(Td, { colSpan: columns.length +
145
158
  (isCheckbox ? 1 : 0) +
146
159
  (isContent ? 1 : 0) +
147
- (isLink ? 1 : 0), p: 2, children: _jsx(Box, { p: 2, bg: theme.colors.background[50], borderRadius: "lg", children: rowContent(row) }) }) }))] }));
160
+ (isLink ? 1 : 0), p: 2, children: _jsx(Box, { p: 2, bg: theme.colors.background[50], borderRadius: "lg", children: rowContent(row) }) }) })), detailOpen && detailIds && (_jsx(RowDetailRow, { ref: detailRowRef, id: detailIds.detailId, labelledBy: detailIds.cellId, colSpan: detailColSpan, bg: theme.colors.backgroundColor.secondary, borderColor: variant.row.ruleColor, noBorders: noBorders, children: detailNode }))] }));
148
161
  });
149
162
  const isStructuredEmpty = (value) => !!value && typeof value === "object" && !React.isValidElement(value);
150
163
  const TableEmpty = ({ emptyState }) => {
@@ -155,7 +168,7 @@ const TableEmpty = ({ emptyState }) => {
155
168
  return _jsx(_Fragment, { children: emptyState });
156
169
  return (_jsx(EmptyState, { size: "sm", minH: `${EMPTY_STATE_HEIGHT}px`, title: structured === null || structured === void 0 ? void 0 : structured.title, description: structured === null || structured === void 0 ? void 0 : structured.description, icon: icon }));
157
170
  };
158
- const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWidths, slotWidth, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe = false, accentColors, groups, onAddItem, emptyState, }) => {
171
+ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWidths, slotWidth, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe = false, accentColors, groups, onAddItem, emptyState, rowDetail, expandedIds, }) => {
159
172
  var _a;
160
173
  const theme = useCustomTheme();
161
174
  const variant = useTableVariant();
@@ -182,6 +195,8 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
182
195
  const OVERSCAN = 6;
183
196
  const VIRTUALIZE_THRESHOLD = 30;
184
197
  const ESTIMATED_EXPAND = 120; // assumed extra height for an expanded-but-not-yet-measured row
198
+ const detailWidth = rowDetail ? DETAIL_SLOT_PX : 0;
199
+ const detailLeft = isCheckbox ? slotWidth : 0;
185
200
  const shouldVirtualize = data.length > VIRTUALIZE_THRESHOLD;
186
201
  // Grouped mode has no pager, so it virtualizes too (when no expandable rows):
187
202
  // group headers + rows form one flat sequence that we window over.
@@ -253,11 +268,12 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
253
268
  const measured = measuredHeights.get(id);
254
269
  const h = measured != null
255
270
  ? measured
256
- : ROW_HEIGHT + (expandedRows.has(startRow + i) ? ESTIMATED_EXPAND : 0);
271
+ : ROW_HEIGHT +
272
+ (expandedRows.has(startRow + i) || (expandedIds === null || expandedIds === void 0 ? void 0 : expandedIds.has(id)) ? ESTIMATED_EXPAND : 0);
257
273
  acc[i + 1] = acc[i] + h;
258
274
  }
259
275
  return acc;
260
- }, [shouldVirtualize, groups, total, data, measuredHeights, expandedRows, startRow, ROW_HEIGHT, ESTIMATED_EXPAND]);
276
+ }, [shouldVirtualize, groups, total, data, measuredHeights, expandedRows, expandedIds, startRow, ROW_HEIGHT, ESTIMATED_EXPAND]);
261
277
  let firstIndex = 0;
262
278
  let lastIndex = total;
263
279
  let topPad = 0;
@@ -300,11 +316,12 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
300
316
  const totalVisibleColumns = useMemo(() => columns.filter((o) => !o.isHidden).length +
301
317
  (isCheckbox ? 1 : 0) +
302
318
  (isContent ? 1 : 0) +
303
- (isLink ? 1 : 0), [columns, isCheckbox, isContent, isLink]);
319
+ (rowDetail ? 1 : 0) +
320
+ (isLink ? 1 : 0), [columns, isCheckbox, isContent, isLink, rowDetail]);
304
321
  const leftOffsets = useMemo(() => {
305
- const checkboxOffset = isCheckbox ? slotWidth : 0;
322
+ const checkboxOffset = (isCheckbox ? slotWidth : 0) + detailWidth;
306
323
  return columnWidths.map((_, idx) => columnWidths.slice(0, idx).reduce((sum, w) => sum + w, 0) + checkboxOffset);
307
- }, [columnWidths, isCheckbox, slotWidth]);
324
+ }, [columnWidths, isCheckbox, slotWidth, detailWidth]);
308
325
  const borderStyle = useMemo(() => noBorders ? "none" : `0.063rem solid ${variant.row.ruleColor}`, [noBorders, variant.row.ruleColor]);
309
326
  // Selected-row tint applied per-cell (NOT a blanket `& td` override) so status
310
327
  // cells keep their solid color + white text instead of white-on-tint.
@@ -332,7 +349,15 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
332
349
  const groupMetrics = useMemo(() => {
333
350
  if (!groupItems || !groupVirtualize)
334
351
  return null;
335
- const heights = groupItems.map((it) => (it.kind === "header" ? GROUP_HEADER_HEIGHT : rowHeight));
352
+ const heights = groupItems.map((it) => {
353
+ var _a;
354
+ if (it.kind === "header")
355
+ return GROUP_HEADER_HEIGHT;
356
+ if (!rowDetail)
357
+ return rowHeight;
358
+ const id = getRowId(it.row);
359
+ return (_a = measuredHeights.get(id)) !== null && _a !== void 0 ? _a : rowHeight + ((expandedIds === null || expandedIds === void 0 ? void 0 : expandedIds.has(id)) ? ESTIMATED_EXPAND : 0);
360
+ });
336
361
  const offsets = [];
337
362
  let acc = 0;
338
363
  for (const h of heights) {
@@ -340,7 +365,7 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
340
365
  acc += h;
341
366
  }
342
367
  return { heights, offsets, totalH: acc };
343
- }, [groupItems, groupVirtualize, rowHeight]);
368
+ }, [groupItems, groupVirtualize, rowHeight, rowDetail, measuredHeights, expandedIds, ESTIMATED_EXPAND]);
344
369
  // Clamp scrollTop back when a shorter data set leaves it past the new content height.
345
370
  useEffect(() => {
346
371
  const totalH = shouldVirtualize && offsets ? offsets[total] : groupVirtualize && groupMetrics ? groupMetrics.totalH : null;
@@ -407,7 +432,7 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
407
432
  const windowItems = groupVirtualize ? items.slice(firstI, lastI) : items;
408
433
  return (_jsxs(_Fragment, { children: [gTopPad > 0 && (_jsx("tr", { "aria-hidden": "true", style: { height: gTopPad }, children: _jsx("td", { colSpan: totalVisibleColumns, style: { padding: 0, border: "none", height: gTopPad } }) })), windowItems.map((it, k) => {
409
434
  var _a;
410
- return it.kind === "header" ? (_jsx(TableGroupRow, { label: it.g.label, value: it.g.value, count: it.g.rows.length, color: it.g.color, collapsed: collapsedGroups.has(it.g.value), colSpan: totalVisibleColumns, ariaRowIndex: firstI + k + 2, onToggle: () => toggleGroup(it.g.value), onAddItem: onAddItem }, `h-${it.g.value}`)) : (_jsx(TableRow, { row: it.row, rowIndex: firstI + k, isChecked: selectionsSet.has(getRowId(it.row)), isExpanded: expandedRows.has(firstI + k), isContent: isContent, isLink: isLink, isCheckbox: isCheckbox, isLoading: isLoading, isActionFreeze: isActionFreeze, columns: columns, leftOffsets: leftOffsets, noBorders: noBorders, freezedBgColor: freezedBgColor, freezedTextColor: freezedTextColor, theme: theme, borderStyle: borderStyle, selectedBg: selectedBg, selectedFrozenBg: selectedFrozenBg, hoverStyle: hoverStyle, rowHeight: rowHeight, cellPy: cellPy, palette: palette, accentColor: it.g.color.solid, isStriped: stripe && it.pos % 2 === 1, ariaRowIndex: firstI + k + 2, handleCheckbox: handleCheckbox, onRowClick: rowClickHandler, toggleRowExpansion: toggleRowExpansion }, (_a = getRowId(it.row)) !== null && _a !== void 0 ? _a : `r-${firstI + k}`));
435
+ return it.kind === "header" ? (_jsx(TableGroupRow, { label: it.g.label, value: it.g.value, count: it.g.rows.length, color: it.g.color, collapsed: collapsedGroups.has(it.g.value), colSpan: totalVisibleColumns, ariaRowIndex: firstI + k + 2, onToggle: () => toggleGroup(it.g.value), onAddItem: onAddItem }, `h-${it.g.value}`)) : (_jsx(TableRow, { row: it.row, rowIndex: firstI + k, isChecked: selectionsSet.has(getRowId(it.row)), isExpanded: expandedRows.has(firstI + k), isContent: isContent, isLink: isLink, isCheckbox: isCheckbox, isLoading: isLoading, isActionFreeze: isActionFreeze, columns: columns, leftOffsets: leftOffsets, noBorders: noBorders, freezedBgColor: freezedBgColor, freezedTextColor: freezedTextColor, theme: theme, borderStyle: borderStyle, selectedBg: selectedBg, selectedFrozenBg: selectedFrozenBg, hoverStyle: hoverStyle, rowHeight: rowHeight, cellPy: cellPy, palette: palette, accentColor: it.g.color.solid, isStriped: stripe && it.pos % 2 === 1, rowDetail: rowDetail, isDetailOpen: !!(expandedIds === null || expandedIds === void 0 ? void 0 : expandedIds.has(getRowId(it.row))), detailLeft: detailLeft, detailColSpan: totalVisibleColumns, ariaRowIndex: firstI + k + 2, handleCheckbox: handleCheckbox, onRowClick: rowClickHandler, toggleRowExpansion: toggleRowExpansion, onMeasure: groupVirtualize && rowDetail ? reportHeight : undefined }, (_a = getRowId(it.row)) !== null && _a !== void 0 ? _a : `r-${firstI + k}`));
411
436
  }), gBottomPad > 0 && (_jsx("tr", { "aria-hidden": "true", style: { height: gBottomPad }, children: _jsx("td", { colSpan: totalVisibleColumns, style: { padding: 0, border: "none", height: gBottomPad } }) }))] }));
412
437
  }
413
438
  return (_jsxs(_Fragment, { children: [isLoading && data.length > 0 && (_jsx(Tr, { children: _jsx(Td, { colSpan: totalVisibleColumns, p: 0, border: "none", position: "relative", height: 0, children: _jsx(Box, { position: "absolute", top: 4, left: "50%", transform: "translateX(-50%)", zIndex: 10, children: _jsx(Spinner, { size: "sm", color: theme.colors.accentText }) }) }) })), shouldVirtualize && topPad > 0 && (_jsx("tr", { "aria-hidden": "true", style: { height: topPad }, children: _jsx("td", { colSpan: totalVisibleColumns, style: { padding: 0, border: "none", height: topPad } }) })), visibleRows.map((row, i) => {
@@ -416,7 +441,7 @@ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWid
416
441
  const rowIndex = startRow + index;
417
442
  const isExpanded = expandedRows.has(rowIndex);
418
443
  const isChecked = selectionsSet.has(getRowId(row));
419
- return (_jsx(TableRow, { row: row, rowIndex: rowIndex, isChecked: isChecked, isExpanded: isExpanded, isContent: isContent, isLink: isLink, isCheckbox: isCheckbox, isLoading: isLoading, isActionFreeze: isActionFreeze, columns: columns, leftOffsets: leftOffsets, noBorders: noBorders, freezedBgColor: freezedBgColor, freezedTextColor: freezedTextColor, theme: theme, borderStyle: borderStyle, selectedBg: selectedBg, selectedFrozenBg: selectedFrozenBg, hoverStyle: hoverStyle, rowHeight: rowHeight, cellPy: cellPy, palette: palette, accentColor: accentColors === null || accentColors === void 0 ? void 0 : accentColors[getRowId(row)], isStriped: stripe && index % 2 === 1, ariaRowIndex: index + 2, handleCheckbox: handleCheckbox, onRowClick: rowClickHandler, toggleRowExpansion: toggleRowExpansion, onMeasure: shouldVirtualize ? reportHeight : undefined }, (_a = getRowId(row)) !== null && _a !== void 0 ? _a : rowIndex));
444
+ return (_jsx(TableRow, { row: row, rowIndex: rowIndex, isChecked: isChecked, isExpanded: isExpanded, isContent: isContent, isLink: isLink, isCheckbox: isCheckbox, isLoading: isLoading, isActionFreeze: isActionFreeze, columns: columns, leftOffsets: leftOffsets, noBorders: noBorders, freezedBgColor: freezedBgColor, freezedTextColor: freezedTextColor, theme: theme, borderStyle: borderStyle, selectedBg: selectedBg, selectedFrozenBg: selectedFrozenBg, hoverStyle: hoverStyle, rowHeight: rowHeight, cellPy: cellPy, palette: palette, accentColor: accentColors === null || accentColors === void 0 ? void 0 : accentColors[getRowId(row)], isStriped: stripe && index % 2 === 1, rowDetail: rowDetail, isDetailOpen: !!(expandedIds === null || expandedIds === void 0 ? void 0 : expandedIds.has(getRowId(row))), detailLeft: detailLeft, detailColSpan: totalVisibleColumns, ariaRowIndex: index + 2, handleCheckbox: handleCheckbox, onRowClick: rowClickHandler, toggleRowExpansion: toggleRowExpansion, onMeasure: shouldVirtualize ? reportHeight : undefined }, (_a = getRowId(row)) !== null && _a !== void 0 ? _a : rowIndex));
420
445
  }), shouldVirtualize && bottomPad > 0 && (_jsx("tr", { "aria-hidden": "true", style: { height: bottomPad }, children: _jsx("td", { colSpan: totalVisibleColumns, style: { padding: 0, border: "none", height: bottomPad } }) }))] }));
421
446
  };
422
447
  export default TableBody;
@@ -12,13 +12,15 @@ import { useTableVariant } from "../variants/TableVariantContext.js";
12
12
  import { transitionFor } from "../../../Theme/tokens/primitives/motion.js";
13
13
  import { reducedMotionSx } from "../../../Theme/chakra/motion.js";
14
14
  import { focusVisibleRing } from "../../../Theme/chakra/focusRing.styles.js";
15
+ import { DETAIL_SLOT_PX, RowDetailHeaderCell } from "./rowDetail.js";
15
16
  const LABEL_JUSTIFY = { start: "space-between", center: "center", end: "flex-start" };
16
- const TableHeader = ({ columns, isCheckbox, handleSort, headerRefs, columnWidths, slotRef, slotWidth, columnsSort, freezedTextColor, noBorders, handleCheckbox, checked, isLoading, isContent, isLink, isActionFreeze, setColumnsSearch, columnsSearch, isSelecting, }) => {
17
+ const TableHeader = ({ columns, isCheckbox, handleSort, headerRefs, columnWidths, slotRef, slotWidth, columnsSort, freezedTextColor, noBorders, handleCheckbox, checked, isLoading, isContent, isLink, hasDetail = false, isActionFreeze, setColumnsSearch, columnsSearch, isSelecting, }) => {
17
18
  const [openFilterId, setOpenFilterId] = useState(null);
18
- return (_jsxs(Tr, { pr: 0, "aria-rowindex": 1, children: [isContent && _jsx(ContentSpacerCell, { noBorders: noBorders }), isCheckbox && (_jsx(SelectAllCell, { slotRef: slotRef, freezedTextColor: freezedTextColor, noBorders: noBorders, isSelecting: isSelecting, isLoading: isLoading, checked: checked, handleCheckbox: handleCheckbox })), columns.map((header, index) => {
19
+ const variant = useTableVariant();
20
+ return (_jsxs(Tr, { pr: 0, "aria-rowindex": 1, children: [isContent && _jsx(ContentSpacerCell, { noBorders: noBorders }), isCheckbox && (_jsx(SelectAllCell, { slotRef: slotRef, freezedTextColor: freezedTextColor, noBorders: noBorders, isSelecting: isSelecting, isLoading: isLoading, checked: checked, handleCheckbox: handleCheckbox })), hasDetail && (_jsx(RowDetailHeaderCell, { left: isCheckbox ? slotWidth : 0, bg: variant.header.bg, ruleColor: variant.header.ruleColor, noBorders: noBorders })), columns.map((header, index) => {
19
21
  if (header === null || header === void 0 ? void 0 : header.isHidden)
20
22
  return null;
21
- return (_jsx(ColumnHeader, { header: header, index: index, columnWidths: columnWidths, slotWidth: slotWidth, handleSort: handleSort, isCheckbox: isCheckbox, noBorders: noBorders, columnsSort: columnsSort, headerRefs: headerRefs, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch, openFilterId: openFilterId, setOpenFilterId: setOpenFilterId }, columnLabelText(header)));
23
+ return (_jsx(ColumnHeader, { header: header, index: index, columnWidths: columnWidths, slotWidth: slotWidth, detailWidth: hasDetail ? DETAIL_SLOT_PX : 0, handleSort: handleSort, isCheckbox: isCheckbox, noBorders: noBorders, columnsSort: columnsSort, headerRefs: headerRefs, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch, openFilterId: openFilterId, setOpenFilterId: setOpenFilterId }, columnLabelText(header)));
22
24
  }), isLink && _jsx(ViewSpacerCell, { noBorders: noBorders, isActionFreeze: isActionFreeze })] }));
23
25
  };
24
26
  export default memo(TableHeader);
@@ -43,7 +45,7 @@ const SortingIcon = memo(({ label, sortState, isVisible, handleSortClick, }) =>
43
45
  color: accentTextOnCanvas(theme.colors.secondary, variant.header.bg),
44
46
  }, children: [sortState === "none" && _jsx(ChevronsUpDown, { size: 14 }), sortState === "asc" && _jsx(ChevronUp, { size: 14 }), sortState === "desc" && _jsx(ChevronDown, { size: 14 })] }));
45
47
  });
46
- const ColumnHeader = memo(({ header, index, columnWidths, slotWidth, isCheckbox, noBorders, columnsSort, headerRefs, columnsSearch, setColumnsSearch, handleSort, openFilterId, setOpenFilterId, }) => {
48
+ const ColumnHeader = memo(({ header, index, columnWidths, slotWidth, detailWidth = 0, isCheckbox, noBorders, columnsSort, headerRefs, columnsSearch, setColumnsSearch, handleSort, openFilterId, setOpenFilterId, }) => {
47
49
  var _a, _b;
48
50
  const [isHovered, setIsHovered] = useState(false);
49
51
  const theme = useCustomTheme();
@@ -51,7 +53,7 @@ const ColumnHeader = memo(({ header, index, columnWidths, slotWidth, isCheckbox,
51
53
  const isFrozen = header.isFreeze;
52
54
  const align = (_a = header.align) !== null && _a !== void 0 ? _a : "start";
53
55
  const leftOffset = isFrozen
54
- ? calculateLeftOffset(columnWidths, index) + (isCheckbox ? slotWidth : 0)
56
+ ? calculateLeftOffset(columnWidths, index) + (isCheckbox ? slotWidth : 0) + detailWidth
55
57
  : undefined;
56
58
  const isSorting = header.isSort
57
59
  ? columnsSort.find((o) => o.column === header.id)
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { Box, IconButton, Td, Th, Tr, VisuallyHidden } from "@chakra-ui/react";
4
+ import { ChevronDown, ChevronRight } from "lucide-react";
5
+ import { focusVisibleRing } from "../../../Theme/chakra/focusRing.styles.js";
6
+ export const DETAIL_SLOT_PX = 40;
7
+ export const detailDomIds = (prefix, rowId) => {
8
+ const safe = `${prefix}-${rowId}`.replace(/\s+/g, "_");
9
+ return { cellId: `${safe}-cell`, detailId: `${safe}-detail` };
10
+ };
11
+ export const RowDetailHeaderCell = ({ left, bg, ruleColor, noBorders, }) => (_jsx(Th, { scope: "col", w: `${DETAIL_SLOT_PX}px`, minW: `${DETAIL_SLOT_PX}px`, maxW: `${DETAIL_SLOT_PX}px`, p: 0, boxSizing: "border-box", backgroundColor: bg, position: "sticky", left: `${left}px`, zIndex: 9, className: "columns sticky-columns", borderBottom: noBorders ? "none" : `0.063rem solid ${ruleColor}`, children: _jsx(VisuallyHidden, { children: "Row details" }) }));
12
+ export const RowDetailToggle = ({ isOpen, detailId, color, theme, onToggle }) => (_jsx(IconButton, { "aria-label": isOpen ? "Collapse row details" : "Expand row details", "aria-expanded": isOpen, "aria-controls": detailId, icon: isOpen ? _jsx(ChevronDown, { size: 16 }) : _jsx(ChevronRight, { size: 16 }), color: color, size: "sm", variant: "ghost", onClick: onToggle, _focusVisible: focusVisibleRing(theme.colors) }));
13
+ export const RowDetailRow = forwardRef(({ id, labelledBy, colSpan, bg, borderColor, noBorders, children }, ref) => (_jsx(Tr, { ref: ref, id: id, "aria-labelledby": labelledBy, children: _jsx(Td, { colSpan: colSpan, p: 0, bg: bg, borderBottom: noBorders ? "none" : `0.063rem solid ${borderColor}`, children: _jsx(Box, { px: 4, py: 3, children: children }) }) })));
14
+ RowDetailRow.displayName = "RowDetailRow";
@@ -0,0 +1,23 @@
1
+ import { useCallback, useMemo, useRef, useState } from "react";
2
+ /** Expanded-row ids, controlled when `controlled` is given and uncontrolled otherwise. */
3
+ export const useRowExpansion = (controlled, onChange) => {
4
+ const [internal, setInternal] = useState([]);
5
+ const isControlled = controlled !== undefined;
6
+ const current = isControlled ? controlled : internal;
7
+ const currentRef = useRef(current);
8
+ currentRef.current = current;
9
+ const isControlledRef = useRef(isControlled);
10
+ isControlledRef.current = isControlled;
11
+ const onChangeRef = useRef(onChange);
12
+ onChangeRef.current = onChange;
13
+ const toggle = useCallback((id) => {
14
+ var _a;
15
+ const prev = currentRef.current;
16
+ const next = prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id];
17
+ if (!isControlledRef.current)
18
+ setInternal(next);
19
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, next);
20
+ }, []);
21
+ const expandedIds = useMemo(() => new Set(current), [current]);
22
+ return { expandedIds, toggle };
23
+ };
@@ -6,11 +6,21 @@ import TabList from "./components/TabList.js";
6
6
  import Tab from "./components/Tab.js";
7
7
  import TabPanels from "./components/TabPanels.js";
8
8
  import TabPanel from "./components/TabPanel.js";
9
- const Tabs = ({ items, children, value, defaultValue, onChange, variant = "line", size = "sm", colorScheme = "primary", orientation = "horizontal", activationMode = "automatic", isLazy = false, isFitted = false, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }) => {
9
+ import TabLinkList from "./components/TabLinkList.js";
10
+ const Tabs = ({ items, children, value, defaultValue, onChange, variant = "line", size = "sm", colorScheme = "primary", orientation = "horizontal", activationMode = "automatic", isLazy = false, isFitted = false, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, linkAs = "a", }) => {
11
+ var _a;
10
12
  const generatedId = useId();
11
13
  const baseId = id !== null && id !== void 0 ? id : generatedId;
12
14
  const isControlled = value !== undefined;
13
- const [internalValue, setInternalValue] = useState(() => { var _a; return defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_a = items === null || items === void 0 ? void 0 : items.find((item) => !item.isDisabled)) === null || _a === void 0 ? void 0 : _a.id; });
15
+ const linkCount = (_a = items === null || items === void 0 ? void 0 : items.filter((item) => item.href !== undefined).length) !== null && _a !== void 0 ? _a : 0;
16
+ const isLinkMode = !children && !!(items === null || items === void 0 ? void 0 : items.length) && linkCount === items.length;
17
+ const isMixed = !children && linkCount > 0 && !isLinkMode;
18
+ useEffect(() => {
19
+ if (isMixed && process.env.NODE_ENV !== "production") {
20
+ console.warn("Tabs: some items have `href` and some do not. Link mode needs `href` on every item; rendering a tablist and ignoring `href`.");
21
+ }
22
+ }, [isMixed]);
23
+ const [internalValue, setInternalValue] = useState(() => { var _a; return defaultValue !== null && defaultValue !== void 0 ? defaultValue : (isLinkMode ? undefined : (_a = items === null || items === void 0 ? void 0 : items.find((item) => !item.isDisabled)) === null || _a === void 0 ? void 0 : _a.id); });
14
24
  const selectedId = isControlled ? value : internalValue;
15
25
  const [focusedId, setFocusedId] = useState(selectedId);
16
26
  const [hasPanels, setHasPanels] = useState(() => !!(items === null || items === void 0 ? void 0 : items.some((item) => item.content !== undefined)));
@@ -59,6 +69,9 @@ const Tabs = ({ items, children, value, defaultValue, onChange, variant = "line"
59
69
  if (children) {
60
70
  return _jsx(TabsContext.Provider, { value: contextValue, children: children });
61
71
  }
72
+ if (isLinkMode) {
73
+ return (_jsx(TabsContext.Provider, { value: contextValue, children: _jsx(TabLinkList, { items: items, linkAs: linkAs, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }) }));
74
+ }
62
75
  const hasContent = !!(items === null || items === void 0 ? void 0 : items.some((item) => item.content !== undefined));
63
76
  return (_jsx(TabsContext.Provider, { value: contextValue, children: _jsxs(Box, { display: "flex", flexDirection: orientation === "vertical" ? "row" : "column", alignItems: orientation === "vertical" ? "flex-start" : undefined, gap: orientation === "vertical" ? 4 : 2, children: [_jsx(TabList, { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children: items === null || items === void 0 ? void 0 : items.map((item) => (_jsx(Tab, { value: item.id, icon: item.icon, count: item.count, isDisabled: item.isDisabled, children: item.label }, item.id))) }), hasContent ? (_jsx(TabPanels, { children: items === null || items === void 0 ? void 0 : items.filter((item) => item.content !== undefined).map((item) => (_jsx(TabPanel, { value: item.id, children: item.content }, item.id))) })) : null] }) }));
64
77
  };
@@ -1,45 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef } from "react";
3
3
  import { Box } from "@chakra-ui/react";
4
- import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
5
- import { FOCUS_VISIBLE_OVER_STATE } from "../../../Theme/chakra/focusRing.styles.js";
6
- import { AA_NON_TEXT, liftToContrast } from "../../../Theme/tokens/builders/outlineRung.js";
7
- import { compositeOver } from "../../../Theme/tokens/builders/color.js";
8
- import { transitionFor } from "../../../Theme/tokens/primitives/motion.js";
9
- import { reducedMotionSx } from "../../../Theme/chakra/motion.js";
10
- import { BUTTON_SIZES } from "../../../Theme/tokens/primitives/controlSizes.js";
11
- import { iconBoxSx } from "../../Common/iconBox.js";
12
- import { resolveTabStyle, scaleForColorScheme } from "../tabStyles.js";
13
4
  import { panelId, tabId, useTabsContext } from "../TabsContext.js";
5
+ import { useTabAppearance } from "../useTabAppearance.js";
14
6
  import CountBadge from "./CountBadge.js";
15
7
  const Tab = ({ value, children, icon, count, isDisabled }) => {
16
- var _a, _b, _c, _d;
17
- const { colors } = useCustomTheme();
8
+ var _a;
18
9
  const ctx = useTabsContext();
19
10
  const ref = useRef(null);
20
11
  const isSelected = ctx.selectedId === value;
21
12
  const isFocusTarget = ((_a = ctx.focusedId) !== null && _a !== void 0 ? _a : ctx.selectedId) === value;
22
- const isVertical = ctx.orientation === "vertical";
23
- const isLine = ctx.variant === "line";
24
- const canvas = colors.backgroundColor.main;
25
- const scale = scaleForColorScheme(colors, ctx.colorScheme);
26
- const style = resolveTabStyle(colors, scale, canvas, ctx.variant, isSelected);
27
- const spec = BUTTON_SIZES[ctx.size];
28
- // The list scrolls on both axes, so an outer ring is clipped into arcs; a negative offset draws it
29
- // inside the tab, clear of its own border rather than thickening it.
30
- const focusRing = {
31
- outline: `1px solid ${liftToContrast(colors.primary[500],
32
- // subtle/ghost fills are translucent tints; contrast has no meaning until they are flattened.
33
- style.bg === "transparent" ? canvas : compositeOver(style.bg, canvas), AA_NON_TEXT)}`,
34
- outlineOffset: "-2px",
35
- };
36
- const borderProps = ctx.variant === "outline"
37
- ? { border: "1px solid", borderColor: (_b = style.border) !== null && _b !== void 0 ? _b : "transparent" }
38
- : isLine
39
- ? isVertical
40
- ? { borderLeft: "0.125rem solid", borderLeftColor: (_c = style.indicator) !== null && _c !== void 0 ? _c : "transparent" }
41
- : { borderBottom: "0.125rem solid", borderBottomColor: (_d = style.indicator) !== null && _d !== void 0 ? _d : "transparent" }
42
- : {};
43
- return (_jsxs(Box, { as: "button", type: "button", ref: ref, role: "tab", id: tabId(ctx.baseId, value), "aria-selected": isSelected, "aria-disabled": isDisabled || undefined, "aria-controls": ctx.hasPanels ? panelId(ctx.baseId, value) : undefined, "data-value": value, tabIndex: isDisabled ? -1 : isFocusTarget ? 0 : -1, disabled: isDisabled, onClick: () => !isDisabled && ctx.select(value), onFocus: () => ctx.focusTab(value), display: "flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flexShrink: 0, flex: ctx.isFitted ? 1 : undefined, w: isVertical ? "100%" : undefined, h: isVertical ? "auto" : spec.height, minH: spec.height, py: isVertical ? "0.5rem" : undefined, px: spec.paddingX, fontSize: spec.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", cursor: isDisabled ? "not-allowed" : "pointer", opacity: isDisabled ? 0.5 : 1, borderRadius: isLine ? "none" : "lg", bg: style.bg, color: style.color, ...borderProps, transition: transitionFor(["background-color", "color", "border-color"]), sx: { _focusVisible: focusRing, [FOCUS_VISIBLE_OVER_STATE]: focusRing, ...reducedMotionSx() }, _hover: !isDisabled ? { bg: style.hoverBg, color: style.hoverColor } : undefined, children: [icon ? (_jsx(Box, { as: "span", "aria-hidden": true, boxSize: `${spec.iconSize}px`, sx: iconBoxSx(spec.iconSize), children: icon })) : null, _jsx(Box, { as: "span", children: children }), count !== undefined ? (_jsx(CountBadge, { count: count, colors: colors, scale: scale, surface: ctx.variant === "solid" && isSelected ? style.bg : canvas, size: ctx.size })) : null] }));
13
+ const { colors, canvas, scale, style, iconSize, iconSx, boxProps } = useTabAppearance(isSelected, isDisabled);
14
+ return (_jsxs(Box, { as: "button", type: "button", ref: ref, role: "tab", id: tabId(ctx.baseId, value), "aria-selected": isSelected, "aria-disabled": isDisabled || undefined, "aria-controls": ctx.hasPanels ? panelId(ctx.baseId, value) : undefined, "data-value": value, tabIndex: isDisabled ? -1 : isFocusTarget ? 0 : -1, disabled: isDisabled, onClick: () => !isDisabled && ctx.select(value), onFocus: () => ctx.focusTab(value), ...boxProps, children: [icon ? (_jsx(Box, { as: "span", "aria-hidden": true, boxSize: `${iconSize}px`, sx: iconSx, children: icon })) : null, _jsx(Box, { as: "span", children: children }), count !== undefined ? (_jsx(CountBadge, { count: count, colors: colors, scale: scale, surface: ctx.variant === "solid" && isSelected ? style.bg : canvas, size: ctx.size })) : null] }));
44
15
  };
45
16
  export default Tab;