@adea-ai/ui 0.114.0 → 0.115.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/dist/components/composites/list-row/list-row-control.d.ts +6 -0
- package/dist/components/composites/list-row/list-row-control.d.ts.map +1 -1
- package/dist/components/composites/list-row/list-row-control.js +4 -0
- package/dist/components/composites/list-row/list-row-control.js.map +1 -1
- package/dist/components/composites/tree/index.d.ts +1 -1
- package/dist/components/composites/tree/index.d.ts.map +1 -1
- package/dist/components/composites/tree/index.js +2 -2
- package/dist/components/composites/tree/tree.d.ts +12 -0
- package/dist/components/composites/tree/tree.d.ts.map +1 -1
- package/dist/components/composites/tree/tree.js +27 -2
- package/dist/components/composites/tree/tree.js.map +1 -1
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts +7 -0
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts.map +1 -1
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.js +24 -6
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.js.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts +6 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js +3 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
- package/dist/components/layout/top-bar/top-bar.d.ts.map +1 -1
- package/dist/components/layout/top-bar/top-bar.js +2 -2
- package/dist/components/layout/top-bar/top-bar.js.map +1 -1
- package/dist/components/ui/input/input-control.d.ts +7 -1
- package/dist/components/ui/input/input-control.d.ts.map +1 -1
- package/dist/components/ui/input/input-control.js +10 -2
- package/dist/components/ui/input/input-control.js.map +1 -1
- package/dist/components/ui/input/input.d.ts +5 -4
- package/dist/components/ui/input/input.d.ts.map +1 -1
- package/dist/components/ui/input/input.js.map +1 -1
- package/dist/components/ui/status-chip/status-chip.d.ts +5 -3
- package/dist/components/ui/status-chip/status-chip.d.ts.map +1 -1
- package/dist/components/ui/status-chip/status-chip.js +5 -1
- package/dist/components/ui/status-chip/status-chip.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/r/src/components/composites/list-row/list-row-control.tsx +8 -0
- package/dist/r/src/components/composites/tree/index.ts +1 -1
- package/dist/r/src/components/composites/tree/tree.tsx +31 -1
- package/dist/r/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +20 -4
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +14 -1
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +7 -2
- package/dist/r/src/components/ui/input/input-control.tsx +14 -3
- package/dist/r/src/components/ui/input/input.tsx +5 -5
- package/dist/r/src/components/ui/status-chip/status-chip.tsx +13 -2
- package/dist/r/src/styles/base.css +7 -6
- package/package.json +1 -1
- package/src/components/composites/list-row/list-row-control.tsx +8 -0
- package/src/components/composites/tree/index.ts +1 -1
- package/src/components/composites/tree/tree.tsx +31 -1
- package/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +20 -4
- package/src/components/layout/sidebar-nav/sidebar-nav.tsx +14 -1
- package/src/components/layout/top-bar/top-bar.tsx +7 -2
- package/src/components/ui/input/input-control.tsx +14 -3
- package/src/components/ui/input/input.tsx +5 -5
- package/src/components/ui/status-chip/status-chip.tsx +13 -2
- package/src/styles/base.css +7 -6
|
@@ -27,6 +27,13 @@ export type ContextualSidebarProps = {
|
|
|
27
27
|
label: string
|
|
28
28
|
/** Visible navigation title and mobile dialog title. */
|
|
29
29
|
title: string
|
|
30
|
+
/**
|
|
31
|
+
* Where the title row shows. `always` (default) draws it on desktop and in
|
|
32
|
+
* the mobile sheet; `mobile` keeps the desktop title for assistive
|
|
33
|
+
* technology only and drops the desktop row, for hosts whose top bar
|
|
34
|
+
* already names the context. A `header` slot keeps the row visible.
|
|
35
|
+
*/
|
|
36
|
+
titleVisibility?: 'always' | 'mobile'
|
|
30
37
|
/** Host-controlled expanded state for the desktop panel and mobile dialog. */
|
|
31
38
|
open: boolean
|
|
32
39
|
onOpenChange(open: boolean): void
|
|
@@ -169,10 +176,19 @@ export function ContextualSidebar(props: ContextualSidebarProps) {
|
|
|
169
176
|
/>
|
|
170
177
|
</Show>
|
|
171
178
|
<Show when={!mobile || mobileMount()?.connected}>
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
{
|
|
175
|
-
|
|
179
|
+
<Show
|
|
180
|
+
when={mobile || props.titleVisibility !== 'mobile' || props.header}
|
|
181
|
+
fallback={
|
|
182
|
+
<SidebarNavTitle as={props.headingAs ?? 'h2'} class="sr-only">
|
|
183
|
+
{props.title}
|
|
184
|
+
</SidebarNavTitle>
|
|
185
|
+
}
|
|
186
|
+
>
|
|
187
|
+
<SidebarNavHeader class={cn(mobile && 'pe-12', props.headerClass)}>
|
|
188
|
+
<SidebarNavTitle as={props.headingAs ?? 'h2'}>{props.title}</SidebarNavTitle>
|
|
189
|
+
{props.header?.(context)}
|
|
190
|
+
</SidebarNavHeader>
|
|
191
|
+
</Show>
|
|
176
192
|
<SidebarNavContent class={props.contentClass}>{props.content(context)}</SidebarNavContent>
|
|
177
193
|
{props.footer ? (
|
|
178
194
|
<SidebarNavFooter class={props.footerClass}>{props.footer(context)}</SidebarNavFooter>
|
|
@@ -140,6 +140,12 @@ export type SidebarNavSectionProps = ComponentProps<'div'> & {
|
|
|
140
140
|
count?: number
|
|
141
141
|
/** A control at the trailing edge of the heading row. */
|
|
142
142
|
action?: JSX.Element
|
|
143
|
+
/**
|
|
144
|
+
* When the trailing action shows. `hover` (default) reveals it on hover or
|
|
145
|
+
* focus; `always` keeps it visible, for a section whose primary action is
|
|
146
|
+
* creating its first item.
|
|
147
|
+
*/
|
|
148
|
+
actionVisibility?: 'hover' | 'always'
|
|
143
149
|
/** Semantic heading element for a host's section hierarchy. */
|
|
144
150
|
headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
145
151
|
}
|
|
@@ -156,6 +162,7 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
|
|
|
156
162
|
'triggerProps',
|
|
157
163
|
'count',
|
|
158
164
|
'action',
|
|
165
|
+
'actionVisibility',
|
|
159
166
|
'headingAs',
|
|
160
167
|
'children',
|
|
161
168
|
])
|
|
@@ -237,7 +244,13 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
|
|
|
237
244
|
</Show>
|
|
238
245
|
</Polymorphic>
|
|
239
246
|
<Show when={local.action}>
|
|
240
|
-
<span
|
|
247
|
+
<span
|
|
248
|
+
data-slot="sidebar-nav-section-action"
|
|
249
|
+
class={cn('shrink-0', {
|
|
250
|
+
'opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(any-pointer:coarse)]:opacity-100':
|
|
251
|
+
local.actionVisibility !== 'always',
|
|
252
|
+
})}
|
|
253
|
+
>
|
|
241
254
|
{local.action}
|
|
242
255
|
</span>
|
|
243
256
|
</Show>
|
|
@@ -99,7 +99,9 @@ export function TopBarTitle(
|
|
|
99
99
|
<div
|
|
100
100
|
data-slot="top-bar-title"
|
|
101
101
|
class={cn(
|
|
102
|
-
|
|
102
|
+
/* The title slot sits in the drag region; a link, button or field
|
|
103
|
+
placed in it (a breadcrumb, a view's search) must stay pressable. */
|
|
104
|
+
'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',
|
|
103
105
|
local.align === 'center' && 'text-center',
|
|
104
106
|
local.class
|
|
105
107
|
)}
|
|
@@ -114,7 +116,10 @@ export function TopBarBreadcrumb(props: ComponentProps<'div'>) {
|
|
|
114
116
|
return (
|
|
115
117
|
<div
|
|
116
118
|
data-slot="top-bar-breadcrumb"
|
|
117
|
-
class={cn(
|
|
119
|
+
class={cn(
|
|
120
|
+
'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',
|
|
121
|
+
local.class
|
|
122
|
+
)}
|
|
118
123
|
{...rest}
|
|
119
124
|
/>
|
|
120
125
|
)
|
|
@@ -9,17 +9,28 @@ import { cn } from '../../../lib/utils'
|
|
|
9
9
|
* when a consumer needs only the control; `Input` adds native datalist
|
|
10
10
|
* suggestions for forms that need them.
|
|
11
11
|
*/
|
|
12
|
-
export type InputControlProps = ComponentProps<'input'>
|
|
12
|
+
export type InputControlProps = Omit<ComponentProps<'input'>, 'size'> & {
|
|
13
|
+
/**
|
|
14
|
+
* The control rung, matching `SelectTrigger`: `sm` for a dense toolbar or
|
|
15
|
+
* pane filter beside `icon-sm` actions, `md` (the default) everywhere else.
|
|
16
|
+
*/
|
|
17
|
+
size?: 'sm' | 'md' | 'lg'
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
export function InputControl(props: InputControlProps) {
|
|
15
|
-
const [local, rest] = splitProps(props, ['class', 'type'])
|
|
21
|
+
const [local, rest] = splitProps(props, ['class', 'type', 'size'])
|
|
16
22
|
|
|
17
23
|
return (
|
|
18
24
|
<input
|
|
19
25
|
data-slot="input"
|
|
20
26
|
type={local.type ?? 'text'}
|
|
21
27
|
class={cn(
|
|
22
|
-
'
|
|
28
|
+
'w-full min-w-0 rounded-md border border-input bg-transparent py-1',
|
|
29
|
+
{
|
|
30
|
+
'h-control-sm px-control-sm text-xs': local.size === 'sm',
|
|
31
|
+
'h-control-md px-control-md text-sm': local.size === 'md' || local.size === undefined,
|
|
32
|
+
'h-control-lg px-control-lg text-sm': local.size === 'lg',
|
|
33
|
+
},
|
|
23
34
|
'transition-[color,box-shadow,border-color] ease-out outline-none',
|
|
24
35
|
'placeholder:text-muted-foreground',
|
|
25
36
|
'selection:bg-primary selection:text-primary-foreground',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { ComponentProps } from 'solid-js'
|
|
2
1
|
import { createUniqueId, For, Show, splitProps } from 'solid-js'
|
|
3
2
|
import { useFormFieldControl } from '../../../lib/form-field'
|
|
4
|
-
import { InputControl } from './input-control'
|
|
3
|
+
import { InputControl, type InputControlProps } from './input-control'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Input.
|
|
@@ -12,10 +11,11 @@ import { InputControl } from './input-control'
|
|
|
12
11
|
* them separate is what lets an input sit inside a custom control without
|
|
13
12
|
* inheriting a form's ARIA graph.
|
|
14
13
|
*
|
|
15
|
-
* Height comes from
|
|
16
|
-
*
|
|
14
|
+
* Height comes from the control tokens (`size`, `md` by default); an input
|
|
15
|
+
* cannot be given a height off that ladder, which is what keeps it aligned
|
|
16
|
+
* with Button and Select.
|
|
17
17
|
*/
|
|
18
|
-
export type InputProps =
|
|
18
|
+
export type InputProps = InputControlProps & {
|
|
19
19
|
/** Native text suggestions; custom values remain valid and submit unchanged. */
|
|
20
20
|
suggestions?: readonly string[]
|
|
21
21
|
}
|
|
@@ -13,7 +13,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'
|
|
|
13
13
|
* with no text (unreadable to anyone who cannot see it) and a bare word with no
|
|
14
14
|
* explanation ("Needs configuration" — of what?).
|
|
15
15
|
*
|
|
16
|
-
* The tones are deliberately not a rainbow.
|
|
16
|
+
* The tones are deliberately not a rainbow. Seven cover what a status can actually
|
|
17
17
|
* mean:
|
|
18
18
|
*
|
|
19
19
|
* - `neutral` — fine, and there is nothing to say about it.
|
|
@@ -21,6 +21,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'
|
|
|
21
21
|
* - `warning` — degraded, or needs a decision from the user.
|
|
22
22
|
* - `danger` — blocked, failed, unrecoverable without intervention.
|
|
23
23
|
* - `info` — in flight or scheduled; work is happening, nothing is wrong.
|
|
24
|
+
* - `pending` — running now with no result yet (a check, a build). The ring
|
|
25
|
+
* turns, so it reads as live rather than as a settled state.
|
|
24
26
|
* - `unknown` — the honest absence of information.
|
|
25
27
|
*
|
|
26
28
|
* `unknown` is not the same as `neutral`, and collapsing them is the mistake
|
|
@@ -39,6 +41,7 @@ export const statusDotVariants = cva('size-2 shrink-0 rounded-full', {
|
|
|
39
41
|
warning: 'bg-warning',
|
|
40
42
|
danger: 'bg-destructive',
|
|
41
43
|
info: 'bg-info',
|
|
44
|
+
pending: 'border-2 border-info border-r-transparent motion-safe:animate-spin',
|
|
42
45
|
unknown: 'border border-muted-foreground bg-transparent',
|
|
43
46
|
},
|
|
44
47
|
},
|
|
@@ -51,10 +54,18 @@ const labelTone: Record<StatusTone, string> = {
|
|
|
51
54
|
warning: 'text-foreground',
|
|
52
55
|
danger: 'text-foreground',
|
|
53
56
|
info: 'text-foreground',
|
|
57
|
+
pending: 'text-foreground',
|
|
54
58
|
unknown: 'text-muted-foreground',
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
export type StatusTone =
|
|
61
|
+
export type StatusTone =
|
|
62
|
+
| 'neutral'
|
|
63
|
+
| 'success'
|
|
64
|
+
| 'warning'
|
|
65
|
+
| 'danger'
|
|
66
|
+
| 'info'
|
|
67
|
+
| 'pending'
|
|
68
|
+
| 'unknown'
|
|
58
69
|
|
|
59
70
|
export type StatusChipProps = Omit<ComponentProps<'span'>, 'children'> & {
|
|
60
71
|
tone?: StatusTone
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/* Emitted only when ListRowControl is used. A described row changes its own
|
|
99
|
-
height and stacks trailing actions when its available width gets tight
|
|
99
|
+
height and stacks trailing actions when its available width gets tight,
|
|
100
|
+
unless the row opts out with `stackTrailing={false}`. */
|
|
100
101
|
@utility list-row-description {
|
|
101
102
|
&[data-description] {
|
|
102
103
|
align-items: flex-start;
|
|
@@ -111,23 +112,23 @@
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
@container list-row (width < 28rem) {
|
|
114
|
-
&[data-description] [data-slot='list-row-content'] {
|
|
115
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-content'] {
|
|
115
116
|
align-items: stretch;
|
|
116
117
|
flex-direction: column;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
&[data-description] [data-slot='list-row-label'],
|
|
120
|
-
&[data-description] [data-slot='list-row-trailing'] {
|
|
120
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-label'],
|
|
121
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-trailing'] {
|
|
121
122
|
flex: none;
|
|
122
123
|
width: 100%;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
&[data-description] [data-slot='list-row-trailing'] {
|
|
126
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-trailing'] {
|
|
126
127
|
flex-wrap: wrap;
|
|
127
128
|
justify-content: flex-end;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
&[data-description] [data-slot='list-row-trailing'] button {
|
|
131
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-trailing'] button {
|
|
131
132
|
max-width: 100%;
|
|
132
133
|
white-space: normal;
|
|
133
134
|
}
|
package/package.json
CHANGED
|
@@ -18,6 +18,12 @@ export type ListRowControlProps<T extends ValidComponent = 'div'> = PolymorphicP
|
|
|
18
18
|
trailing?: JSX.Element
|
|
19
19
|
/** A second line that wraps and grows the row to fit its content. */
|
|
20
20
|
description?: string
|
|
21
|
+
/**
|
|
22
|
+
* Whether a described row may drop its trailing slot onto its own line when
|
|
23
|
+
* the row gets narrow (the default). Pass `false` for a short, truncating
|
|
24
|
+
* label whose single action must stay beside it.
|
|
25
|
+
*/
|
|
26
|
+
stackTrailing?: boolean
|
|
21
27
|
/** Native button type; defaults to `button` for button rows. */
|
|
22
28
|
type?: ComponentProps<'button'>['type']
|
|
23
29
|
}
|
|
@@ -37,6 +43,7 @@ export function ListRowControl<T extends ValidComponent = 'div'>(props: ListRowC
|
|
|
37
43
|
'leading',
|
|
38
44
|
'trailing',
|
|
39
45
|
'description',
|
|
46
|
+
'stackTrailing',
|
|
40
47
|
'type',
|
|
41
48
|
'tabIndex',
|
|
42
49
|
'children',
|
|
@@ -52,6 +59,7 @@ export function ListRowControl<T extends ValidComponent = 'div'>(props: ListRowC
|
|
|
52
59
|
aria-current={local.selected ? 'true' : undefined}
|
|
53
60
|
data-selected={local.selected ? '' : undefined}
|
|
54
61
|
data-description={local.description ? '' : undefined}
|
|
62
|
+
data-stack-trailing={local.stackTrailing === false ? 'never' : undefined}
|
|
55
63
|
class={cn(
|
|
56
64
|
'group/row list-row-description flex min-w-0 items-center gap-2.5 rounded-md px-2 text-sm',
|
|
57
65
|
'transition-colors ease-out outline-none',
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Keyboard-accessible tree rows with virtualization-aware focus handoff. */
|
|
2
|
-
export { Tree, TreeRow, type TreeProps, type TreeRowProps } from './tree'
|
|
2
|
+
export { Tree, TreeRow, TreeRowActions, type TreeProps, type TreeRowProps } from './tree'
|
|
3
3
|
export type { TreeItemDescriptor } from './tree-navigation'
|
|
@@ -238,7 +238,12 @@ export function Tree(props: TreeProps) {
|
|
|
238
238
|
role="tree"
|
|
239
239
|
aria-multiselectable={local.selectionMode === 'multiple' ? 'true' : undefined}
|
|
240
240
|
data-slot="tree"
|
|
241
|
-
class={cn(
|
|
241
|
+
class={cn(
|
|
242
|
+
/* A virtualized host remounts rows as it scrolls; scroll anchoring
|
|
243
|
+
would latch onto the focused row and fight the scrollbar. */
|
|
244
|
+
'flex min-w-0 flex-col [overflow-anchor:none]',
|
|
245
|
+
local.class
|
|
246
|
+
)}
|
|
242
247
|
onKeyDown={handleKeyDown}
|
|
243
248
|
>
|
|
244
249
|
{local.children}
|
|
@@ -382,6 +387,31 @@ export function TreeRow(props: TreeRowProps) {
|
|
|
382
387
|
)
|
|
383
388
|
}
|
|
384
389
|
|
|
390
|
+
/**
|
|
391
|
+
* TreeRowActions.
|
|
392
|
+
*
|
|
393
|
+
* Row-level commands (rename, copy, delete) that stay out of the way until the
|
|
394
|
+
* row is hovered or holds focus, so a long tree reads as names rather than a
|
|
395
|
+
* wall of buttons. Put it in `TreeRow`'s `trailing` slot beside any status that
|
|
396
|
+
* must stay visible. `pinned` keeps the actions shown, e.g. while a destructive
|
|
397
|
+
* action waits for its confirming second press.
|
|
398
|
+
*/
|
|
399
|
+
export function TreeRowActions(props: ComponentProps<'span'> & { pinned?: boolean }) {
|
|
400
|
+
const [local, rest] = splitProps(props, ['class', 'pinned'])
|
|
401
|
+
return (
|
|
402
|
+
<span
|
|
403
|
+
data-slot="tree-row-actions"
|
|
404
|
+
data-pinned={local.pinned ? '' : undefined}
|
|
405
|
+
class={cn(
|
|
406
|
+
'flex shrink-0 items-center gap-1 opacity-0 transition-opacity ease-out',
|
|
407
|
+
'group-hover/tree-row:opacity-100 group-focus-within/tree-row:opacity-100 data-pinned:opacity-100',
|
|
408
|
+
local.class
|
|
409
|
+
)}
|
|
410
|
+
{...rest}
|
|
411
|
+
/>
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
|
|
385
415
|
function isActionTarget(target: EventTarget | null, currentTarget: HTMLElement): boolean {
|
|
386
416
|
if (!(target instanceof Element) || target === currentTarget) return false
|
|
387
417
|
return Boolean(
|
|
@@ -27,6 +27,13 @@ export type ContextualSidebarProps = {
|
|
|
27
27
|
label: string
|
|
28
28
|
/** Visible navigation title and mobile dialog title. */
|
|
29
29
|
title: string
|
|
30
|
+
/**
|
|
31
|
+
* Where the title row shows. `always` (default) draws it on desktop and in
|
|
32
|
+
* the mobile sheet; `mobile` keeps the desktop title for assistive
|
|
33
|
+
* technology only and drops the desktop row, for hosts whose top bar
|
|
34
|
+
* already names the context. A `header` slot keeps the row visible.
|
|
35
|
+
*/
|
|
36
|
+
titleVisibility?: 'always' | 'mobile'
|
|
30
37
|
/** Host-controlled expanded state for the desktop panel and mobile dialog. */
|
|
31
38
|
open: boolean
|
|
32
39
|
onOpenChange(open: boolean): void
|
|
@@ -169,10 +176,19 @@ export function ContextualSidebar(props: ContextualSidebarProps) {
|
|
|
169
176
|
/>
|
|
170
177
|
</Show>
|
|
171
178
|
<Show when={!mobile || mobileMount()?.connected}>
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
{
|
|
175
|
-
|
|
179
|
+
<Show
|
|
180
|
+
when={mobile || props.titleVisibility !== 'mobile' || props.header}
|
|
181
|
+
fallback={
|
|
182
|
+
<SidebarNavTitle as={props.headingAs ?? 'h2'} class="sr-only">
|
|
183
|
+
{props.title}
|
|
184
|
+
</SidebarNavTitle>
|
|
185
|
+
}
|
|
186
|
+
>
|
|
187
|
+
<SidebarNavHeader class={cn(mobile && 'pe-12', props.headerClass)}>
|
|
188
|
+
<SidebarNavTitle as={props.headingAs ?? 'h2'}>{props.title}</SidebarNavTitle>
|
|
189
|
+
{props.header?.(context)}
|
|
190
|
+
</SidebarNavHeader>
|
|
191
|
+
</Show>
|
|
176
192
|
<SidebarNavContent class={props.contentClass}>{props.content(context)}</SidebarNavContent>
|
|
177
193
|
{props.footer ? (
|
|
178
194
|
<SidebarNavFooter class={props.footerClass}>{props.footer(context)}</SidebarNavFooter>
|
|
@@ -140,6 +140,12 @@ export type SidebarNavSectionProps = ComponentProps<'div'> & {
|
|
|
140
140
|
count?: number
|
|
141
141
|
/** A control at the trailing edge of the heading row. */
|
|
142
142
|
action?: JSX.Element
|
|
143
|
+
/**
|
|
144
|
+
* When the trailing action shows. `hover` (default) reveals it on hover or
|
|
145
|
+
* focus; `always` keeps it visible, for a section whose primary action is
|
|
146
|
+
* creating its first item.
|
|
147
|
+
*/
|
|
148
|
+
actionVisibility?: 'hover' | 'always'
|
|
143
149
|
/** Semantic heading element for a host's section hierarchy. */
|
|
144
150
|
headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
145
151
|
}
|
|
@@ -156,6 +162,7 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
|
|
|
156
162
|
'triggerProps',
|
|
157
163
|
'count',
|
|
158
164
|
'action',
|
|
165
|
+
'actionVisibility',
|
|
159
166
|
'headingAs',
|
|
160
167
|
'children',
|
|
161
168
|
])
|
|
@@ -237,7 +244,13 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
|
|
|
237
244
|
</Show>
|
|
238
245
|
</Polymorphic>
|
|
239
246
|
<Show when={local.action}>
|
|
240
|
-
<span
|
|
247
|
+
<span
|
|
248
|
+
data-slot="sidebar-nav-section-action"
|
|
249
|
+
class={cn('shrink-0', {
|
|
250
|
+
'opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(any-pointer:coarse)]:opacity-100':
|
|
251
|
+
local.actionVisibility !== 'always',
|
|
252
|
+
})}
|
|
253
|
+
>
|
|
241
254
|
{local.action}
|
|
242
255
|
</span>
|
|
243
256
|
</Show>
|
|
@@ -99,7 +99,9 @@ export function TopBarTitle(
|
|
|
99
99
|
<div
|
|
100
100
|
data-slot="top-bar-title"
|
|
101
101
|
class={cn(
|
|
102
|
-
|
|
102
|
+
/* The title slot sits in the drag region; a link, button or field
|
|
103
|
+
placed in it (a breadcrumb, a view's search) must stay pressable. */
|
|
104
|
+
'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',
|
|
103
105
|
local.align === 'center' && 'text-center',
|
|
104
106
|
local.class
|
|
105
107
|
)}
|
|
@@ -114,7 +116,10 @@ export function TopBarBreadcrumb(props: ComponentProps<'div'>) {
|
|
|
114
116
|
return (
|
|
115
117
|
<div
|
|
116
118
|
data-slot="top-bar-breadcrumb"
|
|
117
|
-
class={cn(
|
|
119
|
+
class={cn(
|
|
120
|
+
'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',
|
|
121
|
+
local.class
|
|
122
|
+
)}
|
|
118
123
|
{...rest}
|
|
119
124
|
/>
|
|
120
125
|
)
|
|
@@ -9,17 +9,28 @@ import { cn } from '#lib/utils'
|
|
|
9
9
|
* when a consumer needs only the control; `Input` adds native datalist
|
|
10
10
|
* suggestions for forms that need them.
|
|
11
11
|
*/
|
|
12
|
-
export type InputControlProps = ComponentProps<'input'>
|
|
12
|
+
export type InputControlProps = Omit<ComponentProps<'input'>, 'size'> & {
|
|
13
|
+
/**
|
|
14
|
+
* The control rung, matching `SelectTrigger`: `sm` for a dense toolbar or
|
|
15
|
+
* pane filter beside `icon-sm` actions, `md` (the default) everywhere else.
|
|
16
|
+
*/
|
|
17
|
+
size?: 'sm' | 'md' | 'lg'
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
export function InputControl(props: InputControlProps) {
|
|
15
|
-
const [local, rest] = splitProps(props, ['class', 'type'])
|
|
21
|
+
const [local, rest] = splitProps(props, ['class', 'type', 'size'])
|
|
16
22
|
|
|
17
23
|
return (
|
|
18
24
|
<input
|
|
19
25
|
data-slot="input"
|
|
20
26
|
type={local.type ?? 'text'}
|
|
21
27
|
class={cn(
|
|
22
|
-
'
|
|
28
|
+
'w-full min-w-0 rounded-md border border-input bg-transparent py-1',
|
|
29
|
+
{
|
|
30
|
+
'h-control-sm px-control-sm text-xs': local.size === 'sm',
|
|
31
|
+
'h-control-md px-control-md text-sm': local.size === 'md' || local.size === undefined,
|
|
32
|
+
'h-control-lg px-control-lg text-sm': local.size === 'lg',
|
|
33
|
+
},
|
|
23
34
|
'transition-[color,box-shadow,border-color] ease-out outline-none',
|
|
24
35
|
'placeholder:text-muted-foreground',
|
|
25
36
|
'selection:bg-primary selection:text-primary-foreground',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { ComponentProps } from 'solid-js'
|
|
2
1
|
import { createUniqueId, For, Show, splitProps } from 'solid-js'
|
|
3
2
|
import { useFormFieldControl } from '#lib/form-field'
|
|
4
|
-
import { InputControl } from './input-control'
|
|
3
|
+
import { InputControl, type InputControlProps } from './input-control'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Input.
|
|
@@ -12,10 +11,11 @@ import { InputControl } from './input-control'
|
|
|
12
11
|
* them separate is what lets an input sit inside a custom control without
|
|
13
12
|
* inheriting a form's ARIA graph.
|
|
14
13
|
*
|
|
15
|
-
* Height comes from
|
|
16
|
-
*
|
|
14
|
+
* Height comes from the control tokens (`size`, `md` by default); an input
|
|
15
|
+
* cannot be given a height off that ladder, which is what keeps it aligned
|
|
16
|
+
* with Button and Select.
|
|
17
17
|
*/
|
|
18
|
-
export type InputProps =
|
|
18
|
+
export type InputProps = InputControlProps & {
|
|
19
19
|
/** Native text suggestions; custom values remain valid and submit unchanged. */
|
|
20
20
|
suggestions?: readonly string[]
|
|
21
21
|
}
|
|
@@ -13,7 +13,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'
|
|
|
13
13
|
* with no text (unreadable to anyone who cannot see it) and a bare word with no
|
|
14
14
|
* explanation ("Needs configuration" — of what?).
|
|
15
15
|
*
|
|
16
|
-
* The tones are deliberately not a rainbow.
|
|
16
|
+
* The tones are deliberately not a rainbow. Seven cover what a status can actually
|
|
17
17
|
* mean:
|
|
18
18
|
*
|
|
19
19
|
* - `neutral` — fine, and there is nothing to say about it.
|
|
@@ -21,6 +21,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'
|
|
|
21
21
|
* - `warning` — degraded, or needs a decision from the user.
|
|
22
22
|
* - `danger` — blocked, failed, unrecoverable without intervention.
|
|
23
23
|
* - `info` — in flight or scheduled; work is happening, nothing is wrong.
|
|
24
|
+
* - `pending` — running now with no result yet (a check, a build). The ring
|
|
25
|
+
* turns, so it reads as live rather than as a settled state.
|
|
24
26
|
* - `unknown` — the honest absence of information.
|
|
25
27
|
*
|
|
26
28
|
* `unknown` is not the same as `neutral`, and collapsing them is the mistake
|
|
@@ -39,6 +41,7 @@ export const statusDotVariants = cva('size-2 shrink-0 rounded-full', {
|
|
|
39
41
|
warning: 'bg-warning',
|
|
40
42
|
danger: 'bg-destructive',
|
|
41
43
|
info: 'bg-info',
|
|
44
|
+
pending: 'border-2 border-info border-r-transparent motion-safe:animate-spin',
|
|
42
45
|
unknown: 'border border-muted-foreground bg-transparent',
|
|
43
46
|
},
|
|
44
47
|
},
|
|
@@ -51,10 +54,18 @@ const labelTone: Record<StatusTone, string> = {
|
|
|
51
54
|
warning: 'text-foreground',
|
|
52
55
|
danger: 'text-foreground',
|
|
53
56
|
info: 'text-foreground',
|
|
57
|
+
pending: 'text-foreground',
|
|
54
58
|
unknown: 'text-muted-foreground',
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
export type StatusTone =
|
|
61
|
+
export type StatusTone =
|
|
62
|
+
| 'neutral'
|
|
63
|
+
| 'success'
|
|
64
|
+
| 'warning'
|
|
65
|
+
| 'danger'
|
|
66
|
+
| 'info'
|
|
67
|
+
| 'pending'
|
|
68
|
+
| 'unknown'
|
|
58
69
|
|
|
59
70
|
export type StatusChipProps = Omit<ComponentProps<'span'>, 'children'> & {
|
|
60
71
|
tone?: StatusTone
|
package/src/styles/base.css
CHANGED
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/* Emitted only when ListRowControl is used. A described row changes its own
|
|
99
|
-
height and stacks trailing actions when its available width gets tight
|
|
99
|
+
height and stacks trailing actions when its available width gets tight,
|
|
100
|
+
unless the row opts out with `stackTrailing={false}`. */
|
|
100
101
|
@utility list-row-description {
|
|
101
102
|
&[data-description] {
|
|
102
103
|
align-items: flex-start;
|
|
@@ -111,23 +112,23 @@
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
@container list-row (width < 28rem) {
|
|
114
|
-
&[data-description] [data-slot='list-row-content'] {
|
|
115
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-content'] {
|
|
115
116
|
align-items: stretch;
|
|
116
117
|
flex-direction: column;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
&[data-description] [data-slot='list-row-label'],
|
|
120
|
-
&[data-description] [data-slot='list-row-trailing'] {
|
|
120
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-label'],
|
|
121
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-trailing'] {
|
|
121
122
|
flex: none;
|
|
122
123
|
width: 100%;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
&[data-description] [data-slot='list-row-trailing'] {
|
|
126
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-trailing'] {
|
|
126
127
|
flex-wrap: wrap;
|
|
127
128
|
justify-content: flex-end;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
&[data-description] [data-slot='list-row-trailing'] button {
|
|
131
|
+
&[data-description]:not([data-stack-trailing='never']) [data-slot='list-row-trailing'] button {
|
|
131
132
|
max-width: 100%;
|
|
132
133
|
white-space: normal;
|
|
133
134
|
}
|