@adea-ai/ui 0.99.0 → 0.99.2
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.
- package/dist/components/composites/appearance-editor/appearance-choices.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-choices.js +32 -28
- package/dist/components/composites/appearance-editor/appearance-choices.js.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-rows.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-rows.js +1 -1
- package/dist/components/composites/appearance-editor/appearance-rows.js.map +1 -1
- package/dist/components/layout/orbit-layout/orbit-layout.d.ts +5 -1
- package/dist/components/layout/orbit-layout/orbit-layout.d.ts.map +1 -1
- package/dist/components/layout/orbit-layout/orbit-layout.js.map +1 -1
- package/dist/components/layout/side-rail/side-rail.d.ts +4 -1
- package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
- package/dist/components/layout/side-rail/side-rail.js +58 -64
- package/dist/components/layout/side-rail/side-rail.js.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js +2 -2
- package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
- package/dist/components/motion/presence.d.ts +17 -7
- package/dist/components/motion/presence.d.ts.map +1 -1
- package/dist/components/motion/presence.js.map +1 -1
- package/dist/components/theme/theme-picker.d.ts.map +1 -1
- package/dist/components/theme/theme-picker.js +1 -1
- package/dist/components/theme/theme-picker.js.map +1 -1
- package/dist/components/theme/theme-provider.d.ts.map +1 -1
- package/dist/components/theme/theme-provider.js +15 -4
- package/dist/components/theme/theme-provider.js.map +1 -1
- package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts +2 -2
- package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/ui/aspect-ratio/aspect-ratio.js +15 -1
- package/dist/components/ui/aspect-ratio/aspect-ratio.js.map +1 -1
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts +1 -1
- package/dist/components/ui/calendar/calendar.d.ts.map +1 -1
- package/dist/components/ui/calendar/calendar.js +1 -1
- package/dist/components/ui/calendar/calendar.js.map +1 -1
- package/dist/components/ui/card/card.d.ts +2 -2
- package/dist/components/ui/card/card.js +2 -2
- package/dist/components/ui/card/card.js.map +1 -1
- package/dist/components/ui/chart/chart-options.d.ts +50 -0
- package/dist/components/ui/chart/chart-options.d.ts.map +1 -0
- package/dist/components/ui/chart/chart-options.js +178 -0
- package/dist/components/ui/chart/chart-options.js.map +1 -0
- package/dist/components/ui/chart/chart.d.ts +21 -18
- package/dist/components/ui/chart/chart.d.ts.map +1 -1
- package/dist/components/ui/chart/chart.js +175 -155
- package/dist/components/ui/chart/chart.js.map +1 -1
- package/dist/components/ui/chart/index.js +2 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +4 -2
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/input-otp/input-otp.d.ts.map +1 -1
- package/dist/components/ui/input-otp/input-otp.js +2 -2
- package/dist/components/ui/input-otp/input-otp.js.map +1 -1
- package/dist/components/ui/pagination/pagination.d.ts.map +1 -1
- package/dist/components/ui/pagination/pagination.js +1 -1
- package/dist/components/ui/pagination/pagination.js.map +1 -1
- package/dist/components/ui/resizable/resizable.d.ts +0 -12
- package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
- package/dist/components/ui/resizable/resizable.js +43 -9
- package/dist/components/ui/resizable/resizable.js.map +1 -1
- package/dist/lib/cn-tables.generated.js +10 -10
- package/dist/lib/cn-tables.generated.js.map +1 -1
- package/dist/r/chart.json +5 -0
- package/dist/r/registry.json +5 -0
- package/dist/r/src/components/composites/appearance-editor/appearance-choices.tsx +35 -20
- package/dist/r/src/components/composites/appearance-editor/appearance-rows.tsx +6 -2
- package/dist/r/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +29 -16
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
- package/dist/r/src/components/motion/presence.tsx +17 -7
- package/dist/r/src/components/theme/theme-picker.tsx +5 -1
- package/dist/r/src/components/theme/theme-provider.tsx +19 -9
- package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
- package/dist/r/src/components/ui/calendar/calendar.tsx +6 -1
- package/dist/r/src/components/ui/card/card.tsx +2 -2
- package/dist/r/src/components/ui/chart/chart-options.ts +196 -0
- package/dist/r/src/components/ui/chart/chart.tsx +149 -101
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +10 -2
- package/dist/r/src/components/ui/input-otp/input-otp.tsx +14 -2
- package/dist/r/src/components/ui/pagination/pagination.tsx +8 -1
- package/dist/r/src/components/ui/resizable/resizable.tsx +46 -8
- package/dist/r/src/lib/cn-tables.generated.ts +10 -10
- package/dist/r/src/styles/base.css +4 -1
- package/dist/r/src/styles/theme.css +23 -43
- package/package.json +1 -1
- package/registry.json +5 -0
- package/src/components/composites/appearance-editor/appearance-choices.tsx +35 -20
- package/src/components/composites/appearance-editor/appearance-rows.tsx +6 -2
- package/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
- package/src/components/layout/side-rail/side-rail.tsx +29 -16
- package/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
- package/src/components/motion/presence.tsx +17 -7
- package/src/components/theme/theme-picker.tsx +5 -1
- package/src/components/theme/theme-provider.tsx +19 -9
- package/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
- package/src/components/ui/calendar/calendar.tsx +6 -1
- package/src/components/ui/card/card.tsx +2 -2
- package/src/components/ui/chart/chart-options.ts +196 -0
- package/src/components/ui/chart/chart.tsx +149 -101
- package/src/components/ui/dropdown-menu/dropdown-menu.tsx +10 -2
- package/src/components/ui/input-otp/input-otp.tsx +14 -2
- package/src/components/ui/pagination/pagination.tsx +8 -1
- package/src/components/ui/resizable/resizable.tsx +46 -8
- package/src/lib/cn-tables.generated.ts +10 -10
- package/src/styles/base.css +4 -1
- package/src/styles/theme.css +23 -43
|
@@ -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 —
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
45
|
+
* 1. Colour — light theme (the `:root` fallback; Storybook opens dark)
|
|
45
46
|
*
|
|
46
|
-
* Contrast: body text 8
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* system's
|
|
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
|
|
185
|
-
*
|
|
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
|
|
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
|
|
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
|
|
625
|
-
same colour as the panel — which is the light theme, where `--card`
|
|
626
|
-
|
|
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.
|
|
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
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",
|
|
@@ -83,6 +83,11 @@ export function ModeChoices(props: AppearanceEditorProps) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export function AccentChoices(props: AppearanceEditorProps) {
|
|
86
|
+
// The resolved appearance decides which side of a preset pair — and which
|
|
87
|
+
// theme accent — the swatches paint, so the row previews each choice as the
|
|
88
|
+
// user will see it.
|
|
89
|
+
const resolvedAppearance = () =>
|
|
90
|
+
props.draft.mode === 'system' ? props.resolvedAppearance : props.draft.mode
|
|
86
91
|
const custom = () =>
|
|
87
92
|
props.draft.accent !== 'theme' &&
|
|
88
93
|
!props.accentOptions.some((option) => option.id === props.draft.accent)
|
|
@@ -93,9 +98,9 @@ export function AccentChoices(props: AppearanceEditorProps) {
|
|
|
93
98
|
disabled={props.saving}
|
|
94
99
|
orientation="horizontal"
|
|
95
100
|
aria-label="Accent"
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
101
|
+
// One chip (custom) beside a capped swatch grid: a chip-per-state column
|
|
102
|
+
// beside a full-width swatch line overflowed the settings sidebar's
|
|
103
|
+
// control column, so the pair stays a narrow chip plus a wrapped grid.
|
|
99
104
|
class="flex items-center gap-3"
|
|
100
105
|
onChange={(accent) =>
|
|
101
106
|
props.onChange({
|
|
@@ -103,21 +108,29 @@ export function AccentChoices(props: AppearanceEditorProps) {
|
|
|
103
108
|
})
|
|
104
109
|
}
|
|
105
110
|
>
|
|
106
|
-
<div class="
|
|
107
|
-
|
|
111
|
+
<div class="grid grid-cols-4 gap-2">
|
|
112
|
+
{/* The theme's own accent is the first swatch. Picking it is the same
|
|
113
|
+
act the old dedicated "Theme default" chip performed, without a
|
|
114
|
+
second control for the state the row starts in. */}
|
|
115
|
+
<Radio.Item
|
|
116
|
+
value="theme"
|
|
117
|
+
class="rounded-full focus-within:ring-3 focus-within:ring-ring/50"
|
|
118
|
+
>
|
|
108
119
|
<Radio.ItemInput />
|
|
109
|
-
<Radio.ItemLabel class="
|
|
110
|
-
|
|
120
|
+
<Radio.ItemLabel class="block size-7 cursor-pointer rounded-full border p-0.5 data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
|
|
121
|
+
<span
|
|
122
|
+
class="block size-full rounded-full"
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
style={{
|
|
125
|
+
'background-color':
|
|
126
|
+
resolvedAppearance() === 'light'
|
|
127
|
+
? props.lightTheme.colors.accent
|
|
128
|
+
: props.darkTheme.colors.accent,
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
<span class="sr-only">Theme default</span>
|
|
111
132
|
</Radio.ItemLabel>
|
|
112
133
|
</Radio.Item>
|
|
113
|
-
<Radio.Item value="custom">
|
|
114
|
-
<Radio.ItemInput />
|
|
115
|
-
<Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs whitespace-nowrap data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
|
|
116
|
-
Custom
|
|
117
|
-
</Radio.ItemLabel>
|
|
118
|
-
</Radio.Item>
|
|
119
|
-
</div>
|
|
120
|
-
<div class="grid grid-cols-3 gap-2">
|
|
121
134
|
<For each={props.accentOptions}>
|
|
122
135
|
{(option) => (
|
|
123
136
|
<Radio.Item
|
|
@@ -131,11 +144,7 @@ export function AccentChoices(props: AppearanceEditorProps) {
|
|
|
131
144
|
aria-hidden="true"
|
|
132
145
|
style={{
|
|
133
146
|
'background-color':
|
|
134
|
-
(
|
|
135
|
-
? props.resolvedAppearance
|
|
136
|
-
: props.draft.mode) === 'light'
|
|
137
|
-
? option.light
|
|
138
|
-
: option.dark,
|
|
147
|
+
resolvedAppearance() === 'light' ? option.light : option.dark,
|
|
139
148
|
}}
|
|
140
149
|
/>
|
|
141
150
|
<span class="sr-only">{option.label}</span>
|
|
@@ -144,6 +153,12 @@ export function AccentChoices(props: AppearanceEditorProps) {
|
|
|
144
153
|
)}
|
|
145
154
|
</For>
|
|
146
155
|
</div>
|
|
156
|
+
<Radio.Item value="custom">
|
|
157
|
+
<Radio.ItemInput />
|
|
158
|
+
<Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs whitespace-nowrap data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
|
|
159
|
+
Custom
|
|
160
|
+
</Radio.ItemLabel>
|
|
161
|
+
</Radio.Item>
|
|
147
162
|
</Radio>
|
|
148
163
|
)
|
|
149
164
|
}
|
|
@@ -210,10 +210,14 @@ function ThemeSelectMenu(props: {
|
|
|
210
210
|
</DropdownMenuTrigger>
|
|
211
211
|
<DropdownMenuContent
|
|
212
212
|
portalMount={portalMount()}
|
|
213
|
-
//
|
|
213
|
+
// A fixed height with its own scroll: an adaptive cap measured the
|
|
214
|
+
// host panel instead, and opening a long theme list scrolled the
|
|
215
|
+
// sidebar to reveal the menu. The content scrolls internally at the
|
|
216
|
+
// cap, so the panel around the trigger never moves. Width still
|
|
217
|
+
// follows content (at least the trigger width, never wider than the
|
|
214
218
|
// popper allows) so long theme names render in full with padding for
|
|
215
219
|
// the selection indicator instead of truncating at the anchor width.
|
|
216
|
-
class="max-h-
|
|
220
|
+
class="max-h-80 w-max min-w-(--kb-popper-anchor-width) max-w-(--kb-popper-content-available-width) overflow-x-hidden overflow-y-auto"
|
|
217
221
|
onInteractOutside={(event) => {
|
|
218
222
|
if (event.detail.originalEvent.type !== 'pointerdown') return
|
|
219
223
|
const parentDialog = portalMount()?.closest<HTMLElement>('[role="dialog"]')
|
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
* <
|
|
18
|
-
* <
|
|
19
|
-
* <div
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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`
|
|
24
|
-
*
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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={
|
|
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
|
-
|
|
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',
|
|
@@ -6,8 +6,8 @@ import { cn } from '#lib/utils'
|
|
|
6
6
|
* Card.
|
|
7
7
|
*
|
|
8
8
|
* A surface that sits on the canvas. Every card carries its own edge — a
|
|
9
|
-
* border — because in the light theme `--card`
|
|
10
|
-
*
|
|
9
|
+
* border — because in the light theme `--card` is one faint step off a white
|
|
10
|
+
* `--background`, and a borderless card there all but disappears. Making the border
|
|
11
11
|
* structural rather than optional is what stops one app's cards from reading
|
|
12
12
|
* as floating panels and another's as nothing at all.
|
|
13
13
|
*
|