@adea-ai/ui 0.114.0 → 0.115.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/components/composites/list-row/list-row-control.d.ts +6 -0
  2. package/dist/components/composites/list-row/list-row-control.d.ts.map +1 -1
  3. package/dist/components/composites/list-row/list-row-control.js +4 -0
  4. package/dist/components/composites/list-row/list-row-control.js.map +1 -1
  5. package/dist/components/composites/tree/index.d.ts +1 -1
  6. package/dist/components/composites/tree/index.d.ts.map +1 -1
  7. package/dist/components/composites/tree/index.js +2 -2
  8. package/dist/components/composites/tree/tree.d.ts +12 -0
  9. package/dist/components/composites/tree/tree.d.ts.map +1 -1
  10. package/dist/components/composites/tree/tree.js +27 -2
  11. package/dist/components/composites/tree/tree.js.map +1 -1
  12. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts +7 -0
  13. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts.map +1 -1
  14. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js +25 -6
  15. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js.map +1 -1
  16. package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts +6 -0
  17. package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
  18. package/dist/components/layout/sidebar-nav/sidebar-nav.js +5 -3
  19. package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
  20. package/dist/components/layout/top-bar/top-bar.d.ts.map +1 -1
  21. package/dist/components/layout/top-bar/top-bar.js +3 -3
  22. package/dist/components/layout/top-bar/top-bar.js.map +1 -1
  23. package/dist/components/ui/input/input-control.d.ts +7 -1
  24. package/dist/components/ui/input/input-control.d.ts.map +1 -1
  25. package/dist/components/ui/input/input-control.js +10 -2
  26. package/dist/components/ui/input/input-control.js.map +1 -1
  27. package/dist/components/ui/input/input.d.ts +5 -4
  28. package/dist/components/ui/input/input.d.ts.map +1 -1
  29. package/dist/components/ui/input/input.js.map +1 -1
  30. package/dist/components/ui/modal-dialog/modal-dialog.d.ts +2 -1
  31. package/dist/components/ui/modal-dialog/modal-dialog.d.ts.map +1 -1
  32. package/dist/components/ui/modal-dialog/modal-dialog.js +3 -0
  33. package/dist/components/ui/modal-dialog/modal-dialog.js.map +1 -1
  34. package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
  35. package/dist/components/ui/resizable/resizable.js +1 -1
  36. package/dist/components/ui/resizable/resizable.js.map +1 -1
  37. package/dist/components/ui/status-chip/status-chip.d.ts +5 -3
  38. package/dist/components/ui/status-chip/status-chip.d.ts.map +1 -1
  39. package/dist/components/ui/status-chip/status-chip.js +5 -1
  40. package/dist/components/ui/status-chip/status-chip.js.map +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/r/src/components/composites/list-row/list-row-control.tsx +8 -0
  43. package/dist/r/src/components/composites/tree/index.ts +1 -1
  44. package/dist/r/src/components/composites/tree/tree.tsx +31 -1
  45. package/dist/r/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +23 -4
  46. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +25 -2
  47. package/dist/r/src/components/layout/top-bar/top-bar.tsx +14 -5
  48. package/dist/r/src/components/ui/input/input-control.tsx +14 -3
  49. package/dist/r/src/components/ui/input/input.tsx +5 -5
  50. package/dist/r/src/components/ui/modal-dialog/modal-dialog.tsx +3 -1
  51. package/dist/r/src/components/ui/resizable/resizable.tsx +4 -3
  52. package/dist/r/src/components/ui/status-chip/status-chip.tsx +13 -2
  53. package/dist/r/src/styles/base.css +7 -6
  54. package/package.json +1 -1
  55. package/src/components/composites/list-row/list-row-control.tsx +8 -0
  56. package/src/components/composites/tree/index.ts +1 -1
  57. package/src/components/composites/tree/tree.tsx +31 -1
  58. package/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +23 -4
  59. package/src/components/layout/sidebar-nav/sidebar-nav.tsx +25 -2
  60. package/src/components/layout/top-bar/top-bar.tsx +14 -5
  61. package/src/components/ui/input/input-control.tsx +14 -3
  62. package/src/components/ui/input/input.tsx +5 -5
  63. package/src/components/ui/modal-dialog/modal-dialog.tsx +3 -1
  64. package/src/components/ui/resizable/resizable.tsx +4 -3
  65. package/src/components/ui/status-chip/status-chip.tsx +13 -2
  66. 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
