@adea-ai/ui 0.99.1 → 0.99.3

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 (123) hide show
  1. package/dist/components/layout/orbit-layout/orbit-layout.d.ts +5 -1
  2. package/dist/components/layout/orbit-layout/orbit-layout.d.ts.map +1 -1
  3. package/dist/components/layout/orbit-layout/orbit-layout.js.map +1 -1
  4. package/dist/components/layout/side-rail/side-rail.d.ts +4 -1
  5. package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
  6. package/dist/components/layout/side-rail/side-rail.js +58 -64
  7. package/dist/components/layout/side-rail/side-rail.js.map +1 -1
  8. package/dist/components/layout/sidebar-nav/sidebar-nav.js +2 -2
  9. package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
  10. package/dist/components/motion/presence.d.ts +17 -7
  11. package/dist/components/motion/presence.d.ts.map +1 -1
  12. package/dist/components/motion/presence.js.map +1 -1
  13. package/dist/components/theme/theme-picker.d.ts.map +1 -1
  14. package/dist/components/theme/theme-picker.js +1 -1
  15. package/dist/components/theme/theme-picker.js.map +1 -1
  16. package/dist/components/theme/theme-provider.d.ts.map +1 -1
  17. package/dist/components/theme/theme-provider.js +15 -4
  18. package/dist/components/theme/theme-provider.js.map +1 -1
  19. package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts +2 -2
  20. package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts.map +1 -1
  21. package/dist/components/ui/aspect-ratio/aspect-ratio.js +15 -1
  22. package/dist/components/ui/aspect-ratio/aspect-ratio.js.map +1 -1
  23. package/dist/components/ui/badge/badge.d.ts +1 -1
  24. package/dist/components/ui/button/button.d.ts +1 -1
  25. package/dist/components/ui/calendar/calendar.d.ts.map +1 -1
  26. package/dist/components/ui/calendar/calendar.js +1 -1
  27. package/dist/components/ui/calendar/calendar.js.map +1 -1
  28. package/dist/components/ui/card/card.d.ts +2 -2
  29. package/dist/components/ui/card/card.js +2 -2
  30. package/dist/components/ui/card/card.js.map +1 -1
  31. package/dist/components/ui/chart/chart-options.d.ts +50 -0
  32. package/dist/components/ui/chart/chart-options.d.ts.map +1 -0
  33. package/dist/components/ui/chart/chart-options.js +178 -0
  34. package/dist/components/ui/chart/chart-options.js.map +1 -0
  35. package/dist/components/ui/chart/chart.d.ts +21 -18
  36. package/dist/components/ui/chart/chart.d.ts.map +1 -1
  37. package/dist/components/ui/chart/chart.js +175 -155
  38. package/dist/components/ui/chart/chart.js.map +1 -1
  39. package/dist/components/ui/chart/index.js +2 -1
  40. package/dist/components/ui/context-menu/context-menu.d.ts +8 -0
  41. package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
  42. package/dist/components/ui/context-menu/context-menu.js +8 -0
  43. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  44. package/dist/components/ui/drawer/drawer.d.ts +8 -1
  45. package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
  46. package/dist/components/ui/drawer/drawer.js.map +1 -1
  47. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +8 -0
  48. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  49. package/dist/components/ui/dropdown-menu/dropdown-menu.js +8 -0
  50. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  51. package/dist/components/ui/input-otp/input-otp.d.ts.map +1 -1
  52. package/dist/components/ui/input-otp/input-otp.js +2 -2
  53. package/dist/components/ui/input-otp/input-otp.js.map +1 -1
  54. package/dist/components/ui/menubar/menubar.d.ts +8 -0
  55. package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
  56. package/dist/components/ui/menubar/menubar.js +8 -0
  57. package/dist/components/ui/menubar/menubar.js.map +1 -1
  58. package/dist/components/ui/navigation-menu/navigation-menu.d.ts +14 -2
  59. package/dist/components/ui/navigation-menu/navigation-menu.d.ts.map +1 -1
  60. package/dist/components/ui/navigation-menu/navigation-menu.js +28 -23
  61. package/dist/components/ui/navigation-menu/navigation-menu.js.map +1 -1
  62. package/dist/components/ui/pagination/pagination.d.ts.map +1 -1
  63. package/dist/components/ui/pagination/pagination.js +1 -1
  64. package/dist/components/ui/pagination/pagination.js.map +1 -1
  65. package/dist/components/ui/resizable/resizable.d.ts +0 -12
  66. package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
  67. package/dist/components/ui/resizable/resizable.js +43 -9
  68. package/dist/components/ui/resizable/resizable.js.map +1 -1
  69. package/dist/components/ui/toast/toast.d.ts.map +1 -1
  70. package/dist/components/ui/toast/toast.js +19 -6
  71. package/dist/components/ui/toast/toast.js.map +1 -1
  72. package/dist/lib/cn-tables.generated.js +10 -10
  73. package/dist/lib/cn-tables.generated.js.map +1 -1
  74. package/dist/r/chart.json +5 -0
  75. package/dist/r/registry.json +5 -0
  76. package/dist/r/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
  77. package/dist/r/src/components/layout/side-rail/side-rail.tsx +29 -16
  78. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
  79. package/dist/r/src/components/motion/presence.tsx +17 -7
  80. package/dist/r/src/components/theme/theme-picker.tsx +5 -1
  81. package/dist/r/src/components/theme/theme-provider.tsx +19 -9
  82. package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
  83. package/dist/r/src/components/ui/calendar/calendar.tsx +6 -1
  84. package/dist/r/src/components/ui/card/card.tsx +2 -2
  85. package/dist/r/src/components/ui/chart/chart-options.ts +196 -0
  86. package/dist/r/src/components/ui/chart/chart.tsx +149 -101
  87. package/dist/r/src/components/ui/context-menu/context-menu.tsx +8 -0
  88. package/dist/r/src/components/ui/drawer/drawer.tsx +8 -1
  89. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
  90. package/dist/r/src/components/ui/input-otp/input-otp.tsx +14 -2
  91. package/dist/r/src/components/ui/menubar/menubar.tsx +8 -0
  92. package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
  93. package/dist/r/src/components/ui/pagination/pagination.tsx +8 -1
  94. package/dist/r/src/components/ui/resizable/resizable.tsx +46 -8
  95. package/dist/r/src/components/ui/toast/toast.tsx +21 -6
  96. package/dist/r/src/lib/cn-tables.generated.ts +10 -10
  97. package/dist/r/src/styles/base.css +4 -1
  98. package/dist/r/src/styles/theme.css +23 -43
  99. package/package.json +1 -1
  100. package/registry.json +5 -0
  101. package/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
  102. package/src/components/layout/side-rail/side-rail.tsx +29 -16
  103. package/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
  104. package/src/components/motion/presence.tsx +17 -7
  105. package/src/components/theme/theme-picker.tsx +5 -1
  106. package/src/components/theme/theme-provider.tsx +19 -9
  107. package/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
  108. package/src/components/ui/calendar/calendar.tsx +6 -1
  109. package/src/components/ui/card/card.tsx +2 -2
  110. package/src/components/ui/chart/chart-options.ts +196 -0
  111. package/src/components/ui/chart/chart.tsx +149 -101
  112. package/src/components/ui/context-menu/context-menu.tsx +8 -0
  113. package/src/components/ui/drawer/drawer.tsx +8 -1
  114. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
  115. package/src/components/ui/input-otp/input-otp.tsx +14 -2
  116. package/src/components/ui/menubar/menubar.tsx +8 -0
  117. package/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
  118. package/src/components/ui/pagination/pagination.tsx +8 -1
  119. package/src/components/ui/resizable/resizable.tsx +46 -8
  120. package/src/components/ui/toast/toast.tsx +21 -6
  121. package/src/lib/cn-tables.generated.ts +10 -10
  122. package/src/styles/base.css +4 -1
  123. package/src/styles/theme.css +23 -43
