@keboola/design 11.4.0 → 11.5.0

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 (173) hide show
  1. package/AGENTS.md +191 -1
  2. package/CHANGELOG.md +358 -0
  3. package/COMPONENT-API.md +68 -47
  4. package/dist/{AIKit-DDg5DBCG.js → AIKit-CNX6aiKm.js} +208 -62
  5. package/dist/AIKit-CNX6aiKm.js.map +1 -0
  6. package/dist/{Alert-DHJFrWnS.js → Alert-Ba5b2YPq.js} +2 -2
  7. package/dist/{Alert-DHJFrWnS.js.map → Alert-Ba5b2YPq.js.map} +1 -1
  8. package/dist/{Badge-Ch26CXoP.js → Badge-D0pXdekt.js} +2 -2
  9. package/dist/Badge-D0pXdekt.js.map +1 -0
  10. package/dist/{BrandEditor-Go7w8Zj2.js → BrandEditor-CSANsxUz.js} +6 -6
  11. package/dist/{BrandEditor-Go7w8Zj2.js.map → BrandEditor-CSANsxUz.js.map} +1 -1
  12. package/dist/{BrandLogo-Dmi7jo21.js → BrandLogo-CoKBk4MI.js} +4 -4
  13. package/dist/BrandLogo-CoKBk4MI.js.map +1 -0
  14. package/dist/{Breadcrumb-uEkM6adI.js → Breadcrumb-g_xn_-Nf.js} +2 -2
  15. package/dist/{Breadcrumb-uEkM6adI.js.map → Breadcrumb-g_xn_-Nf.js.map} +1 -1
  16. package/dist/{Checkbox-fC0BuO2B.js → Checkbox-CUIn2jap.js} +2 -2
  17. package/dist/{Checkbox-fC0BuO2B.js.map → Checkbox-CUIn2jap.js.map} +1 -1
  18. package/dist/{Clipboard-ChPdiUy3.js → Clipboard-BTW6C-b_.js} +2 -2
  19. package/dist/{Clipboard-ChPdiUy3.js.map → Clipboard-BTW6C-b_.js.map} +1 -1
  20. package/dist/{ClipboardButton-D9pDjgkB.js → ClipboardButton-CwZJdRaz.js} +2 -2
  21. package/dist/{ClipboardButton-D9pDjgkB.js.map → ClipboardButton-CwZJdRaz.js.map} +1 -1
  22. package/dist/{CodeEditor-Cxy3HnKH.js → CodeEditor-C9zy7PxK.js} +6 -6
  23. package/dist/{CodeEditor-Cxy3HnKH.js.map → CodeEditor-C9zy7PxK.js.map} +1 -1
  24. package/dist/{ColorInput-Bxj-1eLT.js → ColorInput-BZV-u_94.js} +3 -3
  25. package/dist/{ColorInput-Bxj-1eLT.js.map → ColorInput-BZV-u_94.js.map} +1 -1
  26. package/dist/{Combobox-Cs_1pj2q.js → Combobox-BfIfaVMA.js} +273 -104
  27. package/dist/Combobox-BfIfaVMA.js.map +1 -0
  28. package/dist/{Command-B7ML9YMv.js → Command-Bfa1kpbt.js} +2 -2
  29. package/dist/{Command-B7ML9YMv.js.map → Command-Bfa1kpbt.js.map} +1 -1
  30. package/dist/{CopyField-BO_sh-HO.js → CopyField-DpT0Ngz1.js} +2 -2
  31. package/dist/{CopyField-BO_sh-HO.js.map → CopyField-DpT0Ngz1.js.map} +1 -1
  32. package/dist/{DateInput-ZdaMQo5i.js → DateInput-DiEd5uCL.js} +3 -3
  33. package/dist/{DateInput-ZdaMQo5i.js.map → DateInput-DiEd5uCL.js.map} +1 -1
  34. package/dist/{DateRangeInput-DkvNh_sT.js → DateRangeInput-DPfVYchl.js} +3 -3
  35. package/dist/{DateRangeInput-DkvNh_sT.js.map → DateRangeInput-DPfVYchl.js.map} +1 -1
  36. package/dist/{FieldItem-CCd9-xnp.js → FieldItem-B7XMmZSz.js} +3 -3
  37. package/dist/{FieldItem-CCd9-xnp.js.map → FieldItem-B7XMmZSz.js.map} +1 -1
  38. package/dist/{Form-k6PH-em3.js → Form-CsttTHn4.js} +7 -7
  39. package/dist/Form-CsttTHn4.js.map +1 -0
  40. package/dist/{FormGroup-DbcDtrgU.js → FormGroup-DHvTFdno.js} +3 -3
  41. package/dist/{FormGroup-DbcDtrgU.js.map → FormGroup-DHvTFdno.js.map} +1 -1
  42. package/dist/{InputGroup-BnLHY0eq.js → InputGroup-DcZRlVnN.js} +22 -14
  43. package/dist/InputGroup-DcZRlVnN.js.map +1 -0
  44. package/dist/{Kbd-CvSM3D3-.js → Kbd-Cvvt91Q8.js} +14 -4
  45. package/dist/Kbd-Cvvt91Q8.js.map +1 -0
  46. package/dist/{MermaidDiagram-WkonYErO.js → MermaidDiagram-CXG5tC6J.js} +19 -18
  47. package/dist/MermaidDiagram-CXG5tC6J.js.map +1 -0
  48. package/dist/{MultiActionButtonGroup-CxWH7yWK.js → MultiActionButtonGroup-DokHbPgM.js} +2 -2
  49. package/dist/{MultiActionButtonGroup-CxWH7yWK.js.map → MultiActionButtonGroup-DokHbPgM.js.map} +1 -1
  50. package/dist/{Navbar-B2UsolVU.js → Navbar-Liy1TPI7.js} +6 -6
  51. package/dist/{Navbar-B2UsolVU.js.map → Navbar-Liy1TPI7.js.map} +1 -1
  52. package/dist/PageHeader-Q3IpCnQd.js.map +1 -1
  53. package/dist/{PageToolbar-DnbFt14Q.js → PageToolbar-DExqEYGl.js} +2 -2
  54. package/dist/{PageToolbar-DnbFt14Q.js.map → PageToolbar-DExqEYGl.js.map} +1 -1
  55. package/dist/{Popconfirm-CILjmzKI.js → Popconfirm-5pBzT_HQ.js} +10 -11
  56. package/dist/{Popconfirm-CILjmzKI.js.map → Popconfirm-5pBzT_HQ.js.map} +1 -1
  57. package/dist/{RadioGroup-XFzFd1ti.js → RadioGroup-DXPbSj8P.js} +2 -2
  58. package/dist/{RadioGroup-XFzFd1ti.js.map → RadioGroup-DXPbSj8P.js.map} +1 -1
  59. package/dist/{Search-4Bxtt8hS.js → Search-5uQAiNIP.js} +2 -2
  60. package/dist/{Search-4Bxtt8hS.js.map → Search-5uQAiNIP.js.map} +1 -1
  61. package/dist/{SearchInput-f_oY9T4Z.js → SearchInput-D4UgAXJK.js} +3 -3
  62. package/dist/{SearchInput-f_oY9T4Z.js.map → SearchInput-D4UgAXJK.js.map} +1 -1
  63. package/dist/{SectionNav-BpJlisbT.js → SectionNav-OkKCjSA-.js} +3 -3
  64. package/dist/{SectionNav-BpJlisbT.js.map → SectionNav-OkKCjSA-.js.map} +1 -1
  65. package/dist/{Select-DpcgKqKB.js → Select-DV3Hak_l.js} +3 -4
  66. package/dist/Select-DV3Hak_l.js.map +1 -0
  67. package/dist/{Sidebar-Cm6I4dE1.js → Sidebar-D3cY1kha.js} +44 -20
  68. package/dist/Sidebar-D3cY1kha.js.map +1 -0
  69. package/dist/{Slider-pPC_3gz9.js → Slider-CRf-bB4y.js} +2 -2
  70. package/dist/{Slider-pPC_3gz9.js.map → Slider-CRf-bB4y.js.map} +1 -1
  71. package/dist/{Switch-ChfPKcTE.js → Switch-B0WdGSjz.js} +7 -7
  72. package/dist/{Switch-ChfPKcTE.js.map → Switch-B0WdGSjz.js.map} +1 -1
  73. package/dist/{TextInput-DHM6dHmn.js → TextInput-DH5SkbsE.js} +3 -3
  74. package/dist/{TextInput-DHM6dHmn.js.map → TextInput-DH5SkbsE.js.map} +1 -1
  75. package/dist/{ThemeProvider-DrQJtzpd.js → ThemeProvider-2RMdVGxO.js} +17 -12
  76. package/dist/{ThemeProvider-DrQJtzpd.js.map → ThemeProvider-2RMdVGxO.js.map} +1 -1
  77. package/dist/{Timeline-6btyJl4D.js → Timeline-BG5Xogxh.js} +11 -11
  78. package/dist/{Timeline-6btyJl4D.js.map → Timeline-BG5Xogxh.js.map} +1 -1
  79. package/dist/{Workbench-BzE-Vvd9.js → Workbench-UR2FBC7I.js} +3 -1
  80. package/dist/Workbench-UR2FBC7I.js.map +1 -0
  81. package/dist/{ZoomableSvg-BWnDkR61.js → ZoomableSvg-pooPb1Cu.js} +3 -3
  82. package/dist/{ZoomableSvg-BWnDkR61.js.map → ZoomableSvg-pooPb1Cu.js.map} +1 -1
  83. package/dist/editors.js +4 -4
  84. package/dist/flint.js +134 -8
  85. package/dist/flint.js.map +1 -1
  86. package/dist/index.d.ts +537 -135
  87. package/dist/index.js +40 -39
  88. package/dist/internal/blocks/AIKit.js +2 -2
  89. package/dist/internal/blocks/AuthLayout.js +118 -0
  90. package/dist/internal/blocks/AuthLayout.js.map +1 -0
  91. package/dist/internal/blocks/BrandEditor.js +1 -1
  92. package/dist/internal/blocks/ClipboardButton.js +1 -1
  93. package/dist/internal/blocks/ColorInput.js +1 -1
  94. package/dist/internal/blocks/Command.js +1 -1
  95. package/dist/internal/blocks/CopyField.js +1 -1
  96. package/dist/internal/blocks/DateInput.js +1 -1
  97. package/dist/internal/blocks/DateRangeInput.js +1 -1
  98. package/dist/internal/blocks/ExportMenu.js +1 -1
  99. package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
  100. package/dist/internal/blocks/Form.js +1 -1
  101. package/dist/internal/blocks/FormGroup.js +1 -1
  102. package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
  103. package/dist/internal/blocks/MultiCombobox.js +107 -48
  104. package/dist/internal/blocks/MultiCombobox.js.map +1 -1
  105. package/dist/internal/blocks/NumberInput.js +1 -1
  106. package/dist/internal/blocks/PageToolbar.js +1 -1
  107. package/dist/internal/blocks/PasswordInput.js +2 -2
  108. package/dist/internal/blocks/SearchInput.js +1 -1
  109. package/dist/internal/blocks/SearchInputWithNavigation.js +2 -2
  110. package/dist/internal/blocks/SectionNav.js +1 -1
  111. package/dist/internal/blocks/TextInput.js +1 -1
  112. package/dist/internal/blocks/Textarea.js +1 -1
  113. package/dist/internal/blocks/TimeInput.js +2 -2
  114. package/dist/internal/components/Alert.js +1 -1
  115. package/dist/internal/components/Avatar.js +1 -1
  116. package/dist/internal/components/Badge.js +1 -1
  117. package/dist/internal/components/BrandLogo.js +1 -1
  118. package/dist/internal/components/Breadcrumb.js +1 -1
  119. package/dist/internal/components/Checkbox.js +1 -1
  120. package/dist/internal/components/Clipboard.js +1 -1
  121. package/dist/internal/components/CollapsiblePanel.js +1 -1
  122. package/dist/internal/components/Combobox.js +2 -2
  123. package/dist/internal/components/InputGroup.js +1 -1
  124. package/dist/internal/components/Kbd.js +1 -1
  125. package/dist/internal/components/Navbar.js +1 -1
  126. package/dist/internal/components/Popconfirm.js +1 -1
  127. package/dist/internal/components/RadioGroup.js +1 -1
  128. package/dist/internal/components/Search.js +1 -1
  129. package/dist/internal/components/Select.js +1 -1
  130. package/dist/internal/components/Sidebar.js +1 -1
  131. package/dist/internal/components/Slider.js +1 -1
  132. package/dist/internal/components/Switch.js +1 -1
  133. package/dist/internal/components/ThemeProvider.js +1 -1
  134. package/dist/internal/components/Timeline.js +1 -1
  135. package/dist/internal/components/Tooltip.js +1 -1
  136. package/dist/internal/components/Truncated.js +1 -1
  137. package/dist/internal/components/Workbench.js +1 -1
  138. package/dist/internal/components/ZoomableSvg.js +1 -1
  139. package/dist/internal/utils.js +2 -2
  140. package/dist/markdown-editor.js +29 -23
  141. package/dist/markdown-editor.js.map +1 -1
  142. package/dist/mermaid.js +1 -1
  143. package/dist/rjsf.js +22 -23
  144. package/dist/rjsf.js.map +1 -1
  145. package/dist/styles.css +1 -1
  146. package/dist/testing.js +1 -1
  147. package/dist/{useColorSchemeState-klihG9-i.js → themeScopeContext-D7fSyb06.js} +16 -3
  148. package/dist/themeScopeContext-D7fSyb06.js.map +1 -0
  149. package/dist/{themeTokens-D70HeNVF.js → themeTokens-qP1-55O7.js} +37 -10
  150. package/dist/themeTokens-qP1-55O7.js.map +1 -0
  151. package/dist/{utils-B5Z52hYs.js → utils-DSvrhU8E.js} +6 -2
  152. package/dist/{utils-B5Z52hYs.js.map → utils-DSvrhU8E.js.map} +1 -1
  153. package/dist/vega.d.ts +1 -1
  154. package/dist/vega.js +3 -3
  155. package/dist/vega.js.map +1 -1
  156. package/dist/{vegaTheme-DfoEQhOy.js → vegaTheme-CSvFEMBl.js} +6 -6
  157. package/dist/vegaTheme-CSvFEMBl.js.map +1 -0
  158. package/package.json +12 -10
  159. package/src/styles.css +3 -0
  160. package/dist/AIKit-DDg5DBCG.js.map +0 -1
  161. package/dist/Badge-Ch26CXoP.js.map +0 -1
  162. package/dist/BrandLogo-Dmi7jo21.js.map +0 -1
  163. package/dist/Combobox-Cs_1pj2q.js.map +0 -1
  164. package/dist/Form-k6PH-em3.js.map +0 -1
  165. package/dist/InputGroup-BnLHY0eq.js.map +0 -1
  166. package/dist/Kbd-CvSM3D3-.js.map +0 -1
  167. package/dist/MermaidDiagram-WkonYErO.js.map +0 -1
  168. package/dist/Select-DpcgKqKB.js.map +0 -1
  169. package/dist/Sidebar-Cm6I4dE1.js.map +0 -1
  170. package/dist/Workbench-BzE-Vvd9.js.map +0 -1
  171. package/dist/themeTokens-D70HeNVF.js.map +0 -1
  172. package/dist/useColorSchemeState-klihG9-i.js.map +0 -1
  173. package/dist/vegaTheme-DfoEQhOy.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BrandEditor-Go7w8Zj2.js","names":[],"sources":["../src/blocks/BrandEditor/color.ts","../src/blocks/BrandEditor/ColorField.tsx","../src/blocks/BrandEditor/contrast.ts","../src/blocks/BrandEditor/ControlSection.tsx","../src/blocks/BrandEditor/LogoField.tsx","../src/blocks/BrandEditor/TextField.tsx","../src/blocks/BrandEditor/BrandEditor.tsx"],"sourcesContent":["// Brand colours are stored as space-separated \"R G B\" channel strings (see\n// `rgbChannelsSchema` in @keboola/brand-registry); `ColorInput` speaks hex.\n// These helpers convert at that boundary.\n\nexport const hexToChannels = (hex: string): string => {\n let h = hex.trim().replace(/^#/, '');\n // Expand 3/4-digit shorthand and drop alpha (4- and 8-digit `#rgba`/`#rrggbbaa`) — brand\n // channels carry no alpha. Anything else is malformed → fall back to black.\n if (h.length === 3 || h.length === 4) {\n h = h\n .slice(0, 3)\n .split('')\n .map((c) => c + c)\n .join('');\n } else if (h.length === 8) {\n h = h.slice(0, 6);\n }\n if (!/^[0-9a-fA-F]{6}$/.test(h)) return '0 0 0';\n const n = parseInt(h, 16);\n return `${(n >> 16) & 255} ${(n >> 8) & 255} ${n & 255}`;\n};\n\nexport const channelsToHex = (channels: string): string => {\n const [r = 0, g = 0, b = 0] = channels\n .trim()\n .split(/\\s+/)\n .map((n) => Number(n) || 0);\n const to2 = (v: number) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0');\n return `#${to2(r)}${to2(g)}${to2(b)}`;\n};\n","import { Label } from '../../components/Label';\nimport { ColorInput } from '../ColorInput';\n\nimport { channelsToHex, hexToChannels } from './color';\n\ntype Props = {\n id: string;\n label: string;\n /** Current value as an \"R G B\" channel string. */\n channels: string;\n /** Called with a new \"R G B\" channel string when a valid colour is picked. */\n onChange: (channels: string) => void;\n};\n\n// ColorInput fires `onValueChange` on every keystroke (it only coerces on blur), so commit a\n// channel value only for a COMPLETE 3-, 4-, 6-, or 8-digit hex — otherwise a partial \"#1\"/\"#\"\n// would write unintended channels. Alpha (4-/8-digit) is truncated to RGB, since brand tokens\n// carry no alpha.\nconst COMPLETE_HEX = /^([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n/** A labelled hex colour picker bound to a brand token (stored as RGB channels). */\nexport const ColorField = ({ id, label, channels, onChange }: Props) => {\n const hex = channelsToHex(channels);\n\n return (\n <div className=\"tw:flex tw:items-center tw:justify-between tw:gap-2\">\n <Label htmlFor={id} className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {label}\n </Label>\n <ColorInput\n id={id}\n sizing=\"medium\"\n value={hex}\n className=\"tw:w-36\"\n onValueChange={(next) => {\n const clean = next.trim().replace(/^#/, '');\n // Commit only complete hex (hexToChannels handles the 3/6/8-digit shapes + alpha drop).\n if (COMPLETE_HEX.test(clean)) onChange(hexToChannels(`#${clean}`));\n }}\n />\n </div>\n );\n};\n","// WCAG contrast helpers over `\"R G B\"` channel strings (the brand token format).\n\n// Coerce non-numeric/blank channels to 0 and clamp to the valid 0–255 range, so a malformed\n// token (e.g. \"999 999 999\") can neither NaN-out nor push luminance past WCAG's expected bounds.\nconst clampChannel = (n: number): number =>\n Number.isFinite(n) ? Math.min(255, Math.max(0, n)) : 0;\n\nconst parseChannels = (channels: string): [number, number, number] => {\n const [r = 0, g = 0, b = 0] = channels\n .trim()\n .split(/\\s+/)\n .map((n) => clampChannel(Number(n)));\n return [r, g, b];\n};\n\nconst relativeLuminance = (channels: string): number => {\n // Destructure with defaults (not an `as` assertion) so `r`/`g`/`b` are `number` under\n // `noUncheckedIndexedAccess` without a production-code type cast.\n const [r = 0, g = 0, b = 0] = parseChannels(channels).map((c) => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n};\n\n/** WCAG contrast ratio (1–21) between two `\"R G B\"` colours. */\nexport const contrastRatio = (fg: string, bg: string): number => {\n const l1 = relativeLuminance(fg);\n const l2 = relativeLuminance(bg);\n const [lighter, darker] = l1 >= l2 ? [l1, l2] : [l2, l1];\n return (lighter + 0.05) / (darker + 0.05);\n};\n\n/** WCAG level for normal-size text at a given ratio. */\nexport const wcagLevel = (ratio: number): 'AAA' | 'AA' | 'fail' =>\n ratio >= 7 ? 'AAA' : ratio >= 4.5 ? 'AA' : 'fail';\n","import type { ReactNode } from 'react';\n\ntype Props = {\n title: string;\n children: ReactNode;\n};\n\n/** A titled block inside `BrandEditor`. */\nexport const ControlSection = ({ title, children }: Props) => (\n <section className=\"tw:flex tw:flex-col tw:gap-3 tw:border-b tw:border-border tw:p-4\">\n <h3 className=\"tw:m-0 tw:text-xs tw:font-semibold tw:uppercase tw:tracking-wide tw:text-muted-foreground\">\n {title}\n </h3>\n {children}\n </section>\n);\n","import { Label } from '../../components/Label';\nimport { Textarea } from '../Textarea';\n\ntype Props = {\n id: string;\n label: string;\n /** Current logo source: SVG markup, a `data:` URI, or an `http(s)` URL. */\n value: string;\n /** Called with the new source (empty string clears the slot). */\n onChange: (value: string) => void;\n};\n\n/** A labelled multi-line field for a brand logo source. */\nexport const LogoField = ({ id, label, value, onChange }: Props) => (\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label htmlFor={id} className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {label}\n </Label>\n <Textarea\n id={id}\n value={value}\n onValueChange={onChange}\n maxRows={4}\n placeholder=\"<svg…>, a data: URI, or an https:// URL\"\n className=\"tw:font-mono tw:text-xs\"\n />\n </div>\n);\n","import { Label } from '../../components/Label';\nimport { TextInput } from '../TextInput';\n\ntype Props = {\n id: string;\n label: string;\n /** Current CSS value (empty clears the token, falling back to the stylesheet default). */\n value: string;\n /** Called with the new CSS value on change. */\n onChange: (value: string) => void;\n /** The inherited default, shown as placeholder when the field is empty. */\n placeholder?: string;\n};\n\n/** A labelled single-line field for a free-form CSS-value token (shadow, motion, gradient). */\nexport const TextField = ({ id, label, value, onChange, placeholder }: Props) => (\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label htmlFor={id} className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {label}\n </Label>\n <TextInput\n id={id}\n sizing=\"small\"\n value={value}\n onValueChange={onChange}\n placeholder={placeholder}\n className=\"tw:font-mono tw:text-xs\"\n />\n </div>\n);\n","import { useId, useMemo } from 'react';\nimport DOMPurify from 'dompurify';\n\nimport type { Brand } from '@keboola/brand-registry';\nimport { keboola } from '@keboola/brand-registry';\n\nimport { Badge } from '../../components/Badge';\nimport { Button } from '../../components/Button';\nimport { Collapsible } from '../../components/Collapsible';\nimport { Label } from '../../components/Label';\nimport { Select } from '../../components/Select';\nimport { Slider } from '../../components/Slider';\nimport { ToggleGroup } from '../../components/ToggleGroup';\n\nimport { ColorField } from './ColorField';\nimport { contrastRatio, wcagLevel } from './contrast';\nimport { ControlSection } from './ControlSection';\nimport { LogoField } from './LogoField';\nimport { TextField } from './TextField';\n\nexport type ColorSlot = keyof Omit<NonNullable<Brand['theme']['colors']>, 'sidebar' | 'chart'>;\nexport type SidebarSlot = keyof NonNullable<NonNullable<Brand['theme']['colors']>['sidebar']>;\nexport type ChartSlot = keyof NonNullable<NonNullable<Brand['theme']['colors']>['chart']>;\nexport type RadiusSize = keyof NonNullable<Brand['theme']['radii']>;\n\ntype Typography = NonNullable<Brand['theme']['typography']>;\ntype FontFace = 'fontFamily' | 'fontDisplay' | 'fontMono';\ntype FontWeightSlot = keyof NonNullable<Typography['weight']>;\ntype ShadowSlot = keyof NonNullable<Brand['theme']['shadows']>;\ntype SpacingSlot = keyof NonNullable<Brand['theme']['spacing']>;\ntype MotionSlot = keyof NonNullable<Brand['theme']['motion']>;\n\nexport const DEFAULT_FALLBACK_FONT = \"'Inter', system-ui, sans-serif\";\n\nexport const DEFAULT_FONT_OPTIONS: { value: string; label: string }[] = [\n { value: DEFAULT_FALLBACK_FONT, label: 'Inter (default)' },\n { value: \"'Space Grotesk', system-ui, sans-serif\", label: 'Space Grotesk' },\n { value: \"'Poppins', system-ui, sans-serif\", label: 'Poppins' },\n { value: \"'Manrope', system-ui, sans-serif\", label: 'Manrope' },\n { value: \"'Quicksand', system-ui, sans-serif\", label: 'Quicksand' },\n { value: \"'Baloo 2', system-ui, sans-serif\", label: 'Baloo 2' },\n { value: \"'Fraunces', Georgia, serif\", label: 'Fraunces (serif)' },\n { value: \"Georgia, 'Times New Roman', serif\", label: 'Georgia (serif)' },\n { value: \"'JetBrains Mono', ui-monospace, monospace\", label: 'JetBrains Mono' },\n { value: 'system-ui, sans-serif', label: 'System UI' },\n];\n\n// Label maps double as a compile-time exhaustiveness guard: typed as\n// `Record<ColorSlot, string>` / `Record<SidebarSlot, string>`, a new token added\n// to the upstream `Brand` schema fails to compile here until it gets a control,\n// so it can't silently drift out of the editor. Insertion order is display order.\nconst COLOR_LABELS: Record<ColorSlot, string> = {\n primary: 'Primary',\n primaryForeground: 'Primary foreground',\n background: 'Background',\n foreground: 'Foreground',\n card: 'Card',\n cardForeground: 'Card foreground',\n popover: 'Popover',\n popoverForeground: 'Popover foreground',\n secondary: 'Secondary',\n secondaryForeground: 'Secondary foreground',\n muted: 'Muted',\n mutedForeground: 'Muted foreground',\n accent: 'Accent',\n accentForeground: 'Accent foreground',\n border: 'Border',\n input: 'Input border',\n inputBackground: 'Input background',\n ring: 'Ring',\n ringOffset: 'Ring offset',\n destructive: 'Destructive',\n destructiveForeground: 'Destructive foreground',\n success: 'Success',\n successForeground: 'Success foreground',\n warning: 'Warning',\n warningForeground: 'Warning foreground',\n info: 'Info',\n infoForeground: 'Info foreground',\n};\n\nconst SIDEBAR_LABELS: Record<SidebarSlot, string> = {\n sidebar: 'Sidebar',\n sidebarForeground: 'Foreground',\n sidebarPrimary: 'Primary',\n sidebarPrimaryForeground: 'Primary foreground',\n sidebarAccent: 'Accent',\n sidebarAccentForeground: 'Accent foreground',\n sidebarBorder: 'Border',\n sidebarRing: 'Ring',\n};\n\nconst CHART_LABELS: Record<ChartSlot, string> = {\n chart1: 'Chart 1',\n chart2: 'Chart 2',\n chart3: 'Chart 3',\n chart4: 'Chart 4',\n chart5: 'Chart 5',\n};\n\nconst WEIGHT_LABELS: Record<FontWeightSlot, string> = {\n normal: 'Normal',\n medium: 'Medium',\n semibold: 'Semibold',\n bold: 'Bold',\n};\n\nconst SHADOW_LABELS: Record<ShadowSlot, string> = {\n sm: 'Small',\n md: 'Medium',\n lg: 'Large',\n lift: 'Lift (raised)',\n};\n\nconst SPACING_LABELS: Record<SpacingSlot, string> = {\n gutter: 'Gutter (between sections)',\n inset: 'Inset (in-container)',\n};\n\nconst MOTION_LABELS: Record<MotionSlot, string> = {\n duration: 'Duration',\n easing: 'Easing',\n};\n\nconst COLOR_SLOTS = Object.keys(COLOR_LABELS) as ColorSlot[];\nconst SIDEBAR_SLOTS = Object.keys(SIDEBAR_LABELS) as SidebarSlot[];\nconst CHART_SLOTS = Object.keys(CHART_LABELS) as ChartSlot[];\nconst WEIGHT_SLOTS = Object.keys(WEIGHT_LABELS) as FontWeightSlot[];\nconst SHADOW_SLOTS = Object.keys(SHADOW_LABELS) as ShadowSlot[];\nconst SPACING_SLOTS = Object.keys(SPACING_LABELS) as SpacingSlot[];\nconst MOTION_SLOTS = Object.keys(MOTION_LABELS) as MotionSlot[];\nconst RADIUS_SIZES: RadiusSize[] = ['sm', 'md', 'lg'];\n\n// Foreground/background pairs whose contrast decides everyday legibility.\nconst A11Y_PAIRS: { label: string; fg: ColorSlot; bg: ColorSlot }[] = [\n { label: 'Text on background', fg: 'foreground', bg: 'background' },\n { label: 'Text on card', fg: 'cardForeground', bg: 'card' },\n { label: 'Primary button', fg: 'primaryForeground', bg: 'primary' },\n { label: 'Muted text', fg: 'mutedForeground', bg: 'muted' },\n { label: 'Accent', fg: 'accentForeground', bg: 'accent' },\n];\n\n// Fallback sidebar palette — the `keboola` brand doesn't set `colors.sidebar` /\n// `colorsDark.sidebar`, so a slot needs a value to seed from when a user starts\n// editing sidebar tokens on a brand that leaves them unset.\nconst DEFAULT_SIDEBAR: Record<'light' | 'dark', Record<SidebarSlot, string>> = {\n light: {\n sidebar: '255 255 255',\n sidebarForeground: '30 41 59',\n sidebarPrimary: '31 143 255',\n sidebarPrimaryForeground: '255 255 255',\n sidebarAccent: '219 234 254',\n sidebarAccentForeground: '30 41 59',\n sidebarBorder: '229 231 235',\n sidebarRing: '31 143 255',\n },\n dark: {\n sidebar: '14 14 18',\n sidebarForeground: '229 230 236',\n sidebarPrimary: '31 143 255',\n sidebarPrimaryForeground: '255 255 255',\n sidebarAccent: '38 38 45',\n sidebarAccentForeground: '229 230 236',\n sidebarBorder: '48 49 57',\n sidebarRing: '31 143 255',\n },\n};\n\nconst DEFAULT_RADII: NonNullable<Brand['theme']['radii']> = {\n sm: '0.25rem',\n md: '0.375rem',\n lg: '0.5rem',\n};\n\n// The `@keboola/design/styles` categorical chart palette — seeds the chart\n// pickers when a brand leaves `colors.chart` / `colorsDark.chart` unset.\nconst DEFAULT_CHART: Record<'light' | 'dark', Record<ChartSlot, string>> = {\n light: {\n chart1: '31 143 255',\n chart2: '0 194 135',\n chart3: '146 92 255',\n chart4: '245 147 0',\n chart5: '237 94 223',\n },\n dark: {\n chart1: '92 173 255',\n chart2: '0 230 161',\n chart3: '173 133 255',\n chart4: '255 190 92',\n chart5: '242 140 232',\n },\n};\n\n// Chrome font-weight defaults (the Tailwind scale `@keboola/design` falls back to\n// when `typography.weight` is unset) — seed values for the weight sliders.\nconst DEFAULT_FONT_WEIGHTS: Record<FontWeightSlot, string> = {\n normal: '400',\n medium: '500',\n semibold: '600',\n bold: '700',\n};\n\n// Chrome tracking default (behind the hardcoded `tw:tracking-[1px]`).\nconst DEFAULT_LETTER_SPACING = '1px';\n\n// The monospace stack the mono-face Select falls back to when `fontMono` is unset\n// (matches the `DEFAULT_FONT_OPTIONS` JetBrains Mono entry).\nconst MONO_FALLBACK = \"'JetBrains Mono', ui-monospace, monospace\";\n\n// `@keboola/design/styles` defaults for the advanced token groups, shown as\n// placeholders so an unset field reveals the value it inherits.\nconst DEFAULT_SHADOWS: Record<ShadowSlot, string> = {\n sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',\n md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',\n lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',\n lift: '0 10px 30px -12px rgb(0 0 0 / 0.28), 0 2px 6px rgb(0 0 0 / 0.06)',\n};\n\nconst DEFAULT_SPACING: Record<SpacingSlot, string> = {\n gutter: '1.5rem',\n inset: '1.5rem',\n};\n\nconst DEFAULT_MOTION: Record<MotionSlot, string> = {\n duration: '200ms',\n easing: 'cubic-bezier(0.2, 0.7, 0.2, 1)',\n};\n\nconst DEFAULT_GRADIENT =\n 'linear-gradient(135deg, rgb(var(--primary)), rgb(var(--accent, var(--primary))))';\n\n// Leading numeric value of a token string (font-weight \"500\").\nconst numOf = (value: string, fallback: number): number => {\n const n = Number.parseFloat(value);\n return Number.isFinite(n) ? n : fallback;\n};\n\n// Letter-spacing may be px or em. Drive the slider in px (treating `em` as ×16,\n// like the radius slider treats `rem`) and write back in the value's original\n// unit, so an `em`-authored brand (e.g. `0.02em`) stays `em` instead of being\n// silently rewritten to `px`.\nconst letterSpacingToPx = (value: string): number => {\n const n = Number.parseFloat(value);\n if (!Number.isFinite(n)) return 0;\n return value.trim().endsWith('em') ? n * 16 : n;\n};\n\nconst pxToLetterSpacing = (px: number, currentValue: string): string =>\n currentValue.trim().endsWith('em') ? `${Number((px / 16).toFixed(3))}em` : `${px}px`;\n\n// The Brand schema allows any CSS length for radii (`rem`, `px`, unitless). Convert to px for\n// the slider treating `rem` as ×16 and everything else as already-px, and write back preserving\n// the slot's original unit so `8px` stays `8px`, `0.5rem` stays `0.5rem`, and a unitless `8` stays `8`.\nconst lengthToPx = (value: string): number => {\n const n = Number.parseFloat(value);\n if (!Number.isFinite(n)) return 0;\n return Math.round(value.trim().endsWith('rem') ? n * 16 : n);\n};\n\nconst pxToLength = (px: number, currentValue: string): string => {\n const trimmed = currentValue.trim();\n if (trimmed.endsWith('rem')) return `${px / 16}rem`;\n // Preserve a unitless token (the schema allows it) rather than forcing `px` onto it.\n if (/^-?\\d*\\.?\\d+$/.test(trimmed)) return `${px}`;\n return `${px}px`;\n};\n\nexport type BrandEditorProps = {\n value: Brand;\n onChange: (next: Brand) => void;\n /** Palette being edited. Controlled. Default 'light'. */\n colorScheme?: 'light' | 'dark';\n /** If provided, the editor shows a light/dark toggle that calls this. If omitted, no toggle. */\n onColorSchemeChange?: (scheme: 'light' | 'dark') => void;\n /** Font choices for the typography Select. Defaults to DEFAULT_FONT_OPTIONS. */\n fontOptions?: { value: string; label: string }[];\n};\n\n// Match `BrandLogo`'s sniffing: any string starting with `<` is inline markup (covers\n// `<?xml …?><svg>` / `<!doctype …>` prefixes); only `data:` / `http(s)://` are image URLs.\nconst isSvgMarkup = (source: string) => source.trimStart().startsWith('<');\nconst isImageSource = (source: string) => /^(data:|https?:\\/\\/)/.test(source.trim());\n\n/** Renders a logo source (SVG markup, `data:` URI, or `http(s)` URL) from the raw string. */\nconst LogoPreviewImage = ({ source, className }: { source: string; className?: string }) => {\n const safeSvg = useMemo(\n () =>\n isSvgMarkup(source)\n ? DOMPurify.sanitize(source, { USE_PROFILES: { svg: true, svgFilters: true } })\n : null,\n [source],\n );\n\n if (safeSvg !== null) {\n // Decorative preview of what the caller is editing — the textarea holds the real value, so\n // hide the injected graphic from assistive tech (parity with the `alt=\"\"` image branch below).\n return <span aria-hidden className={className} dangerouslySetInnerHTML={{ __html: safeSvg }} />;\n }\n if (isImageSource(source)) {\n return <img src={source} alt=\"\" className={className} />;\n }\n return null;\n};\n\n/**\n * Controlled brand-editing block — colors (light/dark), sidebar colors, WCAG contrast readout,\n * corner radius, typography, and logo. Reads/writes a `Brand` (`@keboola/brand-registry`) via\n * `value` / `onChange`; owns no state of its own, so the caller decides where the brand and the\n * active color scheme live (Kai build-mode preview, theme-generator, …).\n */\nexport const BrandEditor = ({\n value,\n onChange,\n colorScheme = 'light',\n onColorSchemeChange,\n fontOptions = DEFAULT_FONT_OPTIONS,\n}: BrandEditorProps) => {\n // Prefix every control id so multiple BrandEditors on one page don't collide (broken label↔control).\n const baseId = useId();\n const { theme } = value;\n const coreColors = colorScheme === 'light' ? theme.colors : theme.colorsDark;\n const sidebarColors = colorScheme === 'light' ? theme.colors?.sidebar : theme.colorsDark?.sidebar;\n const chartColors = colorScheme === 'light' ? theme.colors?.chart : theme.colorsDark?.chart;\n // Fall back to the keboola default of the SAME scheme — never light values while editing dark.\n const defaultColors = colorScheme === 'light' ? keboola.theme.colors : keboola.theme.colorsDark;\n const radii = theme.radii ?? DEFAULT_RADII;\n const typography = theme.typography;\n\n const setColor = (slot: ColorSlot, channels: string) => {\n if (colorScheme === 'light') {\n onChange({ ...value, theme: { ...theme, colors: { ...theme.colors, [slot]: channels } } });\n return;\n }\n // Keep colorsDark sparse (mirrors the light path) so an unset dark palette inherits the\n // design-system defaults instead of baking in a full keboola palette on the first edit.\n onChange({\n ...value,\n theme: { ...theme, colorsDark: { ...theme.colorsDark, [slot]: channels } },\n });\n };\n\n const setSidebarColor = (slot: SidebarSlot, channels: string) => {\n if (colorScheme === 'light') {\n const base = theme.colors?.sidebar ?? DEFAULT_SIDEBAR.light;\n onChange({\n ...value,\n theme: { ...theme, colors: { ...theme.colors, sidebar: { ...base, [slot]: channels } } },\n });\n return;\n }\n // Spread the existing (possibly-empty) colorsDark so setting a sidebar token doesn't seed the\n // whole core dark palette; only the sidebar sub-object is seeded, since it has no default to inherit.\n const sidebarBase = theme.colorsDark?.sidebar ?? DEFAULT_SIDEBAR.dark;\n onChange({\n ...value,\n theme: {\n ...theme,\n colorsDark: { ...theme.colorsDark, sidebar: { ...sidebarBase, [slot]: channels } },\n },\n });\n };\n\n const setChart = (slot: ChartSlot, channels: string) => {\n const base = chartColors ?? DEFAULT_CHART[colorScheme];\n const chart = { ...base, [slot]: channels };\n if (colorScheme === 'light') {\n onChange({ ...value, theme: { ...theme, colors: { ...theme.colors, chart } } });\n return;\n }\n onChange({ ...value, theme: { ...theme, colorsDark: { ...theme.colorsDark, chart } } });\n };\n\n const setRadius = (size: RadiusSize, radius: string) =>\n onChange({\n ...value,\n theme: { ...theme, radii: { ...(theme.radii ?? DEFAULT_RADII), [size]: radius } },\n });\n\n // Merge a typography patch, keeping sibling faces / weight / letterSpacing.\n const patchTypography = (patch: Partial<Typography>) =>\n onChange({ ...value, theme: { ...theme, typography: { ...theme.typography, ...patch } } });\n\n const setFontFace = (face: FontFace, font: string) =>\n patchTypography({ [face]: font.trim() || undefined });\n\n const setFontWeight = (slot: FontWeightSlot, weight: string) =>\n patchTypography({ weight: { ...theme.typography?.weight, [slot]: weight || undefined } });\n\n const setLetterSpacing = (chrome: string) =>\n patchTypography({ letterSpacing: { chrome: chrome.trim() || undefined } });\n\n const setShadow = (slot: ShadowSlot, shadow: string) =>\n onChange({\n ...value,\n theme: { ...theme, shadows: { ...theme.shadows, [slot]: shadow.trim() || undefined } },\n });\n\n const setSpacing = (slot: SpacingSlot, length: string) =>\n onChange({\n ...value,\n theme: { ...theme, spacing: { ...theme.spacing, [slot]: length.trim() || undefined } },\n });\n\n const setMotion = (slot: MotionSlot, token: string) =>\n onChange({\n ...value,\n theme: { ...theme, motion: { ...theme.motion, [slot]: token.trim() || undefined } },\n });\n\n const setGradient = (gradient: string) =>\n onChange({\n ...value,\n theme: { ...theme, gradient: { brand: gradient.trim() || undefined } },\n });\n\n const setLogo = (slot: keyof NonNullable<Brand['logo']>, source: string) => {\n const trimmed = source.trim();\n const nextLogo: NonNullable<Brand['logo']> = { ...value.logo };\n if (trimmed) nextLogo[slot] = trimmed;\n else delete nextLogo[slot];\n const { logo: _drop, ...rest } = value;\n onChange(Object.keys(nextLogo).length > 0 ? { ...value, logo: nextLogo } : rest);\n };\n\n return (\n <div className=\"tw:flex tw:flex-col\">\n <ControlSection title=\"Colors\">\n {onColorSchemeChange ? (\n <ToggleGroup\n type=\"single\"\n value={colorScheme}\n onValueChange={(next) => {\n if (next === 'light' || next === 'dark') onColorSchemeChange(next);\n }}\n aria-label=\"Color scheme\"\n className=\"tw:w-full\"\n >\n <ToggleGroup.Item value=\"light\" className=\"tw:flex-1\">\n Light\n </ToggleGroup.Item>\n <ToggleGroup.Item value=\"dark\" className=\"tw:flex-1\">\n Dark\n </ToggleGroup.Item>\n </ToggleGroup>\n ) : null}\n <div className=\"tw:flex tw:flex-col tw:gap-2\">\n {COLOR_SLOTS.map((slot) => (\n <ColorField\n key={slot}\n id={`${baseId}color-${colorScheme}-${slot}`}\n label={COLOR_LABELS[slot]}\n channels={coreColors?.[slot] ?? defaultColors?.[slot] ?? '0 0 0'}\n onChange={(channels) => setColor(slot, channels)}\n />\n ))}\n </div>\n </ControlSection>\n\n <ControlSection title=\"Accessibility\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n WCAG contrast for the {colorScheme} palette (AA needs 4.5:1).\n </p>\n <div className=\"tw:flex tw:flex-col tw:gap-1.5\">\n {A11Y_PAIRS.map((pair) => {\n const fg = coreColors?.[pair.fg] ?? defaultColors?.[pair.fg] ?? '0 0 0';\n const bg = coreColors?.[pair.bg] ?? defaultColors?.[pair.bg] ?? '255 255 255';\n const ratio = contrastRatio(fg, bg);\n const level = wcagLevel(ratio);\n return (\n <div key={pair.label} className=\"tw:flex tw:items-center tw:justify-between tw:gap-2\">\n <span className=\"tw:text-xs tw:text-foreground\">{pair.label}</span>\n <span className=\"tw:flex tw:items-center tw:gap-1.5\">\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {ratio.toFixed(1)}:1\n </span>\n <Badge color={level === 'fail' ? 'red' : 'green'}>\n {level === 'fail' ? 'Fail' : level}\n </Badge>\n </span>\n </div>\n );\n })}\n </div>\n </ControlSection>\n\n <ControlSection title=\"Sidebar colors\">\n <Collapsible className=\"tw:flex tw:flex-col tw:gap-2\">\n <Collapsible.Trigger asChild>\n <Button variant=\"outline\" size=\"small\" className=\"tw:w-full\">\n Toggle sidebar tokens ({colorScheme})\n </Button>\n </Collapsible.Trigger>\n <Collapsible.Content className=\"tw:flex tw:flex-col tw:gap-2\">\n {SIDEBAR_SLOTS.map((slot) => (\n <ColorField\n key={slot}\n id={`${baseId}sidebar-${colorScheme}-${slot}`}\n label={SIDEBAR_LABELS[slot]}\n channels={sidebarColors?.[slot] ?? DEFAULT_SIDEBAR[colorScheme][slot]}\n onChange={(channels) => setSidebarColor(slot, channels)}\n />\n ))}\n </Collapsible.Content>\n </Collapsible>\n </ControlSection>\n\n <ControlSection title=\"Chart palette\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Categorical series colours for the {colorScheme} palette (`--chart-1…5`).\n </p>\n <div className=\"tw:flex tw:flex-col tw:gap-2\">\n {CHART_SLOTS.map((slot) => (\n <ColorField\n key={slot}\n id={`${baseId}chart-${colorScheme}-${slot}`}\n label={CHART_LABELS[slot]}\n channels={chartColors?.[slot] ?? DEFAULT_CHART[colorScheme][slot]}\n onChange={(channels) => setChart(slot, channels)}\n />\n ))}\n </div>\n </ControlSection>\n\n <ControlSection title=\"Radius\">\n {RADIUS_SIZES.map((size) => {\n const current = radii[size];\n const px = lengthToPx(current);\n return (\n <div key={size} className=\"tw:flex tw:flex-col tw:gap-1\">\n <div className=\"tw:flex tw:items-center tw:justify-between\">\n <Label className=\"tw:text-xs tw:font-normal tw:uppercase tw:text-foreground\">\n {size}\n </Label>\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {radii[size]}\n </span>\n </div>\n <Slider\n min={0}\n max={24}\n step={1}\n value={[px]}\n onValueChange={(values) => setRadius(size, pxToLength(values[0] ?? 0, current))}\n />\n </div>\n );\n })}\n </ControlSection>\n\n <ControlSection title=\"Typography\">\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label\n htmlFor={`${baseId}font-family`}\n className=\"tw:text-xs tw:font-normal tw:text-foreground\"\n >\n Font family\n </Label>\n <Select\n id={`${baseId}font-family`}\n value={typography?.fontFamily ?? DEFAULT_FALLBACK_FONT}\n onValueChange={(font) => setFontFace('fontFamily', font)}\n items={fontOptions}\n />\n </div>\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label\n htmlFor={`${baseId}font-display`}\n className=\"tw:text-xs tw:font-normal tw:text-foreground\"\n >\n Display font (headings)\n </Label>\n <Select\n id={`${baseId}font-display`}\n value={typography?.fontDisplay ?? typography?.fontFamily ?? DEFAULT_FALLBACK_FONT}\n onValueChange={(font) => setFontFace('fontDisplay', font)}\n items={fontOptions}\n />\n </div>\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label\n htmlFor={`${baseId}font-mono`}\n className=\"tw:text-xs tw:font-normal tw:text-foreground\"\n >\n Monospace font\n </Label>\n <Select\n id={`${baseId}font-mono`}\n value={typography?.fontMono ?? MONO_FALLBACK}\n onValueChange={(font) => setFontFace('fontMono', font)}\n items={fontOptions}\n />\n </div>\n\n <span className=\"tw:text-xs tw:text-muted-foreground\">Chrome font weights</span>\n {WEIGHT_SLOTS.map((slot) => {\n const current = typography?.weight?.[slot] ?? DEFAULT_FONT_WEIGHTS[slot];\n return (\n <div key={slot} className=\"tw:flex tw:flex-col tw:gap-1\">\n <div className=\"tw:flex tw:items-center tw:justify-between\">\n <Label className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {WEIGHT_LABELS[slot]}\n </Label>\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {current}\n </span>\n </div>\n <Slider\n min={100}\n max={900}\n step={25}\n value={[numOf(current, 400)]}\n onValueChange={(values) => setFontWeight(slot, String(values[0] ?? 400))}\n />\n </div>\n );\n })}\n\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <div className=\"tw:flex tw:items-center tw:justify-between\">\n <Label className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n Chrome letter spacing\n </Label>\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {typography?.letterSpacing?.chrome ?? DEFAULT_LETTER_SPACING}\n </span>\n </div>\n <Slider\n min={-2}\n max={4}\n step={0.25}\n value={[letterSpacingToPx(typography?.letterSpacing?.chrome ?? DEFAULT_LETTER_SPACING)]}\n onValueChange={(values) =>\n setLetterSpacing(\n pxToLetterSpacing(\n values[0] ?? 0,\n typography?.letterSpacing?.chrome ?? DEFAULT_LETTER_SPACING,\n ),\n )\n }\n />\n </div>\n </ControlSection>\n\n <ControlSection title=\"Elevation\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Box-shadow scale (`--shadow-*`), shared across light & dark. Empty inherits the default.\n </p>\n {SHADOW_SLOTS.map((slot) => (\n <TextField\n key={slot}\n id={`${baseId}shadow-${slot}`}\n label={SHADOW_LABELS[slot]}\n value={theme.shadows?.[slot] ?? ''}\n placeholder={DEFAULT_SHADOWS[slot]}\n onChange={(next) => setShadow(slot, next)}\n />\n ))}\n </ControlSection>\n\n <ControlSection title=\"Density\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Semantic spacing (`--space-*`), as CSS lengths.\n </p>\n {SPACING_SLOTS.map((slot) => (\n <TextField\n key={slot}\n id={`${baseId}spacing-${slot}`}\n label={SPACING_LABELS[slot]}\n value={theme.spacing?.[slot] ?? ''}\n placeholder={DEFAULT_SPACING[slot]}\n onChange={(next) => setSpacing(slot, next)}\n />\n ))}\n </ControlSection>\n\n <ControlSection title=\"Motion\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Base transition `duration` + `easing` (`--motion-*`).\n </p>\n {MOTION_SLOTS.map((slot) => (\n <TextField\n key={slot}\n id={`${baseId}motion-${slot}`}\n label={MOTION_LABELS[slot]}\n value={theme.motion?.[slot] ?? ''}\n placeholder={DEFAULT_MOTION[slot]}\n onChange={(next) => setMotion(slot, next)}\n />\n ))}\n </ControlSection>\n\n <ControlSection title=\"Gradient\">\n <TextField\n id={`${baseId}gradient-brand`}\n label=\"Brand gradient\"\n value={theme.gradient?.brand ?? ''}\n placeholder={DEFAULT_GRADIENT}\n onChange={setGradient}\n />\n </ControlSection>\n\n <ControlSection title=\"Logo\">\n <LogoField\n id={`${baseId}logo-mark`}\n label=\"Mark (icon)\"\n value={value.logo?.mark ?? ''}\n onChange={(source) => setLogo('mark', source)}\n />\n <LogoField\n id={`${baseId}logo-wordmark`}\n label=\"Wordmark (full)\"\n value={value.logo?.wordmark ?? ''}\n onChange={(source) => setLogo('wordmark', source)}\n />\n <Collapsible className=\"tw:flex tw:flex-col tw:gap-2\">\n <Collapsible.Trigger asChild>\n <Button variant=\"outline\" size=\"small\" className=\"tw:w-full\">\n Toggle dark-mode logos\n </Button>\n </Collapsible.Trigger>\n <Collapsible.Content className=\"tw:flex tw:flex-col tw:gap-2\">\n <LogoField\n id={`${baseId}logo-mark-dark`}\n label=\"Mark (dark)\"\n value={value.logo?.markDark ?? ''}\n onChange={(source) => setLogo('markDark', source)}\n />\n <LogoField\n id={`${baseId}logo-wordmark-dark`}\n label=\"Wordmark (dark)\"\n value={value.logo?.wordmarkDark ?? ''}\n onChange={(source) => setLogo('wordmarkDark', source)}\n />\n </Collapsible.Content>\n </Collapsible>\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <span className=\"tw:text-xs tw:text-muted-foreground\">Preview</span>\n <div className=\"tw:flex tw:min-h-12 tw:items-center tw:gap-4 tw:rounded-md tw:border tw:border-border tw:bg-background tw:p-3\">\n {value.logo?.mark || value.logo?.wordmark ? (\n <>\n {value.logo.mark ? (\n <LogoPreviewImage source={value.logo.mark} className=\"tw:h-8\" />\n ) : null}\n {value.logo.wordmark ? (\n <LogoPreviewImage source={value.logo.wordmark} className=\"tw:h-8\" />\n ) : null}\n </>\n ) : (\n <span className=\"tw:text-xs tw:text-muted-foreground\">No logo set</span>\n )}\n </div>\n </div>\n </ControlSection>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"BrandEditor-CSANsxUz.js","names":[],"sources":["../src/blocks/BrandEditor/color.ts","../src/blocks/BrandEditor/ColorField.tsx","../src/blocks/BrandEditor/contrast.ts","../src/blocks/BrandEditor/ControlSection.tsx","../src/blocks/BrandEditor/LogoField.tsx","../src/blocks/BrandEditor/TextField.tsx","../src/blocks/BrandEditor/BrandEditor.tsx"],"sourcesContent":["// Brand colours are stored as space-separated \"R G B\" channel strings (see\n// `rgbChannelsSchema` in @keboola/brand-registry); `ColorInput` speaks hex.\n// These helpers convert at that boundary.\n\nexport const hexToChannels = (hex: string): string => {\n let h = hex.trim().replace(/^#/, '');\n // Expand 3/4-digit shorthand and drop alpha (4- and 8-digit `#rgba`/`#rrggbbaa`) — brand\n // channels carry no alpha. Anything else is malformed → fall back to black.\n if (h.length === 3 || h.length === 4) {\n h = h\n .slice(0, 3)\n .split('')\n .map((c) => c + c)\n .join('');\n } else if (h.length === 8) {\n h = h.slice(0, 6);\n }\n if (!/^[0-9a-fA-F]{6}$/.test(h)) return '0 0 0';\n const n = parseInt(h, 16);\n return `${(n >> 16) & 255} ${(n >> 8) & 255} ${n & 255}`;\n};\n\nexport const channelsToHex = (channels: string): string => {\n const [r = 0, g = 0, b = 0] = channels\n .trim()\n .split(/\\s+/)\n .map((n) => Number(n) || 0);\n const to2 = (v: number) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0');\n return `#${to2(r)}${to2(g)}${to2(b)}`;\n};\n","import { Label } from '../../components/Label';\nimport { ColorInput } from '../ColorInput';\n\nimport { channelsToHex, hexToChannels } from './color';\n\ntype Props = {\n id: string;\n label: string;\n /** Current value as an \"R G B\" channel string. */\n channels: string;\n /** Called with a new \"R G B\" channel string when a valid colour is picked. */\n onChange: (channels: string) => void;\n};\n\n// ColorInput fires `onValueChange` on every keystroke (it only coerces on blur), so commit a\n// channel value only for a COMPLETE 3-, 4-, 6-, or 8-digit hex — otherwise a partial \"#1\"/\"#\"\n// would write unintended channels. Alpha (4-/8-digit) is truncated to RGB, since brand tokens\n// carry no alpha.\nconst COMPLETE_HEX = /^([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n/** A labelled hex colour picker bound to a brand token (stored as RGB channels). */\nexport const ColorField = ({ id, label, channels, onChange }: Props) => {\n const hex = channelsToHex(channels);\n\n return (\n <div className=\"tw:flex tw:items-center tw:justify-between tw:gap-2\">\n <Label htmlFor={id} className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {label}\n </Label>\n <ColorInput\n id={id}\n sizing=\"medium\"\n value={hex}\n className=\"tw:w-36\"\n onValueChange={(next) => {\n const clean = next.trim().replace(/^#/, '');\n // Commit only complete hex (hexToChannels handles the 3/6/8-digit shapes + alpha drop).\n if (COMPLETE_HEX.test(clean)) onChange(hexToChannels(`#${clean}`));\n }}\n />\n </div>\n );\n};\n","// WCAG contrast helpers over `\"R G B\"` channel strings (the brand token format).\n\n// Coerce non-numeric/blank channels to 0 and clamp to the valid 0–255 range, so a malformed\n// token (e.g. \"999 999 999\") can neither NaN-out nor push luminance past WCAG's expected bounds.\nconst clampChannel = (n: number): number =>\n Number.isFinite(n) ? Math.min(255, Math.max(0, n)) : 0;\n\nconst parseChannels = (channels: string): [number, number, number] => {\n const [r = 0, g = 0, b = 0] = channels\n .trim()\n .split(/\\s+/)\n .map((n) => clampChannel(Number(n)));\n return [r, g, b];\n};\n\nconst relativeLuminance = (channels: string): number => {\n // Destructure with defaults (not an `as` assertion) so `r`/`g`/`b` are `number` under\n // `noUncheckedIndexedAccess` without a production-code type cast.\n const [r = 0, g = 0, b = 0] = parseChannels(channels).map((c) => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n};\n\n/** WCAG contrast ratio (1–21) between two `\"R G B\"` colours. */\nexport const contrastRatio = (fg: string, bg: string): number => {\n const l1 = relativeLuminance(fg);\n const l2 = relativeLuminance(bg);\n const [lighter, darker] = l1 >= l2 ? [l1, l2] : [l2, l1];\n return (lighter + 0.05) / (darker + 0.05);\n};\n\n/** WCAG level for normal-size text at a given ratio. */\nexport const wcagLevel = (ratio: number): 'AAA' | 'AA' | 'fail' =>\n ratio >= 7 ? 'AAA' : ratio >= 4.5 ? 'AA' : 'fail';\n","import type { ReactNode } from 'react';\n\ntype Props = {\n title: string;\n children: ReactNode;\n};\n\n/** A titled block inside `BrandEditor`. */\nexport const ControlSection = ({ title, children }: Props) => (\n <section className=\"tw:flex tw:flex-col tw:gap-3 tw:border-b tw:border-border tw:p-4\">\n <h3 className=\"tw:m-0 tw:text-xs tw:font-semibold tw:uppercase tw:tracking-wide tw:text-muted-foreground\">\n {title}\n </h3>\n {children}\n </section>\n);\n","import { Label } from '../../components/Label';\nimport { Textarea } from '../Textarea';\n\ntype Props = {\n id: string;\n label: string;\n /** Current logo source: SVG markup, a `data:` URI, or an `http(s)` URL. */\n value: string;\n /** Called with the new source (empty string clears the slot). */\n onChange: (value: string) => void;\n};\n\n/** A labelled multi-line field for a brand logo source. */\nexport const LogoField = ({ id, label, value, onChange }: Props) => (\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label htmlFor={id} className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {label}\n </Label>\n <Textarea\n id={id}\n value={value}\n onValueChange={onChange}\n maxRows={4}\n placeholder=\"<svg…>, a data: URI, or an https:// URL\"\n className=\"tw:font-mono tw:text-xs\"\n />\n </div>\n);\n","import { Label } from '../../components/Label';\nimport { TextInput } from '../TextInput';\n\ntype Props = {\n id: string;\n label: string;\n /** Current CSS value (empty clears the token, falling back to the stylesheet default). */\n value: string;\n /** Called with the new CSS value on change. */\n onChange: (value: string) => void;\n /** The inherited default, shown as placeholder when the field is empty. */\n placeholder?: string;\n};\n\n/** A labelled single-line field for a free-form CSS-value token (shadow, motion, gradient). */\nexport const TextField = ({ id, label, value, onChange, placeholder }: Props) => (\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label htmlFor={id} className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {label}\n </Label>\n <TextInput\n id={id}\n sizing=\"small\"\n value={value}\n onValueChange={onChange}\n placeholder={placeholder}\n className=\"tw:font-mono tw:text-xs\"\n />\n </div>\n);\n","import { useId, useMemo } from 'react';\nimport DOMPurify from 'dompurify';\n\nimport type { Brand } from '@keboola/brand-registry';\nimport { keboola } from '@keboola/brand-registry';\n\nimport { Badge } from '../../components/Badge';\nimport { Button } from '../../components/Button';\nimport { Collapsible } from '../../components/Collapsible';\nimport { Label } from '../../components/Label';\nimport { Select } from '../../components/Select';\nimport { Slider } from '../../components/Slider';\nimport { ToggleGroup } from '../../components/ToggleGroup';\n\nimport { ColorField } from './ColorField';\nimport { contrastRatio, wcagLevel } from './contrast';\nimport { ControlSection } from './ControlSection';\nimport { LogoField } from './LogoField';\nimport { TextField } from './TextField';\n\nexport type ColorSlot = keyof Omit<NonNullable<Brand['theme']['colors']>, 'sidebar' | 'chart'>;\nexport type SidebarSlot = keyof NonNullable<NonNullable<Brand['theme']['colors']>['sidebar']>;\nexport type ChartSlot = keyof NonNullable<NonNullable<Brand['theme']['colors']>['chart']>;\nexport type RadiusSize = keyof NonNullable<Brand['theme']['radii']>;\n\ntype Typography = NonNullable<Brand['theme']['typography']>;\ntype FontFace = 'fontFamily' | 'fontDisplay' | 'fontMono';\ntype FontWeightSlot = keyof NonNullable<Typography['weight']>;\ntype ShadowSlot = keyof NonNullable<Brand['theme']['shadows']>;\ntype SpacingSlot = keyof NonNullable<Brand['theme']['spacing']>;\ntype MotionSlot = keyof NonNullable<Brand['theme']['motion']>;\n\nexport const DEFAULT_FALLBACK_FONT = \"'Inter', system-ui, sans-serif\";\n\nexport const DEFAULT_FONT_OPTIONS: { value: string; label: string }[] = [\n { value: DEFAULT_FALLBACK_FONT, label: 'Inter (default)' },\n { value: \"'Space Grotesk', system-ui, sans-serif\", label: 'Space Grotesk' },\n { value: \"'Poppins', system-ui, sans-serif\", label: 'Poppins' },\n { value: \"'Manrope', system-ui, sans-serif\", label: 'Manrope' },\n { value: \"'Quicksand', system-ui, sans-serif\", label: 'Quicksand' },\n { value: \"'Baloo 2', system-ui, sans-serif\", label: 'Baloo 2' },\n { value: \"'Fraunces', Georgia, serif\", label: 'Fraunces (serif)' },\n { value: \"Georgia, 'Times New Roman', serif\", label: 'Georgia (serif)' },\n { value: \"'JetBrains Mono', ui-monospace, monospace\", label: 'JetBrains Mono' },\n { value: 'system-ui, sans-serif', label: 'System UI' },\n];\n\n// Label maps double as a compile-time exhaustiveness guard: typed as\n// `Record<ColorSlot, string>` / `Record<SidebarSlot, string>`, a new token added\n// to the upstream `Brand` schema fails to compile here until it gets a control,\n// so it can't silently drift out of the editor. Insertion order is display order.\nconst COLOR_LABELS: Record<ColorSlot, string> = {\n primary: 'Primary',\n primaryForeground: 'Primary foreground',\n background: 'Background',\n foreground: 'Foreground',\n card: 'Card',\n cardForeground: 'Card foreground',\n popover: 'Popover',\n popoverForeground: 'Popover foreground',\n secondary: 'Secondary',\n secondaryForeground: 'Secondary foreground',\n muted: 'Muted',\n mutedForeground: 'Muted foreground',\n accent: 'Accent',\n accentForeground: 'Accent foreground',\n border: 'Border',\n input: 'Input border',\n inputBackground: 'Input background',\n ring: 'Ring',\n ringOffset: 'Ring offset',\n destructive: 'Destructive',\n destructiveForeground: 'Destructive foreground',\n success: 'Success',\n successForeground: 'Success foreground',\n warning: 'Warning',\n warningForeground: 'Warning foreground',\n info: 'Info',\n infoForeground: 'Info foreground',\n};\n\nconst SIDEBAR_LABELS: Record<SidebarSlot, string> = {\n sidebar: 'Sidebar',\n sidebarForeground: 'Foreground',\n sidebarPrimary: 'Primary',\n sidebarPrimaryForeground: 'Primary foreground',\n sidebarAccent: 'Accent',\n sidebarAccentForeground: 'Accent foreground',\n sidebarBorder: 'Border',\n sidebarRing: 'Ring',\n};\n\nconst CHART_LABELS: Record<ChartSlot, string> = {\n chart1: 'Chart 1',\n chart2: 'Chart 2',\n chart3: 'Chart 3',\n chart4: 'Chart 4',\n chart5: 'Chart 5',\n};\n\nconst WEIGHT_LABELS: Record<FontWeightSlot, string> = {\n normal: 'Normal',\n medium: 'Medium',\n semibold: 'Semibold',\n bold: 'Bold',\n};\n\nconst SHADOW_LABELS: Record<ShadowSlot, string> = {\n sm: 'Small',\n md: 'Medium',\n lg: 'Large',\n lift: 'Lift (raised)',\n};\n\nconst SPACING_LABELS: Record<SpacingSlot, string> = {\n gutter: 'Gutter (between sections)',\n inset: 'Inset (in-container)',\n};\n\nconst MOTION_LABELS: Record<MotionSlot, string> = {\n duration: 'Duration',\n easing: 'Easing',\n};\n\nconst COLOR_SLOTS = Object.keys(COLOR_LABELS) as ColorSlot[];\nconst SIDEBAR_SLOTS = Object.keys(SIDEBAR_LABELS) as SidebarSlot[];\nconst CHART_SLOTS = Object.keys(CHART_LABELS) as ChartSlot[];\nconst WEIGHT_SLOTS = Object.keys(WEIGHT_LABELS) as FontWeightSlot[];\nconst SHADOW_SLOTS = Object.keys(SHADOW_LABELS) as ShadowSlot[];\nconst SPACING_SLOTS = Object.keys(SPACING_LABELS) as SpacingSlot[];\nconst MOTION_SLOTS = Object.keys(MOTION_LABELS) as MotionSlot[];\nconst RADIUS_SIZES: RadiusSize[] = ['sm', 'md', 'lg'];\n\n// Foreground/background pairs whose contrast decides everyday legibility.\nconst A11Y_PAIRS: { label: string; fg: ColorSlot; bg: ColorSlot }[] = [\n { label: 'Text on background', fg: 'foreground', bg: 'background' },\n { label: 'Text on card', fg: 'cardForeground', bg: 'card' },\n { label: 'Primary button', fg: 'primaryForeground', bg: 'primary' },\n { label: 'Muted text', fg: 'mutedForeground', bg: 'muted' },\n { label: 'Accent', fg: 'accentForeground', bg: 'accent' },\n];\n\n// Fallback sidebar palette — the `keboola` brand doesn't set `colors.sidebar` /\n// `colorsDark.sidebar`, so a slot needs a value to seed from when a user starts\n// editing sidebar tokens on a brand that leaves them unset.\nconst DEFAULT_SIDEBAR: Record<'light' | 'dark', Record<SidebarSlot, string>> = {\n light: {\n sidebar: '255 255 255',\n sidebarForeground: '30 41 59',\n sidebarPrimary: '31 143 255',\n sidebarPrimaryForeground: '255 255 255',\n sidebarAccent: '219 234 254',\n sidebarAccentForeground: '30 41 59',\n sidebarBorder: '229 231 235',\n sidebarRing: '31 143 255',\n },\n dark: {\n sidebar: '14 14 18',\n sidebarForeground: '229 230 236',\n sidebarPrimary: '31 143 255',\n sidebarPrimaryForeground: '255 255 255',\n sidebarAccent: '38 38 45',\n sidebarAccentForeground: '229 230 236',\n sidebarBorder: '48 49 57',\n sidebarRing: '31 143 255',\n },\n};\n\nconst DEFAULT_RADII: NonNullable<Brand['theme']['radii']> = {\n sm: '0.25rem',\n md: '0.375rem',\n lg: '0.5rem',\n};\n\n// The `@keboola/design/styles` categorical chart palette — seeds the chart\n// pickers when a brand leaves `colors.chart` / `colorsDark.chart` unset.\nconst DEFAULT_CHART: Record<'light' | 'dark', Record<ChartSlot, string>> = {\n light: {\n chart1: '31 143 255',\n chart2: '0 194 135',\n chart3: '146 92 255',\n chart4: '245 147 0',\n chart5: '237 94 223',\n },\n dark: {\n chart1: '92 173 255',\n chart2: '0 230 161',\n chart3: '173 133 255',\n chart4: '255 190 92',\n chart5: '242 140 232',\n },\n};\n\n// Chrome font-weight defaults (the Tailwind scale `@keboola/design` falls back to\n// when `typography.weight` is unset) — seed values for the weight sliders.\nconst DEFAULT_FONT_WEIGHTS: Record<FontWeightSlot, string> = {\n normal: '400',\n medium: '500',\n semibold: '600',\n bold: '700',\n};\n\n// Chrome tracking default (behind the hardcoded `tw:tracking-[1px]`).\nconst DEFAULT_LETTER_SPACING = '1px';\n\n// The monospace stack the mono-face Select falls back to when `fontMono` is unset\n// (matches the `DEFAULT_FONT_OPTIONS` JetBrains Mono entry).\nconst MONO_FALLBACK = \"'JetBrains Mono', ui-monospace, monospace\";\n\n// `@keboola/design/styles` defaults for the advanced token groups, shown as\n// placeholders so an unset field reveals the value it inherits.\nconst DEFAULT_SHADOWS: Record<ShadowSlot, string> = {\n sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',\n md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',\n lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',\n lift: '0 10px 30px -12px rgb(0 0 0 / 0.28), 0 2px 6px rgb(0 0 0 / 0.06)',\n};\n\nconst DEFAULT_SPACING: Record<SpacingSlot, string> = {\n gutter: '1.5rem',\n inset: '1.5rem',\n};\n\nconst DEFAULT_MOTION: Record<MotionSlot, string> = {\n duration: '200ms',\n easing: 'cubic-bezier(0.2, 0.7, 0.2, 1)',\n};\n\nconst DEFAULT_GRADIENT =\n 'linear-gradient(135deg, rgb(var(--primary)), rgb(var(--accent, var(--primary))))';\n\n// Leading numeric value of a token string (font-weight \"500\").\nconst numOf = (value: string, fallback: number): number => {\n const n = Number.parseFloat(value);\n return Number.isFinite(n) ? n : fallback;\n};\n\n// Letter-spacing may be px or em. Drive the slider in px (treating `em` as ×16,\n// like the radius slider treats `rem`) and write back in the value's original\n// unit, so an `em`-authored brand (e.g. `0.02em`) stays `em` instead of being\n// silently rewritten to `px`.\nconst letterSpacingToPx = (value: string): number => {\n const n = Number.parseFloat(value);\n if (!Number.isFinite(n)) return 0;\n return value.trim().endsWith('em') ? n * 16 : n;\n};\n\nconst pxToLetterSpacing = (px: number, currentValue: string): string =>\n currentValue.trim().endsWith('em') ? `${Number((px / 16).toFixed(3))}em` : `${px}px`;\n\n// The Brand schema allows any CSS length for radii (`rem`, `px`, unitless). Convert to px for\n// the slider treating `rem` as ×16 and everything else as already-px, and write back preserving\n// the slot's original unit so `8px` stays `8px`, `0.5rem` stays `0.5rem`, and a unitless `8` stays `8`.\nconst lengthToPx = (value: string): number => {\n const n = Number.parseFloat(value);\n if (!Number.isFinite(n)) return 0;\n return Math.round(value.trim().endsWith('rem') ? n * 16 : n);\n};\n\nconst pxToLength = (px: number, currentValue: string): string => {\n const trimmed = currentValue.trim();\n if (trimmed.endsWith('rem')) return `${px / 16}rem`;\n // Preserve a unitless token (the schema allows it) rather than forcing `px` onto it.\n if (/^-?\\d*\\.?\\d+$/.test(trimmed)) return `${px}`;\n return `${px}px`;\n};\n\nexport type BrandEditorProps = {\n value: Brand;\n onChange: (next: Brand) => void;\n /** Palette being edited. Controlled. Default 'light'. */\n colorScheme?: 'light' | 'dark';\n /** If provided, the editor shows a light/dark toggle that calls this. If omitted, no toggle. */\n onColorSchemeChange?: (scheme: 'light' | 'dark') => void;\n /** Font choices for the typography Select. Defaults to DEFAULT_FONT_OPTIONS. */\n fontOptions?: { value: string; label: string }[];\n};\n\n// Match `BrandLogo`'s sniffing: any string starting with `<` is inline markup (covers\n// `<?xml …?><svg>` / `<!doctype …>` prefixes); only `data:` / `http(s)://` are image URLs.\nconst isSvgMarkup = (source: string) => source.trimStart().startsWith('<');\nconst isImageSource = (source: string) => /^(data:|https?:\\/\\/)/.test(source.trim());\n\n/** Renders a logo source (SVG markup, `data:` URI, or `http(s)` URL) from the raw string. */\nconst LogoPreviewImage = ({ source, className }: { source: string; className?: string }) => {\n const safeSvg = useMemo(\n () =>\n isSvgMarkup(source)\n ? DOMPurify.sanitize(source, { USE_PROFILES: { svg: true, svgFilters: true } })\n : null,\n [source],\n );\n\n if (safeSvg !== null) {\n // Decorative preview of what the caller is editing — the textarea holds the real value, so\n // hide the injected graphic from assistive tech (parity with the `alt=\"\"` image branch below).\n return <span aria-hidden className={className} dangerouslySetInnerHTML={{ __html: safeSvg }} />;\n }\n if (isImageSource(source)) {\n return <img src={source} alt=\"\" className={className} />;\n }\n return null;\n};\n\n/**\n * Controlled brand-editing block — colors (light/dark), sidebar colors, WCAG contrast readout,\n * corner radius, typography, and logo. Reads/writes a `Brand` (`@keboola/brand-registry`) via\n * `value` / `onChange`; owns no state of its own, so the caller decides where the brand and the\n * active color scheme live (Kai build-mode preview, theme-generator, …).\n */\nexport const BrandEditor = ({\n value,\n onChange,\n colorScheme = 'light',\n onColorSchemeChange,\n fontOptions = DEFAULT_FONT_OPTIONS,\n}: BrandEditorProps) => {\n // Prefix every control id so multiple BrandEditors on one page don't collide (broken label↔control).\n const baseId = useId();\n const { theme } = value;\n const coreColors = colorScheme === 'light' ? theme.colors : theme.colorsDark;\n const sidebarColors = colorScheme === 'light' ? theme.colors?.sidebar : theme.colorsDark?.sidebar;\n const chartColors = colorScheme === 'light' ? theme.colors?.chart : theme.colorsDark?.chart;\n // Fall back to the keboola default of the SAME scheme — never light values while editing dark.\n const defaultColors = colorScheme === 'light' ? keboola.theme.colors : keboola.theme.colorsDark;\n const radii = theme.radii ?? DEFAULT_RADII;\n const typography = theme.typography;\n\n const setColor = (slot: ColorSlot, channels: string) => {\n if (colorScheme === 'light') {\n onChange({ ...value, theme: { ...theme, colors: { ...theme.colors, [slot]: channels } } });\n return;\n }\n // Keep colorsDark sparse (mirrors the light path) so an unset dark palette inherits the\n // design-system defaults instead of baking in a full keboola palette on the first edit.\n onChange({\n ...value,\n theme: { ...theme, colorsDark: { ...theme.colorsDark, [slot]: channels } },\n });\n };\n\n const setSidebarColor = (slot: SidebarSlot, channels: string) => {\n if (colorScheme === 'light') {\n const base = theme.colors?.sidebar ?? DEFAULT_SIDEBAR.light;\n onChange({\n ...value,\n theme: { ...theme, colors: { ...theme.colors, sidebar: { ...base, [slot]: channels } } },\n });\n return;\n }\n // Spread the existing (possibly-empty) colorsDark so setting a sidebar token doesn't seed the\n // whole core dark palette; only the sidebar sub-object is seeded, since it has no default to inherit.\n const sidebarBase = theme.colorsDark?.sidebar ?? DEFAULT_SIDEBAR.dark;\n onChange({\n ...value,\n theme: {\n ...theme,\n colorsDark: { ...theme.colorsDark, sidebar: { ...sidebarBase, [slot]: channels } },\n },\n });\n };\n\n const setChart = (slot: ChartSlot, channels: string) => {\n const base = chartColors ?? DEFAULT_CHART[colorScheme];\n const chart = { ...base, [slot]: channels };\n if (colorScheme === 'light') {\n onChange({ ...value, theme: { ...theme, colors: { ...theme.colors, chart } } });\n return;\n }\n onChange({ ...value, theme: { ...theme, colorsDark: { ...theme.colorsDark, chart } } });\n };\n\n const setRadius = (size: RadiusSize, radius: string) =>\n onChange({\n ...value,\n theme: { ...theme, radii: { ...(theme.radii ?? DEFAULT_RADII), [size]: radius } },\n });\n\n // Merge a typography patch, keeping sibling faces / weight / letterSpacing.\n const patchTypography = (patch: Partial<Typography>) =>\n onChange({ ...value, theme: { ...theme, typography: { ...theme.typography, ...patch } } });\n\n const setFontFace = (face: FontFace, font: string) =>\n patchTypography({ [face]: font.trim() || undefined });\n\n const setFontWeight = (slot: FontWeightSlot, weight: string) =>\n patchTypography({ weight: { ...theme.typography?.weight, [slot]: weight || undefined } });\n\n const setLetterSpacing = (chrome: string) =>\n patchTypography({ letterSpacing: { chrome: chrome.trim() || undefined } });\n\n const setShadow = (slot: ShadowSlot, shadow: string) =>\n onChange({\n ...value,\n theme: { ...theme, shadows: { ...theme.shadows, [slot]: shadow.trim() || undefined } },\n });\n\n const setSpacing = (slot: SpacingSlot, length: string) =>\n onChange({\n ...value,\n theme: { ...theme, spacing: { ...theme.spacing, [slot]: length.trim() || undefined } },\n });\n\n const setMotion = (slot: MotionSlot, token: string) =>\n onChange({\n ...value,\n theme: { ...theme, motion: { ...theme.motion, [slot]: token.trim() || undefined } },\n });\n\n const setGradient = (gradient: string) =>\n onChange({\n ...value,\n theme: { ...theme, gradient: { brand: gradient.trim() || undefined } },\n });\n\n const setLogo = (slot: keyof NonNullable<Brand['logo']>, source: string) => {\n const trimmed = source.trim();\n const nextLogo: NonNullable<Brand['logo']> = { ...value.logo };\n if (trimmed) nextLogo[slot] = trimmed;\n else delete nextLogo[slot];\n const { logo: _drop, ...rest } = value;\n onChange(Object.keys(nextLogo).length > 0 ? { ...value, logo: nextLogo } : rest);\n };\n\n return (\n <div className=\"tw:flex tw:flex-col\">\n <ControlSection title=\"Colors\">\n {onColorSchemeChange ? (\n <ToggleGroup\n type=\"single\"\n value={colorScheme}\n onValueChange={(next) => {\n if (next === 'light' || next === 'dark') onColorSchemeChange(next);\n }}\n aria-label=\"Color scheme\"\n className=\"tw:w-full\"\n >\n <ToggleGroup.Item value=\"light\" className=\"tw:flex-1\">\n Light\n </ToggleGroup.Item>\n <ToggleGroup.Item value=\"dark\" className=\"tw:flex-1\">\n Dark\n </ToggleGroup.Item>\n </ToggleGroup>\n ) : null}\n <div className=\"tw:flex tw:flex-col tw:gap-2\">\n {COLOR_SLOTS.map((slot) => (\n <ColorField\n key={slot}\n id={`${baseId}color-${colorScheme}-${slot}`}\n label={COLOR_LABELS[slot]}\n channels={coreColors?.[slot] ?? defaultColors?.[slot] ?? '0 0 0'}\n onChange={(channels) => setColor(slot, channels)}\n />\n ))}\n </div>\n </ControlSection>\n\n <ControlSection title=\"Accessibility\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n WCAG contrast for the {colorScheme} palette (AA needs 4.5:1).\n </p>\n <div className=\"tw:flex tw:flex-col tw:gap-1.5\">\n {A11Y_PAIRS.map((pair) => {\n const fg = coreColors?.[pair.fg] ?? defaultColors?.[pair.fg] ?? '0 0 0';\n const bg = coreColors?.[pair.bg] ?? defaultColors?.[pair.bg] ?? '255 255 255';\n const ratio = contrastRatio(fg, bg);\n const level = wcagLevel(ratio);\n return (\n <div key={pair.label} className=\"tw:flex tw:items-center tw:justify-between tw:gap-2\">\n <span className=\"tw:text-xs tw:text-foreground\">{pair.label}</span>\n <span className=\"tw:flex tw:items-center tw:gap-1.5\">\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {ratio.toFixed(1)}:1\n </span>\n <Badge color={level === 'fail' ? 'red' : 'green'}>\n {level === 'fail' ? 'Fail' : level}\n </Badge>\n </span>\n </div>\n );\n })}\n </div>\n </ControlSection>\n\n <ControlSection title=\"Sidebar colors\">\n <Collapsible className=\"tw:flex tw:flex-col tw:gap-2\">\n <Collapsible.Trigger asChild>\n <Button variant=\"outline\" size=\"small\" className=\"tw:w-full\">\n Toggle sidebar tokens ({colorScheme})\n </Button>\n </Collapsible.Trigger>\n <Collapsible.Content className=\"tw:flex tw:flex-col tw:gap-2\">\n {SIDEBAR_SLOTS.map((slot) => (\n <ColorField\n key={slot}\n id={`${baseId}sidebar-${colorScheme}-${slot}`}\n label={SIDEBAR_LABELS[slot]}\n channels={sidebarColors?.[slot] ?? DEFAULT_SIDEBAR[colorScheme][slot]}\n onChange={(channels) => setSidebarColor(slot, channels)}\n />\n ))}\n </Collapsible.Content>\n </Collapsible>\n </ControlSection>\n\n <ControlSection title=\"Chart palette\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Categorical series colours for the {colorScheme} palette (`--chart-1…5`).\n </p>\n <div className=\"tw:flex tw:flex-col tw:gap-2\">\n {CHART_SLOTS.map((slot) => (\n <ColorField\n key={slot}\n id={`${baseId}chart-${colorScheme}-${slot}`}\n label={CHART_LABELS[slot]}\n channels={chartColors?.[slot] ?? DEFAULT_CHART[colorScheme][slot]}\n onChange={(channels) => setChart(slot, channels)}\n />\n ))}\n </div>\n </ControlSection>\n\n <ControlSection title=\"Radius\">\n {RADIUS_SIZES.map((size) => {\n const current = radii[size];\n const px = lengthToPx(current);\n return (\n <div key={size} className=\"tw:flex tw:flex-col tw:gap-1\">\n <div className=\"tw:flex tw:items-center tw:justify-between\">\n <Label className=\"tw:text-xs tw:font-normal tw:uppercase tw:text-foreground\">\n {size}\n </Label>\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {radii[size]}\n </span>\n </div>\n <Slider\n min={0}\n max={24}\n step={1}\n value={[px]}\n onValueChange={(values) => setRadius(size, pxToLength(values[0] ?? 0, current))}\n />\n </div>\n );\n })}\n </ControlSection>\n\n <ControlSection title=\"Typography\">\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label\n htmlFor={`${baseId}font-family`}\n className=\"tw:text-xs tw:font-normal tw:text-foreground\"\n >\n Font family\n </Label>\n <Select\n id={`${baseId}font-family`}\n value={typography?.fontFamily ?? DEFAULT_FALLBACK_FONT}\n onValueChange={(font) => setFontFace('fontFamily', font)}\n items={fontOptions}\n />\n </div>\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label\n htmlFor={`${baseId}font-display`}\n className=\"tw:text-xs tw:font-normal tw:text-foreground\"\n >\n Display font (headings)\n </Label>\n <Select\n id={`${baseId}font-display`}\n value={typography?.fontDisplay ?? typography?.fontFamily ?? DEFAULT_FALLBACK_FONT}\n onValueChange={(font) => setFontFace('fontDisplay', font)}\n items={fontOptions}\n />\n </div>\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <Label\n htmlFor={`${baseId}font-mono`}\n className=\"tw:text-xs tw:font-normal tw:text-foreground\"\n >\n Monospace font\n </Label>\n <Select\n id={`${baseId}font-mono`}\n value={typography?.fontMono ?? MONO_FALLBACK}\n onValueChange={(font) => setFontFace('fontMono', font)}\n items={fontOptions}\n />\n </div>\n\n <span className=\"tw:text-xs tw:text-muted-foreground\">Chrome font weights</span>\n {WEIGHT_SLOTS.map((slot) => {\n const current = typography?.weight?.[slot] ?? DEFAULT_FONT_WEIGHTS[slot];\n return (\n <div key={slot} className=\"tw:flex tw:flex-col tw:gap-1\">\n <div className=\"tw:flex tw:items-center tw:justify-between\">\n <Label className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n {WEIGHT_LABELS[slot]}\n </Label>\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {current}\n </span>\n </div>\n <Slider\n min={100}\n max={900}\n step={25}\n value={[numOf(current, 400)]}\n onValueChange={(values) => setFontWeight(slot, String(values[0] ?? 400))}\n />\n </div>\n );\n })}\n\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <div className=\"tw:flex tw:items-center tw:justify-between\">\n <Label className=\"tw:text-xs tw:font-normal tw:text-foreground\">\n Chrome letter spacing\n </Label>\n <span className=\"tw:text-xs tw:tabular-nums tw:text-muted-foreground\">\n {typography?.letterSpacing?.chrome ?? DEFAULT_LETTER_SPACING}\n </span>\n </div>\n <Slider\n min={-2}\n max={4}\n step={0.25}\n value={[letterSpacingToPx(typography?.letterSpacing?.chrome ?? DEFAULT_LETTER_SPACING)]}\n onValueChange={(values) =>\n setLetterSpacing(\n pxToLetterSpacing(\n values[0] ?? 0,\n typography?.letterSpacing?.chrome ?? DEFAULT_LETTER_SPACING,\n ),\n )\n }\n />\n </div>\n </ControlSection>\n\n <ControlSection title=\"Elevation\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Box-shadow scale (`--shadow-*`), shared across light & dark. Empty inherits the default.\n </p>\n {SHADOW_SLOTS.map((slot) => (\n <TextField\n key={slot}\n id={`${baseId}shadow-${slot}`}\n label={SHADOW_LABELS[slot]}\n value={theme.shadows?.[slot] ?? ''}\n placeholder={DEFAULT_SHADOWS[slot]}\n onChange={(next) => setShadow(slot, next)}\n />\n ))}\n </ControlSection>\n\n <ControlSection title=\"Density\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Semantic spacing (`--space-*`), as CSS lengths.\n </p>\n {SPACING_SLOTS.map((slot) => (\n <TextField\n key={slot}\n id={`${baseId}spacing-${slot}`}\n label={SPACING_LABELS[slot]}\n value={theme.spacing?.[slot] ?? ''}\n placeholder={DEFAULT_SPACING[slot]}\n onChange={(next) => setSpacing(slot, next)}\n />\n ))}\n </ControlSection>\n\n <ControlSection title=\"Motion\">\n <p className=\"tw:m-0 tw:text-xs tw:text-muted-foreground\">\n Base transition `duration` + `easing` (`--motion-*`).\n </p>\n {MOTION_SLOTS.map((slot) => (\n <TextField\n key={slot}\n id={`${baseId}motion-${slot}`}\n label={MOTION_LABELS[slot]}\n value={theme.motion?.[slot] ?? ''}\n placeholder={DEFAULT_MOTION[slot]}\n onChange={(next) => setMotion(slot, next)}\n />\n ))}\n </ControlSection>\n\n <ControlSection title=\"Gradient\">\n <TextField\n id={`${baseId}gradient-brand`}\n label=\"Brand gradient\"\n value={theme.gradient?.brand ?? ''}\n placeholder={DEFAULT_GRADIENT}\n onChange={setGradient}\n />\n </ControlSection>\n\n <ControlSection title=\"Logo\">\n <LogoField\n id={`${baseId}logo-mark`}\n label=\"Mark (icon)\"\n value={value.logo?.mark ?? ''}\n onChange={(source) => setLogo('mark', source)}\n />\n <LogoField\n id={`${baseId}logo-wordmark`}\n label=\"Wordmark (full)\"\n value={value.logo?.wordmark ?? ''}\n onChange={(source) => setLogo('wordmark', source)}\n />\n <Collapsible className=\"tw:flex tw:flex-col tw:gap-2\">\n <Collapsible.Trigger asChild>\n <Button variant=\"outline\" size=\"small\" className=\"tw:w-full\">\n Toggle dark-mode logos\n </Button>\n </Collapsible.Trigger>\n <Collapsible.Content className=\"tw:flex tw:flex-col tw:gap-2\">\n <LogoField\n id={`${baseId}logo-mark-dark`}\n label=\"Mark (dark)\"\n value={value.logo?.markDark ?? ''}\n onChange={(source) => setLogo('markDark', source)}\n />\n <LogoField\n id={`${baseId}logo-wordmark-dark`}\n label=\"Wordmark (dark)\"\n value={value.logo?.wordmarkDark ?? ''}\n onChange={(source) => setLogo('wordmarkDark', source)}\n />\n </Collapsible.Content>\n </Collapsible>\n <div className=\"tw:flex tw:flex-col tw:gap-1\">\n <span className=\"tw:text-xs tw:text-muted-foreground\">Preview</span>\n <div className=\"tw:flex tw:min-h-12 tw:items-center tw:gap-4 tw:rounded-md tw:border tw:border-border tw:bg-background tw:p-3\">\n {value.logo?.mark || value.logo?.wordmark ? (\n <>\n {value.logo.mark ? (\n <LogoPreviewImage source={value.logo.mark} className=\"tw:h-8\" />\n ) : null}\n {value.logo.wordmark ? (\n <LogoPreviewImage source={value.logo.wordmark} className=\"tw:h-8\" />\n ) : null}\n </>\n ) : (\n <span className=\"tw:text-xs tw:text-muted-foreground\">No logo set</span>\n )}\n </div>\n </div>\n </ControlSection>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -17,7 +17,7 @@ const wrapperClass = "tw:inline-flex tw:h-6 tw:items-center tw:[&>svg]:h-full tw
