pixelize-design-library 2.4.2-beta.63 → 2.4.2-beta.65

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 (135) hide show
  1. package/CHANGELOG.md +130 -8
  2. package/COMPONENT-GOTCHAS.md +63 -7
  3. package/DESIGN-GUIDE.md +8 -3
  4. package/dist/Components/Button/Button.styles.js +7 -2
  5. package/dist/Components/CopyButton/CopyButton.d.ts +3 -1
  6. package/dist/Components/CopyButton/CopyButton.js +2 -2
  7. package/dist/Components/Drawer/Drawer.d.ts +1 -1
  8. package/dist/Components/Drawer/Drawer.js +6 -2
  9. package/dist/Components/Drawer/DrawerProps.d.ts +4 -0
  10. package/dist/Components/Dropdown/DropDown.js +2 -9
  11. package/dist/Components/FilePreview/FilePreview.js +2 -1
  12. package/dist/Components/FileUploader/FileUploader.js +4 -0
  13. package/dist/Components/FileUploader/FileUploaderProps.d.ts +1 -1
  14. package/dist/Components/FormDrawer/FormDrawer.d.ts +1 -1
  15. package/dist/Components/FormDrawer/FormDrawer.js +3 -3
  16. package/dist/Components/FormDrawer/FormDrawerProps.d.ts +2 -0
  17. package/dist/Components/Input/TextInput.styles.js +10 -6
  18. package/dist/Components/KanbanBoard/KanbanBoard.js +6 -5
  19. package/dist/Components/Modal/dialogNaming.d.ts +8 -0
  20. package/dist/Components/Modal/dialogNaming.js +21 -0
  21. package/dist/Components/NavLauncher/LauncherTabs.js +2 -2
  22. package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
  23. package/dist/Components/NavLauncher/NavLauncher.js +257 -47
  24. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +79 -3
  25. package/dist/Components/NavLauncher/ScopeBar.d.ts +14 -0
  26. package/dist/Components/NavLauncher/ScopeBar.js +137 -0
  27. package/dist/Components/NavLauncher/engine/distance.d.ts +4 -0
  28. package/dist/Components/NavLauncher/engine/distance.js +41 -0
  29. package/dist/Components/NavLauncher/engine/engine.d.ts +84 -0
  30. package/dist/Components/NavLauncher/engine/engine.js +471 -0
  31. package/dist/Components/NavLauncher/engine/fold.d.ts +15 -0
  32. package/dist/Components/NavLauncher/engine/fold.js +83 -0
  33. package/dist/Components/NavLauncher/engine/index.d.ts +21 -0
  34. package/dist/Components/NavLauncher/engine/index.js +17 -0
  35. package/dist/Components/NavLauncher/launcherParts.d.ts +2 -0
  36. package/dist/Components/NavLauncher/launcherParts.js +11 -7
  37. package/dist/Components/NavLauncher/match.d.ts +13 -2
  38. package/dist/Components/NavLauncher/match.js +26 -12
  39. package/dist/Components/NavLauncher/searchLayout.d.ts +60 -0
  40. package/dist/Components/NavLauncher/searchLayout.js +108 -0
  41. package/dist/Components/NavLauncher/spotlight.d.ts +39 -0
  42. package/dist/Components/NavLauncher/spotlight.js +202 -0
  43. package/dist/Components/NavLauncher/spotlightParts.d.ts +49 -0
  44. package/dist/Components/NavLauncher/spotlightParts.js +108 -0
  45. package/dist/Components/NavigationBar/NavigationBar.js +2 -1
  46. package/dist/Components/OrgSwitcher/OrgSwitcher.js +2 -4
  47. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +6 -5
  48. package/dist/Components/ScrollToTop/ScrollToTop.js +4 -3
  49. package/dist/Components/SearchSelect/SearchSelect.js +4 -16
  50. package/dist/Components/Select/SelectPopup.js +1 -3
  51. package/dist/Components/Select/optionRowInk.d.ts +12 -0
  52. package/dist/Components/Select/optionRowInk.js +10 -1
  53. package/dist/Components/SelectSearch/SelectSearch.js +1 -10
  54. package/dist/Components/Table/Table.js +3 -2
  55. package/dist/Components/Table/TableProps.d.ts +1 -1
  56. package/dist/Components/Table/components/TableBody.js +9 -1
  57. package/dist/Components/Table/components/TableHeader.js +9 -2
  58. package/dist/Components/Table/components/TableLoading.d.ts +4 -1
  59. package/dist/Components/Table/components/TableLoading.js +13 -6
  60. package/dist/Components/Table/filters/CompactSelect.js +1 -10
  61. package/dist/Components/Table/variants/TableVariantContext.d.ts +1 -1
  62. package/dist/Components/Table/variants/TableVariantContext.js +3 -3
  63. package/dist/Components/WizardDialog/WizardDialog.d.ts +1 -1
  64. package/dist/Components/WizardDialog/WizardDialog.js +15 -3
  65. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +5 -3
  66. package/dist/Components/WorkspaceWindow/WorkspaceTrayPreview.js +4 -0
  67. package/dist/Components/WorkspaceWindow/WorkspaceWindow.d.ts +2 -2
  68. package/dist/Components/WorkspaceWindow/WorkspaceWindow.js +26 -18
  69. package/dist/Components/WorkspaceWindow/WorkspaceWindowProps.d.ts +38 -8
  70. package/dist/Theme/chakra/Menu.styles.d.ts +8 -0
  71. package/dist/Theme/chakra/Menu.styles.js +7 -0
  72. package/dist/Theme/chakra/componentStyles.d.ts +8 -0
  73. package/dist/Theme/chakra/componentStyles.js +2 -2
  74. package/dist/Theme/chakra/ringShadow.d.ts +3 -0
  75. package/dist/Theme/chakra/ringShadow.js +7 -0
  76. package/dist/Theme/tokens/builders/accentText.d.ts +2 -0
  77. package/dist/Theme/tokens/builders/accentText.js +7 -1
  78. package/dist/Theme/tokens/builders/buildDarkPalette.d.ts +1 -1
  79. package/dist/Theme/tokens/builders/buildDarkPalette.js +5 -5
  80. package/dist/Theme/tokens/builders/filledSurface.d.ts +5 -1
  81. package/dist/Theme/tokens/builders/filledSurface.js +17 -4
  82. package/dist/Theme/tokens/builders/planeStep.d.ts +1 -0
  83. package/dist/Theme/tokens/builders/planeStep.js +5 -4
  84. package/dist/Utils/table.js +2 -2
  85. package/dist/esm/Components/Button/Button.styles.js +8 -3
  86. package/dist/esm/Components/CopyButton/CopyButton.js +2 -2
  87. package/dist/esm/Components/Drawer/Drawer.js +6 -2
  88. package/dist/esm/Components/Dropdown/DropDown.js +2 -9
  89. package/dist/esm/Components/FilePreview/FilePreview.js +3 -2
  90. package/dist/esm/Components/FileUploader/FileUploader.js +4 -0
  91. package/dist/esm/Components/FormDrawer/FormDrawer.js +3 -3
  92. package/dist/esm/Components/Input/TextInput.styles.js +10 -6
  93. package/dist/esm/Components/KanbanBoard/KanbanBoard.js +6 -5
  94. package/dist/esm/Components/Modal/dialogNaming.js +18 -0
  95. package/dist/esm/Components/NavLauncher/LauncherTabs.js +2 -2
  96. package/dist/esm/Components/NavLauncher/NavLauncher.js +259 -49
  97. package/dist/esm/Components/NavLauncher/ScopeBar.js +134 -0
  98. package/dist/esm/Components/NavLauncher/engine/distance.js +37 -0
  99. package/dist/esm/Components/NavLauncher/engine/engine.js +458 -0
  100. package/dist/esm/Components/NavLauncher/engine/fold.js +74 -0
  101. package/dist/esm/Components/NavLauncher/engine/index.js +14 -0
  102. package/dist/esm/Components/NavLauncher/launcherParts.js +10 -6
  103. package/dist/esm/Components/NavLauncher/match.js +25 -12
  104. package/dist/esm/Components/NavLauncher/searchLayout.js +100 -0
  105. package/dist/esm/Components/NavLauncher/spotlight.js +194 -0
  106. package/dist/esm/Components/NavLauncher/spotlightParts.js +95 -0
  107. package/dist/esm/Components/NavigationBar/NavigationBar.js +2 -1
  108. package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +2 -4
  109. package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +7 -6
  110. package/dist/esm/Components/ScrollToTop/ScrollToTop.js +5 -4
  111. package/dist/esm/Components/SearchSelect/SearchSelect.js +6 -18
  112. package/dist/esm/Components/Select/SelectPopup.js +2 -4
  113. package/dist/esm/Components/Select/optionRowInk.js +7 -0
  114. package/dist/esm/Components/SelectSearch/SelectSearch.js +2 -11
  115. package/dist/esm/Components/Table/Table.js +4 -3
  116. package/dist/esm/Components/Table/components/TableBody.js +10 -2
  117. package/dist/esm/Components/Table/components/TableHeader.js +10 -3
  118. package/dist/esm/Components/Table/components/TableLoading.js +10 -6
  119. package/dist/esm/Components/Table/filters/CompactSelect.js +2 -11
  120. package/dist/esm/Components/Table/variants/TableVariantContext.js +3 -3
  121. package/dist/esm/Components/WizardDialog/WizardDialog.js +15 -3
  122. package/dist/esm/Components/WorkspaceWindow/WorkspaceTrayPreview.js +4 -0
  123. package/dist/esm/Components/WorkspaceWindow/WorkspaceWindow.js +28 -20
  124. package/dist/esm/Theme/chakra/Menu.styles.js +7 -0
  125. package/dist/esm/Theme/chakra/componentStyles.js +2 -2
  126. package/dist/esm/Theme/chakra/ringShadow.js +3 -0
  127. package/dist/esm/Theme/tokens/builders/accentText.js +5 -0
  128. package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +5 -5
  129. package/dist/esm/Theme/tokens/builders/filledSurface.js +18 -5
  130. package/dist/esm/Theme/tokens/builders/planeStep.js +3 -3
  131. package/dist/esm/Utils/table.js +2 -2
  132. package/dist/esm/index.js +6 -5
  133. package/dist/index.d.ts +10 -8
  134. package/dist/index.js +14 -6
  135. package/package.json +1 -1
