pixelize-design-library 2.4.2-beta.3 → 2.4.2-beta.30

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 (279) hide show
  1. package/dist/Components/Accordion/Accordion.js +5 -3
  2. package/dist/Components/AppSwitcher/AppMark.d.ts +19 -0
  3. package/dist/Components/AppSwitcher/AppMark.js +62 -0
  4. package/dist/Components/AppSwitcher/AppSwitcher.d.ts +4 -0
  5. package/dist/Components/AppSwitcher/AppSwitcher.js +315 -0
  6. package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +64 -0
  7. package/dist/Components/AppSwitcher/AppSwitcherProps.js +86 -0
  8. package/dist/Components/AppSwitcher/useAppSwitcherShortcut.d.ts +3 -0
  9. package/dist/Components/AppSwitcher/useAppSwitcherShortcut.js +31 -0
  10. package/dist/Components/AppearanceSettings/AppearanceSettings.js +2 -1
  11. package/dist/Components/Button/Button.d.ts +2 -2
  12. package/dist/Components/Button/Button.js +4 -3
  13. package/dist/Components/Button/Button.styles.js +62 -15
  14. package/dist/Components/Button/ButtonProps.d.ts +6 -1
  15. package/dist/Components/ButtonGroupIcon/ButtonGroupIcon.js +6 -6
  16. package/dist/Components/Card/Card.d.ts +1 -1
  17. package/dist/Components/Card/Card.js +2 -2
  18. package/dist/Components/Card/CardProps.d.ts +0 -1
  19. package/dist/Components/Card/PaymentCard/PaymentCard.js +7 -7
  20. package/dist/Components/Checkbox/Checkbox.js +1 -1
  21. package/dist/Components/Checkbox/Checkbox.styles.js +12 -7
  22. package/dist/Components/Common/fieldStyles.js +1 -1
  23. package/dist/Components/CopyButton/CopyButton.js +6 -2
  24. package/dist/Components/CustomModulesTable/EditModuleModal.js +1 -1
  25. package/dist/Components/DatePicker/CalendarPanel.d.ts +5 -0
  26. package/dist/Components/DatePicker/CalendarPanel.js +24 -5
  27. package/dist/Components/DatePicker/DatePickerProps.d.ts +21 -0
  28. package/dist/Components/DatePicker/RangeDatePicker.js +4 -4
  29. package/dist/Components/DatePicker/SingleDatePicker.js +2 -2
  30. package/dist/Components/DatePicker/TimeOnlyPicker.js +1 -1
  31. package/dist/Components/DatePicker/dayStates.d.ts +56 -0
  32. package/dist/Components/DatePicker/dayStates.js +87 -0
  33. package/dist/Components/Dropdown/DropDown.js +7 -15
  34. package/dist/Components/EmailCards/VerifyEmailOtp/VerifyEmailOtp.js +1 -1
  35. package/dist/Components/EmptyState/EmptyState.js +2 -1
  36. package/dist/Components/FilePreview/FilePreview.js +1 -1
  37. package/dist/Components/FileUpload/FileUpload.js +5 -4
  38. package/dist/Components/FileUpload/FileUploadProps.d.ts +3 -1
  39. package/dist/Components/FileUpload/constants.js +1 -0
  40. package/dist/Components/FilterSidebar/FilterSidebar.js +4 -4
  41. package/dist/Components/FilterSidebar/filterHoverStyle.d.ts +0 -9
  42. package/dist/Components/FilterSidebar/filterHoverStyle.js +4 -10
  43. package/dist/Components/Form/FormWrapper.js +1 -1
  44. package/dist/Components/Header/components/mobileMenu.js +12 -3
  45. package/dist/Components/Input/TextInput.styles.js +6 -4
  46. package/dist/Components/InputTextArea/InputTextArea.style.js +5 -4
  47. package/dist/Components/KanbanBoard/KanbanBoard.js +5 -45
  48. package/dist/Components/LimitReachedModal/LimitReachedModal.js +4 -5
  49. package/dist/Components/Loading/Loading.js +15 -14
  50. package/dist/Components/MultiSelect/MultiSelect.js +1 -1
  51. package/dist/Components/NavigationBar/NavigationBar.js +3 -3
  52. package/dist/Components/NoteTextArea/NoteTextArea.js +9 -4
  53. package/dist/Components/NumberInput/NumberInput.js +3 -1
  54. package/dist/Components/NumberInput/NumberInput.styles.js +5 -4
  55. package/dist/Components/OrgSwitcher/OrgSwitcher.js +2 -2
  56. package/dist/Components/PdfViewer/PdfViewer.js +6 -1
  57. package/dist/Components/PinInput/PinInput.js +1 -1
  58. package/dist/Components/PlanPill/PlanPill.js +5 -5
  59. package/dist/Components/PlanPill/planPillGradient.d.ts +2 -14
  60. package/dist/Components/PlanPill/planPillGradient.js +3 -15
  61. package/dist/Components/Primitives/Primitives.d.ts +6 -0
  62. package/dist/Components/Primitives/Primitives.js +113 -0
  63. package/dist/Components/ProductCard/ProductPrice.js +1 -1
  64. package/dist/Components/ProductCard/ProductReview.js +6 -1
  65. package/dist/Components/ProductCard/ProductTags.js +1 -1
  66. package/dist/Components/ProfileCard/ProfileCard.js +1 -1
  67. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +1 -1
  68. package/dist/Components/RadioButton/RadioButton.js +3 -2
  69. package/dist/Components/Reorder/Reorder.js +1 -1
  70. package/dist/Components/ScrollToTop/ScrollToTop.js +2 -0
  71. package/dist/Components/ScrollbarBox/ScrollbarBox.d.ts +13 -0
  72. package/dist/Components/ScrollbarBox/ScrollbarBox.js +11 -0
  73. package/dist/Components/Search/Search.js +2 -2
  74. package/dist/Components/SearchSelect/SearchSelect.js +14 -9
  75. package/dist/Components/SegmentedControl/SegmentedControl.d.ts +2 -0
  76. package/dist/Components/SegmentedControl/SegmentedControl.js +58 -0
  77. package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +16 -0
  78. package/dist/Components/SegmentedControl/SegmentedControlProps.js +2 -0
  79. package/dist/Components/Select/Select.d.ts +1 -1
  80. package/dist/Components/Select/Select.js +4 -2
  81. package/dist/Components/Select/Select.styles.js +3 -2
  82. package/dist/Components/Select/SelectProps.d.ts +3 -0
  83. package/dist/Components/SelectSearch/SelectSearch.js +7 -4
  84. package/dist/Components/SelectV2/SelectV2.js +18 -14
  85. package/dist/Components/SideBar/SecondaryBar.js +1 -1
  86. package/dist/Components/SideBar/SideBar.js +1 -1
  87. package/dist/Components/SideBar/components/MenuPopoverContent.js +2 -2
  88. package/dist/Components/Slider/Slider.js +8 -1
  89. package/dist/Components/StageProgress/StageProgress.js +2 -9
  90. package/dist/Components/StageProgress/StepperStage.js +2 -2
  91. package/dist/Components/Table/Table.d.ts +1 -1
  92. package/dist/Components/Table/Table.js +17 -24
  93. package/dist/Components/Table/TableProps.d.ts +4 -0
  94. package/dist/Components/Table/components/Pagination.js +1 -1
  95. package/dist/Components/Table/components/TableActions.js +2 -2
  96. package/dist/Components/Table/components/TableBody.js +41 -12
  97. package/dist/Components/Table/components/TableGroupRow.js +1 -1
  98. package/dist/Components/Table/components/TableHeader.js +21 -8
  99. package/dist/Components/Table/components/TableSearch.js +1 -1
  100. package/dist/Components/Table/filters/ActiveFilters.js +2 -18
  101. package/dist/Components/Table/settings/ManageColumns.js +4 -17
  102. package/dist/Components/Table/settings/TableSettings.js +5 -11
  103. package/dist/Components/Table/variants/TableVariantContext.d.ts +4 -0
  104. package/dist/Components/Table/variants/TableVariantContext.js +17 -0
  105. package/dist/Components/Table/variants/classic.d.ts +6 -0
  106. package/dist/Components/Table/variants/classic.js +47 -0
  107. package/dist/Components/Table/variants/index.d.ts +7 -0
  108. package/dist/Components/Table/variants/index.js +27 -0
  109. package/dist/Components/Table/variants/pillInk.d.ts +6 -0
  110. package/dist/Components/Table/variants/pillInk.js +21 -0
  111. package/dist/Components/Table/variants/studio.d.ts +6 -0
  112. package/dist/Components/Table/variants/studio.js +50 -0
  113. package/dist/Components/Table/variants/tokens.d.ts +49 -0
  114. package/dist/Components/Table/variants/tokens.js +2 -0
  115. package/dist/Components/Tag/Tag.styles.js +3 -4
  116. package/dist/Components/Timeline/Timeline.js +2 -2
  117. package/dist/Components/Toaster/Toaster.js +1 -1
  118. package/dist/Components/Toggle/TableToggle.js +11 -7
  119. package/dist/Components/ToggleSwitch/ToggleSwitch.d.ts +2 -0
  120. package/dist/Components/ToggleSwitch/ToggleSwitch.js +18 -0
  121. package/dist/Components/ToggleSwitch/ToggleSwitchProps.d.ts +11 -0
  122. package/dist/Components/ToggleSwitch/ToggleSwitchProps.js +2 -0
  123. package/dist/Components/ToolTip/ToolTip.js +6 -6
  124. package/dist/Components/ToolTip/ToolTipProps.d.ts +3 -1
  125. package/dist/Components/Trail/TrailWorks.js +1 -1
  126. package/dist/Components/UpgradeButton/UpgradeButton.js +3 -3
  127. package/dist/Constants/Sidebar.js +3 -2
  128. package/dist/Theme/assertPalette.d.ts +1 -1
  129. package/dist/Theme/assertPalette.js +3 -2
  130. package/dist/Theme/chakra/Menu.styles.d.ts +3 -0
  131. package/dist/Theme/chakra/Menu.styles.js +5 -0
  132. package/dist/Theme/chakra/Popover.styles.d.ts +3 -0
  133. package/dist/Theme/chakra/Popover.styles.js +5 -1
  134. package/dist/Theme/chakra/componentStyles.d.ts +6 -0
  135. package/dist/Theme/chakra/createBrandTheme.js +10 -16
  136. package/dist/Theme/chakra/focusRing.styles.d.ts +52 -0
  137. package/dist/Theme/chakra/focusRing.styles.js +46 -0
  138. package/dist/Theme/provider/ThemeSwitcher.js +5 -2
  139. package/dist/Theme/tokens/brands/index.js +6 -1
  140. package/dist/Theme/tokens/brands/zinc/palette.dark.d.ts +1 -0
  141. package/dist/Theme/tokens/builders/buildDarkPalette.js +6 -1
  142. package/dist/Theme/tokens/builders/chartColorsFromTheme.js +1 -1
  143. package/dist/Theme/tokens/builders/focusRing.d.ts +52 -0
  144. package/dist/Theme/tokens/builders/focusRing.js +92 -0
  145. package/dist/Theme/tokens/builders/gradientInk.d.ts +22 -0
  146. package/dist/Theme/tokens/builders/gradientInk.js +28 -0
  147. package/dist/Theme/tokens/builders/index.d.ts +4 -0
  148. package/dist/Theme/tokens/builders/index.js +4 -0
  149. package/dist/Theme/tokens/builders/outlineRung.d.ts +46 -3
  150. package/dist/Theme/tokens/builders/outlineRung.js +101 -5
  151. package/dist/Theme/tokens/builders/placeholderInk.d.ts +19 -0
  152. package/dist/Theme/tokens/builders/placeholderInk.js +35 -0
  153. package/dist/Theme/tokens/builders/resolvedRungs.d.ts +5 -1
  154. package/dist/Theme/tokens/builders/resolvedRungs.js +7 -2
  155. package/dist/Theme/tokens/builders/rowStates.d.ts +13 -0
  156. package/dist/Theme/tokens/builders/rowStates.js +19 -0
  157. package/dist/Theme/tokens/builders/scrollbar.d.ts +12 -0
  158. package/dist/Theme/tokens/builders/scrollbar.js +57 -0
  159. package/dist/Theme/tokens/builders/surfaceRungs.d.ts +5 -0
  160. package/dist/Theme/tokens/builders/surfaceRungs.js +20 -5
  161. package/dist/Theme/tokens/types.d.ts +21 -1
  162. package/dist/esm/Components/Accordion/Accordion.js +5 -3
  163. package/dist/esm/Components/AppSwitcher/AppMark.js +56 -0
  164. package/dist/esm/Components/AppSwitcher/AppSwitcher.js +280 -0
  165. package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +78 -0
  166. package/dist/esm/Components/AppSwitcher/useAppSwitcherShortcut.js +27 -0
  167. package/dist/esm/Components/AppearanceSettings/AppearanceSettings.js +2 -1
  168. package/dist/esm/Components/Button/Button.js +5 -4
  169. package/dist/esm/Components/Button/Button.styles.js +63 -16
  170. package/dist/esm/Components/ButtonGroupIcon/ButtonGroupIcon.js +6 -6
  171. package/dist/esm/Components/Card/Card.js +2 -2
  172. package/dist/esm/Components/Card/PaymentCard/PaymentCard.js +7 -7
  173. package/dist/esm/Components/Checkbox/Checkbox.js +1 -1
  174. package/dist/esm/Components/Checkbox/Checkbox.styles.js +13 -8
  175. package/dist/esm/Components/Common/fieldStyles.js +1 -1
  176. package/dist/esm/Components/CopyButton/CopyButton.js +7 -3
  177. package/dist/esm/Components/CustomModulesTable/EditModuleModal.js +1 -1
  178. package/dist/esm/Components/DatePicker/CalendarPanel.js +24 -5
  179. package/dist/esm/Components/DatePicker/RangeDatePicker.js +4 -4
  180. package/dist/esm/Components/DatePicker/SingleDatePicker.js +2 -2
  181. package/dist/esm/Components/DatePicker/TimeOnlyPicker.js +1 -1
  182. package/dist/esm/Components/DatePicker/dayStates.js +79 -0
  183. package/dist/esm/Components/Dropdown/DropDown.js +7 -15
  184. package/dist/esm/Components/EmailCards/VerifyEmailOtp/VerifyEmailOtp.js +1 -1
  185. package/dist/esm/Components/EmptyState/EmptyState.js +2 -1
  186. package/dist/esm/Components/FilePreview/FilePreview.js +1 -1
  187. package/dist/esm/Components/FileUpload/FileUpload.js +6 -5
  188. package/dist/esm/Components/FileUpload/constants.js +1 -0
  189. package/dist/esm/Components/FilterSidebar/FilterSidebar.js +4 -4
  190. package/dist/esm/Components/FilterSidebar/filterHoverStyle.js +4 -10
  191. package/dist/esm/Components/Form/FormWrapper.js +1 -1
  192. package/dist/esm/Components/Header/components/mobileMenu.js +12 -3
  193. package/dist/esm/Components/Input/TextInput.styles.js +6 -4
  194. package/dist/esm/Components/InputTextArea/InputTextArea.style.js +5 -4
  195. package/dist/esm/Components/KanbanBoard/KanbanBoard.js +5 -45
  196. package/dist/esm/Components/LimitReachedModal/LimitReachedModal.js +4 -5
  197. package/dist/esm/Components/Loading/Loading.js +12 -11
  198. package/dist/esm/Components/MultiSelect/MultiSelect.js +1 -1
  199. package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
  200. package/dist/esm/Components/NoteTextArea/NoteTextArea.js +9 -4
  201. package/dist/esm/Components/NumberInput/NumberInput.js +4 -2
  202. package/dist/esm/Components/NumberInput/NumberInput.styles.js +5 -4
  203. package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +2 -2
  204. package/dist/esm/Components/PdfViewer/PdfViewer.js +6 -1
  205. package/dist/esm/Components/PinInput/PinInput.js +1 -1
  206. package/dist/esm/Components/PlanPill/PlanPill.js +6 -6
  207. package/dist/esm/Components/PlanPill/planPillGradient.js +3 -15
  208. package/dist/esm/Components/Primitives/Primitives.js +6 -0
  209. package/dist/esm/Components/ProductCard/ProductPrice.js +1 -1
  210. package/dist/esm/Components/ProductCard/ProductReview.js +6 -1
  211. package/dist/esm/Components/ProductCard/ProductTags.js +1 -1
  212. package/dist/esm/Components/ProfileCard/ProfileCard.js +1 -1
  213. package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +1 -1
  214. package/dist/esm/Components/RadioButton/RadioButton.js +3 -2
  215. package/dist/esm/Components/Reorder/Reorder.js +1 -1
  216. package/dist/esm/Components/ScrollToTop/ScrollToTop.js +2 -0
  217. package/dist/esm/Components/ScrollbarBox/ScrollbarBox.js +9 -0
  218. package/dist/esm/Components/Search/Search.js +2 -2
  219. package/dist/esm/Components/SearchSelect/SearchSelect.js +14 -9
  220. package/dist/esm/Components/SegmentedControl/SegmentedControl.js +55 -0
  221. package/dist/esm/Components/SegmentedControl/SegmentedControlProps.js +1 -0
  222. package/dist/esm/Components/Select/Select.js +5 -3
  223. package/dist/esm/Components/Select/Select.styles.js +3 -2
  224. package/dist/esm/Components/SelectSearch/SelectSearch.js +7 -4
  225. package/dist/esm/Components/SelectV2/SelectV2.js +18 -14
  226. package/dist/esm/Components/SideBar/SecondaryBar.js +1 -1
  227. package/dist/esm/Components/SideBar/SideBar.js +1 -1
  228. package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +2 -2
  229. package/dist/esm/Components/Slider/Slider.js +8 -1
  230. package/dist/esm/Components/StageProgress/StageProgress.js +2 -9
  231. package/dist/esm/Components/StageProgress/StepperStage.js +2 -2
  232. package/dist/esm/Components/Table/Table.js +17 -24
  233. package/dist/esm/Components/Table/components/Pagination.js +1 -1
  234. package/dist/esm/Components/Table/components/TableActions.js +2 -2
  235. package/dist/esm/Components/Table/components/TableBody.js +42 -13
  236. package/dist/esm/Components/Table/components/TableGroupRow.js +1 -1
  237. package/dist/esm/Components/Table/components/TableHeader.js +21 -8
  238. package/dist/esm/Components/Table/components/TableSearch.js +1 -1
  239. package/dist/esm/Components/Table/filters/ActiveFilters.js +2 -18
  240. package/dist/esm/Components/Table/settings/ManageColumns.js +4 -17
  241. package/dist/esm/Components/Table/settings/TableSettings.js +5 -11
  242. package/dist/esm/Components/Table/variants/TableVariantContext.js +13 -0
  243. package/dist/esm/Components/Table/variants/classic.js +43 -0
  244. package/dist/esm/Components/Table/variants/index.js +9 -0
  245. package/dist/esm/Components/Table/variants/pillInk.js +18 -0
  246. package/dist/esm/Components/Table/variants/studio.js +46 -0
  247. package/dist/esm/Components/Table/variants/tokens.js +1 -0
  248. package/dist/esm/Components/Tag/Tag.styles.js +3 -4
  249. package/dist/esm/Components/Timeline/Timeline.js +2 -2
  250. package/dist/esm/Components/Toaster/Toaster.js +1 -1
  251. package/dist/esm/Components/Toggle/TableToggle.js +11 -7
  252. package/dist/esm/Components/ToggleSwitch/ToggleSwitch.js +15 -0
  253. package/dist/esm/Components/ToggleSwitch/ToggleSwitchProps.js +1 -0
  254. package/dist/esm/Components/ToolTip/ToolTip.js +6 -6
  255. package/dist/esm/Components/Trail/TrailWorks.js +1 -1
  256. package/dist/esm/Components/UpgradeButton/UpgradeButton.js +3 -3
  257. package/dist/esm/Constants/Sidebar.js +3 -2
  258. package/dist/esm/Theme/assertPalette.js +3 -2
  259. package/dist/esm/Theme/chakra/Menu.styles.js +5 -0
  260. package/dist/esm/Theme/chakra/Popover.styles.js +5 -1
  261. package/dist/esm/Theme/chakra/createBrandTheme.js +10 -16
  262. package/dist/esm/Theme/chakra/focusRing.styles.js +40 -0
  263. package/dist/esm/Theme/provider/ThemeSwitcher.js +5 -2
  264. package/dist/esm/Theme/tokens/brands/index.js +6 -1
  265. package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +6 -1
  266. package/dist/esm/Theme/tokens/builders/chartColorsFromTheme.js +1 -1
  267. package/dist/esm/Theme/tokens/builders/focusRing.js +85 -0
  268. package/dist/esm/Theme/tokens/builders/gradientInk.js +22 -0
  269. package/dist/esm/Theme/tokens/builders/index.js +4 -0
  270. package/dist/esm/Theme/tokens/builders/outlineRung.js +96 -4
  271. package/dist/esm/Theme/tokens/builders/placeholderInk.js +29 -0
  272. package/dist/esm/Theme/tokens/builders/resolvedRungs.js +7 -2
  273. package/dist/esm/Theme/tokens/builders/rowStates.js +15 -0
  274. package/dist/esm/Theme/tokens/builders/scrollbar.js +52 -0
  275. package/dist/esm/Theme/tokens/builders/surfaceRungs.js +20 -5
  276. package/dist/esm/index.js +24 -1
  277. package/dist/index.d.ts +27 -3
  278. package/dist/index.js +64 -4
  279. package/package.json +8 -2
