@adea-ai/ui 0.81.1 → 0.82.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.
Files changed (65) hide show
  1. package/README.md +41 -8
  2. package/dist/components/composites/account-menu/account-menu.d.ts +5 -0
  3. package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
  4. package/dist/components/composites/account-menu/account-menu.js +122 -70
  5. package/dist/components/composites/account-menu/account-menu.js.map +1 -1
  6. package/dist/components/composites/action-button/action-button.d.ts +27 -0
  7. package/dist/components/composites/action-button/action-button.d.ts.map +1 -0
  8. package/dist/components/composites/action-button/action-button.js +121 -0
  9. package/dist/components/composites/action-button/action-button.js.map +1 -0
  10. package/dist/components/composites/action-button/index.d.ts +3 -0
  11. package/dist/components/composites/action-button/index.d.ts.map +1 -0
  12. package/dist/components/composites/action-button/index.js +2 -0
  13. package/dist/components/conversation/thread-panel.d.ts +2 -0
  14. package/dist/components/conversation/thread-panel.d.ts.map +1 -1
  15. package/dist/components/conversation/thread-panel.js +36 -31
  16. package/dist/components/conversation/thread-panel.js.map +1 -1
  17. package/dist/components/layout/sidebar-nav/index.d.ts +1 -1
  18. package/dist/components/layout/sidebar-nav/index.d.ts.map +1 -1
  19. package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts +6 -1
  20. package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
  21. package/dist/components/layout/sidebar-nav/sidebar-nav.js +66 -62
  22. package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
  23. package/dist/components/ui/board/board.d.ts.map +1 -1
  24. package/dist/components/ui/board/board.js +54 -7
  25. package/dist/components/ui/board/board.js.map +1 -1
  26. package/dist/components/ui/command/command.d.ts +5 -1
  27. package/dist/components/ui/command/command.d.ts.map +1 -1
  28. package/dist/components/ui/command/command.js +8 -3
  29. package/dist/components/ui/command/command.js.map +1 -1
  30. package/dist/components/ui/input/index.d.ts +1 -1
  31. package/dist/components/ui/input/index.d.ts.map +1 -1
  32. package/dist/components/ui/input/input.d.ts +5 -1
  33. package/dist/components/ui/input/input.d.ts.map +1 -1
  34. package/dist/components/ui/input/input.js +36 -17
  35. package/dist/components/ui/input/input.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -1
  39. package/dist/r/account-menu.json +1 -0
  40. package/dist/r/action-button.json +24 -0
  41. package/dist/r/conversation.json +1 -0
  42. package/dist/r/registry.json +26 -0
  43. package/dist/r/src/components/composites/account-menu/account-menu.tsx +100 -44
  44. package/dist/r/src/components/composites/action-button/action-button.tsx +139 -0
  45. package/dist/r/src/components/composites/action-button/index.ts +2 -0
  46. package/dist/r/src/components/conversation/thread-panel.tsx +22 -12
  47. package/dist/r/src/components/layout/sidebar-nav/index.ts +1 -0
  48. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +29 -21
  49. package/dist/r/src/components/ui/board/board.tsx +74 -6
  50. package/dist/r/src/components/ui/command/command.tsx +15 -4
  51. package/dist/r/src/components/ui/input/index.ts +1 -1
  52. package/dist/r/src/components/ui/input/input.tsx +38 -21
  53. package/package.json +1 -1
  54. package/registry.json +26 -0
  55. package/src/components/composites/account-menu/account-menu.tsx +100 -44
  56. package/src/components/composites/action-button/action-button.tsx +139 -0
  57. package/src/components/composites/action-button/index.ts +2 -0
  58. package/src/components/conversation/thread-panel.tsx +22 -12
  59. package/src/components/layout/sidebar-nav/index.ts +1 -0
  60. package/src/components/layout/sidebar-nav/sidebar-nav.tsx +29 -21
  61. package/src/components/ui/board/board.tsx +74 -6
  62. package/src/components/ui/command/command.tsx +15 -4
  63. package/src/components/ui/input/index.ts +1 -1
  64. package/src/components/ui/input/input.tsx +38 -21
  65. package/src/index.ts +1 -0