@@ -5,13 +5,13 @@ exports.useTableVariant = useTableVariant;
5
5
  const react_1 = require("react");
6
6
  const useCustomTheme_1 = require("../../../Theme/useCustomTheme");
7
7
  const accentText_1 = require("../../../Theme/tokens/builders/accentText");
8
- const classic_1 = require("./classic");
8
+ const studio_1 = require("./studio");
9
9
  const TableVariantContext = (0, react_1.createContext)(null);
10
10
  exports.TableVariantProvider = TableVariantContext.Provider;
11
- /** No provider means the table as it always was — the default is a variant, not a gap. */
11
+ /** No provider means the default variant — studio, the same preset `Table` falls back to. */
12
12
  function useTableVariant() {
13
13
  const provided = (0, react_1.useContext)(TableVariantContext);
14
14
  const theme = (0, useCustomTheme_1.useCustomTheme)();
15
15
  const isDark = (0, accentText_1.isDarkCanvas)(theme.colors.backgroundColor.main);
16
- return (0, react_1.useMemo)(() => provided !== null && provided !== void 0 ? provided : (0, classic_1.classicPreset)(theme.colors, isDark), [provided, theme.colors, isDark]);
16
+ return (0, react_1.useMemo)(() => provided !== null && provided !== void 0 ? provided : (0, studio_1.studioPreset)(theme.colors, isDark), [provided, theme.colors, isDark]);
17
17
  }
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import type { WizardDialogProps, WizardProps } from "./WizardDialogProps";
3
3
  export { wizardTransition, firstInvalidStep, INITIAL_WIZARD_STATE } from "./wizardSteps";
4
4
  export type { WizardAction, WizardState, StepValidator } from "./wizardSteps";
5
- /** Modal wizard that owns step state: validated Continue, locked future steps, error summary, review on the last step. */
5
+ /** Wizard that owns step state: validated Continue, locked future steps, error summary, review on the last step. A Modal by default; `chrome="inline"` draws it in place. */
6
6
  export declare const WizardDialog: React.FC<WizardDialogProps>;
7
7
  /** The same wizard inline, for a full-page flow rather than a modal. */
8
8
  export declare const Wizard: React.FC<WizardProps>;
@@ -41,6 +41,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
41
41
  const react_1 = require("react");
42
42
  const react_2 = require("@chakra-ui/react");
43
43
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
44
+ const surfaceRaised_1 = require("../../Theme/tokens/builders/surfaceRaised");
44
45
  const Modal_1 = __importStar(require("../Modal/Modal"));
45
46
  const Button_1 = __importDefault(require("../Button/Button"));
46
47
  const StageProgress_1 = __importDefault(require("../StageProgress/StageProgress"));
@@ -86,15 +87,26 @@ const WizardFooter = ({ flow, labels, isFinishing, finishDisabled, finishBlocked
86
87
  const reason = finishDisabled && !isFinishing ? finishBlockedReason : undefined;
87
88
  return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { w: "100%", justify: "space-between", gap: 2, wrap: "wrap", children: [onCancel ? ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "ghost", colorScheme: "gray", label: labels.cancel, onClick: onCancel, isDisabled: isFinishing, "data-testid": "wizard-cancel" })) : ((0, jsx_runtime_1.jsx)("span", {})), (0, jsx_runtime_1.jsxs)(react_2.Flex, { gap: 2, align: reason ? "center" : undefined, children: [!flow.isFirst ? ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "outline", colorScheme: "gray", label: labels.back, onClick: flow.back, isDisabled: isFinishing, "data-testid": "wizard-back" })) : null, flow.isLast && reason ? ((0, jsx_runtime_1.jsx)(react_2.Text, { id: reasonId, fontSize: "xs", color: colors.textMuted, "data-testid": "wizard-finish-reason", children: reason })) : null, flow.isLast ? ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "solid", colorScheme: "primary", label: labels.finish, onClick: flow.finish, isLoading: isFinishing, isDisabled: finishDisabled, "aria-describedby": reason ? reasonId : undefined, loadingText: labels.finish, "data-testid": "wizard-finish" })) : ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "solid", colorScheme: "primary", label: labels.next, onClick: flow.next, "data-testid": "wizard-next" }))] })] }));
88
89
  };