@@ -2,14 +2,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, IconButton } from "@chakra-ui/react";
3
3
  import { Columns3, Table2 } from "lucide-react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
+ import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
5
6
  import ToolTip from "../ToolTip/ToolTip.js";
6
7
  const TableToggle = ({ value, onChange }) => {
7
8
  const theme = useCustomTheme();
9
+ const { colors } = theme;
10
+ const restInk = liftToContrast(colors.background[800], colors.background[600], AA_NON_TEXT);
11
+ const hoverInk = liftToContrast(colors.background[900], colors.background[400], AA_NON_TEXT);
8
12
  const styles = {
9
13
  container: {
10
14
  display: "flex",
11
15
  alignItems: "center",
12
- bg: theme.colors.background[600],
16
+ bg: colors.background[600],
13
17
  p: "0.125rem",
14
18
  borderRadius: "md",
15
19
  height: "2rem",
@@ -17,19 +21,19 @@ const TableToggle = ({ value, onChange }) => {
17
21
  width: "6.3rem",
18
22
  },
19
23
  button: (mode) => ({
20
- bg: value === mode ? theme.colors.background[50] : "transparent",
24
+ bg: value === mode ? colors.background[50] : "transparent",
21
25
  boxShadow: value === mode ? "md" : "none",
22
- color: value === mode ? theme.colors.text[900] : theme.colors.background[800],
26
+ color: value === mode ? colors.text[900] : restInk,
23
27
  borderRadius: "base",
24
28
  height: "1.75rem",
25
29
  border: "0.031rem solid",
26
- borderColor: value === mode ? theme.colors.background[400] : "transparent",
30
+ borderColor: value === mode ? colors.background[400] : "transparent",
27
31
  padding: "0.313rem 0.813rem",
28
32
  _hover: {
29
33
  bg: value === mode
30
- ? theme.colors.background[50]
31
- : theme.colors.background[400],
32
- color: value === mode ? theme.colors.text[900] : theme.colors.background[900],
34
+ ? colors.background[50]
35
+ : colors.background[400],
36
+ color: value === mode ? colors.text[900] : hoverInk,
33
37
  },
34
38
  transition: "all 0.3s ease-in-out",
35
39
  }),
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box } from "@chakra-ui/react";
3
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
+ const SIZES = {
5
+ sm: { w: "2rem", h: "1.125rem", knob: "0.875rem", travel: "0.75rem" },
6
+ md: { w: "2.375rem", h: "1.375rem", knob: "1.125rem", travel: "0.875rem" },
7
+ };
8
+ export default function ToggleSwitch({ isChecked, onChange, isDisabled = false, size = "md", id, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, }) {
9
+ const { colors } = useCustomTheme();
10
+ const dims = SIZES[size];
11
+ return (_jsx(Box, { as: "button", type: "button", id: id, role: "switch", "aria-checked": isChecked, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, disabled: isDisabled, onClick: () => onChange(!isChecked), position: "relative", display: "inline-flex", alignItems: "center", flexShrink: 0, w: dims.w, h: dims.h, p: 0, borderRadius: "full", borderWidth: "1px", borderStyle: "solid", borderColor: isChecked ? colors.primary[500] : colors.boxborder[500], bg: isChecked ? colors.primary[500] : colors.background[600], opacity: isDisabled ? 0.5 : 1, cursor: isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, border-color 0.2s ease", _focusVisible: {
12
+ outline: "none",
13
+ boxShadow: `0 0 0 2px ${colors.primary[300]}`,
14
+ }, children: _jsx(Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.background[50], boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
15
+ }
@@ -4,18 +4,18 @@ import { Tooltip as ChakraTooltip } from "@chakra-ui/react";
4
4
  import { useIsTruncated } from "../../Hooks/useIsTruncated.js";
5
5
  import { mergeRefs } from "../../Utils/mergeRefs.js";
6
6
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
7
- export default function ToolTip({ placement = "top", label, children, hasArrow = true, fontSize = "14px", bg, color = "white", isDisabled, overflowOnly = false, isOpen, defaultIsOpen, openDelay = 0, closeDelay = 0, arrowSize = 10, closeOnClick = false, size, width, height, arrowPadding, arrowShadowColor, direction, gutter = 8, onClose, modifiers, closeOnPointerDown, styles, }) {
7
+ import { onFilled } from "../../Theme/tokens/builders/accentText.js";
8
+ export default function ToolTip({ placement = "top", label, children, hasArrow = true, fontSize = "14px", bg, color, isDisabled, overflowOnly = false, isOpen, defaultIsOpen, openDelay = 0, closeDelay = 0, arrowSize = 10, closeOnClick = false, size, width, height, arrowPadding, arrowShadowColor, direction, gutter = 8, onClose, modifiers, closeOnPointerDown, styles, }) {
8
9
  // Measure the child for `overflowOnly`. The hook is always called (Rules of
9
10
  // Hooks) but is fully inert unless `overflowOnly` is set — no layout reads,
10
11
  // no ResizeObserver, no re-renders for ordinary tooltips. Measurement never
11
12
  // runs on hover/scroll, so showing the tooltip stays as cheap as before.
12
13
  const { ref, isTruncated } = useIsTruncated(label, overflowOnly && isValidElement(children));
13
- // A tooltip is an INVERTED surface: near-black under a light canvas, carrying a white
14
- // label. `sidebar.background` is the only ramp `buildDarkPalette` deliberately leaves
15
- // un-inverted, so it stays near-black in dark mode and the white label stays legible —
16
- // every other surface ramp flips light and would render the label invisible.
14
+ // `sidebar.background` stays near-black in both modes; ink is computed via `onFilled`.
17
15
  const theme = useCustomTheme();
18
16
  const surface = theme.colors.sidebar.background[500];
17
+ const resolvedBg = bg !== null && bg !== void 0 ? bg : surface;
18
+ const resolvedColor = color !== null && color !== void 0 ? color : onFilled(resolvedBg, theme.colors.white, theme.colors.black);
19
19
  let content = children;
20
20
  let effectiveDisabled = isDisabled;
21
21
  if (overflowOnly && isValidElement(children)) {
@@ -26,5 +26,5 @@ export default function ToolTip({ placement = "top", label, children, hasArrow =
26
26
  });
27
27
  effectiveDisabled = Boolean(isDisabled) || !isTruncated;
28
28
  }
29
- return (_jsx(ChakraTooltip, { label: label, placement: placement, hasArrow: hasArrow, fontSize: fontSize, bg: bg !== null && bg !== void 0 ? bg : surface, color: color, isDisabled: effectiveDisabled, isOpen: isOpen, defaultIsOpen: defaultIsOpen, openDelay: openDelay, closeDelay: closeDelay, arrowSize: arrowSize, closeOnClick: closeOnClick, size: size, width: width, height: height, arrowPadding: arrowPadding, arrowShadowColor: arrowShadowColor, direction: direction, gutter: gutter, onClose: onClose, modifiers: modifiers, closeOnPointerDown: closeOnPointerDown, style: styles, px: 3, py: 2, borderRadius: "lg", fontWeight: "400", lineHeight: "1.5", maxW: "300px", children: content }));
29
+ return (_jsx(ChakraTooltip, { label: label, placement: placement, hasArrow: hasArrow, fontSize: fontSize, bg: resolvedBg, color: resolvedColor, isDisabled: effectiveDisabled, isOpen: isOpen, defaultIsOpen: defaultIsOpen, openDelay: openDelay, closeDelay: closeDelay, arrowSize: arrowSize, closeOnClick: closeOnClick, size: size, width: width, height: height, arrowPadding: arrowPadding, arrowShadowColor: arrowShadowColor, direction: direction, gutter: gutter, onClose: onClose, modifiers: modifiers, closeOnPointerDown: closeOnPointerDown, style: styles, px: 3, py: 2, borderRadius: "lg", fontWeight: "400", lineHeight: "1.5", maxW: "300px", children: content }));
30
30
  }
@@ -3,7 +3,7 @@ import { Box, Thead, Table, Tr, Th, Tbody, Td, Checkbox, } from "@chakra-ui/reac
3
3
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
4
  const TrailWorks = () => {
5
5
  const { colors } = useCustomTheme();
6
- const cellBorder = colors.border[500];
6
+ const cellBorder = colors.boxborder[200];
7
7
  const data = [
8
8
  {
9
9
  contact: "John Doe",
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Flex, Spinner, Text } from "@chakra-ui/react";
3
3
  import { Sparkles } from "lucide-react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
- import { onGradient } from "../../Theme/tokens/builders/accentText.js";
5
+ import { resolveGradient } from "../../Theme/tokens/builders/gradientInk.js";
6
6
  import { contrastRatio } from "../../Theme/tokens/builders/color.js";
7
7
  import { AA_NON_TEXT } from "../../Theme/tokens/builders/outlineRung.js";
8
8
  /** The brand's gold sparkle accent, kept only where it clears 3:1 on the button's own fill. */
@@ -33,10 +33,10 @@ const UpgradeButton = ({ label, mobileLabel, subtitle, icon = Sparkles, iconColo
33
33
  danger: { from: c.semantic.error[400], to: c.semantic.error[600] },
34
34
  info: { from: c.semantic.info[400], to: c.semantic.info[600] },
35
35
  };
36
- const resolved = gradient !== null && gradient !== void 0 ? gradient : variantGradient[variant];
37
36
  // `iconColor`'s gold default fails AA as body text against this gradient (measured ~1.4-3.3
38
37
  // across brands) — the label/subtitle share this computed ink instead.
39
- const ink = onGradient(resolved.from, resolved.to, c.white, c.black);
38
+ const resolved = resolveGradient(gradient !== null && gradient !== void 0 ? gradient : variantGradient[variant], c.white, c.black);
39
+ const ink = resolved.ink;
40
40
  // The gold only clears 1.4.11's 3:1 on a DARK gradient. zinc dark inverts `primary` to a
41
41
  // near-white fill, where gold-on-near-white measures ~1.3:1 and the icon vanishes, so fall
42
42
  // back to the label's ink whenever the gold misses either stop.
@@ -1,7 +1,8 @@
1
+ import { SCROLLBAR_WIDTH } from "../Theme/tokens/builders/scrollbar.js";
1
2
  export const HEADER_HEIGHT = "4.3rem";
2
3
  export const FlexCss = {
3
4
  "&::-webkit-scrollbar": {
4
- width: "0.188rem",
5
+ width: SCROLLBAR_WIDTH,
5
6
  },
6
7
  "&::-webkit-scrollbar-track": {
7
8
  background: "transparent",
@@ -16,7 +17,7 @@ export const FlexCss = {
16
17
  };
17
18
  export const cssForScrollBar = (theme) => ({
18
19
  "&::-webkit-scrollbar": {
19
- width: "0.125rem",
20
+ width: SCROLLBAR_WIDTH,
20
21
  },
21
22
  "&::-webkit-scrollbar-track": {
22
23
  background: theme.colors.sidebar.background[500],
@@ -2,8 +2,8 @@ const REQUIRED_GROUPS = [
2
2
  "gradient", "primary", "secondary", "tertiary", "transparent", "black", "white",
3
3
  "semantic", "gray", "red", "orange", "yellow", "green", "teal", "blue", "cyan",
4
4
  "purple", "pink", "backgroundColor", "background", "text", "header", "placeholder",
5
- "boxShadow", "sidebar", "boxborder", "border", "table", "disabled", "accentText",
6
- "textMuted", "semanticText", "onPrimary", "onSemantic",
5
+ "boxShadow", "focusRing", "sidebar", "boxborder", "border", "table", "disabled",
6
+ "accentText", "textMuted", "semanticText", "onPrimary", "onSemantic",
7
7
  ];
8
8
  export const allPaletteTokensListed = true;
9
9
  const NESTED = [
@@ -14,6 +14,7 @@ const NESTED = [
14
14
  ]],
15
15
  ["semanticText", ["success", "error", "warning", "info"]],
16
16
  ["onSemantic", ["success", "error", "warning", "info"]],
17
+ ["focusRing", ["ring", "halo"]],
17
18
  ["backgroundColor", ["main", "base"]],
18
19
  ["boxShadow", ["primary", "default"]],
19
20
  ];
@@ -28,6 +28,11 @@ const baseStyle = definePartsStyle(({ theme }) => {
28
28
  [$bg.variable]: colors.backgroundColor.light,
29
29
  bg: $bg.reference,
30
30
  color: colors.text[900],
31
+ // A rule at CHROME_RULE weight, not Chakra's gray.200: that default read as a hard edge,
32
+ // which is why this shipped borderless. At 1.31:1 the edge defines the surface instead.
33
+ borderWidth: "1px",
34
+ borderStyle: "solid",
35
+ borderColor: colors.boxborder[200],
31
36
  },
32
37
  // Own CSS var (`--menu-item-bg`), NOT the list's `--menu-bg` — Chakra's stock item
33
38
  // baseStyle rewrites `--menu-bg` itself on hover/focus, so reusing that same var name
@@ -18,8 +18,12 @@ const baseStyle = definePartsStyle(({ theme }) => {
18
18
  content: {
19
19
  [$popperBg.variable]: colors.backgroundColor.light,
20
20
  bg: $popperBg.reference,
21
- [$arrowShadowColor.variable]: colors.border[200],
21
+ [$arrowShadowColor.variable]: colors.boxborder[200],
22
22
  color: colors.text[900],
23
+ // Matches Menu: a CHROME_RULE-weight edge, not Chakra's gray.200 hard edge.
24
+ borderWidth: "1px",
25
+ borderStyle: "solid",
26
+ borderColor: colors.boxborder[200],
23
27
  },
24
28
  };
25
29
  });
@@ -3,6 +3,8 @@ import common from "./common.web.js";
3
3
  import fonts from "./fonts.web.js";
4
4
  import { componentStyles } from "./componentStyles.js";
5
5
  import { onFilled } from "../tokens/builders/accentText.js";
6
+ import { globalScrollbarStyles } from "../tokens/builders/scrollbar.js";
7
+ import { focusRingShadow } from "./focusRing.styles.js";
6
8
  /**
7
9
  * Builds a Chakra theme from a pure brand palette.
8
10
  *
@@ -20,6 +22,12 @@ export const createBrandTheme = (palette, options = {}) => {
20
22
  colors: palette,
21
23
  ...fonts,
22
24
  ...common,
25
+ // `shadows.outline` is what Chakra's stock `_focusVisible` reaches for on EVERY component it
26
+ // themes. The primitive ships one hardcoded blue (`rgba(66,153,225,.6)`), which measured
27
+ // 1.83:1 on the lavender canvas and 1.38:1 on a primary button — below 1.4.11's 3:1 on all
28
+ // nine brands, in both modes. A primitive cannot know the palette; this is the first place
29
+ // that does, so the palette-derived ring is bound here and every stock focus state inherits it.
30
+ shadows: { ...common.shadows, outline: focusRingShadow(palette) },
23
31
  };
24
32
  return extendTheme({
25
33
  ...brand,
@@ -83,22 +91,8 @@ export const createBrandTheme = (palette, options = {}) => {
83
91
  color: onFilled(palette.primary[500], palette.white, palette.black),
84
92
  },
85
93
  // `color-scheme: dark` gives WebKit/Blink a dark scrollbar by default, but it's the
86
- // OS's generic dark gray, not brand-aware — style it explicitly so it matches the
87
- // canvas instead of floating as a mismatched gray bar.
88
- "::-webkit-scrollbar": {
89
- width: "10px",
90
- height: "10px",
91
- },
92
- "::-webkit-scrollbar-track": {
93
- background: palette.backgroundColor.main,
94
- },
95
- "::-webkit-scrollbar-thumb": {
96
- background: palette.gray[600],
97
- borderRadius: "6px",
98
- },
99
- "::-webkit-scrollbar-thumb:hover": {
100
- background: palette.gray[500],
101
- },
94
+ // OS's generic dark gray, not brand-aware.
95
+ ...globalScrollbarStyles(palette),
102
96
  // Chrome/Edge autofill paints its own opaque yellow/white fill behind the input
103
97
  // text via `-webkit-box-shadow` (not `background`), so a plain `bg` override never
104
98
  // reaches it — a bright light box survives on every dark autofilled field unless
@@ -0,0 +1,40 @@
1
+ /**
2
+ * WEB binding for the `focusRing` token pair. The colours are derived in
3
+ * `tokens/builders/focusRing.ts` (pure, shared with the mobile library); the CSS shape —
4
+ * box-shadow bands, the forced-colors outline — is web-only and lives here.
5
+ */
6
+ /** Inner band, in the surface tone: separates the ring from the control's own fill. */
7
+ export const FOCUS_HALO_WIDTH = 2;
8
+ /** Outer edge of the brand band. The band itself is the 2px between halo and ring. */
9
+ export const FOCUS_RING_WIDTH = 4;
10
+ /**
11
+ * The two-tone ring as a single `box-shadow`. Drawn OUTSIDE the border box, so it costs no
12
+ * layout and never resizes the control — the reason this is a shadow and not an outline.
13
+ */
14
+ export const focusRingShadow = (colors) => `0 0 0 ${FOCUS_HALO_WIDTH}px ${colors.focusRing.halo}, ` +
15
+ `0 0 0 ${FOCUS_RING_WIDTH}px ${colors.focusRing.ring}`;
16
+ /**
17
+ * `:hover`, `:active` and `:focus-visible` all score the same specificity, so which one paints
18
+ * comes down to source order — and in the merged style object Chakra's own Button baseStyle
19
+ * registers `_focusVisible` BEFORE `_hover`. A focused button that is also hovered would then
20
+ * drop the ring for `_hover`'s `boxShadow: "sm"`. Stacking the pseudo-classes raises the
21
+ * specificity above both, so the ring survives whatever else the state repaints.
22
+ */
23
+ export const FOCUS_VISIBLE_OVER_STATE = "&:hover:focus-visible, &:active:focus-visible, &[data-active]:focus-visible";
24
+ /**
25
+ * `:focus-visible`, never `:focus` — a mouse click must not draw a ring.
26
+ *
27
+ * The transparent outline is not decoration: in Windows High Contrast / `forced-colors` mode
28
+ * box-shadows are dropped entirely, and a transparent outline is the one thing the OS repaints
29
+ * in its own focus colour. Without it, forced-colors users get no indicator at all.
30
+ */
31
+ export const focusVisibleRing = (colors) => ({
32
+ outline: "2px solid transparent",
33
+ outlineOffset: "2px",
34
+ boxShadow: focusRingShadow(colors),
35
+ });
36
+ /** Both selectors a control needs to keep the ring on top of its own hover/active styling. */
37
+ export const focusVisibleStyles = (colors) => {
38
+ const ring = focusVisibleRing(colors);
39
+ return { _focusVisible: ring, [FOCUS_VISIBLE_OVER_STATE]: ring };
40
+ };
@@ -23,7 +23,10 @@ const toBrandLabel = (brand) => brand.charAt(0).toUpperCase() + brand.slice(1);
23
23
  * regardless of which brand/mode is currently active.
24
24
  */
25
25
  const brandSwatchColor = (brand) => brandThemes[brand].light.colors.primary[500];
26
- const BrandSwatch = ({ brand }) => (_jsx(Box, { as: "span", display: "inline-block", w: "0.625rem", h: "0.625rem", borderRadius: "full", bg: brandSwatchColor(brand), border: "1px solid", borderColor: "whiteAlpha.300", flexShrink: 0 }));
26
+ const BrandSwatch = ({ brand }) => {
27
+ const { colors } = useCustomTheme();
28
+ return (_jsx(Box, { as: "span", display: "inline-block", w: "0.625rem", h: "0.625rem", borderRadius: "full", bg: brandSwatchColor(brand), border: "1px solid", borderColor: colors.boxborder[200], flexShrink: 0 }));
29
+ };
27
30
  /**
28
31
  * Palette values already differ per color mode (`brandThemes[brand].light|dark`), so tokens
29
32
  * are read straight from the active theme — no `useColorModeValue` pairing needed here.
@@ -36,7 +39,7 @@ const useSwitcherColors = () => {
36
39
  activeColor: colors.primary[500],
37
40
  mutedColor: colors.text[600],
38
41
  hoverBg: colors.gray[100],
39
- borderColor: colors.border[200],
42
+ borderColor: colors.boxborder[200],
40
43
  // NOTE: menuBg lives in Menu.styles.ts — re-pushing it via `sx` flickers on every render.
41
44
  menuColor: colors.text[900],
42
45
  };
@@ -22,6 +22,7 @@ import { withSidebarTokens } from "../builders/sidebarAccent.js";
22
22
  import { buildMutedText } from "../builders/mutedText.js";
23
23
  import { buildSemanticText, buildOnSemantic } from "../builders/semanticText.js";
24
24
  import { withPlaceholderRung, withTableHoverRung } from "../builders/resolvedRungs.js";
25
+ import { buildFocusRing, withFocusRing } from "../builders/focusRing.js";
25
26
  import { withSurfaceRungs } from "../builders/surfaceRungs.js";
26
27
  /** Derived, never hand-written: a copied hex goes stale the moment a canvas moves. */
27
28
  export const finalizeLightPalette = (source) => ({
@@ -32,10 +33,14 @@ export const finalizeLightPalette = (source) => ({
32
33
  semanticText: buildSemanticText(source.semantic, source.backgroundColor.main),
33
34
  onPrimary: onFilled(source.primary[500], source.white, source.black),
34
35
  onSemantic: buildOnSemantic(source.semantic, source.white, source.black),
36
+ focusRing: buildFocusRing(source),
35
37
  });
36
38
  export { lavender, meadow, radiant, skyline, slate, emerald, rosewood, storefront, zinc };
37
39
  export { lavenderDark, meadowDark, radiantDark, skylineDark, slateDark, emeraldDark, rosewoodDark, storefrontDark, zincDark, };
38
- const resolveRungs = (palette) => withSurfaceRungs(withTableHoverRung(withPlaceholderRung(withOutlineRung(palette))));
40
+ // `withFocusRing` is outermost: it is the only step that reads `primary` and the surfaces
41
+ // TOGETHER, so it must see both after every earlier step — and after a brand file's own
42
+ // post-derivation overrides (zinc dark replaces `primary` and `background` wholesale).
43
+ const resolveRungs = (palette) => withFocusRing(withSurfaceRungs(withTableHoverRung(withPlaceholderRung(withOutlineRung(palette)))));
39
44
  const lightBrand = (source) => resolveRungs(finalizeLightPalette(source));
40
45
  /** Light palette per brand. `lavender` is the base the other six extend. */
41
46
  export const lightPalettes = {
@@ -4,6 +4,7 @@ import { pickAccentText, onFilled } from "./accentText.js";
4
4
  import { withSidebarTokens } from "./sidebarAccent.js";
5
5
  import { buildMutedText } from "./mutedText.js";
6
6
  import { buildSemanticText, buildOnSemantic } from "./semanticText.js";
7
+ import { buildFocusRing } from "./focusRing.js";
7
8
  /**
8
9
  * Derives a brand's dark palette from its light palette.
9
10
  *
@@ -238,6 +239,7 @@ export function buildDarkPalette(light) {
238
239
  ]));
239
240
  const white = light.white;
240
241
  const primary = liftBrand(light.primary, white);
242
+ const background = fromTargets(hueSource, SURFACE_TARGETS, SURFACE_SAT);
241
243
  const semantic = {
242
244
  success: liftAccent(light.semantic.success, white),
243
245
  error: liftAccent(light.semantic.error, white),
@@ -268,7 +270,7 @@ export function buildDarkPalette(light) {
268
270
  purple: reverseScale(light.purple),
269
271
  pink: reverseScale(light.pink),
270
272
  backgroundColor: namedSurfaces,
271
- background: fromTargets(hueSource, SURFACE_TARGETS, SURFACE_SAT),
273
+ background,
272
274
  text: fromTargets(hueSource, TEXT_TARGETS, TEXT_SAT),
273
275
  header: fromTargets(hueSource, TEXT_TARGETS, TEXT_SAT + 2),
274
276
  placeholder: fromTargets(hueSource, PLACEHOLDER_TARGETS, PLACEHOLDER_SAT),
@@ -285,6 +287,9 @@ export function buildDarkPalette(light) {
285
287
  semanticText: buildSemanticText(semantic, namedSurfaces.main),
286
288
  onPrimary: onFilled(primary[500], light.white, light.black),
287
289
  onSemantic: buildOnSemantic(semantic, light.white, light.black),
290
+ // The dark ring is derived from the LIFTED accent against the dark canvas, not copied from
291
+ // light: the two modes squeeze it from opposite sides, so a shared value fails one of them.
292
+ focusRing: buildFocusRing({ primary, background, backgroundColor: namedSurfaces }),
288
293
  // Sidebar is already near-black in light mode, so it needs no inversion.
289
294
  sidebar: withSidebarTokens(light),
290
295
  boxShadow: {
@@ -25,7 +25,7 @@ export function chartInk(colors) {
25
25
  surface,
26
26
  text: colors.text[900],
27
27
  muted: colors.text[500],
28
- grid: colors.border[500],
28
+ grid: colors.boxborder[200],
29
29
  axis: colors.text[500],
30
30
  tooltipBg: colors.backgroundColor.secondary,
31
31
  };
@@ -0,0 +1,85 @@
1
+ import { contrastRatio, hexToHsl, withLightness } from "./color.js";
2
+ import { AA_NON_TEXT } from "./outlineRung.js";
3
+ /** Same lightness granularity `buildDarkPalette` walks its accents at. */
4
+ const LIGHTNESS_STEP = 0.5;
5
+ /**
6
+ * The nearest re-lighting of `hex` that clears `target` against EVERY backdrop at once.
7
+ *
8
+ * Not `settleAcross`: that folds one `liftToContrast` per surface, and each lift walks AWAY from
9
+ * the surface it was handed. That is correct when every backdrop sits on the same side of the ink
10
+ * (its case — a set of light surfaces, or a set of dark ones), and wrong here, where a focus ring
11
+ * is squeezed BETWEEN a near-white canvas and a mid-dark button fill: the lift against the fill
12
+ * walks straight back across the canvas it had just cleared. Seeding `settleAcross` with
13
+ * `primary[500]` measured 1.08-1.99 on the light canvas for every brand for exactly that reason.
14
+ *
15
+ * Walking outward from the seed and testing every backdrop at each step keeps the result the
16
+ * CLOSEST passing colour to the brand accent, so the ring still reads as brand chrome. Contrast is
17
+ * a function of relative luminance alone, and lightness sweeps luminance monotonically from 0 to 1,
18
+ * so if any colour on this hue clears the set, this finds it.
19
+ */
20
+ export function settleBetween(hex, backdrops, target = AA_NON_TEXT) {
21
+ const clears = (candidate) => backdrops.every((backdrop) => contrastRatio(candidate, backdrop) >= target);
22
+ if (clears(hex))
23
+ return hex;
24
+ const { l } = hexToHsl(hex);
25
+ for (let step = LIGHTNESS_STEP; step <= 100; step += LIGHTNESS_STEP) {
26
+ const darker = withLightness(hex, l - step);
27
+ if (clears(darker))
28
+ return darker;
29
+ const lighter = withLightness(hex, l + step);
30
+ if (clears(lighter))
31
+ return lighter;
32
+ }
33
+ // Unreachable for every shipped brand (the gate proves it) — a brand whose fill and canvas are
34
+ // close enough to leave no gap would land here. Take the end that is least bad rather than throw
35
+ // at import time, and let `focusRing.test.ts` be what reports it.
36
+ const ends = [withLightness(hex, 0), withLightness(hex, 100)];
37
+ const worst = (candidate) => Math.min(...backdrops.map((backdrop) => contrastRatio(candidate, backdrop)));
38
+ return worst(ends[0]) >= worst(ends[1]) ? ends[0] : ends[1];
39
+ }
40
+ /**
41
+ * The three backdrops a focused button's ring is actually drawn against: the page canvas, a card
42
+ * surface, and — because a solid button's ring sits directly against its own fill — `primary[500]`.
43
+ *
44
+ * Deliberately NOT every named surface. `muted`/`accent`/`quaternary` are mid-tone, and a single
45
+ * band asked to clear those AND a dark brand fill has no solution at any hue — contrast is
46
+ * luminance alone, and `skyline`/`storefront` come out with an empty band. That is what the second
47
+ * tone is for: `halo` owns the fill-side adjacency instead.
48
+ */
49
+ export const focusRingBackdrops = (source) => [
50
+ source.backgroundColor.main,
51
+ source.background[50],
52
+ source.primary[500],
53
+ ];
54
+ /**
55
+ * The two-tone `:focus-visible` indicator.
56
+ *
57
+ * Chakra's stock ring (`shadows.outline`, a fixed `rgba(66,153,225,.6)`) measured 1.83:1 on the
58
+ * lavender canvas and 1.38:1 on a primary button — a focus indicator you cannot see, on all nine
59
+ * brands. It is one hardcoded blue, so no palette could have saved it.
60
+ *
61
+ * `halo` is the surface tone, drawn as the inner band so the indicator separates from a filled
62
+ * control whatever `colorScheme` painted it. `ring` is the brand accent, walked to the nearest
63
+ * lightness that clears 3:1 on all three backdrops at once — including the halo, so the two tones
64
+ * are always told apart.
65
+ */
66
+ export const buildFocusRing = (source) => {
67
+ const halo = settleBetween(source.background[50], [source.primary[500]]);
68
+ return {
69
+ halo,
70
+ ring: settleBetween(source.primary[500], [...focusRingBackdrops(source), halo]),
71
+ };
72
+ };
73
+ /**
74
+ * Re-derives the ring from the FINISHED palette, as the last step of the rung chain.
75
+ *
76
+ * `finalizeLightPalette` and `buildDarkPalette` seed it, but neither is the final word: a brand
77
+ * file may replace `primary` or `background` after the generic derivation runs — `zinc`'s dark
78
+ * palette replaces both, inverting the filled button to near-white — and a ring resolved against
79
+ * the pre-override values is then silently wrong. Same reason `withSurfaceRungs` re-lifts inks the
80
+ * finalizers already derived. `focusRing.test.ts` asserts this is a fixed point.
81
+ */
82
+ export const withFocusRing = (palette) => ({
83
+ ...palette,
84
+ focusRing: buildFocusRing(palette),
85
+ });
@@ -0,0 +1,22 @@
1
+ import { onGradient } from "./accentText.js";
2
+ import { resolveAvatarFill } from "./statusTone.js";
3
+ /**
4
+ * Sinks both stops until WHITE ink clears AA, keeping each stop's hue. Any mid-tone gradient
5
+ * otherwise flips `onGradient` to black ink, so the same element reads white-on-colour in one
6
+ * brand and black-on-pastel in the next.
7
+ */
8
+ export const inkSafeGradient = (gradient) => ({
9
+ from: resolveAvatarFill(gradient.from),
10
+ to: resolveAvatarFill(gradient.to),
11
+ });
12
+ /**
13
+ * The only correct way to paint text on a gradient: returns the stops to draw AND the ink for
14
+ * them. Calling `onGradient` on raw stops is the black-text bug — it picks ink for a backdrop
15
+ * that is then painted differently.
16
+ */
17
+ export const resolveGradient = (gradient, white, black) => {
18
+ const { from, to } = inkSafeGradient(gradient);
19
+ return { from, to, ink: onGradient(from, to, white, black) };
20
+ };
21
+ /** `linear-gradient(...)` for a resolved pair — one spelling of the CSS everywhere. */
22
+ export const gradientCss = (g, angle = "135deg") => `linear-gradient(${angle}, ${g.from} 0%, ${g.to} 100%)`;
@@ -3,3 +3,7 @@ export * from "./buildBrandTokens.js";
3
3
  export * from "./buildDarkPalette.js";
4
4
  export * from "./chartColorsFromTheme.js";
5
5
  export * from "./color.js";
6
+ export * from "./focusRing.js";
7
+ export * from "./scrollbar.js";
8
+ export * from "./gradientInk.js";
9
+ export * from "./placeholderInk.js";
@@ -7,6 +7,35 @@ import { contrastRatio, hexToHsl, relativeLuminance, withLightness } from "./col
7
7
  export const MIN_PERCEPTIBLE_STEP = 1.15;
8
8
  /** WCAG 1.4.11 — a control outline is a UI component boundary, held to 3:1. */
9
9
  export const AA_NON_TEXT = 3;
10
+ /**
11
+ * Resting chrome — card, popover and field outlines — settles here, not at `AA_NON_TEXT`.
12
+ * At 3:1 a container border is text-weight and the UI reads as a wireframe; every mainstream
13
+ * system draws this line far lighter (Tailwind gray-200 1.24, Chakra gray.200 1.30, Material
14
+ * outline-variant 1.61). 1.4.11 asks that a control be identifiable, not that its resting
15
+ * border carry the whole job: the fill, the label and the focus ring do that.
16
+ *
17
+ * That defence was untrue when it was written. Buttons defined no `_focusVisible` at all, so the
18
+ * focus ring was Chakra's stock `shadows.outline` — a fixed blue measuring 1.38:1 on a primary
19
+ * button — and nothing in the library was held to `AA_NON_TEXT` at the moment of focus. It is
20
+ * true now: `focusRing` (`builders/focusRing.ts`) is derived per brand per mode and gated at 3:1
21
+ * on the canvas, on a card and on the button's own fill by `builders/focusRing.test.ts`. If that
22
+ * gate is ever relaxed, this constant loses its justification with it.
23
+ */
24
+ export const CHROME_BORDER = 1.5;
25
+ // The resting rule between two surfaces — a card edge, a divider, a menu outline. Lighter than
26
+ // CHROME_BORDER, which outlines a control the user acts on.
27
+ export const CHROME_RULE = 1.31;
28
+ /**
29
+ * A rule is decorative, so it has a ceiling and no floor: soften it until the surface it reads
30
+ * hardest against sits at `target`, and leave a already-fainter rung alone. Never `settleAcross`
31
+ * here — that clears a floor on EVERY surface, so one tinted surface drags the rule darker.
32
+ */
33
+ export function ruleInk(hex, surfaces, target) {
34
+ const loudest = surfaces.reduce((worst, s) => contrastRatio(hex, s) > contrastRatio(hex, worst) ? s : worst);
35
+ return contrastRatio(hex, loudest) > target
36
+ ? softenToContrast(hex, loudest, target)
37
+ : hex;
38
+ }
10
39
  /**
11
40
  * Walks a hex toward `target` contrast against `backdrop`, keeping hue and saturation.
12
41
  * Direction is DERIVED from the backdrop, never passed: a mode argument here was redundant
@@ -30,11 +59,30 @@ export function liftToContrast(hex, backdrop, target = AA_NON_TEXT) {
30
59
  const ends = [withLightness(hex, 100, s), withLightness(hex, 0, s)];
31
60
  return contrastRatio(ends[0], backdrop) >= contrastRatio(ends[1], backdrop) ? ends[0] : ends[1];
32
61
  }
62
+ /**
63
+ * Mirror of `liftToContrast`: walks ink TOWARD the backdrop, stopping at the last step that
64
+ * still clears `target`. For ink that must stay legible without reading as body copy.
65
+ */
66
+ export function softenToContrast(hex, backdrop, target) {
67
+ if (contrastRatio(hex, backdrop) <= target)
68
+ return hex;
69
+ const { s, l: start } = hexToHsl(hex);
70
+ const toward = relativeLuminance(backdrop) < 0.5 ? -1 : 1;
71
+ let softest = hex;
72
+ for (let l = start; l > 0 && l < 100; l += toward) {
73
+ const next = withLightness(hex, l, s);
74
+ if (contrastRatio(next, backdrop) < target)
75
+ break;
76
+ softest = next;
77
+ }
78
+ return softest;
79
+ }
33
80
  /**
34
81
  * `boxborder[500]` is the rung every control outline reads (Checkbox, SearchSelect, the date
35
- * pickers, the editor's inputs). It was authored as a decorative value measured 1.30-2.56:1
36
- * across brands, so those controls rendered effectively borderless in BOTH modes. Resolved here
37
- * rather than hand-pinned per brand: a copied hex goes stale the moment a canvas moves.
82
+ * pickers, the editor's inputs). Settled onto `CHROME_BORDER` lifted when a brand authored it
83
+ * invisibly pale, softened when it overshoots. It was previously lifted to `AA_NON_TEXT`, which
84
+ * shipped 3.85-4.95:1 outlines: a text-weight box around every card and popover.
85
+ * Resolved here rather than hand-pinned per brand: a copied hex goes stale the moment a canvas moves.
38
86
  *
39
87
  * `border[*]` is deliberately untouched — that ramp draws table gridlines, which are decorative
40
88
  * and outside 1.4.11.
@@ -47,7 +95,9 @@ export function withOutlineRung(palette) {
47
95
  contrastRatio(current, palette.background[50])
48
96
  ? palette.backgroundColor.main
49
97
  : palette.background[50];
50
- const resolved = liftToContrast(current, backdrop);
98
+ const resolved = contrastRatio(current, backdrop) > CHROME_BORDER
99
+ ? softenToContrast(current, backdrop, CHROME_BORDER)
100
+ : liftToContrast(current, backdrop, CHROME_BORDER);
51
101
  if (resolved === current)
52
102
  return palette;
53
103
  return { ...palette, boxborder: { ...palette.boxborder, 500: resolved } };
@@ -63,3 +113,45 @@ export function withOutlineRung(palette) {
63
113
  export function resolveHoverTint(seed, restSurface) {
64
114
  return liftToContrast(seed, restSurface, MIN_PERCEPTIBLE_STEP);
65
115
  }
116
+ /**
117
+ * Settles ink into `target` against EVERY surface at once. Softening one step can change which
118
+ * surface is hardest, so resolving against a single pre-computed "hardest" one lands under the
119
+ * floor on another — that is how radiant and rosewood ended up with an invisible outline on
120
+ * their muted surface.
121
+ */
122
+ export function settleAcross(hex, surfaces, target) {
123
+ const clearsAll = (c) => surfaces.every((s) => contrastRatio(c, s) >= target);
124
+ if (!clearsAll(hex)) {
125
+ return surfaces.reduce((ink, s) => liftToContrast(ink, s, target), hex);
126
+ }
127
+ const { s: sat, l: start } = hexToHsl(hex);
128
+ const toward = relativeLuminance(surfaces[0]) < 0.5 ? -1 : 1;
129
+ let settled = hex;
130
+ for (let l = start; l > 0 && l < 100; l += toward) {
131
+ const next = withLightness(hex, l, sat);
132
+ if (!clearsAll(next))
133
+ break;
134
+ settled = next;
135
+ }
136
+ return settled;
137
+ }
138
+ /**
139
+ * A pure-grey surface sitting `target` contrast away from `backdrop`. Binary search, not a walk:
140
+ * `liftToContrast` cannot move a seed already at lightness 0 or 100, and a pure-white panel seeded
141
+ * itself straight to black. Neutral on purpose — the light palettes are hand-authored and tinted,
142
+ * so lifting one of their surface rungs turns a selected row into a saturated brand block, while
143
+ * the derived dark palettes land on grey and look right.
144
+ */
145
+ export function neutralStep(backdrop, target) {
146
+ const grey = (l) => withLightness("#808080", l, 0);
147
+ let lo = hexToHsl(backdrop).l;
148
+ let hi = relativeLuminance(backdrop) < 0.5 ? 100 : 0;
149
+ for (let i = 0; i < 20; i += 1) {
150
+ const mid = (lo + hi) / 2;
151
+ if (contrastRatio(grey(mid), backdrop) < target)
152
+ lo = mid;
153
+ else
154
+ hi = mid;
155
+ }
156
+ return grey(hi);
157
+ }