@@ -157,6 +164,9 @@ export function ContextualSidebar(props: ContextualSidebarProps) {
157
164
  >
158
165
  <Show when={!mobile && props.open}>
159
166
  <PixelResizeHandle
167
+ // The ruler spans the sidebar as a pointer-events-none overlay; lift
168
+ // it above the neighbouring pane so the edge stays grabbable.
169
+ class="z-(--z-docked)"
160
170
  side="left"
161
171
  value={props.width}
162
172
  minimum={props.minimum}
@@ -169,10 +179,19 @@ export function ContextualSidebar(props: ContextualSidebarProps) {
169
179
  />
170
180
  </Show>
171
181
  <Show when={!mobile || mobileMount()?.connected}>
172
- <SidebarNavHeader class={cn(mobile && 'pe-12', props.headerClass)}>
173
- <SidebarNavTitle as={props.headingAs ?? 'h2'}>{props.title}</SidebarNavTitle>
174
- {props.header?.(context)}
175
- </SidebarNavHeader>
182
+ <Show
183
+ when={mobile || props.titleVisibility !== 'mobile' || props.header}
184
+ fallback={
185
+ <SidebarNavTitle as={props.headingAs ?? 'h2'} class="sr-only">
186
+ {props.title}
187
+ </SidebarNavTitle>
188
+ }
189
+ >
190
+ <SidebarNavHeader class={cn(mobile && 'pe-12', props.headerClass)}>
191
+ <SidebarNavTitle as={props.headingAs ?? 'h2'}>{props.title}</SidebarNavTitle>
192
+ {props.header?.(context)}
193
+ </SidebarNavHeader>
194
+ </Show>
176
195
  <SidebarNavContent class={props.contentClass}>{props.content(context)}</SidebarNavContent>
177
196
  {props.footer ? (
178
197
  <SidebarNavFooter class={props.footerClass}>{props.footer(context)}</SidebarNavFooter>
@@ -88,7 +88,12 @@ export function SidebarNavContent(props: ComponentProps<'div'>) {
88
88
  return (
89
89
  <div
90
90
  data-slot="sidebar-nav-content"
91
- class={cn('flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2', local.class)}
91
+ class={cn(
92
+ /* A stable gutter keeps rows from shifting sideways when the list
93
+ grows past the fold and the scrollbar appears. */
94
+ 'flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2 [scrollbar-gutter:stable]',
95
+ local.class
96
+ )}
92
97
  {...rest}
93
98
  />
94
99
  )
@@ -140,6 +145,12 @@ export type SidebarNavSectionProps = ComponentProps<'div'> & {
140
145
  count?: number
141
146
  /** A control at the trailing edge of the heading row. */
142
147
  action?: JSX.Element
148
+ /**
149
+ * When the trailing action shows. `hover` (default) reveals it on hover or
150
+ * focus; `always` keeps it visible, for a section whose primary action is
151
+ * creating its first item.
152
+ */
153
+ actionVisibility?: 'hover' | 'always'
143
154
  /** Semantic heading element for a host's section hierarchy. */
144
155
  headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
145
156
  }
@@ -156,6 +167,7 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
156
167
  'triggerProps',
157
168
  'count',
158
169
  'action',
170
+ 'actionVisibility',
159
171
  'headingAs',
160
172
  'children',
161
173
  ])
@@ -229,6 +241,11 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
229
241
  }}
230
242
  class={cn(
231
243
  'flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle [@media(any-pointer:coarse)]:min-h-11',
244
+ /* WCAG 2.5.8: the row's vertical padding sits on the header, so
245
+ the bare label would be a ~16px target. Stretch the button
246
+ across that padding; the row's height does not change. A
247
+ coarse pointer already gets min-h-11 on an unpadded row. */
248
+ '-my-1.5 py-1.5 [@media(any-pointer:coarse)]:my-0 [@media(any-pointer:coarse)]:py-0',
232
249
  local.active ? 'text-foreground' : 'text-sidebar-muted-foreground'
233
250
  )}
234
251
  >
@@ -237,7 +254,13 @@ export function SidebarNavSection(props: SidebarNavSectionProps) {
237
254
  </Show>
238
255
  </Polymorphic>
239
256
  <Show when={local.action}>
240
- <span class="shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(any-pointer:coarse)]:opacity-100">
257
+ <span
258
+ data-slot="sidebar-nav-section-action"
259
+ class={cn('shrink-0', {
260
+ 'opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(any-pointer:coarse)]:opacity-100':
261
+ local.actionVisibility !== 'always',
262
+ })}
263
+ >
241
264
  {local.action}
242
265
  </span>
243
266
  </Show>
@@ -42,11 +42,15 @@ export function TopBar(props: TopBarProps) {
42
42
  <header
43
43
  data-slot="top-bar"
44
44
  class={cn(
45
- 'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground px-3 md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',
45
+ 'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',
46
46
  local.glass && 'glass-panel',
47
47
  local.draggable && 'window-drag',
48
- local.macosInset && 'window-inset-macos',
49
- local.windowsInset && 'window-inset-windows',
48
+ /* Each edge gets exactly one inline-padding utility. A window inset
49
+ and px-3 would share a layer and tie-break by build order, and a
50
+ build that put px-3 last hid the leading controls under the
51
+ traffic lights. */
52
+ local.macosInset ? 'window-inset-macos' : 'ps-3',
53
+ local.windowsInset ? 'window-inset-windows' : 'pe-3',
50
54
  local.class
51
55
  )}
52
56
  {...rest}
@@ -99,7 +103,9 @@ export function TopBarTitle(
99
103
  <div
100
104
  data-slot="top-bar-title"
101
105
  class={cn(
102
- 'truncate text-sm font-medium',
106
+ /* The title slot sits in the drag region; a link, button or field
107
+ placed in it (a breadcrumb, a view's search) must stay pressable. */
108
+ 'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',
103
109
  local.align === 'center' && 'text-center',
104
110
  local.class
105
111
  )}
@@ -114,7 +120,10 @@ export function TopBarBreadcrumb(props: ComponentProps<'div'>) {
114
120
  return (
115
121
  <div
116
122
  data-slot="top-bar-breadcrumb"
117
- class={cn('flex min-w-0 items-center gap-1.5 text-sm', local.class)}
123
+ class={cn(
124
+ 'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',
125
+ local.class
126
+ )}
118
127
  {...rest}
119
128
  />
120
129
  )
@@ -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
- 'h-control-md w-full min-w-0 rounded-md border border-input bg-transparent px-control-md py-1 text-sm',
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 `h-control-md`; an input cannot be given a new height
16
- * without a new token, which is what keeps it aligned with Button and Select.
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 = ComponentProps<'input'> & {
18
+ export type InputProps = InputControlProps & {
19
19
  /** Native text suggestions; custom values remain valid and submit unchanged. */
20
20
  suggestions?: readonly string[]
21
21
  }
@@ -42,7 +42,8 @@ import {
42
42
  *
43
43
  * Use `Dialog` directly when you need its composition; use this when you want a
44
44
  * dialog that is correct by default. `size="settings"` supplies a wide, bounded
45
- * shell for a shared settings layout; ordinary dialogs keep their compact default.
45
+ * shell for a shared settings layout, titled by a banded header like a sheet's;
46
+ * ordinary dialogs keep their compact default.
46
47
  */
47
48
  export type ModalDialogProps = Omit<
48
49
  ComponentProps<typeof DialogContent>,
@@ -154,6 +155,7 @@ export function ModalDialog(props: ModalDialogProps) {
154
155
  onCloseAutoFocus={focusRestoration.onCloseAutoFocus}
155
156
  >
156
157
  <DialogHeader
158
+ band={local.size === 'settings'}
157
159
  class={
158
160
  local.size === 'settings'
159
161
  ? 'shrink-0 gap-1 border-b border-border px-5 pt-4 pb-3 pe-14'
@@ -94,12 +94,13 @@ export function ResizableHandle(props: ResizableHandleProps) {
94
94
  class={cn(
95
95
  'group/resize bg-border relative flex w-px shrink-0 items-center justify-center',
96
96
  /* Widen the hit target without moving the visual line: the divider is
97
- a hairline, but a hairline is far too small to grab. */
98
- 'after:absolute after:inset-y-0 after:-inset-x-1 after:w-3',
97
+ a hairline, but a hairline is far too small to grab. 24px on the
98
+ drag axis meets WCAG 2.5.8's minimum target. */
99
+ 'after:absolute after:inset-y-0 after:-inset-x-3 after:w-6',
99
100
  'transition-colors ease-out hover:bg-primary',
100
101
  'focus-visible:bg-primary focus-visible:outline-none',
101
102
  'data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full',
102
- 'data-[orientation=vertical]:after:inset-x-0 data-[orientation=vertical]:after:-inset-y-1 data-[orientation=vertical]:after:h-3 data-[orientation=vertical]:after:w-full',
103
+ 'data-[orientation=vertical]:after:inset-x-0 data-[orientation=vertical]:after:-inset-y-3 data-[orientation=vertical]:after:h-6 data-[orientation=vertical]:after:w-full',
103
104
  local.class
104
105
  )}
105
106
  {...rest}
@@ -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. Six cover what a status can actually
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 = 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'unknown'
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
  }