89
- /** Modal wizard that owns step state: validated Continue, locked future steps, error summary, review on the last step. */
90
- const WizardDialog = ({ isOpen, onClose, title, subtitle, steps, onFinish, isFinishing = false, finishDisabled = false, finishBlockedReason, stepId, onStepChange, size = "3xl", labels, "data-testid": testId, }) => {
90
+ const WizardInlineFrame = ({ title, subtitle, testId, footer, children }) => {
91
+ const { colors } = (0, useCustomTheme_1.useCustomTheme)();
92
+ return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { direction: "column", gap: 4, "data-testid": testId, children: [(0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.Heading, { as: "h2", fontSize: "md", fontWeight: 600, color: colors.header[900], children: title }), subtitle ? ((0, jsx_runtime_1.jsx)(react_2.Text, { mt: 0.5, fontSize: "sm", color: colors.textMuted, children: subtitle })) : null] }), children, (0, jsx_runtime_1.jsx)(react_2.Box, { position: "sticky", bottom: 0, pt: 3, borderTopWidth: "1px", borderColor: (0, surfaceRaised_1.raisedRule)(colors), bg: colors.surfaceRaised, children: footer })] }));
93
+ };
94
+ /** Wizard that owns step state: validated Continue, locked future steps, error summary, review on the last step. A Modal by default; `chrome="inline"` draws it in place. */
95
+ const WizardDialog = ({ isOpen, onClose, title, subtitle, steps, onFinish, isFinishing = false, finishDisabled = false, finishBlockedReason, stepId, onStepChange, size = "3xl", chrome = "modal", labels, "data-testid": testId, }) => {
91
96
  const flow = (0, useWizardFlow_1.useWizardFlow)({ steps, stepId, onStepChange, onFinish, resetWhen: !isOpen });
92
97
  const resolved = { ...DEFAULT_LABELS, ...labels };
93
98
  const guardedClose = () => {
94
99
  if (!isFinishing)
95
100
  onClose();
96
101
  };
97
- return ((0, jsx_runtime_1.jsxs)(Modal_1.default, { isOpen: isOpen, onClose: guardedClose, size: size, isCentered: true, scrollBehavior: "inside", title: title, subtitle: subtitle, closeOnOverlayClick: false, closeOnEsc: !isFinishing, hideCloseButton: isFinishing, children: [(0, jsx_runtime_1.jsx)(Modal_1.ModalBody, { children: (0, jsx_runtime_1.jsx)(WizardBody, { flow: flow, steps: steps, labels: resolved, testId: testId !== null && testId !== void 0 ? testId : "wizard-dialog" }) }), (0, jsx_runtime_1.jsx)(Modal_1.ModalFooter, { children: (0, jsx_runtime_1.jsx)(WizardFooter, { flow: flow, labels: resolved, isFinishing: isFinishing, finishDisabled: finishDisabled, finishBlockedReason: finishBlockedReason, onCancel: guardedClose }) })] }));
102
+ const body = ((0, jsx_runtime_1.jsx)(WizardBody, { flow: flow, steps: steps, labels: resolved, testId: testId !== null && testId !== void 0 ? testId : "wizard-dialog" }));
103
+ const footer = ((0, jsx_runtime_1.jsx)(WizardFooter, { flow: flow, labels: resolved, isFinishing: isFinishing, finishDisabled: finishDisabled, finishBlockedReason: finishBlockedReason, onCancel: guardedClose }));
104
+ if (chrome === "inline") {
105
+ if (!isOpen)
106
+ return null;
107
+ return ((0, jsx_runtime_1.jsx)(WizardInlineFrame, { title: title, subtitle: subtitle, testId: testId ? `${testId}-inline` : "wizard-inline", footer: footer, children: body }));
108
+ }
109
+ return ((0, jsx_runtime_1.jsxs)(Modal_1.default, { isOpen: isOpen, onClose: guardedClose, size: size, isCentered: true, scrollBehavior: "inside", title: title, subtitle: subtitle, closeOnOverlayClick: false, closeOnEsc: !isFinishing, hideCloseButton: isFinishing, children: [(0, jsx_runtime_1.jsx)(Modal_1.ModalBody, { children: body }), (0, jsx_runtime_1.jsx)(Modal_1.ModalFooter, { children: footer })] }));
98
110
  };
99
111
  exports.WizardDialog = WizardDialog;
100
112
  /** The same wizard inline, for a full-page flow rather than a modal. */
@@ -20,7 +20,7 @@ export type WizardLabels = Partial<{
20
20
  fixErrors: string;
21
21
  review: string;
22
22
  }>;