@@ -1,4 +1,13 @@
1
- import { createMemo, createSignal, For, Show, splitProps, type JSX } from 'solid-js'
1
+ import {
2
+ createEffect,
3
+ createMemo,
4
+ createSignal,
5
+ For,
6
+ onCleanup,
7
+ Show,
8
+ splitProps,
9
+ type JSX,
10
+ } from 'solid-js'
2
11
  import { cva, type VariantProps } from '#lib/variants'
3
12
  import { cn } from '#lib/utils'
4
13
 
@@ -88,6 +97,50 @@ export function Board<T>(props: BoardProps<T>) {
88
97
  const [dragging, setDragging] = createSignal<T | null>(null)
89
98
  const [overColumn, setOverColumn] = createSignal<string | null>(null)
90
99
  const [announcement, setAnnouncement] = createSignal('')
100
+ const cardElements = new Map<string, HTMLElement>()
101
+ const [pendingFocus, setPendingFocus] = createSignal<{
102
+ itemId: string
103
+ columnId: string
104
+ source: HTMLElement
105
+ } | null>(null)
106
+ const cancelOnPointer = () => setPendingFocus(null)
107
+
108
+ // A controlled move can settle after a server response. Restore only the
109
+ // focus that belonged to the moved card; another focus action cancels it.
110
+ createEffect(() => {
111
+ const pending = pendingFocus()
112
+ if (!pending) return
113
+ const document = pending.source.ownerDocument
114
+ const cancelOnFocus = (event: FocusEvent) => {
115
+ if (!pending.source.contains(event.target as Node)) setPendingFocus(null)
116
+ }
117
+ document.addEventListener('focusin', cancelOnFocus)
118
+ document.addEventListener('pointerdown', cancelOnPointer)
119
+ onCleanup(() => {
120
+ document.removeEventListener('focusin', cancelOnFocus)
121
+ document.removeEventListener('pointerdown', cancelOnPointer)
122
+ })
123
+ })
124
+
125
+ createEffect(() => {
126
+ const pending = pendingFocus()
127
+ if (!pending) return
128
+ const moved = local.items.find((item) => local.itemId(item) === pending.itemId)
129
+ if (!moved || local.itemColumn(moved) !== pending.columnId) return
130
+ queueMicrotask(() => {
131
+ if (pendingFocus() !== pending) return
132
+ const target = cardElements.get(pending.itemId)
133
+ setPendingFocus(null)
134
+ if (target?.isConnected) target.focus()
135
+ })
136
+ })
137
+
138
+ const prepareFocus = (item: T, columnId: string, source?: HTMLElement) => {
139
+ const card = source ?? cardElements.get(local.itemId(item))
140
+ if (card?.contains(card.ownerDocument.activeElement)) {
141
+ setPendingFocus({ itemId: local.itemId(item), columnId, source: card })
142
+ }
143
+ }
91
144
 
92
145
  const byColumn = createMemo(() => {
93
146
  const grouped = new Map<string, T[]>()
@@ -106,9 +159,11 @@ export function Board<T>(props: BoardProps<T>) {
106
159
  const drop = (column: BoardColumn) => {
107
160
  const item = dragging()
108
161
  const from = item ? local.itemColumn(item) : null
162
+ const accepted = accepts(column)
109
163
  setDragging(null)
110
164
  setOverColumn(null)
111
- if (!item || !from || from === column.id || !accepts(column)) return
165
+ if (!item || !from || from === column.id || !accepted) return
166
+ prepareFocus(item, column.id)
112
167
  local.onMove({ itemId: local.itemId(item), from, to: column.id })
113
168
  }
114
169
 
@@ -117,11 +172,12 @@ export function Board<T>(props: BoardProps<T>) {
117
172
  * thing within its container" that does not collide with scrolling or with the
118
173
  * text caret, and it is the same chord a browser tab or a spreadsheet uses.
119
174
  */
120
- const moveByKeyboard = (item: T, direction: -1 | 1) => {
175
+ const moveByKeyboard = (item: T, direction: -1 | 1, source: HTMLElement) => {
121
176
  const from = local.itemColumn(item)
122
177
  const index = local.columns.findIndex((column) => column.id === from)
123
- const candidates = local.columns.slice(index + direction)
124
- const ordered = direction === 1 ? candidates : candidates.toReversed()
178
+ if (index < 0) return
179
+ const ordered =
180
+ direction === 1 ? local.columns.slice(index + 1) : local.columns.slice(0, index).toReversed()
125
181
  const target = ordered.find(
126
182
  (column) => !column.disabled && local.canDrop(item, from, column.id)
127
183
  )
@@ -129,6 +185,7 @@ export function Board<T>(props: BoardProps<T>) {
129
185
  setAnnouncement(`No column after ${from} accepts this item`)
130
186
  return
131
187
  }
188
+ prepareFocus(item, target.id, source)
132
189
  local.onMove({ itemId: local.itemId(item), from, to: target.id })
133
190
  setAnnouncement(`Moved to ${target.label}`)
134
191
  }
@@ -185,6 +242,13 @@ export function Board<T>(props: BoardProps<T>) {
185
242
  }
186
243
  return (
187
244
  <article
245
+ ref={(element) => {
246
+ const id = local.itemId(item)
247
+ cardElements.set(id, element)
248
+ onCleanup(() => {
249
+ if (cardElements.get(id) === element) cardElements.delete(id)
250
+ })
251
+ }}
188
252
  class={cn(
189
253
  'rounded-md border border-border bg-card text-sm shadow-xs',
190
254
  cardDragState[isDragging() ? 'dragging' : 'idle']
@@ -203,7 +267,11 @@ export function Board<T>(props: BoardProps<T>) {
203
267
  if (!(event.ctrlKey || event.metaKey)) return
204
268
  if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
205
269
  event.preventDefault()
206
- moveByKeyboard(item, event.key === 'ArrowRight' ? 1 : -1)
270
+ moveByKeyboard(
271
+ item,
272
+ event.key === 'ArrowRight' ? 1 : -1,
273
+ event.currentTarget
274
+ )
207
275
  }}
208
276
  >
209
277
  {local.children(item, { dragging: isDragging() })}
@@ -16,6 +16,12 @@ import { createEffect, createSignal, splitProps } from 'solid-js'
16
16
  import { menuItem } from '#lib/overlay'
17
17
  import { cn } from '#lib/utils'
18
18
 
19
+ // cmdk emits data-disabled="false" on enabled items; Kobalte omits it.
20
+ // Presence selectors would dim and disable every command in an app palette.
21
+ const commandItem = menuItem
22
+ .replace('data-[disabled]:pointer-events-none', '')
23
+ .replace('data-[disabled]:opacity-50', '')
24
+
19
25
  /**
20
26
  * Command.
21
27
  *
@@ -181,8 +187,9 @@ export function CommandItem(props: ComponentProps<typeof CmdkItem>) {
181
187
  return (
182
188
  <CmdkItem
183
189
  class={cn(
184
- menuItem,
185
- 'data-[selected=true]:bg-surface-hover data-[selected=true]:text-foreground',
190
+ commandItem,
191
+ 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
192
+ 'text-popover-foreground data-[selected=true]:bg-surface-hover data-[selected=true]:text-popover-foreground',
186
193
  local.class
187
194
  )}
188
195
  {...rest}
@@ -205,14 +212,18 @@ export function CommandLoading(props: ComponentProps<typeof CmdkLoading>) {
205
212
  )
206
213
  }
207
214
 
208
- /** The keyboard hint a palette item usually carries. */
215
+ /**
216
+ * The keyboard hint a palette item usually carries.
217
+ * Muted canvas text can fail contrast on a raised or selected command row;
218
+ * hierarchy comes from its size and monospace face, with the row's readable ink.
219
+ */
209
220
  export function CommandShortcut(props: {
210
221
  class?: string
211
222
  children?: ComponentProps<'span'>['children']
212
223
  }) {
213
224
  return (
214
225
  <span
215
- class={cn('text-muted-foreground ms-auto font-mono text-2xs tracking-widest', props.class)}
226
+ class={cn('text-popover-foreground ms-auto font-mono text-2xs tracking-widest', props.class)}
216
227
  >
217
228
  {props.children}
218
229
  </span>
@@ -1 +1 @@
1
- export { Input } from './input'
1
+ export { Input, type InputProps } from './input'
@@ -1,5 +1,5 @@
1
1
  import type { ComponentProps } from 'solid-js'
2
- import { splitProps } from 'solid-js'
2
+ import { createUniqueId, For, Show, splitProps } from 'solid-js'
3
3
  import { cn } from '#lib/utils'
4
4
 
5
5
  /**
@@ -14,28 +14,45 @@ import { cn } from '#lib/utils'
14
14
  * Height comes from `h-control-md`; an input cannot be given a new height
15
15
  * without a new token, which is what keeps it aligned with Button and Select.
16
16
  */
17
- export function Input(props: ComponentProps<'input'>) {
18
- const [local, rest] = splitProps(props, ['class', 'type'])
17
+ export type InputProps = ComponentProps<'input'> & {
18
+ /** Native text suggestions; custom values remain valid and submit unchanged. */
19
+ suggestions?: readonly string[]
20
+ }
21
+
22
+ export function Input(props: InputProps) {
23
+ const [local, rest] = splitProps(props, ['class', 'type', 'list', 'suggestions'])
24
+ const suggestionsId = createUniqueId()
25
+ const listId = () => local.list ?? `input-suggestions-${suggestionsId}`
19
26
 
20
27
  return (
21
- <input
22
- data-slot="input"
23
- type={local.type ?? 'text'}
24
- class={cn(
25
- 'h-control-md w-full min-w-0 rounded-md border border-input bg-transparent px-control-md py-1 text-sm',
26
- 'transition-[color,box-shadow,border-color] ease-out outline-none',
27
- 'placeholder:text-muted-foreground',
28
- 'selection:bg-primary selection:text-primary-foreground',
29
- 'file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',
30
- /* A pointer click on a text field is a typing affordance, so the ring
28
+ <>
29
+ <input
30
+ data-slot="input"
31
+ type={local.type ?? 'text'}
32
+ list={local.suggestions ? listId() : local.list}
33
+ class={cn(
34
+ 'h-control-md w-full min-w-0 rounded-md border border-input bg-transparent px-control-md py-1 text-sm',
35
+ 'transition-[color,box-shadow,border-color] ease-out outline-none',
36
+ 'placeholder:text-muted-foreground',
37
+ 'selection:bg-primary selection:text-primary-foreground',
38
+ 'file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',
39
+ /* A pointer click on a text field is a typing affordance, so the ring
31
40
  appears where the user agent already matches :focus-visible. */
32
- 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
33
- 'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive-subtle',
34
- 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
35
- 'read-only:bg-surface-hover',
36
- local.class
37
- )}
38
- {...rest}
39
- />
41
+ 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
42
+ 'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive-subtle',
43
+ 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
44
+ 'read-only:bg-surface-hover',
45
+ local.class
46
+ )}
47
+ {...rest}
48
+ />
49
+ <Show when={local.suggestions}>
50
+ {(suggestions) => (
51
+ <datalist id={listId()}>
52
+ <For each={suggestions()}>{(value) => <option value={value} />}</For>
53
+ </datalist>
54
+ )}
55
+ </Show>
56
+ </>
40
57
  )
41
58
  }
package/src/index.ts CHANGED
@@ -95,6 +95,7 @@ export * from './components/theme'
95
95
 
96
96
  /* --- Composites ---------------------------------------------------------- */
97
97
  export * from './components/composites/account-menu'
98
+ export * from './components/composites/action-button'
98
99
  export * from './components/composites/list-row'
99
100
  export * from './components/composites/settings'
100
101
  export * from './components/composites/stat'