@@ -1,7 +1,7 @@
1
1
  import Resizable from '@corvu/resizable'
2
2
  import { GripVertical } from 'lucide-solid'
3
3
  import type { ComponentProps } from 'solid-js'
4
- import { splitProps } from 'solid-js'
4
+ import { createContext, splitProps, useContext } from 'solid-js'
5
5
  import { cn } from '../../../lib/utils'
6
6
 
7
7
  /**
@@ -16,20 +16,54 @@ import { cn } from '../../../lib/utils'
16
16
  * is a real focusable element with an accessible role, so a split pane can be
17
17
  * adjusted from the keyboard; a bare `cursor-col-resize` div cannot.
18
18
  */
19
+ /**
20
+ * Whether the group was given `initialSizes`. Those are corvu's to apply, and a
21
+ * panel's own `initialSize` overrides them — so an unsized panel must not invent
22
+ * one when the group already has an answer.
23
+ */
24
+ const GroupSizesContext = createContext<() => boolean>(() => false)
25
+
19
26
  export function ResizablePanelGroup(props: ComponentProps<typeof Resizable> & { class?: string }) {
20
27
  const [local, rest] = splitProps(props, ['class'])
21
28
 
22
29
  return (
23
- <Resizable
24
- class={cn('flex size-full data-[orientation=vertical]:flex-col', local.class)}
25
- {...rest}
26
- />
30
+ <GroupSizesContext.Provider value={() => rest.initialSizes !== undefined}>
31
+ <Resizable
32
+ class={cn('flex size-full data-[orientation=vertical]:flex-col', local.class)}
33
+ {...rest}
34
+ />
35
+ </GroupSizesContext.Provider>
27
36
  )
28
37
  }
29
38
 
39
+ /**
40
+ * The size an unsized panel registers with: whatever its earlier siblings left.
41
+ *
42
+ * corvu registers a panel without `initialSize` at a flat 50%, so `0.28` beside
43
+ * an unsized panel summed to 78% and left a gap. Panels register in document
44
+ * order, so at registration `sizes` holds exactly the panels before this one.
45
+ * When nothing is left — a second unsized panel — it takes an equal share, and
46
+ * corvu shrinks the earlier panels by that share to keep the total at 100%.
47
+ */
48
+ function remainingSize(sizes: readonly number[]): number {
49
+ const left = 1 - sizes.reduce((total, size) => total + size, 0)
50
+ return left > 0.001 ? left : 1 / (sizes.length + 1)
51
+ }
52
+
30
53
  export function ResizablePanel(props: ComponentProps<typeof Resizable.Panel> & { class?: string }) {
31
- const [local, rest] = splitProps(props, ['class'])
32
- return <Resizable.Panel class={cn('min-h-0 min-w-0 overflow-hidden', local.class)} {...rest} />
54
+ const [local, rest] = splitProps(props, ['class', 'initialSize'])
55
+ const group = Resizable.useContext(rest.contextId)
56
+ const groupHasSizes = useContext(GroupSizesContext)
57
+
58
+ return (
59
+ <Resizable.Panel
60
+ initialSize={
61
+ local.initialSize ?? (groupHasSizes() ? undefined : remainingSize(group.sizes()))
62
+ }
63
+ class={cn('min-h-0 min-w-0 overflow-hidden', local.class)}
64
+ {...rest}
65
+ />
66
+ )
33
67
  }
34
68
 