23
- /** Props for the modal wizard: Modal + stepper + per-step validation + review on the last step. */
23
+ /** Props for the wizard dialog: Modal (or inline with `chrome`) + stepper + per-step validation + review on the last step. */
24
24
  export interface WizardDialogProps {
25
25
  isOpen: boolean;
26
26
  /** Ignored while `isFinishing`. */
@@ -40,13 +40,15 @@ export interface WizardDialogProps {
40
40
  stepId?: string;
41
41
  /** Fires whenever the visible step changes, controlled or not. */
42
42
  onStepChange?: (stepId: string) => void;
43
- /** Modal width; default "3xl". */
43
+ /** Modal width; default "3xl". Ignored when `chrome` is "inline". */
44
44
  size?: "lg" | "xl" | "2xl" | "3xl" | "4xl";
45
+ /** "inline" draws the wizard in place with no Modal (no overlay, focus trap, close X, Esc or dialog role) for a host that is already the dialog, such as a WorkspaceWindow; it renders nothing while closed and Cancel still calls `onClose`. Default "modal". */
46
+ chrome?: "modal" | "inline";
45
47
  labels?: WizardLabels;
46
48
  "data-testid"?: string;
47
49
  }
48
50
  /** Same flow inline (a page section, not a modal) — for full-page wizards. */
49
- export interface WizardProps extends Omit<WizardDialogProps, "isOpen" | "onClose" | "title" | "subtitle" | "size"> {
51
+ export interface WizardProps extends Omit<WizardDialogProps, "isOpen" | "onClose" | "title" | "subtitle" | "size" | "chrome"> {
50
52
  /** Renders a Cancel button in the footer when provided. */
51
53
  onCancel?: () => void;
52
54
  }
@@ -21,7 +21,11 @@ function WorkspaceTrayPreview({ children, preview, enabled = true, openDelayMs =
21
21
  const [open, setOpen] = (0, react_1.useState)(false);
22
22
  const openTimer = (0, react_1.useRef)(null);
23
23
  const closeTimer = (0, react_1.useRef)(null);
24
+ const openRef = (0, react_1.useRef)(false);
24
25
  const setOpenState = (0, react_1.useCallback)((next) => {
26
+ if (openRef.current === next)
27
+ return;
28
+ openRef.current = next;
25
29
  setOpen(next);
26
30
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
27
31
  }, [onOpenChange]);
@@ -1,3 +1,3 @@
1
1
  import type { WorkspaceTrayProps, WorkspaceWindowProps } from "./WorkspaceWindowProps";
2
- export default function WorkspaceWindow({ title, state, children, onMinimize, onClose, onOpenFullPage, onOverlayClick, headerActions, testId, }: WorkspaceWindowProps): import("react/jsx-runtime").JSX.Element | null;
3
- export declare function WorkspaceTray({ items, onRestore, onClose, testId, renderItem, }: WorkspaceTrayProps): import("react/jsx-runtime").JSX.Element | null;
2
+ export default function WorkspaceWindow({ title, state, children, onMinimize, onClose, onOpenFullPage, onOverlayClick, headerActions, labels, finalFocusRef, testId, }: WorkspaceWindowProps): import("react/jsx-runtime").JSX.Element | null;
3
+ export declare function WorkspaceTray({ items, onRestore, onClose, testId, placement, offset, closeLabel, dirtyLabel, label, renderItem, }: WorkspaceTrayProps): import("react/jsx-runtime").JSX.Element | null;
@@ -12,7 +12,7 @@ const lucide_react_1 = require("lucide-react");
12
12
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
13
13
  const zIndices_1 = __importDefault(require("../../Theme/tokens/primitives/zIndices"));
14
14
  const accentText_1 = require("../../Theme/tokens/builders/accentText");
15
- // NOTE: no maximize/restore toggle (removed by product decision) — onMedium/onMaximize kept optional for back-compat.
15
+ // NOTE: no maximize/restore toggle (removed by product decision); onMedium/onMaximize stay as deprecated no-ops.
16
16
  const WINDOW_SIZE = { maxW: "88rem", h: "92vh" };
17
17
  const WindowTitle = ({ testId, color, children }) => {
18
18
  const { headerId, setHeaderMounted } = (0, react_1.useModalContext)();
@@ -23,7 +23,8 @@ const WindowTitle = ({ testId, color, children }) => {
23
23
  return ((0, jsx_runtime_1.jsx)(react_1.Text, { id: headerId, fontSize: "sm", fontWeight: 600, color: color, noOfLines: 1, "data-testid": `${testId}-title`, children: children }));
24
24
  };
25
25
  // Presentational floating "workspace window" (Jira-style dialog), headless — the parent owns window state.
26
- function WorkspaceWindow({ title, state, children, onMinimize, onClose, onOpenFullPage, onOverlayClick, headerActions, testId = "workspace-window", }) {
26
+ function WorkspaceWindow({ title, state, children, onMinimize, onClose, onOpenFullPage, onOverlayClick, headerActions, labels, finalFocusRef, testId = "workspace-window", }) {
27
+ var _a, _b, _c;
27
28
  // Must run before the early return: a minimized render would otherwise skip the hook
28
29
  // and change hook order when the window reopens.
29
30
  const theme = (0, useCustomTheme_1.useCustomTheme)();
@@ -33,28 +34,35 @@ function WorkspaceWindow({ title, state, children, onMinimize, onClose, onOpenFu
33
34
  return ((0, jsx_runtime_1.jsxs)(react_1.Modal, { isOpen: true, onClose: () => onClose === null || onClose === void 0 ? void 0 : onClose(), size: "full", isCentered: true, scrollBehavior: "inside",
34
35
  // Don't discard unsaved work on an accidental overlay click / Esc — the
35
36
  // parent decides via the explicit Close control.
36
- closeOnOverlayClick: false, closeOnEsc: false, children: [(0, jsx_runtime_1.jsx)(react_1.ModalOverlay, { backdropFilter: "blur(3px)" }), (0, jsx_runtime_1.jsx)(react_1.ModalContent, { bg: "transparent", boxShadow: "none", m: 0, display: "flex", alignItems: "center", justifyContent: "center",
37
+ closeOnOverlayClick: false, closeOnEsc: false, finalFocusRef: finalFocusRef, children: [(0, jsx_runtime_1.jsx)(react_1.ModalOverlay, { backdropFilter: "blur(3px)" }), (0, jsx_runtime_1.jsx)(react_1.ModalContent, { bg: "transparent", boxShadow: "none", m: 0, display: "flex", alignItems: "center", justifyContent: "center",
37
38
  // NOTE: size="full" blocks Chakra's own closeOnOverlayClick — detect the outside click ourselves instead.
38
39
  onMouseDown: (e) => {
39
40
  if (e.target === e.currentTarget)
40
41
  onOverlayClick === null || onOverlayClick === void 0 ? void 0 : onOverlayClick();
41
- }, "data-testid": `${testId}-backdrop`, children: (0, jsx_runtime_1.jsxs)(react_1.Box, { bg: theme.colors.surfaceRaised, borderRadius: "xl", boxShadow: "2xl", overflow: "hidden", w: "100%", maxW: size.maxW, h: size.h, display: "flex", flexDirection: "column", "data-testid": testId, "data-window-state": state, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { align: "center", justify: "space-between", px: 3, py: 2, borderBottomWidth: "1px", borderColor: theme.colors.boxborder[200], bg: theme.colors.background[100], flexShrink: 0, children: [(0, jsx_runtime_1.jsx)(WindowTitle, { testId: testId, color: theme.colors.text[700], children: title }), (0, jsx_runtime_1.jsxs)(react_1.HStack, { spacing: 1, children: [headerActions, onOpenFullPage ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": "Open full page", size: "xs", variant: "ghost", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { size: 14 }), onClick: onOpenFullPage, "data-testid": `${testId}-fullpage` })) : null, onMinimize ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": "Minimize", size: "xs", variant: "ghost", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Minus, { size: 15 }), onClick: onMinimize, "data-testid": `${testId}-minimize` })) : null, onClose ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": "Close", size: "xs", variant: "ghost", colorScheme: "red", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15 }), onClick: onClose, "data-testid": `${testId}-close` })) : null] })] }), (0, jsx_runtime_1.jsx)(react_1.Box, { flex: "1", overflow: "auto", px: { base: 3, md: 5 }, py: 4, children: children })] }) })] }));
42
+ }, "data-testid": `${testId}-backdrop`, children: (0, jsx_runtime_1.jsxs)(react_1.Box, { bg: theme.colors.surfaceRaised, borderRadius: "xl", boxShadow: "2xl", overflow: "hidden", w: "100%", maxW: size.maxW, h: size.h, display: "flex", flexDirection: "column", "data-testid": testId, "data-window-state": state, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { align: "center", justify: "space-between", px: 3, py: 2, borderBottomWidth: "1px", borderColor: theme.colors.boxborder[200], bg: theme.colors.background[100], flexShrink: 0, children: [(0, jsx_runtime_1.jsx)(WindowTitle, { testId: testId, color: theme.colors.text[700], children: title }), (0, jsx_runtime_1.jsxs)(react_1.HStack, { spacing: 1, children: [headerActions, onOpenFullPage ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": (_a = labels === null || labels === void 0 ? void 0 : labels.openFullPage) !== null && _a !== void 0 ? _a : "Open full page", size: "xs", variant: "ghost", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { size: 14 }), onClick: onOpenFullPage, "data-testid": `${testId}-fullpage` })) : null, onMinimize ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": (_b = labels === null || labels === void 0 ? void 0 : labels.minimize) !== null && _b !== void 0 ? _b : "Minimize", size: "xs", variant: "ghost", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Minus, { size: 15 }), onClick: onMinimize, "data-testid": `${testId}-minimize` })) : null, onClose ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": (_c = labels === null || labels === void 0 ? void 0 : labels.close) !== null && _c !== void 0 ? _c : "Close", size: "xs", variant: "ghost", colorScheme: "red", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15 }), onClick: onClose, "data-testid": `${testId}-close` })) : null] })] }), (0, jsx_runtime_1.jsx)(react_1.Box, { flex: "1", overflow: "auto", px: { base: 3, md: 5 }, py: 4, children: children })] }) })] }));
42
43
  }
43
- // Bottom tray of restore pills for minimized WorkspaceWindows — presentational, parent supplies handlers.
44
- function WorkspaceTray({ items, onRestore, onClose, testId = "workspace-tray", renderItem, }) {
44
+ const cssLength = (value) => (typeof value === "number" ? `${value}px` : value);
45
+ // Portalled above modal overlays; aria-live keeps Chakra's modal aria-hidden off it, data-no-focus-lock keeps the focus trap off it.
46
+ function WorkspaceTray({ items, onRestore, onClose, testId = "workspace-tray", placement = "bottom-end", offset, closeLabel, dirtyLabel = "Unsaved changes", label = "Minimized windows", renderItem, }) {
47
+ var _a, _b;
45
48
  const theme = (0, useCustomTheme_1.useCustomTheme)();
46
49
  if (!items.length)
47
50
  return null;
48
- return (
49
- // NOTE: floating bottom-right cluster (Gmail/Jira pattern), not a docked bar — needs no reserved page gutter.
50
- (0, jsx_runtime_1.jsx)(react_1.Flex, { position: "fixed", bottom: 4, right: 4, zIndex: zIndices_1.default.modal, direction: "row-reverse", wrap: "wrap-reverse", justify: "flex-start", gap: 2, maxW: { base: "calc(100vw - 2rem)", md: "36rem" }, "data-testid": testId, children: items.map((item) => {
51
- const api = {
52
- restore: () => onRestore === null || onRestore === void 0 ? void 0 : onRestore(item.id),
53
- close: () => onClose === null || onClose === void 0 ? void 0 : onClose(item.id),
54
- };
55
- if (renderItem) {
56
- return (0, jsx_runtime_1.jsx)(react_1.Box, { children: renderItem(item, api) }, item.id);
57
- }
58
- return ((0, jsx_runtime_1.jsxs)(react_1.HStack, { spacing: 1, pl: 3, pr: 1, py: 1, borderRadius: "full", bg: theme.colors.primary[100], color: (0, accentText_1.accentTextOnCanvas)(theme.colors.primary, theme.colors.primary[100]), boxShadow: "md", borderWidth: "1px", borderColor: theme.colors.primary[200], "data-testid": `${testId}-pill`, children: [(0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", fontSize: "sm", onClick: api.restore, "data-testid": `${testId}-restore`, maxW: "16rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: item.title }), onClose ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": "Close", size: "xs", variant: "ghost", borderRadius: "full", minW: "auto", h: "auto", p: 1, color: "inherit", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13 }), onClick: api.close, "data-testid": `${testId}-close` })) : null] }, item.id));
59
- }) }));
51
+ const atEnd = placement === "bottom-end";
52
+ const inset = {
53
+ bottom: cssLength((_a = offset === null || offset === void 0 ? void 0 : offset.y) !== null && _a !== void 0 ? _a : "1rem"),
54
+ [atEnd ? "right" : "left"]: cssLength((_b = offset === null || offset === void 0 ? void 0 : offset.x) !== null && _b !== void 0 ? _b : "1rem"),
55
+ };
56
+ const ink = (0, accentText_1.accentTextOnCanvas)(theme.colors.primary, theme.colors.primary[100]);
57
+ return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsx)(react_1.Flex, { role: "region", "aria-label": label, "aria-live": "polite", "data-no-focus-lock": "true", position: "fixed", ...inset, zIndex: zIndices_1.default.modal + 1, direction: atEnd ? "row-reverse" : "row", wrap: "wrap-reverse", justify: "flex-start", gap: 2, maxW: { base: "calc(100vw - 2rem)", md: "36rem" }, "data-testid": testId, children: items.map((item) => {
58
+ const api = {
59
+ restore: () => onRestore === null || onRestore === void 0 ? void 0 : onRestore(item.id),
60
+ close: () => onClose === null || onClose === void 0 ? void 0 : onClose(item.id),
61
+ };
62
+ const pill = ((0, jsx_runtime_1.jsxs)(react_1.HStack, { spacing: 1, pl: 3, pr: 1, py: 1, borderRadius: "full", bg: theme.colors.primary[100], color: ink, boxShadow: "md", borderWidth: "1px", borderColor: theme.colors.primary[200], "data-testid": `${testId}-pill`, children: [item.isDirty ? ((0, jsx_runtime_1.jsx)(react_1.Box, { role: "img", "aria-label": dirtyLabel, boxSize: "0.5rem", borderRadius: "full", bg: "currentColor", flexShrink: 0, "data-testid": `${testId}-dirty` })) : null, (0, jsx_runtime_1.jsxs)(react_1.Box, { as: "button", type: "button", fontSize: "sm", onClick: api.restore, "data-testid": `${testId}-restore`, maxW: "16rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: [item.title, item.subtitle ? ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", fontWeight: 400, ml: 1.5, children: item.subtitle })) : null] }), onClose ? ((0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": closeLabel ? closeLabel(item) : "Close", size: "xs", variant: "ghost", borderRadius: "full", minW: "auto", h: "auto", p: 1, color: "inherit", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13 }), onClick: api.close, "data-testid": `${testId}-close` })) : null] }));
63
+ if (renderItem) {
64
+ return (0, jsx_runtime_1.jsx)(react_1.Box, { children: renderItem(item, api, pill) }, item.id);
65
+ }
66
+ return (0, jsx_runtime_1.jsx)(react_2.Fragment, { children: pill }, item.id);
67
+ }) }) }));
60
68
  }
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import type { ReactNode, RefObject } from "react";
2
2
  /**
3
3
  * Window state for a WorkspaceWindow.
4
4
  * - "medium": default centered dialog (Jira-style)
@@ -6,6 +6,15 @@ import type { ReactNode } from "react";
6
6
  * - "minimized": collapsed to a tray pill (rendered by WorkspaceTray, not the window)
7
7
  */
8
8
  export type WorkspaceWindowState = "medium" | "maximized" | "minimized";
9
+ /** Accessible names of the window's icon controls; omitted keys keep the English default. */
10
+ export interface WorkspaceWindowLabels {
11
+ /** Default "Minimize". */
12
+ minimize?: string;
13
+ /** Default "Close". */
14
+ close?: string;
15
+ /** Default "Open full page". */
16
+ openFullPage?: string;
17
+ }
9
18
  /** Props for one WorkspaceWindow instance; minimized windows render separately via WorkspaceTrayProps below. */
10
19
  export interface WorkspaceWindowProps {
11
20
  /** Title shown in the window's control bar. */
@@ -29,6 +38,13 @@ export interface WorkspaceWindowProps {
29
38
  * itself (e.g. minimize-on-backdrop-click) via this callback.
30
39
  */
31
40
  onOverlayClick?: () => void;
41
+ /** Translated names for the Minimize, Close and Open full page buttons. */
42
+ labels?: WorkspaceWindowLabels;
43
+ /**
44
+ * Where focus lands when the window closes or minimizes. Without it focus returns to whatever opened the
45
+ * window, which is gone once it was restored from the tray; point it at a stable element (e.g. the page's main region).
46
+ */
47
+ finalFocusRef?: RefObject<HTMLElement>;
32
48
  /** Extra controls rendered in the control bar, left of the window buttons. */
33
49
  headerActions?: ReactNode;
34
50
  /** data-testid for the window container. */
@@ -38,8 +54,12 @@ export interface WorkspaceWindowProps {
38
54
  export interface WorkspaceTrayItem {
39
55
  id: string;
40
56
  title: ReactNode;
57
+ /** Shows an unsaved-changes dot on the built-in pill; named for screen readers by the tray's `dirtyLabel`. */
58
+ isDirty?: boolean;
59
+ /** Secondary text after the title on the built-in pill, e.g. a record type or id. */
60
+ subtitle?: ReactNode;
41
61
  }
42
- /** Props for the bottom-right cluster of minimized-window pills; renders nothing when items is empty. */
62
+ /** Props for the cluster of minimized-window pills; renders nothing when items is empty. */
43
63
  export interface WorkspaceTrayProps {
44
64
  /** Minimized items rendered as restore pills along the bottom. */
45
65
  items: WorkspaceTrayItem[];
@@ -47,16 +67,26 @@ export interface WorkspaceTrayProps {
47
67
  onClose?: (id: string) => void;
48
68
  testId?: string;
49
69
  /**
50
- * Full override for a single item's pill markup — e.g. to wrap it in an
51
- * app-specific hover preview, or to show extra state (a dirty dot) the
52
- * generic pill doesn't know about. Receives the item plus ready-bound
53
- * restore/close callbacks; when provided, the built-in pill is skipped for
54
- * that item and this renders in its place inside the same positioned tray.
70
+ * Which bottom corner the tray sits in. Default "bottom-end". The tray is portalled above modals and stays
71
+ * reachable while a modal is open.
55
72
  */
73
+ placement?: "bottom-end" | "bottom-start";
74
+ /** Distance from the viewport edges, to clear a sticky bar. Numbers are px, strings any CSS length. Default 1rem each. */
75
+ offset?: {
76
+ x?: number | string;
77
+ y?: number | string;
78
+ };
79
+ /** Accessible name for each pill's close button. Default "Close"; pass one that names the item for non-English or per-window labels. */
80
+ closeLabel?: (item: WorkspaceTrayItem) => string;
81
+ /** Accessible name of the unsaved dot on a pill with `isDirty`. Default "Unsaved changes". */
82
+ dirtyLabel?: string;
83
+ /** Accessible name of the tray region. Default "Minimized windows". */
84
+ label?: string;
85
+ /** Replaces one item's pill inside the same tray; gets the item, bound restore/close and `pill`, the built-in pill (dot, subtitle, close button) to return as is or wrap, e.g. in WorkspaceTrayPreview. */
56
86
  renderItem?: (item: WorkspaceTrayItem, api: {
57
87
  restore: () => void;
58
88
  close: () => void;
59
- }) => ReactNode;
89
+ }, pill: ReactNode) => ReactNode;
60
90
  }
61
91
  /** Props for WorkspaceTrayPreview — despite the name, works for any hover trigger, not just tray pills. */
62
92
  export interface WorkspaceTrayPreviewProps {
@@ -15,6 +15,7 @@ export declare const Menu: {
15
15
  borderColor: string;
16
16
  };
17
17
  item: {
18
+ color: string;
18
19
  minH: string;
19
20
  px: string;
20
21
  py: string;
@@ -39,6 +40,13 @@ export declare const Menu: {
39
40
  [$itemBg.variable]: string;
40
41
  bg: string;
41
42
  };
43
+ "&[aria-checked=true]": {
44
+ color: string;
45
+ fontWeight: number;
46
+ "& .chakra-menu__icon-wrapper": {
47
+ color: string;
48
+ };
49
+ };
42
50
  };
43
51
  }) | undefined;
44
52
  sizes?: {
@@ -4,6 +4,7 @@ exports.Menu = void 0;
4
4
  const react_1 = require("@chakra-ui/react");
5
5
  const anatomy_1 = require("@chakra-ui/anatomy");
6
6
  const rowStates_1 = require("../tokens/builders/rowStates");
7
+ const optionRowInk_1 = require("../../Components/Select/optionRowInk");
7
8
  const optionRowSizes_1 = require("../tokens/primitives/optionRowSizes");
8
9
  const paletteFromTheme_1 = require("./paletteFromTheme");
9
10
  const overlay_styles_1 = require("./overlay.styles");
@@ -28,6 +29,7 @@ const baseStyle = definePartsStyle(({ theme }) => {
28
29
  ...overlay_styles_1.POPUP_SURFACE,
29
30
  },
30
31
  item: {
32
+ color: (0, optionRowInk_1.optionRowInk)(colors, false),
31
33
  minH: itemRowSpec.minH,
32
34
  px: itemRowSpec.px,
33
35
  py: itemRowSpec.py,
@@ -40,6 +42,11 @@ const baseStyle = definePartsStyle(({ theme }) => {
40
42
  _hover: { [$itemBg.variable]: hoverBg, bg: $itemBg.reference },
41
43
  _active: { [$itemBg.variable]: activeBg, bg: $itemBg.reference },
42
44
  _expanded: { [$itemBg.variable]: hoverBg, bg: $itemBg.reference },
45
+ "&[aria-checked=true]": {
46
+ color: (0, optionRowInk_1.optionRowInk)(colors, true),
47
+ fontWeight: (0, optionRowInk_1.optionRowWeight)(true),
48
+ "& .chakra-menu__icon-wrapper": { color: colors.accentText },
49
+ },
43
50
  },
44
51
  };
45
52
  });
@@ -182,6 +182,7 @@ export declare const componentStyles: {
182
182
  borderColor: string;
183
183
  };
184
184
  item: {
185
+ color: string;
185
186
  minH: string;
186
187
  px: string;
187
188
  py: string;
@@ -206,6 +207,13 @@ export declare const componentStyles: {
206
207
  [x: string]: string;
207
208
  bg: string;
208
209
  };
210
+ "&[aria-checked=true]": {
211
+ color: string;
212
+ fontWeight: number;
213
+ "& .chakra-menu__icon-wrapper": {
214
+ color: string;
215
+ };
216
+ };
209
217
  };
210
218
  }) | undefined;
211
219
  sizes?: {
@@ -26,7 +26,7 @@ const Card_styles_1 = require("./Card.styles");
26
26
  // NOTE: guards untethemed scales (blackAlpha/whiteAlpha) whose rgba() rungs would throw in hexToRgb.
27
27
  const isHexColor = (color) => typeof color === "string" && /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color);
28
28
  const solidFillStyle = (palette, swatch) => {
29
- const rung = (0, filledSurface_1.solidFillRung)(swatch, palette.white);
29
+ const rung = (0, filledSurface_1.solidFillRung)(swatch, palette.white, palette.backgroundColor.main);
30
30
  const bg = swatch[rung];
31
31
  return {
32
32
  bg,
@@ -161,7 +161,7 @@ const Radio = {
161
161
  baseStyle: ({ theme, colorScheme }) => {
162
162
  const palette = (0, paletteFromTheme_1.paletteOf)(theme);
163
163
  const swatch = (0, paletteFromTheme_1.scaleFor)(palette, colorScheme);
164
- const fill = swatch[(0, filledSurface_1.solidFillRung)(swatch, palette.white)];
164
+ const fill = swatch[(0, filledSurface_1.solidFillRung)(swatch, palette.white, palette.backgroundColor.main)];
165
165
  const dot = (0, filledSurface_1.labelOnFill)(fill, palette.white, palette.black);
166
166
  const hoverFill = (0, outlineRung_1.resolveHoverTint)(fill, fill);
167
167
  return {
@@ -0,0 +1,3 @@
1
+ import { type BorderWidthRole } from "../tokens/primitives/borderWidths";
2
+ /** A stroke drawn inside the border box as a box-shadow, so it never costs layout. */
3
+ export declare const insetRingShadow: (color: string, width?: BorderWidthRole) => string;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.insetRingShadow = void 0;
4
+ const borderWidths_1 = require("../tokens/primitives/borderWidths");
5
+ /** A stroke drawn inside the border box as a box-shadow, so it never costs layout. */
6
+ const insetRingShadow = (color, width = "hairline") => `inset 0 0 0 ${borderWidths_1.BORDER_WIDTHS[width]} ${color}`;
7
+ exports.insetRingShadow = insetRingShadow;
@@ -12,6 +12,8 @@ declare const DARK_CANVAS_LUMINANCE = 0.2;
12
12
  /** True when `canvas` is a dark surface. Lets style configs branch without threading color mode. */
13
13
  export declare const isDarkCanvas: (canvas: string) => boolean;
14
14
  export declare const accentTextOnCanvas: (scale: ColorScale, canvas: string) => string;
15
+ /** Accent ink for chrome that can land on any plane (canvas, card, overlay): the first rung clearing AA on all. */
16
+ export declare const accentTextOnPlanes: (scale: ColorScale, planes: readonly string[]) => string;
15
17
  export declare const onFilled: (fill: string, white: string, black: string) => string;
16
18
  export declare const onGradient: (from: string, to: string, white: string, black: string) => string;
17
19
  export { AA_NORMAL_TEXT, DARK_CANVAS_LUMINANCE };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DARK_CANVAS_LUMINANCE = exports.AA_NORMAL_TEXT = exports.onGradient = exports.onFilled = exports.accentTextOnCanvas = exports.isDarkCanvas = exports.DEFAULT_BLACK = exports.DEFAULT_WHITE = exports.DEFAULT_CANVAS = exports.pickAccentText = void 0;
3
+ exports.DARK_CANVAS_LUMINANCE = exports.AA_NORMAL_TEXT = exports.onGradient = exports.onFilled = exports.accentTextOnPlanes = exports.accentTextOnCanvas = exports.isDarkCanvas = exports.DEFAULT_BLACK = exports.DEFAULT_WHITE = exports.DEFAULT_CANVAS = exports.pickAccentText = void 0;
4
4
  const color_1 = require("./color");
5
5
  /** WCAG AA for normal-size text. Accent-as-text must clear this against the canvas. */
6
6
  const AA_NORMAL_TEXT = 4.5;
@@ -27,6 +27,12 @@ exports.isDarkCanvas = isDarkCanvas;
27
27
  // NOTE: held to AA in both modes now — some brands' light [500] no longer passes through untouched.
28
28
  const accentTextOnCanvas = (scale, canvas) => (0, exports.pickAccentText)(scale, canvas);
29
29
  exports.accentTextOnCanvas = accentTextOnCanvas;
30
+ /** Accent ink for chrome that can land on any plane (canvas, card, overlay): the first rung clearing AA on all. */
31
+ const accentTextOnPlanes = (scale, planes) => {
32
+ const found = SEARCH_ORDER.find((rung) => planes.every((plane) => (0, color_1.contrastRatio)(scale[rung], plane) >= AA_NORMAL_TEXT));
33
+ return scale[found !== null && found !== void 0 ? found : SEARCH_ORDER[SEARCH_ORDER.length - 1]];
34
+ };
35
+ exports.accentTextOnPlanes = accentTextOnPlanes;
30
36
  // NOTE: white/black must be the mode-stable absolutes, not an adaptive token — always clears AA.
31
37
  const onFilled = (fill, white, black) => (0, color_1.contrastRatio)(white, fill) >= AA_NORMAL_TEXT ? white : black;
32
38
  exports.onFilled = onFilled;
@@ -6,7 +6,7 @@ export declare const shiftLighter: (scale: ColorScale) => ColorScale;
6
6
  export declare const accentLightnessDelta: (accent500: string, white: string) => number;
7
7
  /** Raises every rung by the same lightness delta, preserving the ramp's shape and hue. */
8
8
  export declare const liftScale: (scale: ColorScale, delta: number) => ColorScale;
9
- /** `lifted`: brand-tinted canvas, card a CARD_LIFT step above it. `flat`: the hue-neutral canvas the card shares. */
9
+ /** `lifted`: the card sits a CARD_LIFT step above the canvas. `flat`: the card is the canvas. */
10
10
  export type DarkPlanes = "lifted" | "flat";
11
11
  export interface DarkPaletteOptions {
12
12
  planes?: DarkPlanes;
@@ -151,7 +151,8 @@ const NAMED_SURFACE_L = {
151
151
  accent: 17,
152
152
  muted: 18,
153
153
  };
154
- const SURFACE_SAT = { lifted: 14, flat: 0 };
154
+ // NOTE: surfaces are hue-neutral on every brand; brand hue stays on accents, ink and rules.
155
+ const SURFACE_SAT = 0;
155
156
  const TEXT_SAT = 12;
156
157
  const PLACEHOLDER_SAT = 10;
157
158
  const BORDER_SAT = 12;
@@ -182,16 +183,15 @@ const cardPlane = (canvas, white, planes) => {
182
183
  function buildDarkPalette(light, { planes = "lifted" } = {}) {
183
184
  const hueSource = light.primary[500];
184
185
  const { h } = (0, color_1.hexToHsl)(hueSource);
185
- const surfaceSat = SURFACE_SAT[planes];
186
186
  const namedSurfaces = Object.fromEntries(Object.entries(NAMED_SURFACE_L).map(([key, l]) => [
187
187
  key,
188
- (0, color_1.hslToHex)({ h, s: surfaceSat, l }),
188
+ (0, color_1.hslToHex)({ h, s: SURFACE_SAT, l }),
189
189
  ]));
190
190
  const white = light.white;
191
191
  const { card, rise, tableHover } = cardPlane(namedSurfaces.main, white, planes);
192
192
  const primary = liftBrand(light.primary, white);
193
193
  const background = {
194
- ...fromTargets(hueSource, raise(SURFACE_TARGETS, BACKGROUND_FILLS, rise), surfaceSat),
194
+ ...fromTargets(hueSource, raise(SURFACE_TARGETS, BACKGROUND_FILLS, rise), SURFACE_SAT),
195
195
  50: card,
196
196
  };
197
197
  const semantic = {
@@ -231,7 +231,7 @@ function buildDarkPalette(light, { planes = "lifted" } = {}) {
231
231
  border: dividerRamp,
232
232
  boxborder: dividerRamp,
233
233
  table: {
234
- hover: fromTargets(hueSource, tableHover, surfaceSat),
234
+ hover: fromTargets(hueSource, tableHover, SURFACE_SAT),
235
235
  },
236
236
  disabled: fromTargets(hueSource, DISABLED_TARGETS, DISABLED_SAT),
237
237
  accentText: (0, accentText_1.pickAccentText)(primary, namedSurfaces.main),
@@ -12,7 +12,11 @@ export type FilledSurface = {
12
12
  activeColor: string;
13
13
  };
14
14
  export declare const solidFillRung: (scale: ColorScale, white: string, canvas?: string) => keyof ColorScale;
15
- /** Hover (step 0) or active (step 1) fill rung, one step darker than `solidFillRung`. */
15
+ /**
16
+ * Hover (step 0) or active (step 1) fill rung, one step darker than `solidFillRung`. A step may not
17
+ * flip the label's side of AA (zinc dark: near-white fill, black ink) or fall below `CHROME_BORDER`
18
+ * against the canvas (a pressed warning solid used to reach 1.00:1 on the card).
19
+ */
16
20
  export declare const darkerFillRung: (scale: ColorScale, canvas: string, white: string, step: 0 | 1) => keyof ColorScale;
17
21
  /** Label ink for a specific fill swatch — always `onFilled`, exported for style configs. */
18
22
  export declare const labelOnFill: (fill: string, white: string, black: string) => string;
@@ -44,15 +44,28 @@ const steppedRung = (base, canvas, step) => {
44
44
  const dir = (0, accentText_1.isDarkCanvas)(canvas) ? -100 : 100;
45
45
  return Math.min(900, Math.max(50, base + dir * step));
46
46
  };
47
- /** Hover (step 0) or active (step 1) fill rung, one step darker than `solidFillRung`. */
47
+ const steppedTowardBase = (from, base) => RUNGS[RUNGS.indexOf(from) + (from > base ? -1 : 1)];
48
+ /**
49
+ * Hover (step 0) or active (step 1) fill rung, one step darker than `solidFillRung`. A step may not
50
+ * flip the label's side of AA (zinc dark: near-white fill, black ink) or fall below `CHROME_BORDER`
51
+ * against the canvas (a pressed warning solid used to reach 1.00:1 on the card).
52
+ */
48
53
  const darkerFillRung = (scale, canvas, white, step) => {
49
54
  const base = (0, exports.solidFillRung)(scale, white, canvas);
55
+ const wantsWhite = (0, color_1.contrastRatio)(white, scale[base]) >= accentText_1.AA_NORMAL_TEXT;
56
+ const holds = (rung) => (0, color_1.contrastRatio)(scale[rung], canvas) >= outlineRung_1.CHROME_BORDER &&
57
+ ((0, color_1.contrastRatio)(white, scale[rung]) >= accentText_1.AA_NORMAL_TEXT) === wantsWhite;
58
+ let rung;
50
59
  if (base === 500) {
51
60
  const mode = (0, accentText_1.isDarkCanvas)(canvas) ? "dark" : "light";
52
- return FILL_STEP_RUNGS[mode][step];
61
+ rung = FILL_STEP_RUNGS[mode][step];
53
62
  }
54
- const stepped = steppedRung(base, canvas, step === 0 ? 1 : 2);
55
- return stepped;
63
+ else {
64
+ rung = steppedRung(base, canvas, step === 0 ? 1 : 2);
65
+ }
66
+ while (rung !== base && !holds(rung))
67
+ rung = steppedTowardBase(rung, base);
68
+ return rung;
56
69
  };
57
70
  exports.darkerFillRung = darkerFillRung;
58
71
  /** Label ink for a specific fill swatch — always `onFilled`, exported for style configs. */
@@ -1,4 +1,5 @@
1
1
  import type { PaletteProps } from "../types";
2
2
  export declare const bandOn: (fill: string, plane: string) => string;
3
3
  export declare const recessOn: (fill: string, plane: string) => string;
4
+ export declare const ruleOn: (colors: Pick<PaletteProps, "boxborder">, plane: string) => string;
4
5
  export declare const cardRule: (colors: Pick<PaletteProps, "boxborder" | "background">) => string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cardRule = exports.recessOn = exports.bandOn = void 0;
3
+ exports.cardRule = exports.ruleOn = exports.recessOn = exports.bandOn = void 0;
4
4
  const accentText_1 = require("./accentText");
5
5
  const color_1 = require("./color");
6
6
  const outlineRung_1 = require("./outlineRung");
@@ -12,9 +12,10 @@ const recessOn = (fill, plane) => holds(fill, plane, outlineRung_1.MIN_PERCEPTIB
12
12
  ? fill
13
13
  : (0, planes_1.lift)(accentText_1.DEFAULT_BLACK, plane, outlineRung_1.MIN_PERCEPTIBLE_STEP);
14
14
  exports.recessOn = recessOn;
15
- const cardRule = (colors) => {
15
+ const ruleOn = (colors, plane) => {
16
16
  const rule = colors.boxborder[200];
17
- const card = colors.background[50];
18
- return holds(rule, card, outlineRung_1.CHROME_RULE) ? rule : (0, outlineRung_1.liftToContrast)(rule, card, outlineRung_1.CHROME_RULE);
17
+ return holds(rule, plane, outlineRung_1.CHROME_RULE) ? rule : (0, outlineRung_1.liftToContrast)(rule, plane, outlineRung_1.CHROME_RULE);
19
18
  };
19
+ exports.ruleOn = ruleOn;
20
+ const cardRule = (colors) => (0, exports.ruleOn)(colors, colors.background[50]);
20
21
  exports.cardRule = cardRule;
@@ -81,7 +81,7 @@ function buildSemanticStatusPalette(toneByValue, theme) {
81
81
  return Object.fromEntries(Object.entries(toneByValue).map(([value, key]) => {
82
82
  const scale = scales[key];
83
83
  const tone = (0, tone_1.resolveSoftTone)(c, scale);
84
- const solidRung = (0, filledSurface_1.solidFillRung)(scale, c.white);
84
+ const solidRung = (0, filledSurface_1.solidFillRung)(scale, c.white, c.backgroundColor.main);
85
85
  const solid = scale[solidRung];
86
86
  return [
87
87
  value,
@@ -123,7 +123,7 @@ function buildTablePalette(theme) {
123
123
  ];
124
124
  return scales.map((scale) => {
125
125
  const tone = (0, tone_1.resolveSoftTone)(c, scale);
126
- const solidRung = (0, filledSurface_1.solidFillRung)(scale, c.white);
126
+ const solidRung = (0, filledSurface_1.solidFillRung)(scale, c.white, c.backgroundColor.main);
127
127
  const solid = scale[solidRung];
128
128
  return {
129
129
  solid,
@@ -1,4 +1,4 @@
1
- import { accentTextOnCanvas } from "../../Theme/tokens/builders/accentText.js";
1
+ import { accentTextOnPlanes } from "../../Theme/tokens/builders/accentText.js";
2
2
  import { darkerFillRung, labelOnFill, resolveFilledSurface, solidFillRung, } from "../../Theme/tokens/builders/filledSurface.js";
3
3
  import { resolveGradient, gradientCss } from "../../Theme/tokens/builders/gradientInk.js";
4
4
  import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
@@ -21,7 +21,12 @@ const controlSize = (size) => {
21
21
  return { h: height, minW: height, fontSize, px: paddingX };
22
22
  };
23
23
  // NOTE: [500] alone measures ~4.2 on a dark canvas — resolved per-mode so outline/ghost/link text stays readable.
24
- const onCanvasText = (theme, colorScheme) => accentTextOnCanvas(theme.colors[colorScheme], theme.colors.backgroundColor.main);
24
+ // NOTE: a bare button sits on the canvas, a card or an overlay; dark overlays are the hardest plane.
25
+ const onCanvasText = (theme, colorScheme) => accentTextOnPlanes(theme.colors[colorScheme], [
26
+ theme.colors.backgroundColor.main,
27
+ theme.colors.background[50],
28
+ theme.colors.surfaceRaised,
29
+ ]);
25
30
  // NOTE: [50] alone measured ~1.04 (invisible) in both modes — gray borrows the row-state steps.
26
31
  const onCanvasHoverBg = (theme, colorScheme, step) => {
27
32
  const scale = theme.colors[colorScheme];
@@ -33,7 +38,7 @@ const onCanvasHoverBg = (theme, colorScheme, step) => {
33
38
  return step === 16 ? rows.active : rows.selectedActive;
34
39
  };
35
40
  const swatch = (theme, colorScheme) => theme.colors[colorScheme];
36
- const buttonSolidRung = (theme, colorScheme) => solidFillRung(swatch(theme, colorScheme), theme.colors.white);
41
+ const buttonSolidRung = (theme, colorScheme) => solidFillRung(swatch(theme, colorScheme), theme.colors.white, theme.colors.backgroundColor.main);
37
42
  const darkerFill = (theme, colorScheme, step) => {
38
43
  const scale = swatch(theme, colorScheme);
39
44
  const rung = darkerFillRung(scale, theme.colors.backgroundColor.main, theme.colors.white, step);