17
17
  * Renders the active brand's logo from the nearest `<ThemeProvider>`, or `null`
18
18
  * if the brand ships none. Inline SVG inherits `currentColor`; URLs use `<img>`.
19
19
  */
20
- const BrandLogo = ({ variant = "wordmark", className }) => {
20
+ const BrandLogo = ({ variant = "wordmark", className, label }) => {
21
21
  const { currentTheme, resolvedScheme } = useTheme();
22
22
  const source = resolveLogoSource(currentTheme.logo, variant, resolvedScheme);
23
23
  const safeSvg = useMemo(() => source && isInlineSvg(source) ? DOMPurify.sanitize(source, { USE_PROFILES: {
@@ -27,7 +27,7 @@ const BrandLogo = ({ variant = "wordmark", className }) => {
27
27
  if (!source) return null;
28
28
  if (safeSvg !== null) return /* @__PURE__ */ jsx("span", {
29
29
  role: "img",
30
- "aria-label": currentTheme.name,
30
+ "aria-label": label ?? currentTheme.name,
31
31
  className: cn(wrapperClass, className),
32
32
  dangerouslySetInnerHTML: { __html: safeSvg }
33
33
  });
@@ -35,7 +35,7 @@ const BrandLogo = ({ variant = "wordmark", className }) => {
35
35
  className: cn(wrapperClass, className),
36
36
  children: /* @__PURE__ */ jsx("img", {
37
37
  src: source,
38
- alt: currentTheme.name,
38
+ alt: label ?? currentTheme.name,
39
39
  className: "tw:h-full tw:w-auto"
40
40
  })
41
41
  });
@@ -43,4 +43,4 @@ const BrandLogo = ({ variant = "wordmark", className }) => {
43
43
  //#endregion
44
44
  export { BrandLogo };
45
45
 
46
- //# sourceMappingURL=BrandLogo-Dmi7jo21.js.map
46
+ //# sourceMappingURL=BrandLogo-CoKBk4MI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrandLogo-CoKBk4MI.js","names":[],"sources":["../src/components/BrandLogo/BrandLogo.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport DOMPurify from 'dompurify';\n\nimport type { Brand } from '@keboola/brand-registry';\n\nimport { cn } from '../../utils';\nimport { type ResolvedColorScheme, useTheme } from '../ThemeProvider';\n\ntype Props = {\n /** `wordmark` (default, full logo) or `mark` (square icon); each falls back to the other. */\n variant?: 'mark' | 'wordmark';\n /** Sets the logo height; aspect ratio is preserved (e.g. `tw:h-7`). */\n className?: string;\n /**\n * The logo's accessible name.\n *\n * - Defaults to the brand's `name`, which is registry metadata: it labels the brand's row in a\n * picker, so it carries whatever disambiguates it there (`Keboola (V2)`).\n * - Pass the product's own name wherever the logo is what names something to a screen reader — a\n * link around it most of all, which otherwise announces that suffix.\n */\n label?: string;\n};\n\nconst isInlineSvg = (source: string) => source.trimStart().startsWith('<');\n\n// Resolved mode wins, then the other mode, then the other variant.\nconst resolveLogoSource = (\n logo: Brand['logo'],\n variant: 'mark' | 'wordmark',\n mode: ResolvedColorScheme,\n): string | undefined => {\n if (!logo) return undefined;\n const forMode = (light?: string, dark?: string) =>\n mode === 'dark' ? (dark ?? light) : (light ?? dark);\n const mark = forMode(logo.mark, logo.markDark);\n const wordmark = forMode(logo.wordmark, logo.wordmarkDark);\n return variant === 'mark' ? (mark ?? wordmark) : (wordmark ?? mark);\n};\n\n// Reference SVGs omit width/height, so without a default height a bare\n// `<BrandLogo />` renders at the browser's ~150px intrinsic size. `className` overrides it.\nconst wrapperClass = 'tw:inline-flex tw:h-6 tw:items-center tw:[&>svg]:h-full tw:[&>svg]:w-auto';\n\n/**\n * Renders the active brand's logo from the nearest `<ThemeProvider>`, or `null`\n * if the brand ships none. Inline SVG inherits `currentColor`; URLs use `<img>`.\n */\nexport const BrandLogo = ({ variant = 'wordmark', className, label }: Props) => {\n const { currentTheme, resolvedScheme } = useTheme();\n const source = resolveLogoSource(currentTheme.logo, variant, resolvedScheme);\n\n // A brand may come from customer config or a paste field, not just the repo.\n const safeSvg = useMemo(\n () =>\n source && isInlineSvg(source)\n ? DOMPurify.sanitize(source, { USE_PROFILES: { svg: true, svgFilters: true } })\n : null,\n [source],\n );\n\n if (!source) return null;\n\n if (safeSvg !== null) {\n return (\n <span\n role=\"img\"\n aria-label={label ?? currentTheme.name}\n className={cn(wrapperClass, className)}\n dangerouslySetInnerHTML={{ __html: safeSvg }}\n />\n );\n }\n\n return (\n <span className={cn(wrapperClass, className)}>\n <img src={source} alt={label ?? currentTheme.name} className=\"tw:h-full tw:w-auto\" />\n </span>\n );\n};\n"],"mappings":";;;;;;AAwBA,MAAM,eAAe,WAAmB,OAAO,UAAU,CAAC,CAAC,WAAW,GAAG;AAGzE,MAAM,qBACJ,MACA,SACA,SACuB;CACvB,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,MAAM,WAAW,OAAgB,SAC/B,SAAS,SAAU,QAAQ,QAAU,SAAS;CAChD,MAAM,OAAO,QAAQ,KAAK,MAAM,KAAK,QAAQ;CAC7C,MAAM,WAAW,QAAQ,KAAK,UAAU,KAAK,YAAY;CACzD,OAAO,YAAY,SAAU,QAAQ,WAAa,YAAY;AAChE;AAIA,MAAM,eAAe;;;;;AAMrB,MAAa,aAAa,EAAE,UAAU,YAAY,WAAW,YAAmB;CAC9E,MAAM,EAAE,cAAc,mBAAmB,SAAS;CAClD,MAAM,SAAS,kBAAkB,aAAa,MAAM,SAAS,cAAc;CAG3E,MAAM,UAAU,cAEZ,UAAU,YAAY,MAAM,IACxB,UAAU,SAAS,QAAQ,EAAE,cAAc;EAAE,KAAK;EAAM,YAAY;CAAK,EAAE,CAAC,IAC5E,MACN,CAAC,MAAM,CACT;CAEA,IAAI,CAAC,QAAQ,OAAO;CAEpB,IAAI,YAAY,MACd,OACE,oBAAC,QAAD;EACE,MAAK;EACL,cAAY,SAAS,aAAa;EAClC,WAAW,GAAG,cAAc,SAAS;EACrC,yBAAyB,EAAE,QAAQ,QAAQ;CAC5C,CAAA;CAIL,OACE,oBAAC,QAAD;EAAM,WAAW,GAAG,cAAc,SAAS;EACzC,UAAA,oBAAC,OAAD;GAAK,KAAK;GAAQ,KAAK,SAAS,aAAa;GAAM,WAAU;EAAuB,CAAA;CAChF,CAAA;AAEV"}
@@ -1,7 +1,7 @@
1
1
  import { cn } from "./classNames-BzrkDnOD.js";
2
2
  import { ChevronDown, ChevronRight, Ellipsis } from "./icons-Cek151jC.js";
3
3
  import { Popover } from "./Popover-BEbQrC_P.js";
4
- import { SearchInput } from "./SearchInput-f_oY9T4Z.js";
4
+ import { SearchInput } from "./SearchInput-D4UgAXJK.js";
5
5
  import { Slot } from "@radix-ui/react-slot";
6
6
  import { Children, createContext, isValidElement, use, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -327,4 +327,4 @@ Breadcrumb.Dropdown = BreadcrumbDropdown;
327
327
  //#endregion
328
328
  export { Breadcrumb };
329
329
 
330
- //# sourceMappingURL=Breadcrumb-uEkM6adI.js.map
330
+ //# sourceMappingURL=Breadcrumb-g_xn_-Nf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Breadcrumb-uEkM6adI.js","names":[],"sources":["../src/components/Breadcrumb/BreadcrumbDropdownContext.ts","../src/components/Breadcrumb/BreadcrumbDropdownGroup.tsx","../src/components/Breadcrumb/BreadcrumbDropdownItem.tsx","../src/components/Breadcrumb/BreadcrumbLink.tsx","../src/components/Breadcrumb/BreadcrumbDropdown.tsx","../src/components/Breadcrumb/BreadcrumbEllipsis.tsx","../src/components/Breadcrumb/BreadcrumbItem.tsx","../src/components/Breadcrumb/BreadcrumbList.tsx","../src/components/Breadcrumb/BreadcrumbPage.tsx","../src/components/Breadcrumb/BreadcrumbSeparator.tsx","../src/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import { createContext, isValidElement, useCallback, useMemo, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nexport type BreadcrumbDropdownContextValue = {\n /** Current (trimmed, lowercased) filter query. Empty string = no filtering. */\n query: string;\n /** An ancestor group's label matched the query — descendants render regardless of their own match. */\n forceVisible: boolean;\n /** Filtering is off (`filter={false}` or `shouldFilter={false}`) — items never self-hide. */\n filterDisabled: boolean;\n /** Report this node's visibility to the parent scope. Returns the unregister cleanup. */\n register: (id: string, visible: boolean) => () => void;\n /** Close the panel (fired by items on select). */\n close: () => void;\n};\n\nexport const BreadcrumbDropdownContext = createContext<BreadcrumbDropdownContextValue | null>(null);\n\nexport const matchesQuery = (text: string, query: string) => text.toLowerCase().includes(query);\n\n/** Text content of a React tree — the default filter text when no `textValue` is given. */\nexport const getNodeText = (node: ReactNode): string => {\n if (typeof node === 'string' || typeof node === 'number') return String(node);\n if (Array.isArray(node)) return node.map(getNodeText).join('');\n if (isValidElement<{ children?: ReactNode }>(node)) return getNodeText(node.props.children);\n return '';\n};\n\nexport const normalizeQuery = (query: string) => query.trim().toLowerCase();\n\n/**\n * Visibility registry for one filtering scope (the panel root, or one group). Items and groups\n * report their visibility here; `anyVisible` drives the group header / empty-state rendering.\n */\nexport const useMatchRegistry = () => {\n const [matches, setMatches] = useState<ReadonlyMap<string, boolean>>(new Map());\n\n const register = useCallback((id: string, visible: boolean) => {\n setMatches((prev) => (prev.get(id) === visible ? prev : new Map(prev).set(id, visible)));\n return () => {\n setMatches((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n };\n }, []);\n\n const anyVisible = useMemo(() => [...matches.values()].some(Boolean), [matches]);\n\n return { register, anyVisible };\n};\n","import { Children, use, useEffect, useId, useMemo, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport { ChevronDown, ChevronRight } from '../../icons';\nimport { cn } from '../../utils';\n\nimport {\n BreadcrumbDropdownContext,\n getNodeText,\n matchesQuery,\n useMatchRegistry,\n} from './BreadcrumbDropdownContext';\n\nconst headerClass = cn(\n // layout\n 'tw:flex',\n 'tw:w-full',\n 'tw:items-center',\n 'tw:gap-2',\n 'tw:box-border',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:px-2',\n 'tw:py-1.5',\n 'tw:text-left',\n\n // font — single line; wrap the label in `Truncated` (design system) for ellipsis + tooltip\n 'tw:text-sm',\n 'tw:font-medium',\n 'tw:text-foreground',\n 'tw:min-w-0',\n 'tw:overflow-hidden',\n\n // icons\n \"tw:[&_svg:not([class*='tw:size-'])]:size-4\",\n 'tw:[&_svg]:shrink-0',\n);\n\nconst collapsibleHeaderClass = cn(\n 'tw:cursor-pointer',\n 'tw:hover:bg-accent',\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n);\n\ntype BreadcrumbDropdownGroupProps = {\n /** Group header (e.g. bucket name + stage badge). */\n label: ReactNode;\n /**\n * Text the built-in filter matches against. When the group label matches, all its items stay\n * visible. Defaults to the label's rendered text content.\n */\n textValue?: string;\n /** Render the header as a toggle that expands / collapses the group's items. Default `false`. */\n collapsible?: boolean;\n /** Initial expanded state for `collapsible` groups. Default `true`. */\n defaultExpanded?: boolean;\n className?: string;\n children: ReactNode;\n};\n\n/**\n * A labelled group of `Breadcrumb.Dropdown.Item`s (e.g. one bucket with its tables). The group\n * hides itself when neither its label nor any child matches the filter query; an active query\n * force-expands collapsible groups. Non-empty `collapsible` groups toggle their items behind the\n * header with an animated expand / collapse; a `collapsible` group with no items renders a\n * non-interactive, greyed header that can't toggle.\n */\nexport const BreadcrumbDropdownGroup = ({\n label,\n textValue,\n collapsible = false,\n defaultExpanded = true,\n className,\n children,\n}: BreadcrumbDropdownGroupProps) => {\n const parent = use(BreadcrumbDropdownContext);\n if (!parent) {\n throw new Error('Breadcrumb.Dropdown.Group must be rendered inside Breadcrumb.Dropdown');\n }\n const id = useId();\n const { register: registerChild, anyVisible } = useMatchRegistry();\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n const text = textValue ?? getNodeText(label);\n const filtering = !parent.filterDisabled && !!parent.query;\n const labelMatched = filtering && matchesQuery(text, parent.query);\n const visible = !filtering || parent.forceVisible || labelMatched || anyVisible;\n\n useEffect(() => parent.register(id, visible), [parent, id, visible]);\n\n const childContext = useMemo(\n () => ({\n query: parent.query,\n forceVisible: parent.forceVisible || labelMatched,\n filterDisabled: parent.filterDisabled,\n register: registerChild,\n close: parent.close,\n }),\n [parent, labelMatched, registerChild],\n );\n\n // An active query force-expands the group so matches are never hidden behind a collapsed header.\n const isExpanded = !collapsible || expanded || filtering;\n // A collapsible group with no items has nothing to toggle — render a non-interactive header.\n // oxlint-disable-next-line react-x/no-children-count -- emptiness check, no child transformation\n const isEmpty = Children.count(children) === 0;\n\n return (\n <div\n className={cn('tw:flex tw:flex-col', className)}\n style={visible ? undefined : { display: 'none' }}\n >\n {collapsible && isEmpty ? (\n <span\n aria-disabled\n className={cn(\n headerClass,\n 'tw:text-muted-foreground',\n // Fade just the leading chevron lighter than the label text.\n 'tw:[&>svg:first-child]:opacity-50',\n )}\n >\n <ChevronRight />\n {label}\n </span>\n ) : collapsible && !isEmpty ? (\n <button\n type=\"button\"\n aria-expanded={isExpanded}\n className={cn(headerClass, collapsibleHeaderClass)}\n onClick={() => setExpanded((prev) => !prev)}\n >\n {isExpanded ? <ChevronDown /> : <ChevronRight />}\n {label}\n </button>\n ) : (\n <span className={headerClass}>{label}</span>\n )}\n <BreadcrumbDropdownContext value={childContext}>\n {collapsible && !isEmpty ? (\n // Animated expand / collapse via a CSS grid-rows transition (0fr ↔ 1fr). The children stay\n // mounted while collapsed so the filter registry keeps receiving their visibility; `inert`\n // + `aria-hidden` take the collapsed content out of the a11y tree and tab order. An active\n // filter query force-expands (`isExpanded` includes `filtering`).\n <div\n className={cn(\n 'tw:grid tw:transition-[grid-template-rows] tw:duration-200 tw:ease-out',\n 'tw:motion-reduce:transition-none',\n isExpanded ? 'tw:grid-rows-[1fr]' : 'tw:grid-rows-[0fr]',\n )}\n >\n <div\n className=\"tw:overflow-hidden\"\n inert={!isExpanded}\n aria-hidden={isExpanded ? undefined : true}\n >\n <div className=\"tw:flex tw:flex-col tw:pl-6\">{children}</div>\n </div>\n </div>\n ) : (\n <div\n className=\"tw:flex tw:flex-col tw:pl-6\"\n style={isExpanded ? undefined : { display: 'none' }}\n >\n {children}\n </div>\n )}\n </BreadcrumbDropdownContext>\n </div>\n );\n};\n","import { use, useEffect, useId } from 'react';\nimport type { ComponentProps } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { cn } from '../../utils';\n\nimport { BreadcrumbDropdownContext, getNodeText, matchesQuery } from './BreadcrumbDropdownContext';\n\nconst itemClass = cn(\n // layout\n 'tw:flex',\n 'tw:w-full',\n 'tw:items-center',\n 'tw:gap-2',\n 'tw:box-border',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:px-2',\n 'tw:py-1.5',\n 'tw:cursor-pointer',\n 'tw:text-left',\n\n // font — single line; wrap the label in `Truncated` (design system) for ellipsis + tooltip\n 'tw:text-sm',\n 'tw:font-normal',\n 'tw:text-foreground',\n 'tw:no-underline',\n 'tw:min-w-0',\n 'tw:overflow-hidden',\n\n // hover / active entity\n 'tw:hover:bg-accent',\n 'tw:data-[active]:bg-accent',\n 'tw:data-[active]:font-medium',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n\n // icons\n \"tw:[&_svg:not([class*='tw:size-'])]:size-4\",\n 'tw:[&_svg]:shrink-0',\n);\n\ntype BreadcrumbDropdownItemProps = ComponentProps<'button'> & {\n /** Render the consumer's element (typically a router link) instead of a `button`. */\n asChild?: boolean;\n /**\n * Text the built-in filter matches against. Defaults to the item's rendered text content\n * (extracted recursively, so `asChild` links with icons work out of the box).\n */\n textValue?: string;\n /** Marks the currently open entity (highlight + `aria-current`). */\n active?: boolean;\n /** Close the panel when the item is selected. Default `true`. */\n closeOnSelect?: boolean;\n};\n\n/**\n * One selectable entity row inside `Breadcrumb.Dropdown`. Self-hides when it doesn't match the\n * panel's filter query and closes the panel on select. Use `asChild` to render a router link.\n */\nexport const BreadcrumbDropdownItem = ({\n ref,\n asChild = false,\n textValue,\n active = false,\n closeOnSelect = true,\n className,\n children,\n onClick,\n ...props\n}: BreadcrumbDropdownItemProps) => {\n const context = use(BreadcrumbDropdownContext);\n if (!context) {\n throw new Error('Breadcrumb.Dropdown.Item must be rendered inside Breadcrumb.Dropdown');\n }\n const id = useId();\n\n const text = textValue ?? getNodeText(children);\n const visible =\n context.filterDisabled ||\n !context.query ||\n context.forceVisible ||\n matchesQuery(text, context.query);\n\n useEffect(() => context.register(id, visible), [context, id, visible]);\n\n if (!visible) return null;\n\n const Comp = asChild ? Slot : 'button';\n return (\n <Comp\n ref={ref}\n {...(asChild ? {} : { type: 'button' as const })}\n data-active={active || undefined}\n aria-current={active || undefined}\n className={cn(itemClass, className)}\n onClick={(event) => {\n onClick?.(event);\n if (closeOnSelect && !event.defaultPrevented) {\n context.close();\n }\n }}\n {...props}\n >\n {children}\n </Comp>\n );\n};\n","import type { ComponentProps } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'a'> & {\n asChild?: boolean;\n};\n\nexport const BreadcrumbLink = ({ ref, asChild = false, className, ...props }: Props) => {\n const Comp = asChild ? Slot : 'a';\n return (\n <Comp\n ref={ref}\n className={cn(\n 'tw:flex',\n 'tw:items-center',\n 'tw:gap-1.5',\n // font\n 'tw:text-muted-foreground',\n 'tw:font-normal',\n 'tw:no-underline',\n\n // hover\n 'tw:hover:text-foreground',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n 'tw:focus-visible:rounded-sm',\n\n // Icon\n 'tw:[&_svg:not([class*=\"tw:size-\"])]:size-3.5',\n\n // transition\n 'tw:transition-colors',\n className,\n )}\n {...props}\n />\n );\n};\n","import { useCallback, useMemo, useRef, useState } from 'react';\nimport type { ComponentRef, ReactNode } from 'react';\n\nimport { SearchInput } from '../../blocks/SearchInput';\nimport { ChevronDown } from '../../icons';\nimport { cn } from '../../utils';\nimport { Popover } from '../Popover';\n\nimport {\n BreadcrumbDropdownContext,\n normalizeQuery,\n useMatchRegistry,\n} from './BreadcrumbDropdownContext';\nimport { BreadcrumbDropdownGroup } from './BreadcrumbDropdownGroup';\nimport { BreadcrumbDropdownItem } from './BreadcrumbDropdownItem';\nimport { BreadcrumbLink } from './BreadcrumbLink';\n\n// Chevron trigger — the only button in the crumb. It sits tight next to the label so it reads as\n// grouped with it, with no persistent outline: it's a button purely via the hover / open background\n// + colour lift. Used on its own next to a link label (`splitTrigger`) or after a plain label.\nconst chevronButtonClass = cn(\n // layout\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:cursor-pointer',\n // compact square hit target that stays snug against the label\n 'tw:size-5',\n 'tw:text-muted-foreground',\n 'tw:transition-colors',\n 'tw:hover:bg-accent',\n 'tw:hover:text-foreground',\n 'tw:data-[state=open]:bg-accent',\n 'tw:data-[state=open]:text-foreground',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n\n // chevron rotation while open\n 'tw:[&_svg]:transition-transform',\n 'tw:[&[data-state=open]_svg]:rotate-180',\n);\n\n// Plain (non-link) label — same typography as `BreadcrumbLink` but no hover/focus treatment, since\n// it isn't interactive: only the chevron opens the panel.\nconst plainLabelClass = cn(\n 'tw:flex',\n 'tw:items-center',\n 'tw:gap-1.5',\n 'tw:text-muted-foreground',\n 'tw:font-normal',\n 'tw:[&_svg:not([class*=\"tw:size-\"])]:size-3.5',\n);\n\n// Fixed panel width per `size` (default `medium`). Long names truncate rather than wrap or widen\n// the panel. Width still clamps to the viewport via min(); height is clamped the same way.\nconst PANEL_BASE = 'tw:max-h-[min(640px,var(--radix-popover-content-available-height))]';\n\nconst PANEL_WIDTH = {\n small: 'tw:w-[min(360px,var(--radix-popover-content-available-width))]',\n medium: 'tw:w-[min(440px,var(--radix-popover-content-available-width))]',\n large: 'tw:w-[min(560px,var(--radix-popover-content-available-width))]',\n} as const;\n\ntype PanelSize = keyof typeof PANEL_WIDTH;\n\n/**\n * Props for `Breadcrumb.Dropdown`. The panel chrome (trigger typography, popover sizing, pinned\n * filter input) is owned by the component — consumers supply the label and the panel content,\n * composed from `Breadcrumb.Dropdown.Group` / `Breadcrumb.Dropdown.Item`.\n */\ntype BreadcrumbDropdownProps = {\n /**\n * Crumb label, shown next to the chevron button that opens the panel — the label itself is never\n * the trigger. With `splitTrigger`, pass an anchor-like element (e.g. a router link) so the label\n * also navigates; otherwise it renders as a plain, non-navigational crumb label.\n */\n label: ReactNode;\n /**\n * When `true`, the label is a navigable link (pass an anchor-like `label` element, rendered through\n * `Breadcrumb.Link asChild`). Default `false` — the label is plain text. Either way only the\n * chevron button opens the panel; it always sits grouped tight to the label.\n */\n splitTrigger?: boolean;\n /**\n * Accessible name for the chevron trigger button. Defaults to `\"${label} menu\"` when `label` is\n * a plain string, otherwise `\"Open menu\"` — pass explicitly when `label` is a non-string node.\n */\n triggerLabel?: string;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * Fixed panel width: `small` (360px), `medium` (440px, default), or `large` (560px). Names that\n * exceed the width truncate with an ellipsis (hover shows the full name). Clamps to the viewport.\n */\n size?: PanelSize;\n /**\n * Pinned filter input at the top of the panel, focused on open. On by default — items and groups\n * that don't match the query hide themselves (matched against their `textValue` / string\n * content). Set to `false` to opt out of the input entirely.\n */\n filter?: boolean;\n /**\n * Set to `false` to keep the filter input but disable the built-in matching — the consumer\n * filters the content it renders (e.g. server-side or custom matching). Default `true`.\n */\n shouldFilter?: boolean;\n /** Controlled filter value. Omit to let the component manage the query internally. */\n searchValue?: string;\n onSearchChange?: (value: string) => void;\n searchPlaceholder?: string;\n /** Rendered when the built-in filter hides everything. Default `\"No results.\"`. */\n emptyMessage?: ReactNode;\n /** Panel content — `Breadcrumb.Dropdown.Group` / `Breadcrumb.Dropdown.Item` elements. */\n children: ReactNode;\n};\n\n/**\n * Breadcrumb segment that opens a standardized switcher panel — a filterable list / tree of\n * sibling entities the user can jump between (e.g. the Storage bucket/table tree on a table\n * detail page).\n *\n * **When to use:** on the leftmost breadcrumb segment that has siblings / children worth switching\n * between. Use at most one dropdown segment per breadcrumb.\n * **When NOT to use:** for tertiary actions on the breadcrumbs row use the `overflowActions` slot of\n * `PageHeader.Detail`; for a collapsed middle of a long breadcrumb use `Breadcrumb.Ellipsis`.\n * **Composition:** render inside `Breadcrumb.Item` in place of `Breadcrumb.Link`. Compose the panel\n * from `Breadcrumb.Dropdown.Group` and `Breadcrumb.Dropdown.Item` — the built-in filter matches\n * and hides them automatically.\n */\nexport const BreadcrumbDropdown = ({\n label,\n splitTrigger = false,\n triggerLabel,\n open,\n defaultOpen = false,\n onOpenChange,\n size = 'medium',\n filter = true,\n shouldFilter = true,\n searchValue,\n onSearchChange,\n searchPlaceholder = 'Search…',\n emptyMessage = 'No results.',\n children,\n}: BreadcrumbDropdownProps) => {\n const searchRef = useRef<ComponentRef<'input'>>(null);\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const [internalQuery, setInternalQuery] = useState('');\n const { register, anyVisible } = useMatchRegistry();\n\n const isOpen = open ?? internalOpen;\n const handleOpenChange = useCallback(\n (nextOpen: boolean) => {\n setInternalOpen(nextOpen);\n if (!nextOpen) setInternalQuery('');\n onOpenChange?.(nextOpen);\n },\n [onOpenChange],\n );\n const close = useCallback(() => handleOpenChange(false), [handleOpenChange]);\n\n const effectiveTriggerLabel =\n triggerLabel ?? (typeof label === 'string' ? `${label} menu` : 'Open menu');\n\n const rawQuery = searchValue ?? internalQuery;\n const filterDisabled = !filter || !shouldFilter;\n const query = filterDisabled ? '' : normalizeQuery(rawQuery);\n\n const contextValue = useMemo(\n () => ({ query, forceVisible: false, filterDisabled, register, close }),\n [query, filterDisabled, register, close],\n );\n\n return (\n <Popover open={isOpen} onOpenChange={handleOpenChange}>\n <span className=\"tw:flex tw:items-center tw:gap-0.5\">\n {splitTrigger ? (\n <BreadcrumbLink asChild>{label}</BreadcrumbLink>\n ) : (\n <span className={plainLabelClass}>{label}</span>\n )}\n <Popover.Trigger asChild>\n <button type=\"button\" aria-label={effectiveTriggerLabel} className={chevronButtonClass}>\n <ChevronDown className=\"tw:size-3.5\" />\n </button>\n </Popover.Trigger>\n </span>\n <Popover.Content\n align=\"start\"\n sideOffset={8}\n collisionPadding={16}\n data-size={size}\n className={cn('tw:overflow-hidden tw:p-0', PANEL_BASE, PANEL_WIDTH[size])}\n onOpenAutoFocus={\n filter\n ? (event) => {\n event.preventDefault();\n searchRef.current?.focus();\n }\n : undefined\n }\n >\n {filter && (\n <div className=\"tw:shrink-0 tw:border-0 tw:border-b tw:border-solid tw:border-border tw:p-2\">\n <SearchInput\n ref={searchRef}\n value={rawQuery}\n onValueChange={(value) => {\n setInternalQuery(value);\n onSearchChange?.(value);\n }}\n placeholder={searchPlaceholder}\n allowClear\n // Built-in matching is client-side and cheap — filter on every keystroke. Keep the\n // debounce only when the consumer filters itself (shouldFilter={false}, e.g. server-side).\n disableDebounce={shouldFilter}\n />\n </div>\n )}\n <div className=\"tw:flex tw:flex-1 tw:flex-col tw:overflow-y-auto tw:p-2\">\n <BreadcrumbDropdownContext value={contextValue}>{children}</BreadcrumbDropdownContext>\n {!filterDisabled && !!query && !anyVisible && (\n <span className=\"tw:px-2 tw:py-1.5 tw:text-sm tw:text-muted-foreground\">\n {emptyMessage}\n </span>\n )}\n </div>\n </Popover.Content>\n </Popover>\n );\n};\n\nBreadcrumbDropdown.Item = BreadcrumbDropdownItem;\nBreadcrumbDropdown.Group = BreadcrumbDropdownGroup;\n","import type { ComponentProps } from 'react';\n\nimport { Ellipsis } from '../../icons';\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'button'>;\n\nexport const BreadcrumbEllipsis = ({ ref, className, ...props }: Props) => (\n <button\n ref={ref}\n type=\"button\"\n className={cn(\n // layout\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:cursor-pointer',\n 'tw:text-muted-foreground',\n 'tw:transition-colors',\n // Match the Breadcrumb.Dropdown chevron trigger: background + colour lift on hover / while the\n // menu is open, so the two interactive breadcrumb affordances read consistently.\n 'tw:hover:bg-accent',\n 'tw:hover:text-foreground',\n 'tw:data-[state=open]:bg-accent',\n 'tw:data-[state=open]:text-foreground',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n\n 'tw:p-1',\n className,\n )}\n {...props}\n >\n <Ellipsis className=\"tw:size-4\" />\n <span className=\"tw:sr-only\">More</span>\n </button>\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'li'>;\n\nexport const BreadcrumbItem = ({ className, ...props }: Props) => (\n <li className={cn('tw:inline-flex', 'tw:items-center', className)} {...props} />\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'ol'>;\n\nexport const BreadcrumbList = ({ className, ...props }: Props) => (\n <ol\n className={cn(\n // layout\n 'tw:flex',\n 'tw:flex-wrap',\n 'tw:items-center',\n 'tw:gap-2',\n 'tw:m-0',\n 'tw:p-0',\n\n // font\n 'tw:break-words',\n 'tw:text-sm',\n 'tw:text-muted-foreground',\n className,\n )}\n {...props}\n />\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'span'>;\n\nexport const BreadcrumbPage = ({ ref, className, ...props }: Props) => (\n <span\n ref={ref}\n // Do not use the anchor, as this item is not clickable\n // oxlint-disable-next-line jsx_a11y/prefer-tag-over-role\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('tw:font-medium', 'tw:text-foreground', className)}\n {...props}\n />\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'li'>;\n\nexport const BreadcrumbSeparator = ({ children, className, ...props }: Props) => (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\n // layout\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:aspect-square',\n 'tw:size-[15px]',\n 'tw:[&_svg:not([class*=\"tw:size-\"])]:size-2.5',\n 'tw:text-muted-foreground',\n className,\n )}\n {...props}\n >\n {children ?? <span className=\"tw:select-none tw:text-sm tw:text-muted-foreground/50\">/</span>}\n </li>\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\nimport { BreadcrumbDropdown } from './BreadcrumbDropdown';\nimport { BreadcrumbEllipsis } from './BreadcrumbEllipsis';\nimport { BreadcrumbItem } from './BreadcrumbItem';\nimport { BreadcrumbLink } from './BreadcrumbLink';\nimport { BreadcrumbList } from './BreadcrumbList';\nimport { BreadcrumbPage } from './BreadcrumbPage';\nimport { BreadcrumbSeparator } from './BreadcrumbSeparator';\n\ntype Props = ComponentProps<'nav'>;\n\nexport const Breadcrumb = ({ className, ...props }: Props) => (\n <nav aria-label=\"breadcrumb\" className={cn('tailwindcss-preflight', className)} {...props} />\n);\n\nBreadcrumb.List = BreadcrumbList;\nBreadcrumb.Item = BreadcrumbItem;\nBreadcrumb.Link = BreadcrumbLink;\nBreadcrumb.Page = BreadcrumbPage;\nBreadcrumb.Separator = BreadcrumbSeparator;\nBreadcrumb.Ellipsis = BreadcrumbEllipsis;\nBreadcrumb.Dropdown = BreadcrumbDropdown;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Breadcrumb-g_xn_-Nf.js","names":[],"sources":["../src/components/Breadcrumb/BreadcrumbDropdownContext.ts","../src/components/Breadcrumb/BreadcrumbDropdownGroup.tsx","../src/components/Breadcrumb/BreadcrumbDropdownItem.tsx","../src/components/Breadcrumb/BreadcrumbLink.tsx","../src/components/Breadcrumb/BreadcrumbDropdown.tsx","../src/components/Breadcrumb/BreadcrumbEllipsis.tsx","../src/components/Breadcrumb/BreadcrumbItem.tsx","../src/components/Breadcrumb/BreadcrumbList.tsx","../src/components/Breadcrumb/BreadcrumbPage.tsx","../src/components/Breadcrumb/BreadcrumbSeparator.tsx","../src/components/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["import { createContext, isValidElement, useCallback, useMemo, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nexport type BreadcrumbDropdownContextValue = {\n /** Current (trimmed, lowercased) filter query. Empty string = no filtering. */\n query: string;\n /** An ancestor group's label matched the query — descendants render regardless of their own match. */\n forceVisible: boolean;\n /** Filtering is off (`filter={false}` or `shouldFilter={false}`) — items never self-hide. */\n filterDisabled: boolean;\n /** Report this node's visibility to the parent scope. Returns the unregister cleanup. */\n register: (id: string, visible: boolean) => () => void;\n /** Close the panel (fired by items on select). */\n close: () => void;\n};\n\nexport const BreadcrumbDropdownContext = createContext<BreadcrumbDropdownContextValue | null>(null);\n\nexport const matchesQuery = (text: string, query: string) => text.toLowerCase().includes(query);\n\n/** Text content of a React tree — the default filter text when no `textValue` is given. */\nexport const getNodeText = (node: ReactNode): string => {\n if (typeof node === 'string' || typeof node === 'number') return String(node);\n if (Array.isArray(node)) return node.map(getNodeText).join('');\n if (isValidElement<{ children?: ReactNode }>(node)) return getNodeText(node.props.children);\n return '';\n};\n\nexport const normalizeQuery = (query: string) => query.trim().toLowerCase();\n\n/**\n * Visibility registry for one filtering scope (the panel root, or one group). Items and groups\n * report their visibility here; `anyVisible` drives the group header / empty-state rendering.\n */\nexport const useMatchRegistry = () => {\n const [matches, setMatches] = useState<ReadonlyMap<string, boolean>>(new Map());\n\n const register = useCallback((id: string, visible: boolean) => {\n setMatches((prev) => (prev.get(id) === visible ? prev : new Map(prev).set(id, visible)));\n return () => {\n setMatches((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n };\n }, []);\n\n const anyVisible = useMemo(() => [...matches.values()].some(Boolean), [matches]);\n\n return { register, anyVisible };\n};\n","import { Children, use, useEffect, useId, useMemo, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport { ChevronDown, ChevronRight } from '../../icons';\nimport { cn } from '../../utils';\n\nimport {\n BreadcrumbDropdownContext,\n getNodeText,\n matchesQuery,\n useMatchRegistry,\n} from './BreadcrumbDropdownContext';\n\nconst headerClass = cn(\n // layout\n 'tw:flex',\n 'tw:w-full',\n 'tw:items-center',\n 'tw:gap-2',\n 'tw:box-border',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:px-2',\n 'tw:py-1.5',\n 'tw:text-left',\n\n // font — single line; wrap the label in `Truncated` (design system) for ellipsis + tooltip\n 'tw:text-sm',\n 'tw:font-medium',\n 'tw:text-foreground',\n 'tw:min-w-0',\n 'tw:overflow-hidden',\n\n // icons\n \"tw:[&_svg:not([class*='tw:size-'])]:size-4\",\n 'tw:[&_svg]:shrink-0',\n);\n\nconst collapsibleHeaderClass = cn(\n 'tw:cursor-pointer',\n 'tw:hover:bg-accent',\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n);\n\ntype BreadcrumbDropdownGroupProps = {\n /** Group header (e.g. bucket name + stage badge). */\n label: ReactNode;\n /**\n * Text the built-in filter matches against. When the group label matches, all its items stay\n * visible. Defaults to the label's rendered text content.\n */\n textValue?: string;\n /** Render the header as a toggle that expands / collapses the group's items. Default `false`. */\n collapsible?: boolean;\n /** Initial expanded state for `collapsible` groups. Default `true`. */\n defaultExpanded?: boolean;\n className?: string;\n children: ReactNode;\n};\n\n/**\n * A labelled group of `Breadcrumb.Dropdown.Item`s (e.g. one bucket with its tables). The group\n * hides itself when neither its label nor any child matches the filter query; an active query\n * force-expands collapsible groups. Non-empty `collapsible` groups toggle their items behind the\n * header with an animated expand / collapse; a `collapsible` group with no items renders a\n * non-interactive, greyed header that can't toggle.\n */\nexport const BreadcrumbDropdownGroup = ({\n label,\n textValue,\n collapsible = false,\n defaultExpanded = true,\n className,\n children,\n}: BreadcrumbDropdownGroupProps) => {\n const parent = use(BreadcrumbDropdownContext);\n if (!parent) {\n throw new Error('Breadcrumb.Dropdown.Group must be rendered inside Breadcrumb.Dropdown');\n }\n const id = useId();\n const { register: registerChild, anyVisible } = useMatchRegistry();\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n const text = textValue ?? getNodeText(label);\n const filtering = !parent.filterDisabled && !!parent.query;\n const labelMatched = filtering && matchesQuery(text, parent.query);\n const visible = !filtering || parent.forceVisible || labelMatched || anyVisible;\n\n useEffect(() => parent.register(id, visible), [parent, id, visible]);\n\n const childContext = useMemo(\n () => ({\n query: parent.query,\n forceVisible: parent.forceVisible || labelMatched,\n filterDisabled: parent.filterDisabled,\n register: registerChild,\n close: parent.close,\n }),\n [parent, labelMatched, registerChild],\n );\n\n // An active query force-expands the group so matches are never hidden behind a collapsed header.\n const isExpanded = !collapsible || expanded || filtering;\n // A collapsible group with no items has nothing to toggle — render a non-interactive header.\n // oxlint-disable-next-line react-x/no-children-count -- emptiness check, no child transformation\n const isEmpty = Children.count(children) === 0;\n\n return (\n <div\n className={cn('tw:flex tw:flex-col', className)}\n style={visible ? undefined : { display: 'none' }}\n >\n {collapsible && isEmpty ? (\n <span\n aria-disabled\n className={cn(\n headerClass,\n 'tw:text-muted-foreground',\n // Fade just the leading chevron lighter than the label text.\n 'tw:[&>svg:first-child]:opacity-50',\n )}\n >\n <ChevronRight />\n {label}\n </span>\n ) : collapsible && !isEmpty ? (\n <button\n type=\"button\"\n aria-expanded={isExpanded}\n className={cn(headerClass, collapsibleHeaderClass)}\n onClick={() => setExpanded((prev) => !prev)}\n >\n {isExpanded ? <ChevronDown /> : <ChevronRight />}\n {label}\n </button>\n ) : (\n <span className={headerClass}>{label}</span>\n )}\n <BreadcrumbDropdownContext value={childContext}>\n {collapsible && !isEmpty ? (\n // Animated expand / collapse via a CSS grid-rows transition (0fr ↔ 1fr). The children stay\n // mounted while collapsed so the filter registry keeps receiving their visibility; `inert`\n // + `aria-hidden` take the collapsed content out of the a11y tree and tab order. An active\n // filter query force-expands (`isExpanded` includes `filtering`).\n <div\n className={cn(\n 'tw:grid tw:transition-[grid-template-rows] tw:duration-200 tw:ease-out',\n 'tw:motion-reduce:transition-none',\n isExpanded ? 'tw:grid-rows-[1fr]' : 'tw:grid-rows-[0fr]',\n )}\n >\n <div\n className=\"tw:overflow-hidden\"\n inert={!isExpanded}\n aria-hidden={isExpanded ? undefined : true}\n >\n <div className=\"tw:flex tw:flex-col tw:pl-6\">{children}</div>\n </div>\n </div>\n ) : (\n <div\n className=\"tw:flex tw:flex-col tw:pl-6\"\n style={isExpanded ? undefined : { display: 'none' }}\n >\n {children}\n </div>\n )}\n </BreadcrumbDropdownContext>\n </div>\n );\n};\n","import { use, useEffect, useId } from 'react';\nimport type { ComponentProps } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { cn } from '../../utils';\n\nimport { BreadcrumbDropdownContext, getNodeText, matchesQuery } from './BreadcrumbDropdownContext';\n\nconst itemClass = cn(\n // layout\n 'tw:flex',\n 'tw:w-full',\n 'tw:items-center',\n 'tw:gap-2',\n 'tw:box-border',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:px-2',\n 'tw:py-1.5',\n 'tw:cursor-pointer',\n 'tw:text-left',\n\n // font — single line; wrap the label in `Truncated` (design system) for ellipsis + tooltip\n 'tw:text-sm',\n 'tw:font-normal',\n 'tw:text-foreground',\n 'tw:no-underline',\n 'tw:min-w-0',\n 'tw:overflow-hidden',\n\n // hover / active entity\n 'tw:hover:bg-accent',\n 'tw:data-[active]:bg-accent',\n 'tw:data-[active]:font-medium',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n\n // icons\n \"tw:[&_svg:not([class*='tw:size-'])]:size-4\",\n 'tw:[&_svg]:shrink-0',\n);\n\ntype BreadcrumbDropdownItemProps = ComponentProps<'button'> & {\n /** Render the consumer's element (typically a router link) instead of a `button`. */\n asChild?: boolean;\n /**\n * Text the built-in filter matches against. Defaults to the item's rendered text content\n * (extracted recursively, so `asChild` links with icons work out of the box).\n */\n textValue?: string;\n /** Marks the currently open entity (highlight + `aria-current`). */\n active?: boolean;\n /** Close the panel when the item is selected. Default `true`. */\n closeOnSelect?: boolean;\n};\n\n/**\n * One selectable entity row inside `Breadcrumb.Dropdown`. Self-hides when it doesn't match the\n * panel's filter query and closes the panel on select. Use `asChild` to render a router link.\n */\nexport const BreadcrumbDropdownItem = ({\n ref,\n asChild = false,\n textValue,\n active = false,\n closeOnSelect = true,\n className,\n children,\n onClick,\n ...props\n}: BreadcrumbDropdownItemProps) => {\n const context = use(BreadcrumbDropdownContext);\n if (!context) {\n throw new Error('Breadcrumb.Dropdown.Item must be rendered inside Breadcrumb.Dropdown');\n }\n const id = useId();\n\n const text = textValue ?? getNodeText(children);\n const visible =\n context.filterDisabled ||\n !context.query ||\n context.forceVisible ||\n matchesQuery(text, context.query);\n\n useEffect(() => context.register(id, visible), [context, id, visible]);\n\n if (!visible) return null;\n\n const Comp = asChild ? Slot : 'button';\n return (\n <Comp\n ref={ref}\n {...(asChild ? {} : { type: 'button' as const })}\n data-active={active || undefined}\n aria-current={active || undefined}\n className={cn(itemClass, className)}\n onClick={(event) => {\n onClick?.(event);\n if (closeOnSelect && !event.defaultPrevented) {\n context.close();\n }\n }}\n {...props}\n >\n {children}\n </Comp>\n );\n};\n","import type { ComponentProps } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'a'> & {\n asChild?: boolean;\n};\n\nexport const BreadcrumbLink = ({ ref, asChild = false, className, ...props }: Props) => {\n const Comp = asChild ? Slot : 'a';\n return (\n <Comp\n ref={ref}\n className={cn(\n 'tw:flex',\n 'tw:items-center',\n 'tw:gap-1.5',\n // font\n 'tw:text-muted-foreground',\n 'tw:font-normal',\n 'tw:no-underline',\n\n // hover\n 'tw:hover:text-foreground',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n 'tw:focus-visible:rounded-sm',\n\n // Icon\n 'tw:[&_svg:not([class*=\"tw:size-\"])]:size-3.5',\n\n // transition\n 'tw:transition-colors',\n className,\n )}\n {...props}\n />\n );\n};\n","import { useCallback, useMemo, useRef, useState } from 'react';\nimport type { ComponentRef, ReactNode } from 'react';\n\nimport { SearchInput } from '../../blocks/SearchInput';\nimport { ChevronDown } from '../../icons';\nimport { cn } from '../../utils';\nimport { Popover } from '../Popover';\n\nimport {\n BreadcrumbDropdownContext,\n normalizeQuery,\n useMatchRegistry,\n} from './BreadcrumbDropdownContext';\nimport { BreadcrumbDropdownGroup } from './BreadcrumbDropdownGroup';\nimport { BreadcrumbDropdownItem } from './BreadcrumbDropdownItem';\nimport { BreadcrumbLink } from './BreadcrumbLink';\n\n// Chevron trigger — the only button in the crumb. It sits tight next to the label so it reads as\n// grouped with it, with no persistent outline: it's a button purely via the hover / open background\n// + colour lift. Used on its own next to a link label (`splitTrigger`) or after a plain label.\nconst chevronButtonClass = cn(\n // layout\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:cursor-pointer',\n // compact square hit target that stays snug against the label\n 'tw:size-5',\n 'tw:text-muted-foreground',\n 'tw:transition-colors',\n 'tw:hover:bg-accent',\n 'tw:hover:text-foreground',\n 'tw:data-[state=open]:bg-accent',\n 'tw:data-[state=open]:text-foreground',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n\n // chevron rotation while open\n 'tw:[&_svg]:transition-transform',\n 'tw:[&[data-state=open]_svg]:rotate-180',\n);\n\n// Plain (non-link) label — same typography as `BreadcrumbLink` but no hover/focus treatment, since\n// it isn't interactive: only the chevron opens the panel.\nconst plainLabelClass = cn(\n 'tw:flex',\n 'tw:items-center',\n 'tw:gap-1.5',\n 'tw:text-muted-foreground',\n 'tw:font-normal',\n 'tw:[&_svg:not([class*=\"tw:size-\"])]:size-3.5',\n);\n\n// Fixed panel width per `size` (default `medium`). Long names truncate rather than wrap or widen\n// the panel. Width still clamps to the viewport via min(); height is clamped the same way.\nconst PANEL_BASE = 'tw:max-h-[min(640px,var(--radix-popover-content-available-height))]';\n\nconst PANEL_WIDTH = {\n small: 'tw:w-[min(360px,var(--radix-popover-content-available-width))]',\n medium: 'tw:w-[min(440px,var(--radix-popover-content-available-width))]',\n large: 'tw:w-[min(560px,var(--radix-popover-content-available-width))]',\n} as const;\n\ntype PanelSize = keyof typeof PANEL_WIDTH;\n\n/**\n * Props for `Breadcrumb.Dropdown`. The panel chrome (trigger typography, popover sizing, pinned\n * filter input) is owned by the component — consumers supply the label and the panel content,\n * composed from `Breadcrumb.Dropdown.Group` / `Breadcrumb.Dropdown.Item`.\n */\ntype BreadcrumbDropdownProps = {\n /**\n * Crumb label, shown next to the chevron button that opens the panel — the label itself is never\n * the trigger. With `splitTrigger`, pass an anchor-like element (e.g. a router link) so the label\n * also navigates; otherwise it renders as a plain, non-navigational crumb label.\n */\n label: ReactNode;\n /**\n * When `true`, the label is a navigable link (pass an anchor-like `label` element, rendered through\n * `Breadcrumb.Link asChild`). Default `false` — the label is plain text. Either way only the\n * chevron button opens the panel; it always sits grouped tight to the label.\n */\n splitTrigger?: boolean;\n /**\n * Accessible name for the chevron trigger button. Defaults to `\"${label} menu\"` when `label` is\n * a plain string, otherwise `\"Open menu\"` — pass explicitly when `label` is a non-string node.\n */\n triggerLabel?: string;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * Fixed panel width: `small` (360px), `medium` (440px, default), or `large` (560px). Names that\n * exceed the width truncate with an ellipsis (hover shows the full name). Clamps to the viewport.\n */\n size?: PanelSize;\n /**\n * Pinned filter input at the top of the panel, focused on open. On by default — items and groups\n * that don't match the query hide themselves (matched against their `textValue` / string\n * content). Set to `false` to opt out of the input entirely.\n */\n filter?: boolean;\n /**\n * Set to `false` to keep the filter input but disable the built-in matching — the consumer\n * filters the content it renders (e.g. server-side or custom matching). Default `true`.\n */\n shouldFilter?: boolean;\n /** Controlled filter value. Omit to let the component manage the query internally. */\n searchValue?: string;\n onSearchChange?: (value: string) => void;\n searchPlaceholder?: string;\n /** Rendered when the built-in filter hides everything. Default `\"No results.\"`. */\n emptyMessage?: ReactNode;\n /** Panel content — `Breadcrumb.Dropdown.Group` / `Breadcrumb.Dropdown.Item` elements. */\n children: ReactNode;\n};\n\n/**\n * Breadcrumb segment that opens a standardized switcher panel — a filterable list / tree of\n * sibling entities the user can jump between (e.g. the Storage bucket/table tree on a table\n * detail page).\n *\n * **When to use:** on the leftmost breadcrumb segment that has siblings / children worth switching\n * between. Use at most one dropdown segment per breadcrumb.\n * **When NOT to use:** for tertiary actions on the breadcrumbs row use the `overflowActions` slot of\n * `PageHeader.Detail`; for a collapsed middle of a long breadcrumb use `Breadcrumb.Ellipsis`.\n * **Composition:** render inside `Breadcrumb.Item` in place of `Breadcrumb.Link`. Compose the panel\n * from `Breadcrumb.Dropdown.Group` and `Breadcrumb.Dropdown.Item` — the built-in filter matches\n * and hides them automatically.\n */\nexport const BreadcrumbDropdown = ({\n label,\n splitTrigger = false,\n triggerLabel,\n open,\n defaultOpen = false,\n onOpenChange,\n size = 'medium',\n filter = true,\n shouldFilter = true,\n searchValue,\n onSearchChange,\n searchPlaceholder = 'Search…',\n emptyMessage = 'No results.',\n children,\n}: BreadcrumbDropdownProps) => {\n const searchRef = useRef<ComponentRef<'input'>>(null);\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const [internalQuery, setInternalQuery] = useState('');\n const { register, anyVisible } = useMatchRegistry();\n\n const isOpen = open ?? internalOpen;\n const handleOpenChange = useCallback(\n (nextOpen: boolean) => {\n setInternalOpen(nextOpen);\n if (!nextOpen) setInternalQuery('');\n onOpenChange?.(nextOpen);\n },\n [onOpenChange],\n );\n const close = useCallback(() => handleOpenChange(false), [handleOpenChange]);\n\n const effectiveTriggerLabel =\n triggerLabel ?? (typeof label === 'string' ? `${label} menu` : 'Open menu');\n\n const rawQuery = searchValue ?? internalQuery;\n const filterDisabled = !filter || !shouldFilter;\n const query = filterDisabled ? '' : normalizeQuery(rawQuery);\n\n const contextValue = useMemo(\n () => ({ query, forceVisible: false, filterDisabled, register, close }),\n [query, filterDisabled, register, close],\n );\n\n return (\n <Popover open={isOpen} onOpenChange={handleOpenChange}>\n <span className=\"tw:flex tw:items-center tw:gap-0.5\">\n {splitTrigger ? (\n <BreadcrumbLink asChild>{label}</BreadcrumbLink>\n ) : (\n <span className={plainLabelClass}>{label}</span>\n )}\n <Popover.Trigger asChild>\n <button type=\"button\" aria-label={effectiveTriggerLabel} className={chevronButtonClass}>\n <ChevronDown className=\"tw:size-3.5\" />\n </button>\n </Popover.Trigger>\n </span>\n <Popover.Content\n align=\"start\"\n sideOffset={8}\n collisionPadding={16}\n data-size={size}\n className={cn('tw:overflow-hidden tw:p-0', PANEL_BASE, PANEL_WIDTH[size])}\n onOpenAutoFocus={\n filter\n ? (event) => {\n event.preventDefault();\n searchRef.current?.focus();\n }\n : undefined\n }\n >\n {filter && (\n <div className=\"tw:shrink-0 tw:border-0 tw:border-b tw:border-solid tw:border-border tw:p-2\">\n <SearchInput\n ref={searchRef}\n value={rawQuery}\n onValueChange={(value) => {\n setInternalQuery(value);\n onSearchChange?.(value);\n }}\n placeholder={searchPlaceholder}\n allowClear\n // Built-in matching is client-side and cheap — filter on every keystroke. Keep the\n // debounce only when the consumer filters itself (shouldFilter={false}, e.g. server-side).\n disableDebounce={shouldFilter}\n />\n </div>\n )}\n <div className=\"tw:flex tw:flex-1 tw:flex-col tw:overflow-y-auto tw:p-2\">\n <BreadcrumbDropdownContext value={contextValue}>{children}</BreadcrumbDropdownContext>\n {!filterDisabled && !!query && !anyVisible && (\n <span className=\"tw:px-2 tw:py-1.5 tw:text-sm tw:text-muted-foreground\">\n {emptyMessage}\n </span>\n )}\n </div>\n </Popover.Content>\n </Popover>\n );\n};\n\nBreadcrumbDropdown.Item = BreadcrumbDropdownItem;\nBreadcrumbDropdown.Group = BreadcrumbDropdownGroup;\n","import type { ComponentProps } from 'react';\n\nimport { Ellipsis } from '../../icons';\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'button'>;\n\nexport const BreadcrumbEllipsis = ({ ref, className, ...props }: Props) => (\n <button\n ref={ref}\n type=\"button\"\n className={cn(\n // layout\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:rounded-md',\n 'tw:border-none',\n 'tw:bg-transparent',\n 'tw:cursor-pointer',\n 'tw:text-muted-foreground',\n 'tw:transition-colors',\n // Match the Breadcrumb.Dropdown chevron trigger: background + colour lift on hover / while the\n // menu is open, so the two interactive breadcrumb affordances read consistently.\n 'tw:hover:bg-accent',\n 'tw:hover:text-foreground',\n 'tw:data-[state=open]:bg-accent',\n 'tw:data-[state=open]:text-foreground',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n\n 'tw:p-1',\n className,\n )}\n {...props}\n >\n <Ellipsis className=\"tw:size-4\" />\n <span className=\"tw:sr-only\">More</span>\n </button>\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'li'>;\n\nexport const BreadcrumbItem = ({ className, ...props }: Props) => (\n <li className={cn('tw:inline-flex', 'tw:items-center', className)} {...props} />\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'ol'>;\n\nexport const BreadcrumbList = ({ className, ...props }: Props) => (\n <ol\n className={cn(\n // layout\n 'tw:flex',\n 'tw:flex-wrap',\n 'tw:items-center',\n 'tw:gap-2',\n 'tw:m-0',\n 'tw:p-0',\n\n // font\n 'tw:break-words',\n 'tw:text-sm',\n 'tw:text-muted-foreground',\n className,\n )}\n {...props}\n />\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'span'>;\n\nexport const BreadcrumbPage = ({ ref, className, ...props }: Props) => (\n <span\n ref={ref}\n // Do not use the anchor, as this item is not clickable\n // oxlint-disable-next-line jsx_a11y/prefer-tag-over-role\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('tw:font-medium', 'tw:text-foreground', className)}\n {...props}\n />\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\ntype Props = ComponentProps<'li'>;\n\nexport const BreadcrumbSeparator = ({ children, className, ...props }: Props) => (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\n // layout\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:aspect-square',\n 'tw:size-[15px]',\n 'tw:[&_svg:not([class*=\"tw:size-\"])]:size-2.5',\n 'tw:text-muted-foreground',\n className,\n )}\n {...props}\n >\n {children ?? <span className=\"tw:select-none tw:text-sm tw:text-muted-foreground/50\">/</span>}\n </li>\n);\n","import type { ComponentProps } from 'react';\n\nimport { cn } from '../../utils';\n\nimport { BreadcrumbDropdown } from './BreadcrumbDropdown';\nimport { BreadcrumbEllipsis } from './BreadcrumbEllipsis';\nimport { BreadcrumbItem } from './BreadcrumbItem';\nimport { BreadcrumbLink } from './BreadcrumbLink';\nimport { BreadcrumbList } from './BreadcrumbList';\nimport { BreadcrumbPage } from './BreadcrumbPage';\nimport { BreadcrumbSeparator } from './BreadcrumbSeparator';\n\ntype Props = ComponentProps<'nav'>;\n\nexport const Breadcrumb = ({ className, ...props }: Props) => (\n <nav aria-label=\"breadcrumb\" className={cn('tailwindcss-preflight', className)} {...props} />\n);\n\nBreadcrumb.List = BreadcrumbList;\nBreadcrumb.Item = BreadcrumbItem;\nBreadcrumb.Link = BreadcrumbLink;\nBreadcrumb.Page = BreadcrumbPage;\nBreadcrumb.Separator = BreadcrumbSeparator;\nBreadcrumb.Ellipsis = BreadcrumbEllipsis;\nBreadcrumb.Dropdown = BreadcrumbDropdown;\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { cn } from "./classNames-BzrkDnOD.js";
2
- import { Tooltip } from "./utils-B5Z52hYs.js";
2
+ import { Tooltip } from "./utils-DSvrhU8E.js";
3
3
  import { useArgoDesign } from "./context-D7X470dy.js";
4
4
  import { Check, Minus } from "./icons-Cek151jC.js";
5
5
  import { FieldBox } from "./FieldBox-D7DYtX_m.js";
@@ -94,4 +94,4 @@ function Checkbox({ className, id: idProp, children, description, variant, posit
94
94
  //#endregion
95
95
  export { Checkbox };
96
96
 
97
- //# sourceMappingURL=Checkbox-fC0BuO2B.js.map
97
+ //# sourceMappingURL=Checkbox-CUIn2jap.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox-fC0BuO2B.js","names":[],"sources":["../src/components/Checkbox/styles.ts","../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { cva } from 'class-variance-authority';\n\nexport const rootVariants = cva(\n [\n // reset\n 'tailwindcss-preflight',\n\n // position\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:peer',\n 'tw:size-4',\n 'tw:shrink-0',\n 'tw:cursor-pointer',\n\n // border\n 'tw:border',\n 'tw:border-solid',\n\n // disabled\n 'tw:disabled:opacity-50',\n 'tw:disabled:cursor-not-allowed',\n ],\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: [\n 'tw:rounded',\n 'tw:border-neutral-200',\n\n 'tw:data-[state=checked]:bg-primary',\n 'tw:data-[state=checked]:border-primary',\n\n 'tw:data-[state=indeterminate]:bg-white',\n 'tw:data-[state=indeterminate]:border-neutral-200',\n ],\n argo: [\n 'tw:rounded',\n 'tw:border-input',\n 'tw:transition-colors',\n\n 'tw:data-[state=checked]:bg-primary',\n 'tw:data-[state=checked]:border-primary',\n\n 'tw:data-[state=indeterminate]:bg-primary',\n 'tw:data-[state=indeterminate]:border-primary',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:border-primary',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n ],\n },\n },\n },\n);\n\nexport const indicatorVariants = cva(['tw:group', 'tw:flex', 'tw:items-center'], {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: [\n 'tw:data-[state=checked]:text-primary-foreground',\n 'tw:data-[state=indeterminate]:text-neutral-200',\n ],\n argo: ['tw:text-primary-foreground'],\n },\n },\n});\n","import { useId } from 'react';\nimport type { ComponentProps, ReactNode } from 'react';\nimport type { CheckedState } from '@radix-ui/react-checkbox';\nimport { Indicator, Root } from '@radix-ui/react-checkbox';\nimport type { VariantProps } from 'class-variance-authority';\n\nimport { Check, Minus } from '../../icons';\nimport { cn } from '../../utils';\nimport { FieldBox } from '../FieldBox';\nimport type { fieldBoxVariants } from '../FieldBox/styles';\nimport { useArgoDesign } from '../ThemeProvider/context';\nimport type { Props as TooltipProps } from '../Tooltip';\nimport { Tooltip } from '../Tooltip';\n\nimport { indicatorVariants, rootVariants } from './styles';\n\ntype Props = {\n id?: string | number;\n description?: ReactNode;\n variant?: VariantProps<typeof fieldBoxVariants>['variant'];\n position?: 'start' | 'end';\n tooltip?: TooltipProps['tooltip'];\n tooltipPlacement?: TooltipProps['placement'];\n} & Omit<ComponentProps<typeof Root>, 'id'>;\n\n// First overload - for strict boolean usage\nexport function Checkbox(\n prop: Omit<Props, 'checked' | 'onCheckedChange'> & {\n checked?: boolean;\n onCheckedChange?: (checked: boolean) => void;\n },\n): ReactNode;\n\n// Second overload - for CheckedState usage (boolean | 'indeterminate')\nexport function Checkbox(\n prop: Omit<Props, 'checked' | 'onCheckedChange'> & {\n checked?: CheckedState;\n onCheckedChange?: (checked: CheckedState) => void;\n },\n): ReactNode;\n\nexport function Checkbox({\n className,\n id: idProp,\n children,\n description,\n variant,\n position,\n tooltip,\n tooltipPlacement = 'top',\n ...rest\n}: Props) {\n const fallbackId = useId();\n const id = idProp?.toString() ?? fallbackId;\n const styleVariant = useArgoDesign() ? 'argo' : 'default';\n\n const checkboxElement = (\n <Root id={id} {...rest} className={cn(rootVariants({ variant: styleVariant, className }))}>\n <Indicator className={cn(indicatorVariants({ variant: styleVariant }))}>\n <Check className=\"tw:hidden tw:size-3 tw:stroke-[4px] tw:group-data-[state=checked]:block\" />\n <Minus className=\"tw:hidden tw:size-3 tw:stroke-[4px] tw:group-data-[state=indeterminate]:block\" />\n </Indicator>\n </Root>\n );\n\n const rendered = !children ? (\n checkboxElement\n ) : (\n <FieldBox htmlFor={id} variant={variant} position={position} control={checkboxElement}>\n <FieldBox.Title>{children}</FieldBox.Title>\n {description && <FieldBox.Description>{description}</FieldBox.Description>}\n </FieldBox>\n );\n\n if (!tooltip) return rendered;\n\n // Only merge the tooltip trigger onto `rendered` when it's the FieldBox\n // wrapper (i.e. there's a label). For a bare checkbox, `rendered` is the Radix\n // Checkbox `Root` itself — merging a Radix Tooltip trigger onto it via\n // `asChild` stacks two stateful primitives on one element, colliding on\n // `data-state` (the tooltip's `open`/`closed` overwrites the checkbox's\n // `checked`/`unchecked`) so the checkbox can no longer toggle. Falling back to\n // the span-wrapping trigger keeps the checkbox independent.\n return (\n <Tooltip tooltip={tooltip} placement={tooltipPlacement} triggerAsChild={!!children}>\n {rendered}\n </Tooltip>\n );\n}\n"],"mappings":";;;;;;;;;;AAEA,MAAa,eAAe,IAC1B;CAEE;CAGA;CACA;CACA;CAEA;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;AACF,GACA;CACE,iBAAiB,EACf,SAAS,UACX;CACA,UAAU,EACR,SAAS;EACP,SAAS;GACP;GACA;GAEA;GACA;GAEA;GACA;EACF;EACA,MAAM;GACJ;GACA;GACA;GAEA;GACA;GAEA;GACA;GAGA;GACA;GACA;GACA;EACF;CACF,EACF;AACF,CACF;AAEA,MAAa,oBAAoB,IAAI;CAAC;CAAY;CAAW;AAAiB,GAAG;CAC/E,iBAAiB,EACf,SAAS,UACX;CACA,UAAU,EACR,SAAS;EACP,SAAS,CACP,mDACA,gDACF;EACA,MAAM,CAAC,4BAA4B;CACrC,EACF;AACF,CAAC;;;ACnCD,SAAgB,SAAS,EACvB,WACA,IAAI,QACJ,UACA,aACA,SACA,UACA,SACA,mBAAmB,OACnB,GAAG,QACK;CACR,MAAM,aAAa,MAAM;CACzB,MAAM,KAAK,QAAQ,SAAS,KAAK;CACjC,MAAM,eAAe,cAAc,IAAI,SAAS;CAEhD,MAAM,kBACJ,oBAAC,MAAD;EAAU;EAAI,GAAI;EAAM,WAAW,GAAG,aAAa;GAAE,SAAS;GAAc;EAAU,CAAC,CAAC;EACtF,UAAA,qBAAC,WAAD;GAAW,WAAW,GAAG,kBAAkB,EAAE,SAAS,aAAa,CAAC,CAAC;GAArE,UAAA,CACE,oBAAC,OAAD,EAAO,WAAU,0EAA2E,CAAA,GAC5F,oBAAC,OAAD,EAAO,WAAU,gFAAiF,CAAA,CACzF;;CACP,CAAA;CAGR,MAAM,WAAW,CAAC,WAChB,kBAEA,qBAAC,UAAD;EAAU,SAAS;EAAa;EAAmB;EAAU,SAAS;EAAtE,UAAA,CACE,oBAAC,SAAS,OAAV,EAAiB,SAAyB,CAAA,GACzC,eAAe,oBAAC,SAAS,aAAV,EAAA,UAAuB,YAAkC,CAAA,CACjE;;CAGZ,IAAI,CAAC,SAAS,OAAO;CASrB,OACE,oBAAC,SAAD;EAAkB;EAAS,WAAW;EAAkB,gBAAgB,CAAC,CAAC;EACvE,UAAA;CACM,CAAA;AAEb"}
1
+ {"version":3,"file":"Checkbox-CUIn2jap.js","names":[],"sources":["../src/components/Checkbox/styles.ts","../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { cva } from 'class-variance-authority';\n\nexport const rootVariants = cva(\n [\n // reset\n 'tailwindcss-preflight',\n\n // position\n 'tw:flex',\n 'tw:items-center',\n 'tw:justify-center',\n\n 'tw:peer',\n 'tw:size-4',\n 'tw:shrink-0',\n 'tw:cursor-pointer',\n\n // border\n 'tw:border',\n 'tw:border-solid',\n\n // disabled\n 'tw:disabled:opacity-50',\n 'tw:disabled:cursor-not-allowed',\n ],\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: [\n 'tw:rounded',\n 'tw:border-neutral-200',\n\n 'tw:data-[state=checked]:bg-primary',\n 'tw:data-[state=checked]:border-primary',\n\n 'tw:data-[state=indeterminate]:bg-white',\n 'tw:data-[state=indeterminate]:border-neutral-200',\n ],\n argo: [\n 'tw:rounded',\n 'tw:border-input',\n 'tw:transition-colors',\n\n 'tw:data-[state=checked]:bg-primary',\n 'tw:data-[state=checked]:border-primary',\n\n 'tw:data-[state=indeterminate]:bg-primary',\n 'tw:data-[state=indeterminate]:border-primary',\n\n // focus\n 'tw:focus-visible:outline-hidden',\n 'tw:focus-visible:border-primary',\n 'tw:focus-visible:ring-2',\n 'tw:focus-visible:ring-ring/50',\n ],\n },\n },\n },\n);\n\nexport const indicatorVariants = cva(['tw:group', 'tw:flex', 'tw:items-center'], {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: [\n 'tw:data-[state=checked]:text-primary-foreground',\n 'tw:data-[state=indeterminate]:text-neutral-200',\n ],\n argo: ['tw:text-primary-foreground'],\n },\n },\n});\n","import { useId } from 'react';\nimport type { ComponentProps, ReactNode } from 'react';\nimport type { CheckedState } from '@radix-ui/react-checkbox';\nimport { Indicator, Root } from '@radix-ui/react-checkbox';\nimport type { VariantProps } from 'class-variance-authority';\n\nimport { Check, Minus } from '../../icons';\nimport { cn } from '../../utils';\nimport { FieldBox } from '../FieldBox';\nimport type { fieldBoxVariants } from '../FieldBox/styles';\nimport { useArgoDesign } from '../ThemeProvider/context';\nimport type { Props as TooltipProps } from '../Tooltip';\nimport { Tooltip } from '../Tooltip';\n\nimport { indicatorVariants, rootVariants } from './styles';\n\ntype Props = {\n id?: string | number;\n description?: ReactNode;\n variant?: VariantProps<typeof fieldBoxVariants>['variant'];\n position?: 'start' | 'end';\n tooltip?: TooltipProps['tooltip'];\n tooltipPlacement?: TooltipProps['placement'];\n} & Omit<ComponentProps<typeof Root>, 'id'>;\n\n// First overload - for strict boolean usage\nexport function Checkbox(\n prop: Omit<Props, 'checked' | 'onCheckedChange'> & {\n checked?: boolean;\n onCheckedChange?: (checked: boolean) => void;\n },\n): ReactNode;\n\n// Second overload - for CheckedState usage (boolean | 'indeterminate')\nexport function Checkbox(\n prop: Omit<Props, 'checked' | 'onCheckedChange'> & {\n checked?: CheckedState;\n onCheckedChange?: (checked: CheckedState) => void;\n },\n): ReactNode;\n\nexport function Checkbox({\n className,\n id: idProp,\n children,\n description,\n variant,\n position,\n tooltip,\n tooltipPlacement = 'top',\n ...rest\n}: Props) {\n const fallbackId = useId();\n const id = idProp?.toString() ?? fallbackId;\n const styleVariant = useArgoDesign() ? 'argo' : 'default';\n\n const checkboxElement = (\n <Root id={id} {...rest} className={cn(rootVariants({ variant: styleVariant, className }))}>\n <Indicator className={cn(indicatorVariants({ variant: styleVariant }))}>\n <Check className=\"tw:hidden tw:size-3 tw:stroke-[4px] tw:group-data-[state=checked]:block\" />\n <Minus className=\"tw:hidden tw:size-3 tw:stroke-[4px] tw:group-data-[state=indeterminate]:block\" />\n </Indicator>\n </Root>\n );\n\n const rendered = !children ? (\n checkboxElement\n ) : (\n <FieldBox htmlFor={id} variant={variant} position={position} control={checkboxElement}>\n <FieldBox.Title>{children}</FieldBox.Title>\n {description && <FieldBox.Description>{description}</FieldBox.Description>}\n </FieldBox>\n );\n\n if (!tooltip) return rendered;\n\n // Only merge the tooltip trigger onto `rendered` when it's the FieldBox\n // wrapper (i.e. there's a label). For a bare checkbox, `rendered` is the Radix\n // Checkbox `Root` itself — merging a Radix Tooltip trigger onto it via\n // `asChild` stacks two stateful primitives on one element, colliding on\n // `data-state` (the tooltip's `open`/`closed` overwrites the checkbox's\n // `checked`/`unchecked`) so the checkbox can no longer toggle. Falling back to\n // the span-wrapping trigger keeps the checkbox independent.\n return (\n <Tooltip tooltip={tooltip} placement={tooltipPlacement} triggerAsChild={!!children}>\n {rendered}\n </Tooltip>\n );\n}\n"],"mappings":";;;;;;;;;;AAEA,MAAa,eAAe,IAC1B;CAEE;CAGA;CACA;CACA;CAEA;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;AACF,GACA;CACE,iBAAiB,EACf,SAAS,UACX;CACA,UAAU,EACR,SAAS;EACP,SAAS;GACP;GACA;GAEA;GACA;GAEA;GACA;EACF;EACA,MAAM;GACJ;GACA;GACA;GAEA;GACA;GAEA;GACA;GAGA;GACA;GACA;GACA;EACF;CACF,EACF;AACF,CACF;AAEA,MAAa,oBAAoB,IAAI;CAAC;CAAY;CAAW;AAAiB,GAAG;CAC/E,iBAAiB,EACf,SAAS,UACX;CACA,UAAU,EACR,SAAS;EACP,SAAS,CACP,mDACA,gDACF;EACA,MAAM,CAAC,4BAA4B;CACrC,EACF;AACF,CAAC;;;ACnCD,SAAgB,SAAS,EACvB,WACA,IAAI,QACJ,UACA,aACA,SACA,UACA,SACA,mBAAmB,OACnB,GAAG,QACK;CACR,MAAM,aAAa,MAAM;CACzB,MAAM,KAAK,QAAQ,SAAS,KAAK;CACjC,MAAM,eAAe,cAAc,IAAI,SAAS;CAEhD,MAAM,kBACJ,oBAAC,MAAD;EAAU;EAAI,GAAI;EAAM,WAAW,GAAG,aAAa;GAAE,SAAS;GAAc;EAAU,CAAC,CAAC;EACtF,UAAA,qBAAC,WAAD;GAAW,WAAW,GAAG,kBAAkB,EAAE,SAAS,aAAa,CAAC,CAAC;GAArE,UAAA,CACE,oBAAC,OAAD,EAAO,WAAU,0EAA2E,CAAA,GAC5F,oBAAC,OAAD,EAAO,WAAU,gFAAiF,CAAA,CACzF;;CACP,CAAA;CAGR,MAAM,WAAW,CAAC,WAChB,kBAEA,qBAAC,UAAD;EAAU,SAAS;EAAa;EAAmB;EAAU,SAAS;EAAtE,UAAA,CACE,oBAAC,SAAS,OAAV,EAAiB,SAAyB,CAAA,GACzC,eAAe,oBAAC,SAAS,aAAV,EAAA,UAAuB,YAAkC,CAAA,CACjE;;CAGZ,IAAI,CAAC,SAAS,OAAO;CASrB,OACE,oBAAC,SAAD;EAAkB;EAAS,WAAW;EAAkB,gBAAgB,CAAC,CAAC;EACvE,UAAA;CACM,CAAA;AAEb"}
@@ -1,5 +1,5 @@
1
1
  import { cn } from "./classNames-BzrkDnOD.js";
2
- import { Tooltip, copyToClipboard } from "./utils-B5Z52hYs.js";
2
+ import { Tooltip, copyToClipboard } from "./utils-DSvrhU8E.js";
3
3
  import { useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region src/components/Clipboard/Clipboard.tsx
@@ -55,4 +55,4 @@ const Clipboard = ({ ref, text: textProp, children, tooltip = "Copy to Clipboard
55
55
  //#endregion
56
56
  export { Clipboard };
57
57
 
58
- //# sourceMappingURL=Clipboard-ChPdiUy3.js.map
58
+ //# sourceMappingURL=Clipboard-BTW6C-b_.js.map