@adea-ai/ui 0.93.2 → 0.95.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.
- package/README.md +43 -1
- package/dist/NOTICE +10 -0
- package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
- package/dist/components/composites/action-button/action-button.js +2 -0
- package/dist/components/composites/action-button/action-button.js.map +1 -1
- package/dist/components/composites/scene-controls/index.d.ts +3 -0
- package/dist/components/composites/scene-controls/index.d.ts.map +1 -0
- package/dist/components/composites/scene-controls/index.js +2 -0
- package/dist/components/composites/scene-controls/scene-controls.d.ts +41 -0
- package/dist/components/composites/scene-controls/scene-controls.d.ts.map +1 -0
- package/dist/components/composites/scene-controls/scene-controls.js +291 -0
- package/dist/components/composites/scene-controls/scene-controls.js.map +1 -0
- package/dist/components/layout/side-rail/side-rail.d.ts +1 -1
- package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
- package/dist/components/layout/side-rail/side-rail.js +8 -8
- package/dist/components/layout/side-rail/side-rail.js.map +1 -1
- package/dist/components/ui/button/button.d.ts +2 -1
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/button/button.js +9 -3
- package/dist/components/ui/button/button.js.map +1 -1
- package/dist/components/ui/toggle/toggle.d.ts +1 -1
- package/dist/components/ui/toggle/toggle.d.ts.map +1 -1
- package/dist/components/ui/toggle/toggle.js +3 -1
- package/dist/components/ui/toggle/toggle.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/tokens.d.ts.map +1 -1
- package/dist/lib/tokens.js +11 -1
- package/dist/lib/tokens.js.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +2 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/variants.d.ts +5 -2
- package/dist/lib/variants.d.ts.map +1 -1
- package/dist/lib/variants.js +7 -3
- package/dist/lib/variants.js.map +1 -1
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint.js +2 -1
- package/dist/lint.js.map +1 -1
- package/dist/r/registry.json +24 -0
- package/dist/r/scene-controls.json +24 -0
- package/dist/r/src/components/composites/action-button/action-button.tsx +2 -0
- package/dist/r/src/components/composites/scene-controls/index.ts +7 -0
- package/dist/r/src/components/composites/scene-controls/scene-controls.tsx +282 -0
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +18 -10
- package/dist/r/src/components/ui/button/button.tsx +20 -2
- package/dist/r/src/components/ui/toggle/toggle.tsx +2 -0
- package/dist/r/src/lib/tokens.ts +11 -1
- package/dist/r/src/lib/utils.ts +2 -0
- package/dist/r/src/lib/variants.ts +5 -2
- package/dist/r/src/styles/base.css +29 -9
- package/dist/r/src/styles/theme.css +13 -6
- package/package.json +2 -1
- package/registry.json +24 -0
- package/src/components/composites/action-button/action-button.tsx +2 -0
- package/src/components/composites/scene-controls/index.ts +7 -0
- package/src/components/composites/scene-controls/scene-controls.tsx +282 -0
- package/src/components/layout/side-rail/side-rail.tsx +18 -10
- package/src/components/ui/button/button.tsx +20 -2
- package/src/components/ui/toggle/toggle.tsx +2 -0
- package/src/index.ts +1 -0
- package/src/lib/tokens.ts +11 -1
- package/src/lib/utils.ts +2 -0
- package/src/lib/variants.ts +5 -2
- package/src/lint/index.ts +1 -0
- package/src/styles/base.css +29 -9
- package/src/styles/theme.css +13 -6
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ZoomIn, ZoomOut } from 'lucide-solid'
|
|
2
|
+
import { createSignal, onCleanup, Show, type JSX } from 'solid-js'
|
|
3
|
+
import { ActionButton } from '../action-button'
|
|
4
|
+
|
|
5
|
+
/** A host-neutral direction reported while a movement control is held. */
|
|
6
|
+
export type SceneMovementDirection = 'forward' | 'left' | 'backward' | 'right'
|
|
7
|
+
|
|
8
|
+
/** Accessible names and tooltip text for the shared scene controls. */
|
|
9
|
+
export type SceneControlLabels = {
|
|
10
|
+
movementGroup: string
|
|
11
|
+
forward: string
|
|
12
|
+
forwardTooltip: string
|
|
13
|
+
left: string
|
|
14
|
+
leftTooltip: string
|
|
15
|
+
backward: string
|
|
16
|
+
backwardTooltip: string
|
|
17
|
+
right: string
|
|
18
|
+
rightTooltip: string
|
|
19
|
+
jump: string
|
|
20
|
+
jumpTooltip: string
|
|
21
|
+
zoomGroup: string
|
|
22
|
+
zoomOut: string
|
|
23
|
+
zoomIn: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_LABELS: SceneControlLabels = {
|
|
27
|
+
movementGroup: 'Scene movement controls',
|
|
28
|
+
forward: 'Move forward',
|
|
29
|
+
forwardTooltip: 'Hold to move forward',
|
|
30
|
+
left: 'Move left',
|
|
31
|
+
leftTooltip: 'Hold to move left',
|
|
32
|
+
backward: 'Move backward',
|
|
33
|
+
backwardTooltip: 'Hold to move backward',
|
|
34
|
+
right: 'Move right',
|
|
35
|
+
rightTooltip: 'Hold to move right',
|
|
36
|
+
jump: 'Jump',
|
|
37
|
+
jumpTooltip: 'Hold to jump',
|
|
38
|
+
zoomGroup: 'Camera zoom',
|
|
39
|
+
zoomOut: 'Zoom out',
|
|
40
|
+
zoomIn: 'Zoom in',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pointerSource = (pointerId: number) => `pointer:${pointerId}`
|
|
44
|
+
const keySource = (key: string) => `key:${key}`
|
|
45
|
+
|
|
46
|
+
export type SceneControlsProps = {
|
|
47
|
+
/** Called on the transition into and out of each held movement direction. */
|
|
48
|
+
onMovementChange?: (direction: SceneMovementDirection, pressed: boolean) => void
|
|
49
|
+
/** Called when the jump action becomes pressed or released. */
|
|
50
|
+
onJumpChange?: (pressed: boolean) => void
|
|
51
|
+
/** Called once when zoom in is activated. */
|
|
52
|
+
onZoomIn?: () => void
|
|
53
|
+
/** Called once when zoom out is activated. */
|
|
54
|
+
onZoomOut?: () => void
|
|
55
|
+
/** Override the default English accessible names and tooltip copy. */
|
|
56
|
+
labels?: Partial<SceneControlLabels>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type HoldActionProps = {
|
|
60
|
+
label: string
|
|
61
|
+
tooltip: string
|
|
62
|
+
createChangeCallback: () => (pressed: boolean) => void
|
|
63
|
+
children: JSX.Element
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A held action that reports one down/up pair across pointer and keyboard input.
|
|
68
|
+
*
|
|
69
|
+
* The arrangement, rounded scrim group surfaces and release policy translate
|
|
70
|
+
* Adea's on-screen scene controls; host key synthesis and scene APIs are replaced
|
|
71
|
+
* by callbacks.
|
|
72
|
+
*/
|
|
73
|
+
function HoldAction(props: HoldActionProps) {
|
|
74
|
+
const [pressed, setPressed] = createSignal(false)
|
|
75
|
+
const sources = new Set<string>()
|
|
76
|
+
let activeChangeCallback: ((pressed: boolean) => void) | undefined
|
|
77
|
+
|
|
78
|
+
const press = (source: string) => {
|
|
79
|
+
if (sources.has(source)) return
|
|
80
|
+
const wasPressed = sources.size > 0
|
|
81
|
+
sources.add(source)
|
|
82
|
+
if (!wasPressed) {
|
|
83
|
+
setPressed(true)
|
|
84
|
+
activeChangeCallback = props.createChangeCallback()
|
|
85
|
+
activeChangeCallback(true)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const release = (source: string) => {
|
|
90
|
+
if (!sources.delete(source) || sources.size > 0) return
|
|
91
|
+
setPressed(false)
|
|
92
|
+
const callback = activeChangeCallback
|
|
93
|
+
activeChangeCallback = undefined
|
|
94
|
+
callback?.(false)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const releaseAll = () => {
|
|
98
|
+
if (sources.size === 0) return
|
|
99
|
+
sources.clear()
|
|
100
|
+
setPressed(false)
|
|
101
|
+
const callback = activeChangeCallback
|
|
102
|
+
activeChangeCallback = undefined
|
|
103
|
+
callback?.(false)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const onWindowPointerUp = (event: PointerEvent) => release(pointerSource(event.pointerId))
|
|
107
|
+
const onWindowBlur = () => releaseAll()
|
|
108
|
+
|
|
109
|
+
if (typeof window !== 'undefined') {
|
|
110
|
+
window.addEventListener('pointerup', onWindowPointerUp)
|
|
111
|
+
window.addEventListener('blur', onWindowBlur)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onCleanup(() => {
|
|
115
|
+
releaseAll()
|
|
116
|
+
if (typeof window !== 'undefined') {
|
|
117
|
+
window.removeEventListener('pointerup', onWindowPointerUp)
|
|
118
|
+
window.removeEventListener('blur', onWindowBlur)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<ActionButton
|
|
124
|
+
type="button"
|
|
125
|
+
variant="secondary"
|
|
126
|
+
size="icon-2xl"
|
|
127
|
+
aria-label={props.label}
|
|
128
|
+
aria-pressed={pressed()}
|
|
129
|
+
tooltip={props.tooltip}
|
|
130
|
+
class="touch-none select-none"
|
|
131
|
+
onContextMenu={(event) => event.preventDefault()}
|
|
132
|
+
onPointerDown={(event) => {
|
|
133
|
+
if (event.button !== 0) return
|
|
134
|
+
event.preventDefault()
|
|
135
|
+
const source = pointerSource(event.pointerId)
|
|
136
|
+
if (sources.has(source)) return
|
|
137
|
+
try {
|
|
138
|
+
event.currentTarget.setPointerCapture(event.pointerId)
|
|
139
|
+
} catch {
|
|
140
|
+
// Pointer capture is not available in every embedded browser. The
|
|
141
|
+
// window pointerup fallback still closes this held action.
|
|
142
|
+
}
|
|
143
|
+
press(source)
|
|
144
|
+
}}
|
|
145
|
+
onPointerUp={(event) => release(pointerSource(event.pointerId))}
|
|
146
|
+
onPointerCancel={(event) => release(pointerSource(event.pointerId))}
|
|
147
|
+
onLostPointerCapture={(event) => release(pointerSource(event.pointerId))}
|
|
148
|
+
onKeyDown={(event) => {
|
|
149
|
+
if (event.key !== ' ' && event.key !== 'Enter') return
|
|
150
|
+
event.preventDefault()
|
|
151
|
+
press(keySource(event.key))
|
|
152
|
+
}}
|
|
153
|
+
onKeyUp={(event) => {
|
|
154
|
+
if (event.key !== ' ' && event.key !== 'Enter') return
|
|
155
|
+
event.preventDefault()
|
|
156
|
+
release(keySource(event.key))
|
|
157
|
+
}}
|
|
158
|
+
onBlur={releaseAll}
|
|
159
|
+
onClick={(event) => {
|
|
160
|
+
// Assistive technology can activate a button without pointer or keyboard
|
|
161
|
+
// down/up events. Treat that activation as one short held action.
|
|
162
|
+
if (event.detail !== 0 || sources.size > 0) return
|
|
163
|
+
press('activation')
|
|
164
|
+
release('activation')
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{props.children}
|
|
168
|
+
</ActionButton>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Shared movement, jump and zoom controls for a host-rendered scene.
|
|
174
|
+
*
|
|
175
|
+
* Movement and jump callbacks receive pressed/released state from pointer and
|
|
176
|
+
* keyboard input. Zoom callbacks fire on activation. The host owns positioning,
|
|
177
|
+
* scene state and camera behavior.
|
|
178
|
+
*/
|
|
179
|
+
export function SceneControls(props: SceneControlsProps) {
|
|
180
|
+
const labels = () => ({ ...DEFAULT_LABELS, ...props.labels })
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div class="flex min-w-0 max-w-full flex-wrap items-end gap-3">
|
|
184
|
+
<Show when={props.onMovementChange}>
|
|
185
|
+
<div
|
|
186
|
+
role="group"
|
|
187
|
+
aria-label={labels().movementGroup}
|
|
188
|
+
class="glass-scrim grid min-w-0 max-w-full grid-cols-3 gap-1.5 rounded-3xl p-2"
|
|
189
|
+
>
|
|
190
|
+
<span aria-hidden="true" />
|
|
191
|
+
<HoldAction
|
|
192
|
+
label={labels().forward}
|
|
193
|
+
tooltip={labels().forwardTooltip}
|
|
194
|
+
createChangeCallback={() => {
|
|
195
|
+
const callback = props.onMovementChange
|
|
196
|
+
return (pressed) => callback?.('forward', pressed)
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
<ArrowUp aria-hidden="true" />
|
|
200
|
+
</HoldAction>
|
|
201
|
+
<span aria-hidden="true" />
|
|
202
|
+
<HoldAction
|
|
203
|
+
label={labels().left}
|
|
204
|
+
tooltip={labels().leftTooltip}
|
|
205
|
+
createChangeCallback={() => {
|
|
206
|
+
const callback = props.onMovementChange
|
|
207
|
+
return (pressed) => callback?.('left', pressed)
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
<ArrowLeft aria-hidden="true" />
|
|
211
|
+
</HoldAction>
|
|
212
|
+
<HoldAction
|
|
213
|
+
label={labels().backward}
|
|
214
|
+
tooltip={labels().backwardTooltip}
|
|
215
|
+
createChangeCallback={() => {
|
|
216
|
+
const callback = props.onMovementChange
|
|
217
|
+
return (pressed) => callback?.('backward', pressed)
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<ArrowDown aria-hidden="true" />
|
|
221
|
+
</HoldAction>
|
|
222
|
+
<HoldAction
|
|
223
|
+
label={labels().right}
|
|
224
|
+
tooltip={labels().rightTooltip}
|
|
225
|
+
createChangeCallback={() => {
|
|
226
|
+
const callback = props.onMovementChange
|
|
227
|
+
return (pressed) => callback?.('right', pressed)
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<ArrowRight aria-hidden="true" />
|
|
231
|
+
</HoldAction>
|
|
232
|
+
</div>
|
|
233
|
+
</Show>
|
|
234
|
+
<div class="ml-auto flex min-w-0 max-w-full flex-col items-end gap-2">
|
|
235
|
+
<Show when={props.onZoomIn || props.onZoomOut}>
|
|
236
|
+
<div
|
|
237
|
+
role="group"
|
|
238
|
+
aria-label={labels().zoomGroup}
|
|
239
|
+
class="glass-scrim flex min-w-0 max-w-full flex-wrap gap-1.5 rounded-2xl p-1.5"
|
|
240
|
+
>
|
|
241
|
+
<Show when={props.onZoomOut}>
|
|
242
|
+
<ActionButton
|
|
243
|
+
type="button"
|
|
244
|
+
variant="secondary"
|
|
245
|
+
size="icon-2xl"
|
|
246
|
+
aria-label={labels().zoomOut}
|
|
247
|
+
tooltip={labels().zoomOut}
|
|
248
|
+
onClick={() => props.onZoomOut?.()}
|
|
249
|
+
>
|
|
250
|
+
<ZoomOut aria-hidden="true" />
|
|
251
|
+
</ActionButton>
|
|
252
|
+
</Show>
|
|
253
|
+
<Show when={props.onZoomIn}>
|
|
254
|
+
<ActionButton
|
|
255
|
+
type="button"
|
|
256
|
+
variant="secondary"
|
|
257
|
+
size="icon-2xl"
|
|
258
|
+
aria-label={labels().zoomIn}
|
|
259
|
+
tooltip={labels().zoomIn}
|
|
260
|
+
onClick={() => props.onZoomIn?.()}
|
|
261
|
+
>
|
|
262
|
+
<ZoomIn aria-hidden="true" />
|
|
263
|
+
</ActionButton>
|
|
264
|
+
</Show>
|
|
265
|
+
</div>
|
|
266
|
+
</Show>
|
|
267
|
+
<Show when={props.onJumpChange}>
|
|
268
|
+
<HoldAction
|
|
269
|
+
label={labels().jump}
|
|
270
|
+
tooltip={labels().jumpTooltip}
|
|
271
|
+
createChangeCallback={() => {
|
|
272
|
+
const callback = props.onJumpChange
|
|
273
|
+
return (pressed) => callback?.(pressed)
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<ArrowUp aria-hidden="true" />
|
|
277
|
+
</HoldAction>
|
|
278
|
+
</Show>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
@@ -14,7 +14,7 @@ import { SideRailContext, createSideRailValue } from './side-rail-context'
|
|
|
14
14
|
* component rather than something each view arranges.
|
|
15
15
|
*
|
|
16
16
|
* Two forms, one component. Collapsed it is an icon column; expanded it carries
|
|
17
|
-
* labels. Both widths come from `--rail-width` (
|
|
17
|
+
* labels. Both widths come from `--rail-width` (56px) and `--rail-width-expanded`
|
|
18
18
|
* (236px), so an app cannot pick its own and end up a few pixels out of step with
|
|
19
19
|
* the app beside it.
|
|
20
20
|
*
|
|
@@ -145,16 +145,24 @@ export const sideRailItemClass = [
|
|
|
145
145
|
* element, no listener, and it works on whatever element a caller renders the row
|
|
146
146
|
* as — an anchor, a button, a router's own link.
|
|
147
147
|
*
|
|
148
|
-
* The collapsed rail opts out: a 1.02 scale on a
|
|
148
|
+
* The collapsed rail opts out: a 1.02 scale on a 56px column pushes the row past
|
|
149
149
|
* the rail's own edge, which reads as a glitch rather than as feedback.
|
|
150
150
|
*/
|
|
151
151
|
'hover:scale-[1.02] active:scale-[0.97] group-data-[collapsed=true]/rail:hover:scale-100 group-data-[collapsed=true]/rail:active:scale-[0.98]',
|
|
152
|
-
'[&_svg]:size-
|
|
152
|
+
'[&_svg]:size-5 [&_svg]:shrink-0',
|
|
153
153
|
].join(' ')
|
|
154
154
|
|
|
155
|
+
/*
|
|
156
|
+
* The rail's states are accent-driven: the icon carries `--primary` on hover and
|
|
157
|
+
* on the active destination, and both states draw the accent's own tint rather
|
|
158
|
+
* than a neutral fill, so the rail reads as the app's accent chroma instead of a
|
|
159
|
+
* grey column. The active label stays on `sidebar-foreground` because the accent
|
|
160
|
+
* presets guarantee their 3:1 interaction minimum against the surface, not the
|
|
161
|
+
* 4.5:1 small text owes; the icon is a graphic and rides the accent.
|
|
162
|
+
*/
|
|
155
163
|
export const sideRailItemStateClass = {
|
|
156
|
-
active: 'bg-
|
|
157
|
-
idle: 'text-sidebar-muted-foreground hover:bg-
|
|
164
|
+
active: 'bg-primary-subtle text-sidebar-foreground [&_svg]:text-primary',
|
|
165
|
+
idle: 'text-sidebar-muted-foreground hover:bg-primary-subtle/60 hover:text-primary',
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
/**
|
|
@@ -259,7 +267,7 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
|
|
|
259
267
|
{...(rest as Record<string, unknown>)}
|
|
260
268
|
>
|
|
261
269
|
<Show when={local.badge}>
|
|
262
|
-
<span class="relative flex size-
|
|
270
|
+
<span class="relative flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
|
|
263
271
|
{local.children}
|
|
264
272
|
<span class="absolute -top-1 -end-1 flex items-center justify-center">
|
|
265
273
|
{local.badge}
|
|
@@ -267,7 +275,7 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
|
|
|
267
275
|
</span>
|
|
268
276
|
</Show>
|
|
269
277
|
<Show when={!local.badge}>
|
|
270
|
-
<span class="flex size-
|
|
278
|
+
<span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
|
|
271
279
|
{local.children}
|
|
272
280
|
</span>
|
|
273
281
|
</Show>
|
|
@@ -307,7 +315,7 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
|
|
|
307
315
|
}}
|
|
308
316
|
>
|
|
309
317
|
<span
|
|
310
|
-
class={cn('flex size-
|
|
318
|
+
class={cn('flex size-5 shrink-0 items-center justify-center [&_svg]:size-5', {
|
|
311
319
|
'text-primary': local.active,
|
|
312
320
|
})}
|
|
313
321
|
>
|
|
@@ -368,7 +376,7 @@ export function SideRailButton(
|
|
|
368
376
|
)}
|
|
369
377
|
{...rest}
|
|
370
378
|
>
|
|
371
|
-
<span class="flex size-
|
|
379
|
+
<span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
|
|
372
380
|
{local.children}
|
|
373
381
|
</span>
|
|
374
382
|
<span class="min-w-0 flex-1 truncate group-data-[collapsed=true]/rail:sr-only">
|
|
@@ -397,7 +405,7 @@ export function SideRailButton(
|
|
|
397
405
|
height: `${position().height}px`,
|
|
398
406
|
}}
|
|
399
407
|
>
|
|
400
|
-
<span class="flex size-
|
|
408
|
+
<span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
|
|
401
409
|
{local.children}
|
|
402
410
|
</span>
|
|
403
411
|
{local.label}
|
|
@@ -47,6 +47,7 @@ export const buttonVariants = cva(
|
|
|
47
47
|
md: `${controlSize.md} [&_svg]:size-4`,
|
|
48
48
|
lg: `${controlSize.lg} [&_svg]:size-4`,
|
|
49
49
|
xl: `${controlSize.xl} [&_svg]:size-5`,
|
|
50
|
+
'2xl': `${controlSize['2xl']} [&_svg]:size-6`,
|
|
50
51
|
/* Square, for an icon with no label. The accessible name then has to
|
|
51
52
|
come from `aria-label` or a visually hidden label — an icon-only
|
|
52
53
|
button with neither is the accessibility finding the Storybook lane
|
|
@@ -57,6 +58,11 @@ export const buttonVariants = cva(
|
|
|
57
58
|
'icon-md': 'size-control-md p-0 [&_svg]:size-4',
|
|
58
59
|
'icon-lg': 'size-control-lg p-0 [&_svg]:size-4',
|
|
59
60
|
'icon-xl': 'size-control-xl p-0 [&_svg]:size-5',
|
|
61
|
+
'icon-2xl': 'size-control-2xl p-0 [&_svg]:size-6',
|
|
62
|
+
},
|
|
63
|
+
/** Opt in to a stable touch target on devices with any coarse pointer. */
|
|
64
|
+
touchTarget: {
|
|
65
|
+
comfortable: 'touch-target-comfortable',
|
|
60
66
|
},
|
|
61
67
|
},
|
|
62
68
|
defaultVariants: {
|
|
@@ -76,13 +82,25 @@ export type ButtonProps<T extends ValidComponent = 'button'> = PolymorphicProps<
|
|
|
76
82
|
>
|
|
77
83
|
|
|
78
84
|
export function Button<T extends ValidComponent = 'button'>(props: ButtonProps<T>) {
|
|
79
|
-
const [local, rest] = splitProps(props as ButtonProps, [
|
|
85
|
+
const [local, rest] = splitProps(props as ButtonProps, [
|
|
86
|
+
'variant',
|
|
87
|
+
'size',
|
|
88
|
+
'touchTarget',
|
|
89
|
+
'class',
|
|
90
|
+
])
|
|
80
91
|
|
|
81
92
|
return (
|
|
82
93
|
<Polymorphic
|
|
83
94
|
as="button"
|
|
84
95
|
type="button"
|
|
85
|
-
class={cn(
|
|
96
|
+
class={cn(
|
|
97
|
+
buttonVariants({
|
|
98
|
+
variant: local.variant,
|
|
99
|
+
size: local.size,
|
|
100
|
+
touchTarget: local.touchTarget,
|
|
101
|
+
}),
|
|
102
|
+
local.class
|
|
103
|
+
)}
|
|
86
104
|
{...(rest as ComponentProps<'button'>)}
|
|
87
105
|
/>
|
|
88
106
|
)
|
|
@@ -34,12 +34,14 @@ export const toggleVariants = cva(
|
|
|
34
34
|
md: `${controlSize.md} [&_svg]:size-4`,
|
|
35
35
|
lg: `${controlSize.lg} [&_svg]:size-4`,
|
|
36
36
|
xl: `${controlSize.xl} [&_svg]:size-5`,
|
|
37
|
+
'2xl': `${controlSize['2xl']} [&_svg]:size-6`,
|
|
37
38
|
'icon-2xs': 'size-control-2xs p-0 [&_svg]:size-3',
|
|
38
39
|
'icon-xs': 'size-control-xs p-0 [&_svg]:size-3.5',
|
|
39
40
|
'icon-sm': 'size-control-sm p-0 [&_svg]:size-3.5',
|
|
40
41
|
'icon-md': 'size-control-md p-0 [&_svg]:size-4',
|
|
41
42
|
'icon-lg': 'size-control-lg p-0 [&_svg]:size-4',
|
|
42
43
|
'icon-xl': 'size-control-xl p-0 [&_svg]:size-5',
|
|
44
|
+
'icon-2xl': 'size-control-2xl p-0 [&_svg]:size-6',
|
|
43
45
|
},
|
|
44
46
|
},
|
|
45
47
|
defaultVariants: { variant: 'default', size: 'md' },
|
package/dist/r/src/lib/tokens.ts
CHANGED
|
@@ -480,6 +480,11 @@ export const densityTokens: TokenDefinition[] = [
|
|
|
480
480
|
kind: 'dimension',
|
|
481
481
|
description: '40px — a hero control in an empty state.',
|
|
482
482
|
},
|
|
483
|
+
{
|
|
484
|
+
name: 'control-height-2xl',
|
|
485
|
+
kind: 'dimension',
|
|
486
|
+
description: '48px — a stable touch target, unchanged by compact density.',
|
|
487
|
+
},
|
|
483
488
|
{
|
|
484
489
|
name: 'control-padding-2xs',
|
|
485
490
|
kind: 'dimension',
|
|
@@ -510,10 +515,15 @@ export const densityTokens: TokenDefinition[] = [
|
|
|
510
515
|
kind: 'dimension',
|
|
511
516
|
description: 'Inline padding that pairs with the 40px control.',
|
|
512
517
|
},
|
|
518
|
+
{
|
|
519
|
+
name: 'control-padding-2xl',
|
|
520
|
+
kind: 'dimension',
|
|
521
|
+
description: 'Inline padding that pairs with the stable 48px touch target.',
|
|
522
|
+
},
|
|
513
523
|
{ name: 'row-height-sm', kind: 'dimension', description: '28px — a dense list row.' },
|
|
514
524
|
{ name: 'row-height-md', kind: 'dimension', description: '32px — the default list row.' },
|
|
515
525
|
{ name: 'row-height-lg', kind: 'dimension', description: '36px — a two-line list row.' },
|
|
516
|
-
{ name: 'rail-width', kind: 'dimension', description: '
|
|
526
|
+
{ name: 'rail-width', kind: 'dimension', description: '56px — the icon-only rail.' },
|
|
517
527
|
{ name: 'rail-width-expanded', kind: 'dimension', description: '236px — the labelled rail.' },
|
|
518
528
|
{
|
|
519
529
|
name: 'rail-item-height',
|
package/dist/r/src/lib/utils.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const cn: CnFunction = createCn({
|
|
|
36
36
|
'control-md',
|
|
37
37
|
'control-lg',
|
|
38
38
|
'control-xl',
|
|
39
|
+
'control-2xl',
|
|
39
40
|
'row-sm',
|
|
40
41
|
'row-md',
|
|
41
42
|
'row-lg',
|
|
@@ -61,6 +62,7 @@ export const cn: CnFunction = createCn({
|
|
|
61
62
|
'control-md',
|
|
62
63
|
'control-lg',
|
|
63
64
|
'control-xl',
|
|
65
|
+
'control-2xl',
|
|
64
66
|
'rail-item',
|
|
65
67
|
],
|
|
66
68
|
},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* rather than inventing synonyms:
|
|
12
12
|
*
|
|
13
13
|
* variant what the control *is for* — primary, secondary, destructive, …
|
|
14
|
-
* size how much room it takes — 2xs, xs, sm, md, lg, xl
|
|
14
|
+
* size how much room it takes — 2xs, xs, sm, md, lg, xl, 2xl
|
|
15
15
|
* tone what it *communicates* — neutral, success, warning, …
|
|
16
16
|
*
|
|
17
17
|
* A component that needs a fourth axis is usually two components.
|
|
@@ -32,6 +32,8 @@ export const controlSize = {
|
|
|
32
32
|
md: 'h-control-md px-control-md text-sm gap-2',
|
|
33
33
|
lg: 'h-control-lg px-control-lg text-sm gap-2',
|
|
34
34
|
xl: 'h-control-xl px-control-xl text-base gap-2.5',
|
|
35
|
+
/** A stable 48px touch target; compact density intentionally leaves it alone. */
|
|
36
|
+
'2xl': 'h-control-2xl px-control-2xl text-lg gap-3',
|
|
35
37
|
} as const
|
|
36
38
|
|
|
37
39
|
/** Square counterparts for icon-only controls: the height, with equal width. */
|
|
@@ -42,9 +44,10 @@ export const controlSizeIcon = {
|
|
|
42
44
|
md: 'size-control-md p-0',
|
|
43
45
|
lg: 'size-control-lg p-0',
|
|
44
46
|
xl: 'size-control-xl p-0',
|
|
47
|
+
'2xl': 'size-control-2xl p-0',
|
|
45
48
|
} as const
|
|
46
49
|
|
|
47
|
-
export const controlSizes = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl'] as const
|
|
50
|
+
export const controlSizes = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const
|
|
48
51
|
export type ControlSize = (typeof controlSizes)[number]
|
|
49
52
|
|
|
50
53
|
/**
|
|
@@ -250,6 +250,18 @@
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Opt into the stable 2xl control rung only when a coarse pointer is available.
|
|
255
|
+
* `any-pointer` intentionally includes hybrid devices without changing the
|
|
256
|
+
* glyph or desktop density of the selected Button size.
|
|
257
|
+
*/
|
|
258
|
+
@utility touch-target-comfortable {
|
|
259
|
+
@media (any-pointer: coarse) {
|
|
260
|
+
min-inline-size: var(--control-height-2xl);
|
|
261
|
+
min-block-size: var(--control-height-2xl);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
/**
|
|
254
266
|
* The drag region of a frameless desktop window.
|
|
255
267
|
*
|
|
@@ -422,9 +434,9 @@
|
|
|
422
434
|
}
|
|
423
435
|
}
|
|
424
436
|
|
|
425
|
-
/*
|
|
426
|
-
|
|
427
|
-
|
|
437
|
+
/* Glass treatments used by the top bar, rail, scene-control groups and floating
|
|
438
|
+
overlays. Consumers use the owning component; shared composites use the
|
|
439
|
+
named utility instead of restyling a primitive. */
|
|
428
440
|
@utility glass-panel {
|
|
429
441
|
background: color-mix(in oklch, var(--chrome) calc(var(--chrome-alpha) * 100%), transparent);
|
|
430
442
|
backdrop-filter: blur(18px) saturate(1.6);
|
|
@@ -438,6 +450,12 @@
|
|
|
438
450
|
-webkit-backdrop-filter: blur(12px) saturate(1.4);
|
|
439
451
|
}
|
|
440
452
|
|
|
453
|
+
@utility glass-scrim {
|
|
454
|
+
background: color-mix(in oklch, var(--scrim) 20%, transparent);
|
|
455
|
+
backdrop-filter: blur(2px);
|
|
456
|
+
-webkit-backdrop-filter: blur(2px);
|
|
457
|
+
}
|
|
458
|
+
|
|
441
459
|
/* The focus ring. Applied by components that cannot use the global outline
|
|
442
460
|
because they need the ring to follow their own radius. */
|
|
443
461
|
@utility focus-ring {
|
|
@@ -657,7 +675,8 @@
|
|
|
657
675
|
|
|
658
676
|
@media (prefers-reduced-transparency: reduce) {
|
|
659
677
|
.glass-panel,
|
|
660
|
-
.glass-surface
|
|
678
|
+
.glass-surface,
|
|
679
|
+
.glass-scrim {
|
|
661
680
|
backdrop-filter: none !important;
|
|
662
681
|
-webkit-backdrop-filter: none !important;
|
|
663
682
|
background: var(--background) !important;
|
|
@@ -670,18 +689,19 @@
|
|
|
670
689
|
}
|
|
671
690
|
|
|
672
691
|
.glass-panel,
|
|
673
|
-
.glass-surface
|
|
692
|
+
.glass-surface,
|
|
693
|
+
.glass-scrim {
|
|
674
694
|
background: var(--background) !important;
|
|
675
695
|
border: 1px solid var(--foreground) !important;
|
|
676
696
|
}
|
|
677
697
|
}
|
|
678
698
|
|
|
679
|
-
/*
|
|
680
|
-
|
|
681
|
-
surface instead, keeping borders and shadows so panels stay delimited. */
|
|
699
|
+
/* Without backdrop-filter there is no blurred context behind a glass surface.
|
|
700
|
+
Fall back to a solid surface so glass panels and scrim groups stay legible. */
|
|
682
701
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
683
702
|
.glass-panel,
|
|
684
|
-
.glass-surface
|
|
703
|
+
.glass-surface,
|
|
704
|
+
.glass-scrim {
|
|
685
705
|
background: var(--background) !important;
|
|
686
706
|
}
|
|
687
707
|
}
|
|
@@ -582,6 +582,7 @@
|
|
|
582
582
|
--control-height-md: 2rem; /* 32px — form default */
|
|
583
583
|
--control-height-lg: 2.25rem; /* 36px */
|
|
584
584
|
--control-height-xl: 2.5rem; /* 40px */
|
|
585
|
+
--control-height-2xl: 3rem; /* 48px — stable touch target */
|
|
585
586
|
|
|
586
587
|
--control-padding-2xs: 0.375rem;
|
|
587
588
|
--control-padding-xs: 0.5rem;
|
|
@@ -589,6 +590,7 @@
|
|
|
589
590
|
--control-padding-md: 0.75rem;
|
|
590
591
|
--control-padding-lg: 0.875rem;
|
|
591
592
|
--control-padding-xl: 1rem;
|
|
593
|
+
--control-padding-2xl: 1.125rem;
|
|
592
594
|
|
|
593
595
|
--row-height-sm: 1.75rem;
|
|
594
596
|
--row-height-md: 2rem;
|
|
@@ -601,10 +603,11 @@
|
|
|
601
603
|
precede every `[data-font]` block for the reason given there. */
|
|
602
604
|
|
|
603
605
|
/* Shell geometry. One place to change how wide a rail or sidebar is, so the two
|
|
604
|
-
consumer apps cannot disagree.
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
606
|
+
consumer apps cannot disagree. 56px keeps the icon column to its content:
|
|
607
|
+
20px glyphs centred in a 36px row need less chrome than the 74px the donor's
|
|
608
|
+
layout hook published, and the tighter column returns the difference to the
|
|
609
|
+
canvas. */
|
|
610
|
+
--rail-width: 3.5rem; /* 56px */
|
|
608
611
|
--rail-width-expanded: 14.75rem; /* 236px */
|
|
609
612
|
--rail-item-height: 2.25rem;
|
|
610
613
|
--sidebar-width: 16rem; /* 256px */
|
|
@@ -645,14 +648,15 @@
|
|
|
645
648
|
--z-toast: 180;
|
|
646
649
|
}
|
|
647
650
|
|
|
648
|
-
/* Compact density: the
|
|
651
|
+
/* Compact density: the standard control ladder one rung tighter.
|
|
649
652
|
*
|
|
650
653
|
* It is a *shift* rather than a second set of numbers chosen by eye, so the
|
|
651
654
|
* relationship between the rungs is identical at either density and a control keeps
|
|
652
655
|
* its place in the ladder. Height moves by 4px per rung and padding by 2px, which is
|
|
653
656
|
* the step each already uses, and the row heights move by the same 4px. A `sm`
|
|
654
657
|
* control goes from 28px to 24px — the `xs` control's comfortable height. That is the
|
|
655
|
-
* intended meaning, not an oversight.
|
|
658
|
+
* intended meaning, not an oversight. The `2xl` touch target is intentionally
|
|
659
|
+
* omitted here so it stays at least 48px at either density.
|
|
656
660
|
*
|
|
657
661
|
* 16px at `2xs` is the floor. One rung below it would be 12px, under the minimum
|
|
658
662
|
* comfortable touch target, so the ladder compresses from the bottom rather than
|
|
@@ -832,6 +836,7 @@
|
|
|
832
836
|
--height-control-md: var(--control-height-md);
|
|
833
837
|
--height-control-lg: var(--control-height-lg);
|
|
834
838
|
--height-control-xl: var(--control-height-xl);
|
|
839
|
+
--height-control-2xl: var(--control-height-2xl);
|
|
835
840
|
/* Tailwind `size-*` prefers --size-* and then --spacing-*. Without this
|
|
836
841
|
bridge, square icon controls use the padding ladder below for both axes.
|
|
837
842
|
Height, square size and inline padding remain distinct semantic contracts. */
|
|
@@ -841,6 +846,7 @@
|
|
|
841
846
|
--size-control-md: var(--control-height-md);
|
|
842
847
|
--size-control-lg: var(--control-height-lg);
|
|
843
848
|
--size-control-xl: var(--control-height-xl);
|
|
849
|
+
--size-control-2xl: var(--control-height-2xl);
|
|
844
850
|
--height-row-sm: var(--row-height-sm);
|
|
845
851
|
--height-row-md: var(--row-height-md);
|
|
846
852
|
--height-row-lg: var(--row-height-lg);
|
|
@@ -854,6 +860,7 @@
|
|
|
854
860
|
--spacing-control-md: var(--control-padding-md);
|
|
855
861
|
--spacing-control-lg: var(--control-padding-lg);
|
|
856
862
|
--spacing-control-xl: var(--control-padding-xl);
|
|
863
|
+
--spacing-control-2xl: var(--control-padding-2xl);
|
|
857
864
|
|
|
858
865
|
--width-rail: var(--rail-width);
|
|
859
866
|
--width-rail-expanded: var(--rail-width-expanded);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adea-ai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.95.0",
|
|
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",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"check:packed-layout": "node --test scripts/check-packed-layout-process.test.mjs && node scripts/check-packed-layout.mjs",
|
|
104
104
|
"check:packed-consumer": "bun scripts/check-packed-consumer.ts",
|
|
105
105
|
"check:packed-list-row": "bun scripts/check-packed-list-row.ts",
|
|
106
|
+
"check:packed-scene-controls": "bun scripts/check-packed-scene-controls.ts",
|
|
106
107
|
"check:packed-conversation": "bun scripts/check-packed-conversation.ts",
|
|
107
108
|
"check:packed-transcript": "bun scripts/check-packed-transcript.ts",
|
|
108
109
|
"check:packed-layout-renderer": "bun scripts/check-packed-layout-renderer.ts",
|