35
69
  export type ResizableHandleProps = ComponentProps<typeof Resizable.Handle> & {
@@ -46,11 +80,15 @@ export type ResizableHandleProps = ComponentProps<typeof Resizable.Handle> & {
46
80
 
47
81
  export function ResizableHandle(props: ResizableHandleProps) {
48
82
  const [local, rest] = splitProps(props, ['class', 'withHandle', 'label'])
83
+ const group = Resizable.useContext(rest.contextId)
49
84
 
50
85
  return (
51
86
  <Resizable.Handle
52
87
  aria-label={local.label ?? 'Resize panels'}
53
- aria-orientation="vertical"
88
+ // A separator's orientation is the line's own, which runs across the
89
+ // group: a row of panels is divided by a vertical line, a column by a
90
+ // horizontal one. corvu reports the group's orientation instead.
91
+ aria-orientation={group.orientation() === 'horizontal' ? 'vertical' : 'horizontal'}
54
92
  class={cn(
55
93
  'group/resize bg-border relative flex w-px shrink-0 items-center justify-center',
56
94
  /* Widen the hit target without moving the visual line: the divider is
@@ -179,11 +179,23 @@ export type ToasterProps = ComponentProps<typeof KobalteToast.Region> & {
179
179
  region?: string
180
180
  }
181
181
 
182
+ /**
183
+ * The corner is set on the region, not the list. The region is the `fixed`
184
+ * element: an inset on the list inside it positions nothing, and the region falls
185
+ * back to wherever it happens to sit in the document flow.
186
+ */
182
187
  const toasterPosition = {
183
- 'top-left': 'top-0 left-0 items-start',
184
- 'top-right': 'top-0 right-0 items-end',
185
- 'bottom-left': 'bottom-0 left-0 items-start',
186
- 'bottom-right': 'bottom-0 right-0 items-end',
188
+ 'top-left': 'top-0 left-0',
189
+ 'top-right': 'top-0 right-0',
190
+ 'bottom-left': 'bottom-0 left-0',
191
+ 'bottom-right': 'bottom-0 right-0',
192
+ }
193
+
194
+ const toasterAlignment = {
195
+ 'top-left': 'items-start',
196
+ 'top-right': 'items-end',
197
+ 'bottom-left': 'items-start',
198
+ 'bottom-right': 'items-end',
187
199
  }
188
200
 
189
201
  /** Mount once, near the root. Renders the stack and owns its timing defaults. */
@@ -203,13 +215,16 @@ export function Toaster(props: ToasterProps) {
203
215
  * in-flight notification, and clicking a toast must not dismiss the
204
216
  * dialog behind it.
205
217
  */
206
- class="pointer-events-none fixed z-(--z-toast) flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm"
218
+ class={cn(
219
+ 'pointer-events-none fixed z-(--z-toast) flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm',
220
+ toasterPosition[local.position ?? 'bottom-right']
221
+ )}
207
222
  {...rest}
208
223
  >
209
224
  <KobalteToast.List
210
225
  class={cn(
211
226
  'flex flex-col gap-2',
212
- toasterPosition[local.position ?? 'bottom-right'],
227
+ toasterAlignment[local.position ?? 'bottom-right'],
213
228
  local.class
214
229
  )}
215
230
  />
@@ -54,7 +54,7 @@ const DZ = (s: string): Int32Array => {
54
54
  }
55
55
  return out
56
56
  }
57
- const GROUP_COUNT = 387
57
+ const GROUP_COUNT = 384
58
58
  const customValidatorNames: string[] = []
59
59
  const edgeStart = PS(U("E0500002005282000000002000150000020021820000011200000003022202000300004200120000200420001200021200301200010400162000010000220021010:2192001200220012000220012000200200200400010200040000000000400200108200110100000022010313000162002000020020012020080213000228200000000082000000000120002000120020020040101020300130001001010"))
60
60
  const labelStart = PS(U(":11111111211111119311546544411119731869:671397415686432441111111111161114151214313433415:78311132233313187211117221449443411141111151152226611131111112212518142224214215421421542142424242516171151615616347111111111197911327451111111111111111111113134714133513411111311111111111111111111112444411111342312715245411117:3"))
@@ -91,7 +91,7 @@ const nodeVlist = (() => {
91
91
  for (let i = 0; i < A.length; i++) out[A[i]!] = V[i]!
92
92
  return out
93
93
  })()
94
- const SETS = "container |break-after- all auto avoid avoid-page column left page right|break-before- all auto avoid avoid-page column left page right|break-inside-a uto void void-column void-page|box-decoration- clone slice|box- border content| contents flow-root hidden table table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group| not-sr-only sr-only|float- end left none right start|clear- both end left none right start|isolat e ion-auto|overflow- auto clip hidden scroll visible|overflow-x- auto clip hidden scroll visible|overflow-y- auto clip hidden scroll visible|overscroll- auto contain none|overscroll-x- auto contain none|overscroll-y- auto contain none| absolute fixed relative static sticky| collapse invisible visible|justify- around baseline between center center-safe end end-safe evenly normal start stretch|justify-items- center center-safe end end-safe normal start stretch|justify-self- auto center center-safe end end-safe start stretch|items- baseline baseline-last center center-safe end end-safe start stretch|self- auto baseline baseline-last center center-safe end end-safe start stretch|place-content- around baseline between center center-safe end end-safe evenly start stretch|place-items- baseline center center-safe end end-safe start stretch|place-self- auto center center-safe end end-safe start stretch| antialiased subpixel-antialiased| italic not-italic|normal-nums |ordinal |slashed-zero | lining-nums oldstyle-nums| proportional-nums tabular-nums| diagonal-fractions stacked-fractions| no-underline overline| capitalize lowercase normal-case uppercase|truncate |whitespace- break-spaces normal nowrap pre pre-line pre-wrap|break- all keep normal words|wrap- anywhere break-word normal|hyphens- auto manual none|mix-blend- color color-burn color-dodge darken difference exclusion hard-light hue lighten luminosity multiply normal overlay plus-darker plus-lighter saturation screen soft-light|table- auto fixed|caption- bottom top|backface- hidden visible|appearance- auto none|scheme- dark light light-dark normal only-dark only-light|field-sizing- content fixed|pointer-events- auto none|resize -none -x -y|snap- align-none center end start|snap- always normal|snap- both none x y|snap- mandatory proximity|touch- auto manipulation none|touch-pan- left right x|touch-pan- down up y|touch-pinch-zoom |select- all auto none text|forced-color-adjust- auto none| normal size| baseline bottom middle sub super text-bottom text-top top| bounce none ping pulse spin| auto square video| auto fr max min px|none | auto full px| fixed local scroll|clip- border content padding text|origin- border content padding| bottom bottom-left bottom-right center left left-bottom left-top right right-bottom right-top top top-left top-right| no-repeat repeat repeat-round repeat-space repeat-x repeat-y| auto contain cover| gradient-to-b gradient-to-bl gradient-to-br gradient-to-l gradient-to-r gradient-to-t gradient-to-tl gradient-to-tr none|blend- color color-burn color-dodge darken difference exclusion hard-light hue lighten luminosity multiply normal overlay saturation screen soft-light|to- b bl br l r t tl tr| auto dvh fit full lh lvh max min px screen svh| dashed dotted double hidden none solid| collapse separate|px |auto |full | content none strict| inline-size size|layout |paint |style | around baseline between center center-safe end end-safe evenly normal start stretch| alias all-scroll auto cell col-resize context-menu copy crosshair default e-resize ew-resize grab grabbing help move n-resize ne-resize nesw-resize no-drop none not-allowed ns-resize nw-resize nwse-resize pointer progress row-resize s-resize se-resize sw-resize text vertical-text w-resize wait zoom-in zoom-out| dashed dotted double solid wavy| auto from-font|reverse |initial | in in-out initial linear out| col col-reverse row row-reverse| nowrap wrap wrap-reverse| auto initial none| black bold extrabold extralight light medium normal semibold thin| condensed expanded extra-condensed extra-expanded normal semi-condensed semi-expanded ultra-condensed ultra-expanded|flow- col col-dense dense row row-dense| none subgrid| auto dvh dvw fit full lh lvh lvw max min px screen svh svw| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs rail-item row-lg row-md row-sm statusbar topbar| block flex grid table| auto dvw fit full lvw max min px screen svw| loose none normal px relaxed snug tight|through |item | inside outside| decimal disc none| auto px| clip-border clip-content clip-fill clip-padding clip-stroke clip-view no-clip| add exclude intersect subtract| alpha luminance match|origin- border content fill padding stroke view|type- alpha luminance| circle ellipse| closest-corner closest-side farthest-corner farthest-side|at- bottom bottom-left bottom-right center left left-bottom left-top right right-bottom right-top top top-left top-right| dvh fit full lh lvh max min none px screen svh| auto dvh dvw fit full lh lvh lvw max min none px screen svh svw| dvw fit full lvw max min none px screen svw| auto dvh dvw fit full lvh lvw max min none prose px svh svw| auto dvh dvw fit full lvh lvw max min none px screen svh svw| contain cover fill none scale-down| first last none| distant dramatic midrange near none normal|inset | full none|3d | auto smooth|gutter- auto both stable| auto none thin| inner none| auto dvh dvw fit full lvh lvw max min px svh svw| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs rail-item|base | center end justify left right start| clip ellipsis| balance nowrap pretty wrap| normal tight tighter wide wider widest| cpu gpu none| 3d flat| all colors none opacity shadow transform| discrete normal| full px| auto dvh dvw fit full lvh lvw max min px screen svh svw| rail rail-expanded sidebar sidebar-compact| auto contents scroll transform".split('|').map((s) => {
94
+ const SETS = "container |break-after- all auto avoid avoid-page column left page right|break-before- all auto avoid avoid-page column left page right|break-inside-a uto void void-column void-page|box-decoration- clone slice|box- border content| contents flow-root hidden table table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group| not-sr-only sr-only|float- end left none right start|clear- both end left none right start|isolat e ion-auto|overflow- auto clip hidden scroll visible|overflow-x- auto clip hidden scroll visible|overflow-y- auto clip hidden scroll visible|overscroll- auto contain none|overscroll-x- auto contain none|overscroll-y- auto contain none| absolute fixed relative static sticky| collapse invisible visible|justify- around baseline between center center-safe end end-safe evenly normal start stretch|justify-items- center center-safe end end-safe normal start stretch|justify-self- auto center center-safe end end-safe start stretch|items- baseline baseline-last center center-safe end end-safe start stretch|self- auto baseline baseline-last center center-safe end end-safe start stretch|place-content- around baseline between center center-safe end end-safe evenly start stretch|place-items- baseline center center-safe end end-safe start stretch|place-self- auto center center-safe end end-safe start stretch| antialiased subpixel-antialiased| italic not-italic|normal-nums |ordinal |slashed-zero | lining-nums oldstyle-nums| proportional-nums tabular-nums| diagonal-fractions stacked-fractions| no-underline overline| capitalize lowercase normal-case uppercase|truncate |whitespace- break-spaces normal nowrap pre pre-line pre-wrap|break- all keep normal words|wrap- anywhere break-word normal|hyphens- auto manual none|mix-blend- color color-burn color-dodge darken difference exclusion hard-light hue lighten luminosity multiply normal overlay plus-darker plus-lighter saturation screen soft-light|table- auto fixed|caption- bottom top|backface- hidden visible|appearance- auto none|scheme- dark light light-dark normal only-dark only-light|field-sizing- content fixed|pointer-events- auto none|resize -none -x -y|snap- align-none center end start|snap- always normal|snap- both none x y|snap- mandatory proximity|touch- auto manipulation none|touch-pan- left right x|touch-pan- down up y|touch-pinch-zoom |select- all auto none text|forced-color-adjust- auto none| normal size| baseline bottom middle sub super text-bottom text-top top| bounce none ping pulse spin| auto square video| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs fr max min px|none | auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs full px| fixed local scroll|clip- border content padding text|origin- border content padding| bottom bottom-left bottom-right center left left-bottom left-top right right-bottom right-top top top-left top-right| no-repeat repeat repeat-round repeat-space repeat-x repeat-y| auto contain cover| gradient-to-b gradient-to-bl gradient-to-br gradient-to-l gradient-to-r gradient-to-t gradient-to-tl gradient-to-tr none|blend- color color-burn color-dodge darken difference exclusion hard-light hue lighten luminosity multiply normal overlay saturation screen soft-light|to- b bl br l r t tl tr| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh fit full lh lvh max min px screen svh| dashed dotted double hidden none solid| collapse separate| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs px|auto |full | content none strict| inline-size size|layout |paint |style | around baseline between center center-safe end end-safe evenly normal start stretch| alias all-scroll auto cell col-resize context-menu copy crosshair default e-resize ew-resize grab grabbing help move n-resize ne-resize nesw-resize no-drop none not-allowed ns-resize nw-resize nwse-resize pointer progress row-resize s-resize se-resize sw-resize text vertical-text w-resize wait zoom-in zoom-out| dashed dotted double solid wavy| auto from-font|reverse |initial | in in-out initial linear out| col col-reverse row row-reverse| nowrap wrap wrap-reverse| auto initial none| black bold extrabold extralight light medium normal semibold thin| condensed expanded extra-condensed extra-expanded normal semi-condensed semi-expanded ultra-condensed ultra-expanded|flow- col col-dense dense row row-dense| none subgrid| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh dvw fit full lh lvh lvw max min px rail-item row-lg row-md row-sm screen statusbar svh svw topbar| block flex grid table| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvw fit full lvw max min px screen svw| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs loose none normal px relaxed snug tight|through |item | inside outside| decimal disc none| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs px| clip-border clip-content clip-fill clip-padding clip-stroke clip-view no-clip| add exclude intersect subtract| alpha luminance match|origin- border content fill padding stroke view|type- alpha luminance| circle ellipse| closest-corner closest-side farthest-corner farthest-side|at- bottom bottom-left bottom-right center left left-bottom left-top right right-bottom right-top top top-left top-right| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh fit full lh lvh max min none px screen svh| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh dvw fit full lh lvh lvw max min none px rail-item row-lg row-md row-sm screen statusbar svh svw topbar| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvw fit full lvw max min none px screen svw| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh dvw fit full lvh lvw max min none prose px rail rail-expanded sidebar sidebar-compact svh svw| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh dvw fit full lvh lvw max min none px rail rail-expanded screen sidebar sidebar-compact svh svw| contain cover fill none scale-down| first last none| distant dramatic midrange near none normal|inset | full none|3d | auto smooth|gutter- auto both stable| auto none thin| inner none| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh dvw fit full lvh lvw max min px rail-item svh svw|base | center end justify left right start| clip ellipsis| balance nowrap pretty wrap| normal tight tighter wide wider widest| cpu gpu none| 3d flat| all colors none opacity shadow transform| discrete normal| control-2xl control-2xs control-lg control-md control-sm control-xl control-xs full px| auto control-2xl control-2xs control-lg control-md control-sm control-xl control-xs dvh dvw fit full lvh lvw max min px rail rail-expanded screen sidebar sidebar-compact svh svw| auto contents scroll transform".split('|').map((s) => {
95
95
  const tails = s.split(' ')
96
96
  const prefix = tails.shift()!
97
97
  for (let i = 0; i < tails.length; i++) {
@@ -99,12 +99,12 @@ const SETS = "container |break-after- all auto avoid avoid-page column left page
99
99
  }
100
100
  return tails
101
101
  })
102
- const AA = DZ("0000000000000000000000000000000000000000000000000000000000000262242:6@200000006:240B428:44224000020460442224262200266426424062026224222824220022400000000\\00N222422242222222224062242222222422226264222422222222222222442804222422222222222222222222220<40<02042600024440202042244022")
103
- const AG = DZ("ɠ222222222222222222222222222222222222222222222222222222222222˕4222226>ʶ22ʷʺʷ2ʸʷ42ʴ2ʓ22>621422ɶ222ɹɼɷɺɷɺ22ɱ42222ɨ2ɧ26622ɘɓ24ɐɋƸƵ222]d24242ǖǓƚÆȭ2263ȪȧȪ2222222ǥ222222222222222222DŽƿ2ǀƷ2222222422222ƞƓ22222222222222222222144Ũť22Ťš222222222222222222222ĺ2ij6İħĪħĪɥŰ4Ĥ¯²ġ822ĘĕĘ2ė22226Čĉ2")
104
- const AS = DZ("02222222222222222222222222222222222222222222222222222222222222222202022222222222EH2200IL021042222Y\\222IL0cf2e10j2222U00X2022]`2{0000120~}€„22‡Š‰:22~22222222222G000sˆOVI00000œ2>40000ŽŸB00000Iª«000®00000000000000021M°¯00²•000000000000000000000˜2222ŸHGHa1¾222Ã2Ä2222ÍÐ000±´22·")
105
- const litAnchor = new Int32Array(1020)
106
- const litGroup = new Int32Array(1020)
107
- const litPool = new Int32Array(1020)
102
+ const AA = DZ("0000000000000000000000000000000000000000000000000000000000000262242:6@200000006:240B428:4422400002046044222426220026642642462026224222824220022400000000\\00N222422242222222224062242222222422226264222422222222222222442804222422222222222222222222220<4<0204260002444020204224422")
103
+ const AG = DZ("ɠ222222222222222222222222222222222222222222222222222222222222˕4222226>ʶ22ʷʺʷ2ʸʷ42ʴ2ʓ22>621422ɶ222ɹɼɷɺɷɺ22ɱ42222ɨ2ɧ26622ɘɓ244ƸƵ222]d24242ǖǓƚÄȫ2263ȨȥȨ2222222ǣ222222222222222222ǂƽ2ƾƵ2222222422222ƜƑ22222222222222222222144Ŧţ22Ţş222222222222222222222ĸ2ı68ĦģĦɡŰ4Ġ«®ĝ822ĔđĔ2ē2222622")
104
+ const AS = DZ("02222222222222222222222222222222222222222222222222222222222222222202022222222222EH2200IL021042222Y\\222IL0cf2e10j2222U00X202[^2y0000120|{~‚22…ˆ‡:22|22222222222G000q†OVI00000}š2›>40000ŒB00000I¨©000¬00000000000000021M®­00°“000000000000000000000–222›HGHa1º222¿2À2222ÉÌ000­°2±")
105
+ const litAnchor = new Int32Array(1635)
106
+ const litGroup = new Int32Array(1635)
107
+ const litPool = new Int32Array(1635)
108
108
  let poolText = ''
109
109
  const poolOffsets = new Int32Array(1072)
110
110
  {
@@ -129,9 +129,9 @@ const poolOffsets = new Int32Array(1072)
129
129
  }
130
130
  }
131
131
  // conflict adjacency (engine builds claim bitmask CSR at init)
132
- const adjGid = DZ("0b2N:222@R>F@286¦2@H2D266226FB22B2>BD\\6N22222Z222D222t")
132
+ const adjGid = DZ("0b2N:222@R>F@286¦2@H2D266226FB22B2>BD\\6N22222Z222D222p")
133
133
  const adjStart = PS(U("1::24444432:442:44:44>222222:44:4421322511111311111114"))
134
- const adjTgt = DZ("2Š4A;33N=C@H4A;33N=C@<27;83:;8393NQ:3NQʰ222ˉºŴŽ2R2=18cƴÅҜ=cĞÛC1ƈǝȈ:ħ25=11D3A@4=<1;1DEr25;11B3?<6;:371BCn9@7=<8192>2E121@9@EHE@9>2T25511<398454131<=V25511<398454131<=„ƧNž2Ĕé242L222290000f22500ɛ000ǘ222")
134
+ const adjTgt = DZ("2Š4A;33N=C@H4A;33N=C@<27;83:;8393NQ:3NQʰ222ˉºŴŽ2R2=18cƴÅҜ=cĞÛC1ƈǝȈ:ħ25=11D3A@4=<1;1DEr25;11B3?<6;:371BCn9@7=<8192>2E121@9@EHE@9>2T25511<398454131<=V25511<398454131<=„ƧNž2Đå242L222290000f22500ɛ000ǘ222")
135
135
  const patGid = U("ij")
136
136
  const patTgt = U("—")
137
137
  const postfixLookupGroups = U("1")
@@ -59,8 +59,11 @@
59
59
  }
60
60
  }
61
61
 
62
+ /* Presence, like the variants above: corvu and Kobalte mark a selected item
63
+ with an empty attribute (`data-selected=""`), while cmdk writes `"true"` and
64
+ `"false"`. Matching only `'true'` left every corvu calendar day unfilled. */
62
65
  @custom-variant data-selected {
63
- &:where([data-selected='true']) {
66
+ &:where([data-selected]:not([data-selected='false'])) {
64
67
  @slot;
65
68
  }
66
69
  }
@@ -16,9 +16,10 @@
16
16
  * lightness step reads as the same step in every hue and a re-hue does not
17
17
  * need its ramp re-derived by eye.
18
18
  * - **`@adea-ai/themes` supplies the values.** The two default themes are composed
19
- * there from upstream palettes — Aardvark Ink's canvas and greyscale for Adea
20
- * Dark, Nord Light's for Adea Light, and GitHub's hues for both — and normalized
21
- * against measured contrast floors. The colour declarations in sections 1 and 2
19
+ * there from upstream palettes — GitHub Dark Default's canvas and greyscale,
20
+ * re-greyed to violet, for Adea Dark; GitHub Light Default's white canvas for Adea
21
+ * Light; and GitHub's dark hues for both — and normalized against measured
22
+ * contrast floors. The colour declarations in sections 1 and 2
22
23
  * are *generated* from that catalogue (`bun run theme:build`) rather than written
23
24
  * here; everything else in this file is this system's own.
24
25
  *
@@ -41,11 +42,11 @@
41
42
  * ========================================================================== */
42
43
 
43
44
  /* ---------------------------------------------------------------------------
44
- * 1. Colour — light theme (the default)
45
+ * 1. Colour — light theme (the `:root` fallback; Storybook opens dark)
45
46
  *
46
- * Contrast: body text 8.6:1 on the canvas and 7.7:1 on a card, `--muted-foreground`
47
- * 5.5:1 on the canvas and 5.0:1 on a card, `--primary-foreground` on `--primary`
48
- * 5.9:1. Measured rather than estimated; `tests/tokens.test.ts` asserts each pairing.
47
+ * Contrast: body text 15.8:1 on the canvas and 14.3:1 on a card, `--muted-foreground`
48
+ * 6.2:1 on the canvas and 5.6:1 on a card and the sidebar, `--primary-foreground` on
49
+ * `--primary` 6.1:1. Measured rather than estimated; `tests/tokens.test.ts` asserts each pairing.
49
50
  * ------------------------------------------------------------------------- */
50
51
  :root {
51
52
  /* @generated theme:root — run `bun run theme:build` after changing the catalogue */
@@ -176,13 +177,15 @@
176
177
  /* ---------------------------------------------------------------------------
177
178
  * 2. Colour — dark theme
178
179
  *
179
- * adea's dark theme is a *soft* neutral rather than a near-black: the canvas is a
180
- * light grey and the primary is near-white. That is the product's existing look,
181
- * and it is deliberately not the near-black blue-tinted dark of the donor this
182
- * system's structure came from.
180
+ * A near-black violet-grey canvas — GitHub Dark Default's greyscale with its chroma
181
+ * halved and its hue turned from blue to violet — and a bright violet primary that
182
+ * carries a near-black label. The tint is what pairs it with the light theme's
183
+ * violet; it is deliberately not the blue-tinted dark of the donor this system's
184
+ * structure came from.
183
185
  *
184
- * Contrast: body text 14.9:1, `--muted-foreground` 6.1:1 on the canvas and 4.9:1
185
- * on a card, `--primary-foreground` on `--primary` 10.4:1.
186
+ * Contrast: body text 16.0:1 on the canvas and 14.9:1 on a card,
187
+ * `--muted-foreground` 7.0:1 on the canvas and 6.5:1 on a card and the sidebar,
188
+ * `--primary-foreground` on `--primary` 7.5:1.
186
189
  * ------------------------------------------------------------------------- */
187
190
  .dark {
188
191
  /* @generated theme:dark — run `bun run theme:build` after changing the catalogue */
@@ -304,7 +307,7 @@
304
307
  /* ---------------------------------------------------------------------------
305
308
  * 3. The accent axis
306
309
  *
307
- * adea's six accents, as a selection rather than a constant. `data-accent` sits on
310
+ * adea's six accent presets, as a selection rather than a constant. `data-accent` sits on
308
311
  * the same element as `dark` — in both applications that is `<html>` — and each
309
312
  * block overrides the roles an accent owns: the interactive primary, its label,
310
313
  * its hover rung, its tint and the focus ring.
@@ -319,7 +322,7 @@
319
322
  * for exactly this reason: a bright violet in dark mode carries a black
320
323
  * label, and the same accent's deep light-mode form carries a white one.
321
324
  *
322
- * `theme` (the default) is the monochrome primary above and needs no block.
325
+ * `theme` (the default) is the theme's own primary above and needs no block.
323
326
  * ------------------------------------------------------------------------- */
324
327
 
325
328
  /* @generated accents — run `bun run theme:build` after changing the catalogue */
@@ -490,23 +493,6 @@
490
493
  --ui-word-spacing: -2.5px;
491
494
  }
492
495
 
493
- /* The default, and it has to be declared *above* the blocks above.
494
- *
495
- * `:root` and `[data-font='…']` have the same specificity, so the later one wins
496
- * outright. With the default written after them — which is where it naturally
497
- * belongs, next to the stacks it references — every `[data-font]` block was
498
- * dead: all five typefaces resolved to Space Grotesk while the toolbar reported a
499
- * different selection.
500
- *
501
- * The references resolve fine from here even though `--font-family-*` is declared
502
- * further down, because a custom property is resolved where it is used rather than
503
- * where it is declared. The ordering that matters is between two declarations of
504
- * the *same* property, and there is only one of those below.
505
- *
506
- * `tests/appearance-axes.test.ts` asserts this order, because "the block is above
507
- * the default" is invisible to review and to every behavioural test.
508
- */
509
-
510
496
  /* ---------------------------------------------------------------------------
511
497
  * 4. Non-colour scales
512
498
  *
@@ -596,12 +582,6 @@
596
582
  --row-height-md: 2rem;
597
583
  --row-height-lg: 2.25rem;
598
584
 
599
- /* Space Grotesk is the default because it is the face the visual language was
600
- drawn against: the donor this system's structure comes from sets it, and its
601
- slightly narrow, geometric forms are part of the look. The *selection* that
602
- picks between it and the other four is the `:root` block above, which has to
603
- precede every `[data-font]` block for the reason given there. */
604
-
605
585
  /* Shell geometry. One place to change how wide a rail or sidebar is, so the two
606
586
  consumer apps cannot disagree. 56px keeps the icon column to its content:
607
587
  20px glyphs centred in a 36px row need less chrome than the 74px the donor's
@@ -621,9 +601,9 @@
621
601
  --tooltip-max-width: min(16rem, calc(100vw - 1.5rem));
622
602
 
623
603
  /* Shadows. Two parts on purpose: a tinted spread that reads as elevation, plus a
624
- hairline that keeps an edge visible when the surface behind a panel is the
625
- same colour as the panel — which is the light theme, where `--card` and
626
- `--background` are both white. */
604
+ hairline that keeps an edge visible when the surface behind a panel is
605
+ nearly the same colour as the panel — which is the light theme, where `--card`
606
+ is one faint step off a white `--background`. */
627
607
  --shadow-2xs: 0 1px 2px 0 oklch(0 0 0 / 0.05);
628
608
  --shadow-xs: 0 1px 2px 0 oklch(0 0 0 / 0.06);
629
609
  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.06), 0 0 0 1px oklch(0 0 0 / 0.04);
@@ -631,8 +611,8 @@
631
611
  --shadow-lg: 0 12px 28px oklch(0 0 0 / 0.12);
632
612
  --shadow-xl: 0 20px 44px oklch(0 0 0 / 0.16);
633
613
 
634
- /* Motion. Two durations cover the system: `fast` for a state change the user is
635
- already watching, `normal` for an entrance. The easing is KiroCrew's entrance
614
+ /* Motion. Three durations cover the system: `fast` for a state change the user is
615
+ already watching, `normal` for an entrance, `slow` for a layout settle. The easing is KiroCrew's entrance
636
616
  curve — a fast start and a long settle, which reads as decisive rather than
637
617
  floaty. */
638
618
  --duration-fast: 120ms;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.99.1",
3
+ "version": "0.99.3",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/registry.json CHANGED
@@ -325,6 +325,11 @@
325
325
  "lib"
326
326
  ],
327
327
  "files": [
328
+ {
329
+ "path": "src/components/ui/chart/chart-options.ts",
330
+ "type": "registry:ui",
331
+ "target": "components/ui/chart/chart-options.ts"
332
+ },
328
333
  {
329
334
  "path": "src/components/ui/chart/chart.tsx",
330
335
  "type": "registry:ui",
@@ -60,7 +60,11 @@ export function OrbitLayout(props: OrbitLayoutProps) {
60
60
  export type OrbitItemProps = Omit<ComponentProps<'div'>, 'style'> & {
61
61
  /** JSX style props are owned by this geometry primitive. */
62
62
  style?: never
63
- /** Zero-based position in the rendered sequence. */
63
+ /**
64
+ * Zero-based position in the rendered sequence. Index 0 sits at three o'clock
65
+ * (the positive x axis) and the sequence runs clockwise, because the y axis
66
+ * points down the screen: with four items, 0 is east, 1 south, 2 west, 3 north.
67
+ */
64
68
  index: number
65
69
  /** Number of items sharing this orbit. */
66
70
  count: number
@@ -205,7 +205,10 @@ export type SideRailItemProps<T extends ValidComponent = 'a'> = PolymorphicProps
205
205
  * being deleted.
206
206
  */
207
207
  label: string
208
- /** A count or a dot, rendered over the icon's corner. */
208
+ /**
209
+ * A count or a dot. Expanded, it trails the label at the row's end; collapsed,
210
+ * it moves onto the icon's corner, so the rail never loses it.
211
+ */
209
212
  badge?: JSX.Element
210
213
  /** An indicator pinned to the trailing edge in the expanded form. */
211
214
  trailing?: JSX.Element
@@ -258,6 +261,7 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
258
261
  data-active={local.active ? '' : undefined}
259
262
  class={cn(
260
263
  sideRailItemClass,
264
+ 'relative',
261
265
  local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,
262
266
  local.class
263
267
  )}
@@ -267,24 +271,33 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
267
271
  onBlur={hideTip}
268
272
  {...(rest as Record<string, unknown>)}
269
273
  >
270
- <Show when={local.badge}>
271
- <span class="relative flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
272
- {local.children}
273
- <span class="absolute -top-1 -end-1 flex items-center justify-center">
274
- {local.badge}
275
- </span>
276
- </span>
277
- </Show>
278
- <Show when={!local.badge}>
279
- <span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
280
- {local.children}
281
- </span>
282
- </Show>
274
+ <span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
275
+ {local.children}
276
+ </span>
283
277
 
284
- <span class="min-w-0 flex-1 truncate group-data-[collapsed=true]/rail:sr-only">
278
+ <span class="min-w-0 flex-1 truncate text-start group-data-[collapsed=true]/rail:sr-only">
285
279
  {local.label}
286
280
  </span>
287
281
 
282
+ {/*
283
+ * One element, two placements, because the badge is caller content and a DOM
284
+ * node cannot be in two places. Expanded, it is a trailing count in the row's
285
+ * flow, after the label — over the icon it would cover the glyph the row is
286
+ * recognised by. Collapsed, there is no label to trail, so it leaves the flow
287
+ * and pins to the icon's top-end corner: anchored to the row's centre (where
288
+ * the centred icon sits) and pushed out by 14px, the icon's half-width plus
289
+ * the 4px overhang, so a wider count grows back over the icon rather than past
290
+ * the rail's edge.
291
+ */}
292
+ <Show when={local.badge}>
293
+ <span
294
+ data-slot="side-rail-badge"
295
+ class="flex shrink-0 items-center justify-center group-data-[collapsed=true]/rail:absolute group-data-[collapsed=true]/rail:end-1/2 group-data-[collapsed=true]/rail:top-1/2 group-data-[collapsed=true]/rail:-me-3.5 group-data-[collapsed=true]/rail:-mt-3.5"
296
+ >
297
+ {local.badge}
298
+ </span>
299
+ </Show>
300
+
288
301
  <Show when={local.shortcut && local.trailing === undefined}>
289
302
  <span
290
303
  aria-hidden="true"
@@ -380,7 +393,7 @@ export function SideRailButton(
380
393
  <span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
381
394
  {local.children}
382
395
  </span>
383
- <span class="min-w-0 flex-1 truncate group-data-[collapsed=true]/rail:sr-only">
396
+ <span class="min-w-0 flex-1 truncate text-start group-data-[collapsed=true]/rail:sr-only">
384
397
  {local.label}
385
398
  </span>
386
399
  <Show when={local.shortcut}>
@@ -182,7 +182,7 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
182
182
  })}
183
183
  />
184
184
  </Show>
185
- <span class="min-w-0 flex-1 truncate">{local.label}</span>
185
+ <span class="min-w-0 flex-1 truncate text-start">{local.label}</span>
186
186
  <Show when={local.count !== undefined}>
187
187
  <span class="shrink-0 tabular-nums text-muted-foreground">{local.count}</span>
188
188
  </Show>
@@ -290,7 +290,7 @@ export function SidebarNavLabel(props: ComponentProps<'span'>) {
290
290
  return (
291
291
  <span
292
292
  data-slot="sidebar-nav-label"
293
- class={cn('min-w-0 flex-1 truncate', local.class)}
293
+ class={cn('min-w-0 flex-1 truncate text-start', local.class)}
294
294
  {...rest}
295
295
  />
296
296
  )
@@ -14,14 +14,24 @@ import { Presence as MotionPresence } from 'solid-motionone'
14
14
  * this for mounting and unmounting, which is the split that keeps the common case
15
15
  * free of a runtime while making the rare case correct.
16
16
  *
17
- * <Show when={open()}>
18
- * <Presence initial={{ opacity: 0, scale: 0.96 }} exit={{ opacity: 0, scale: 0.96 }}>
19
- * <div>…</div>
20
- * </Presence>
21
- * </Show>
17
+ * <Presence exitBeforeEnter>
18
+ * <Show when={open()}>
19
+ * <Motion.div
20
+ * initial={{ opacity: 0, scale: 0.96 }}
21
+ * animate={{ opacity: 1, scale: 1 }}
22
+ * exit={{ opacity: 0, scale: 0.96 }}
23
+ * >
24
+ * …
25
+ * </Motion.div>
26
+ * </Show>
27
+ * </Presence>
22
28
  *
23
- * `Presence` keeps the child mounted until its exit finishes, so the `<Show>` above
24
- * can stay the only condition in the tree.
29
+ * `Presence` goes *outside* the condition: it has to outlive the `<Show>`'s child
30
+ * to keep that child mounted until its exit finishes. It takes only two booleans —
31
+ * `initial={false}` skips the enter animation on first render, `exitBeforeEnter`
32
+ * holds a replacement back until the outgoing element has left. The animation
33
+ * itself lives on the `Motion.*` element, which must be the direct child of the
34
+ * condition for its `exit` to run.
25
35
  */
26
36
  export type PresenceProps = ComponentProps<typeof MotionPresence> & {
27
37
  children?: JSX.Element
@@ -159,7 +159,11 @@ export function ThemePicker(props: ThemePickerProps) {
159
159
  role="radio"
160
160
  aria-checked={local.selectedId === theme.id}
161
161
  aria-label={`${theme.familyLabel} ${theme.label}`}
162
- class="h-auto flex-col gap-1.5 p-0 hover:bg-transparent"
162
+ // `justify-start`: the options in a wrapped row stretch to its tallest
163
+ // member, and an option whose label has a second line (the family)
164
+ // is that member. Centred, every shorter option's preview dropped below
165
+ // its neighbours'; pinned to the top, the previews share one line.
166
+ class="h-auto flex-col justify-start gap-1.5 p-0 hover:bg-transparent"
163
167
  onClick={() => local.onThemeSelect(theme)}
164
168
  >
165
169
  <ThemePreview theme={theme} selected={local.selectedId === theme.id} />
@@ -121,11 +121,23 @@ export function useTheme(): ThemeContextValue {
121
121
  return value
122
122
  }
123
123
 
124
+ /**
125
+ * The preference store, or nothing.
126
+ *
127
+ * Only ever called inside a `try`: where storage is blocked — a sandboxed iframe, a
128
+ * privacy mode, a denied site-data permission — merely *reading* the `localStorage`
129
+ * global throws ("Access is denied", `SecurityError`), so even the `typeof` guard
130
+ * has to sit inside the `try` rather than in front of it. Without a store the
131
+ * preference lives only in the provider's signal for the session.
132
+ */
133
+ function preferenceStore(): Storage | undefined {
134
+ return typeof localStorage === 'undefined' ? undefined : localStorage
135
+ }
136
+
124
137
  /** The stored preference, validated against the catalogue. */
125
138
  function readSelection(storageKey: string): ThemeSelection {
126
- if (typeof localStorage === 'undefined') return defaultThemeSelection
127
139
  try {
128
- const raw = localStorage.getItem(storageKey)
140
+ const raw = preferenceStore()?.getItem(storageKey)
129
141
  if (!raw) return defaultThemeSelection
130
142
  const parsed = JSON.parse(raw) as Partial<ThemeSelection>
131
143
  return {
@@ -268,13 +280,11 @@ export function ThemeProvider(props: ThemeProviderProps) {
268
280
  setSelection(patch) {
269
281
  const next = { ...selection(), ...patch }
270
282
  setSelectionState(next)
271
- if (typeof localStorage !== 'undefined') {
272
- try {
273
- localStorage.setItem(storageKey(), JSON.stringify(next))
274
- } catch {
275
- // A full or unavailable store must not break the switch: the preference
276
- // simply does not survive the reload.
277
- }
283
+ try {
284
+ preferenceStore()?.setItem(storageKey(), JSON.stringify(next))
285
+ } catch {
286
+ // A full, blocked or unavailable store must not break the switch: the
287
+ // preference simply does not survive the reload.
278
288
  }
279
289
  },
280
290
  }
@@ -1,4 +1,4 @@
1
- import type { ComponentProps } from 'solid-js'
1
+ import type { ComponentProps, JSX } from 'solid-js'
2
2
  import { splitProps } from 'solid-js'
3
3
  import { cn } from '#lib/utils'
4
4
 
@@ -22,13 +22,24 @@ export type AspectRatioProps = ComponentProps<'div'> & {
22
22
  ratio?: number
23
23
  }
24
24
 
25
+ /**
26
+ * The caller's style with the ratio laid over it. A spread `style` replaces the
27
+ * element's rather than merging, so passing any style used to drop the ratio and
28
+ * collapse the box to zero height. `ratio` is the API for the ratio, so it wins.
29
+ */
30
+ function withRatio(style: AspectRatioProps['style'], ratio: number): JSX.CSSProperties | string {
31
+ const value = String(ratio)
32
+ if (typeof style === 'string') return `${style};aspect-ratio:${value}`
33
+ return { ...style, 'aspect-ratio': value }
34
+ }
35
+
25
36
  export function AspectRatio(props: AspectRatioProps) {
26
- const [local, rest] = splitProps(props, ['class', 'ratio', 'children'])
37
+ const [local, rest] = splitProps(props, ['class', 'ratio', 'style', 'children'])
27
38
 
28
39
  return (
29
40
  <div
30
41
  data-slot="aspect-ratio"
31
- style={{ 'aspect-ratio': String(local.ratio ?? 16 / 9) }}
42
+ style={withRatio(local.style, local.ratio ?? 16 / 9)}
32
43
  class={cn('relative w-full [&>*]:absolute [&>*]:inset-0 [&>*]:size-full', local.class)}
33
44
  {...rest}
34
45
  >
@@ -171,7 +171,12 @@ function CalendarGrid(props: {
171
171
  // Styled from data states, so a day can be a range
172
172
  // end and today at once without a prop per pair.
173
173
  'data-selected:bg-primary data-selected:text-primary-foreground',
174
- 'data-in-range:bg-primary-subtle',
174
+ // corvu marks every day of a range selected, so the
175
+ // span between the ends steps back to the subtle
176
+ // fill and the ends take the solid one again.
177
+ 'data-in-range:bg-primary-subtle data-in-range:text-foreground',
178
+ 'data-range-start:bg-primary data-range-start:text-primary-foreground',
179
+ 'data-range-end:bg-primary data-range-end:text-primary-foreground',
175
180
  'data-range-start:rounded-r-none data-range-end:rounded-l-none',
176
181
  'data-today:ring-1 data-today:ring-ring',
177
182
  'data-disabled:text-muted-foreground data-disabled:line-through',