@adea-ai/ui 0.85.0 → 0.88.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 (239) hide show
  1. package/README.md +17 -1
  2. package/dist/NOTICE +34 -0
  3. package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
  4. package/dist/components/composites/action-button/action-button.js +2 -2
  5. package/dist/components/composites/action-button/action-button.js.map +1 -1
  6. package/dist/components/composites/list-row/list-row.d.ts +6 -3
  7. package/dist/components/composites/list-row/list-row.d.ts.map +1 -1
  8. package/dist/components/composites/list-row/list-row.js +69 -7
  9. package/dist/components/composites/list-row/list-row.js.map +1 -1
  10. package/dist/components/composites/settings/settings.d.ts +9 -11
  11. package/dist/components/composites/settings/settings.d.ts.map +1 -1
  12. package/dist/components/composites/settings/settings.js +48 -33
  13. package/dist/components/composites/settings/settings.js.map +1 -1
  14. package/dist/components/conversation/message-composer.js +1 -1
  15. package/dist/components/conversation/scroll-follow.d.ts.map +1 -1
  16. package/dist/components/conversation/scroll-follow.js +95 -15
  17. package/dist/components/conversation/scroll-follow.js.map +1 -1
  18. package/dist/components/layout/app-shell/index.d.ts +1 -0
  19. package/dist/components/layout/app-shell/index.d.ts.map +1 -1
  20. package/dist/components/layout/app-shell/index.js +2 -1
  21. package/dist/components/layout/app-shell/skip-link.d.ts +11 -0
  22. package/dist/components/layout/app-shell/skip-link.d.ts.map +1 -0
  23. package/dist/components/layout/app-shell/skip-link.js +44 -0
  24. package/dist/components/layout/app-shell/skip-link.js.map +1 -0
  25. package/dist/components/layout/floating-preview/floating-preview.d.ts +12 -0
  26. package/dist/components/layout/floating-preview/floating-preview.d.ts.map +1 -0
  27. package/dist/components/layout/floating-preview/floating-preview.js +283 -0
  28. package/dist/components/layout/floating-preview/floating-preview.js.map +1 -0
  29. package/dist/components/layout/floating-preview/index.d.ts +3 -0
  30. package/dist/components/layout/floating-preview/index.d.ts.map +1 -0
  31. package/dist/components/layout/floating-preview/index.js +3 -0
  32. package/dist/components/layout/floating-preview/model.d.ts +74 -0
  33. package/dist/components/layout/floating-preview/model.d.ts.map +1 -0
  34. package/dist/components/layout/floating-preview/model.js +146 -0
  35. package/dist/components/layout/floating-preview/model.js.map +1 -0
  36. package/dist/components/layout/sidebar-nav/index.d.ts +1 -1
  37. package/dist/components/layout/sidebar-nav/index.d.ts.map +1 -1
  38. package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts +18 -2
  39. package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
  40. package/dist/components/layout/sidebar-nav/sidebar-nav.js +42 -11
  41. package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
  42. package/dist/components/theme/index.d.ts +1 -0
  43. package/dist/components/theme/index.d.ts.map +1 -1
  44. package/dist/components/theme/index.js +2 -1
  45. package/dist/components/theme/theme-mode-toggle.d.ts +10 -0
  46. package/dist/components/theme/theme-mode-toggle.d.ts.map +1 -0
  47. package/dist/components/theme/theme-mode-toggle.js +82 -0
  48. package/dist/components/theme/theme-mode-toggle.js.map +1 -0
  49. package/dist/components/ui/checkbox/checkbox.d.ts +2 -0
  50. package/dist/components/ui/checkbox/checkbox.d.ts.map +1 -1
  51. package/dist/components/ui/checkbox/checkbox.js +24 -3
  52. package/dist/components/ui/checkbox/checkbox.js.map +1 -1
  53. package/dist/components/ui/combobox/combobox.d.ts.map +1 -1
  54. package/dist/components/ui/combobox/combobox.js +41 -4
  55. package/dist/components/ui/combobox/combobox.js.map +1 -1
  56. package/dist/components/ui/combobox/index.d.ts +1 -0
  57. package/dist/components/ui/combobox/index.d.ts.map +1 -1
  58. package/dist/components/ui/combobox/index.js +2 -1
  59. package/dist/components/ui/combobox/value-combobox.d.ts +19 -0
  60. package/dist/components/ui/combobox/value-combobox.d.ts.map +1 -0
  61. package/dist/components/ui/combobox/value-combobox.js +42 -0
  62. package/dist/components/ui/combobox/value-combobox.js.map +1 -0
  63. package/dist/components/ui/empty/empty-state.d.ts +15 -0
  64. package/dist/components/ui/empty/empty-state.d.ts.map +1 -0
  65. package/dist/components/ui/empty/empty-state.js +96 -0
  66. package/dist/components/ui/empty/empty-state.js.map +1 -0
  67. package/dist/components/ui/empty/index.d.ts +1 -0
  68. package/dist/components/ui/empty/index.d.ts.map +1 -1
  69. package/dist/components/ui/empty/index.js +2 -1
  70. package/dist/components/ui/entity-icon/entity-icon.d.ts +4 -1
  71. package/dist/components/ui/entity-icon/entity-icon.d.ts.map +1 -1
  72. package/dist/components/ui/entity-icon/entity-icon.js +37 -13
  73. package/dist/components/ui/entity-icon/entity-icon.js.map +1 -1
  74. package/dist/components/ui/field/field.d.ts +36 -13
  75. package/dist/components/ui/field/field.d.ts.map +1 -1
  76. package/dist/components/ui/field/field.js +146 -24
  77. package/dist/components/ui/field/field.js.map +1 -1
  78. package/dist/components/ui/field/index.d.ts +3 -1
  79. package/dist/components/ui/field/index.d.ts.map +1 -1
  80. package/dist/components/ui/field/index.js +3 -2
  81. package/dist/components/ui/field/number-field.d.ts +19 -0
  82. package/dist/components/ui/field/number-field.d.ts.map +1 -0
  83. package/dist/components/ui/field/number-field.js +76 -0
  84. package/dist/components/ui/field/number-field.js.map +1 -0
  85. package/dist/components/ui/input/index.d.ts +1 -0
  86. package/dist/components/ui/input/index.d.ts.map +1 -1
  87. package/dist/components/ui/input/index.js +2 -1
  88. package/dist/components/ui/input/input-control.d.ts +11 -0
  89. package/dist/components/ui/input/input-control.d.ts.map +1 -0
  90. package/dist/components/ui/input/input-control.js +24 -0
  91. package/dist/components/ui/input/input-control.js.map +1 -0
  92. package/dist/components/ui/input/input.d.ts.map +1 -1
  93. package/dist/components/ui/input/input.js +56 -28
  94. package/dist/components/ui/input/input.js.map +1 -1
  95. package/dist/components/ui/input-group/index.d.ts +1 -0
  96. package/dist/components/ui/input-group/index.d.ts.map +1 -1
  97. package/dist/components/ui/input-group/index.js +2 -1
  98. package/dist/components/ui/input-group/input-group.d.ts.map +1 -1
  99. package/dist/components/ui/input-group/input-group.js +42 -5
  100. package/dist/components/ui/input-group/input-group.js.map +1 -1
  101. package/dist/components/ui/input-group/secret-input-group.d.ts +11 -0
  102. package/dist/components/ui/input-group/secret-input-group.d.ts.map +1 -0
  103. package/dist/components/ui/input-group/secret-input-group.js +67 -0
  104. package/dist/components/ui/input-group/secret-input-group.js.map +1 -0
  105. package/dist/components/ui/native-select/native-select.d.ts.map +1 -1
  106. package/dist/components/ui/native-select/native-select.js +39 -4
  107. package/dist/components/ui/native-select/native-select.js.map +1 -1
  108. package/dist/components/ui/progress/progress.d.ts.map +1 -1
  109. package/dist/components/ui/progress/progress.js +30 -18
  110. package/dist/components/ui/progress/progress.js.map +1 -1
  111. package/dist/components/ui/radio-group/radio-group.d.ts.map +1 -1
  112. package/dist/components/ui/radio-group/radio-group.js +41 -4
  113. package/dist/components/ui/radio-group/radio-group.js.map +1 -1
  114. package/dist/components/ui/switch/switch.d.ts +2 -0
  115. package/dist/components/ui/switch/switch.d.ts.map +1 -1
  116. package/dist/components/ui/switch/switch.js +24 -3
  117. package/dist/components/ui/switch/switch.js.map +1 -1
  118. package/dist/components/ui/tabs/tabs.js +1 -1
  119. package/dist/components/ui/tabs/tabs.js.map +1 -1
  120. package/dist/components/ui/text-link/index.d.ts +2 -0
  121. package/dist/components/ui/text-link/index.d.ts.map +1 -0
  122. package/dist/components/ui/text-link/index.js +2 -0
  123. package/dist/components/ui/text-link/text-link.d.ts +9 -0
  124. package/dist/components/ui/text-link/text-link.d.ts.map +1 -0
  125. package/dist/components/ui/text-link/text-link.js +30 -0
  126. package/dist/components/ui/text-link/text-link.js.map +1 -0
  127. package/dist/components/ui/textarea/textarea.d.ts.map +1 -1
  128. package/dist/components/ui/textarea/textarea.js +41 -4
  129. package/dist/components/ui/textarea/textarea.js.map +1 -1
  130. package/dist/index.d.ts +3 -0
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +18 -7
  133. package/dist/lib/clipboard.d.ts +3 -0
  134. package/dist/lib/clipboard.d.ts.map +1 -0
  135. package/dist/lib/clipboard.js +35 -0
  136. package/dist/lib/clipboard.js.map +1 -0
  137. package/dist/lib/download.d.ts +3 -0
  138. package/dist/lib/download.d.ts.map +1 -0
  139. package/dist/lib/download.js +31 -0
  140. package/dist/lib/download.js.map +1 -0
  141. package/dist/lib/form-field.d.ts +37 -0
  142. package/dist/lib/form-field.d.ts.map +1 -0
  143. package/dist/lib/form-field.js +47 -0
  144. package/dist/lib/form-field.js.map +1 -0
  145. package/dist/r/app-shell.json +5 -0
  146. package/dist/r/combobox.json +5 -0
  147. package/dist/r/empty.json +10 -1
  148. package/dist/r/field.json +7 -0
  149. package/dist/r/floating-preview.json +30 -0
  150. package/dist/r/input-group.json +6 -0
  151. package/dist/r/input.json +5 -0
  152. package/dist/r/lib.json +15 -0
  153. package/dist/r/list-row.json +1 -0
  154. package/dist/r/registry.json +112 -1
  155. package/dist/r/src/components/composites/action-button/action-button.tsx +5 -1
  156. package/dist/r/src/components/composites/list-row/list-row.tsx +81 -8
  157. package/dist/r/src/components/composites/settings/settings.tsx +67 -37
  158. package/dist/r/src/components/conversation/scroll-follow.ts +109 -15
  159. package/dist/r/src/components/layout/app-shell/index.ts +1 -0
  160. package/dist/r/src/components/layout/app-shell/skip-link.tsx +35 -0
  161. package/dist/r/src/components/layout/floating-preview/floating-preview.tsx +260 -0
  162. package/dist/r/src/components/layout/floating-preview/index.ts +2 -0
  163. package/dist/r/src/components/layout/floating-preview/model.ts +285 -0
  164. package/dist/r/src/components/layout/sidebar-nav/index.ts +2 -0
  165. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +71 -16
  166. package/dist/r/src/components/theme/index.ts +1 -0
  167. package/dist/r/src/components/theme/theme-mode-toggle.tsx +56 -0
  168. package/dist/r/src/components/ui/checkbox/checkbox.tsx +20 -3
  169. package/dist/r/src/components/ui/combobox/combobox.tsx +24 -1
  170. package/dist/r/src/components/ui/combobox/index.ts +1 -0
  171. package/dist/r/src/components/ui/combobox/value-combobox.tsx +38 -0
  172. package/dist/r/src/components/ui/empty/empty-state.tsx +71 -0
  173. package/dist/r/src/components/ui/empty/index.ts +1 -0
  174. package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +33 -2
  175. package/dist/r/src/components/ui/field/field.tsx +102 -5
  176. package/dist/r/src/components/ui/field/index.ts +5 -0
  177. package/dist/r/src/components/ui/field/number-field.tsx +71 -0
  178. package/dist/r/src/components/ui/input/index.ts +1 -0
  179. package/dist/r/src/components/ui/input/input-control.tsx +38 -0
  180. package/dist/r/src/components/ui/input/input.tsx +32 -20
  181. package/dist/r/src/components/ui/input-group/index.ts +1 -0
  182. package/dist/r/src/components/ui/input-group/input-group.tsx +25 -2
  183. package/dist/r/src/components/ui/input-group/secret-input-group.tsx +58 -0
  184. package/dist/r/src/components/ui/native-select/native-select.tsx +27 -1
  185. package/dist/r/src/components/ui/progress/progress.tsx +12 -8
  186. package/dist/r/src/components/ui/radio-group/radio-group.tsx +30 -2
  187. package/dist/r/src/components/ui/switch/switch.tsx +20 -3
  188. package/dist/r/src/components/ui/tabs/tabs.tsx +1 -1
  189. package/dist/r/src/components/ui/text-link/index.ts +2 -0
  190. package/dist/r/src/components/ui/text-link/text-link.tsx +28 -0
  191. package/dist/r/src/components/ui/textarea/textarea.tsx +24 -1
  192. package/dist/r/src/lib/clipboard.ts +41 -0
  193. package/dist/r/src/lib/download.ts +29 -0
  194. package/dist/r/src/lib/form-field.ts +93 -0
  195. package/dist/r/text-link.json +22 -0
  196. package/dist/r/theming.json +6 -0
  197. package/package.json +1 -1
  198. package/registry.json +112 -1
  199. package/src/components/composites/action-button/action-button.tsx +5 -1
  200. package/src/components/composites/list-row/list-row.tsx +81 -8
  201. package/src/components/composites/settings/settings.tsx +67 -37
  202. package/src/components/conversation/scroll-follow.ts +109 -15
  203. package/src/components/layout/app-shell/index.ts +1 -0
  204. package/src/components/layout/app-shell/skip-link.tsx +35 -0
  205. package/src/components/layout/floating-preview/floating-preview.tsx +260 -0
  206. package/src/components/layout/floating-preview/index.ts +2 -0
  207. package/src/components/layout/floating-preview/model.ts +285 -0
  208. package/src/components/layout/sidebar-nav/index.ts +2 -0
  209. package/src/components/layout/sidebar-nav/sidebar-nav.tsx +71 -16
  210. package/src/components/theme/index.ts +1 -0
  211. package/src/components/theme/theme-mode-toggle.tsx +56 -0
  212. package/src/components/ui/checkbox/checkbox.tsx +20 -3
  213. package/src/components/ui/combobox/combobox.tsx +24 -1
  214. package/src/components/ui/combobox/index.ts +1 -0
  215. package/src/components/ui/combobox/value-combobox.tsx +38 -0
  216. package/src/components/ui/empty/empty-state.tsx +71 -0
  217. package/src/components/ui/empty/index.ts +1 -0
  218. package/src/components/ui/entity-icon/entity-icon.tsx +33 -2
  219. package/src/components/ui/field/field.tsx +102 -5
  220. package/src/components/ui/field/index.ts +5 -0
  221. package/src/components/ui/field/number-field.tsx +71 -0
  222. package/src/components/ui/input/index.ts +1 -0
  223. package/src/components/ui/input/input-control.tsx +38 -0
  224. package/src/components/ui/input/input.tsx +32 -20
  225. package/src/components/ui/input-group/index.ts +1 -0
  226. package/src/components/ui/input-group/input-group.tsx +25 -2
  227. package/src/components/ui/input-group/secret-input-group.tsx +58 -0
  228. package/src/components/ui/native-select/native-select.tsx +27 -1
  229. package/src/components/ui/progress/progress.tsx +12 -8
  230. package/src/components/ui/radio-group/radio-group.tsx +30 -2
  231. package/src/components/ui/switch/switch.tsx +20 -3
  232. package/src/components/ui/tabs/tabs.tsx +1 -1
  233. package/src/components/ui/text-link/index.ts +2 -0
  234. package/src/components/ui/text-link/text-link.tsx +28 -0
  235. package/src/components/ui/textarea/textarea.tsx +24 -1
  236. package/src/index.ts +3 -0
  237. package/src/lib/clipboard.ts +41 -0
  238. package/src/lib/download.ts +29 -0
  239. package/src/lib/form-field.ts +93 -0
@@ -2,6 +2,7 @@ import { RadioGroup as KobalteRadioGroup } from '@kobalte/core/radio-group'
2
2
  import { Circle } from 'lucide-solid'
3
3
  import type { ComponentProps } from 'solid-js'
4
4
  import { splitProps } from 'solid-js'
5
+ import { useFormFieldControl } from '#lib/form-field'
5
6
  import { cn } from '#lib/utils'
6
7
 
7
8
  /**
@@ -17,8 +18,35 @@ import { cn } from '#lib/utils'
17
18
  * tokens so either choice sits correctly next to the other.
18
19
  */
19
20
  export function RadioGroup(props: ComponentProps<typeof KobalteRadioGroup>) {
20
- const [local, rest] = splitProps(props, ['class'])
21
- return <KobalteRadioGroup class={cn('grid gap-2', local.class)} {...rest} />
21
+ const [local, rest] = splitProps(props, [
22
+ 'class',
23
+ 'id',
24
+ 'aria-label',
25
+ 'aria-labelledby',
26
+ 'aria-describedby',
27
+ 'aria-invalid',
28
+ 'aria-errormessage',
29
+ ])
30
+ const field = useFormFieldControl('group', {
31
+ id: local.id,
32
+ 'aria-label': local['aria-label'],
33
+ 'aria-labelledby': local['aria-labelledby'],
34
+ 'aria-describedby': local['aria-describedby'],
35
+ 'aria-invalid': local['aria-invalid'],
36
+ 'aria-errormessage': local['aria-errormessage'],
37
+ })
38
+ return (
39
+ <KobalteRadioGroup
40
+ id={field?.id ?? local.id}
41
+ aria-label={local['aria-label']}
42
+ aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
43
+ aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}
44
+ aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
45
+ aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
46
+ class={cn('grid gap-2', local.class)}
47
+ {...rest}
48
+ />
49
+ )
22
50
  }
23
51
 
24
52
  export type RadioGroupItemProps = ComponentProps<typeof KobalteRadioGroup.Item> & {
@@ -1,6 +1,7 @@
1
1
  import { Switch as KobalteSwitch } from '@kobalte/core/switch'
2
2
  import type { ComponentProps, JSX } from 'solid-js'
3
3
  import { splitProps } from 'solid-js'
4
+ import { useFormFieldControl } from '#lib/form-field'
4
5
  import { cn } from '#lib/utils'
5
6
 
6
7
  /**
@@ -17,6 +18,8 @@ import { cn } from '#lib/utils'
17
18
  * user sees.
18
19
  */
19
20
  export type SwitchProps = Omit<ComponentProps<typeof KobalteSwitch>, 'children'> & {
21
+ /** ID for the hidden native switch input; useful with an external label. */
22
+ inputId?: string
20
23
  controlClass?: string
21
24
  thumbClass?: string
22
25
  labelClass?: string
@@ -44,22 +47,36 @@ export function Switch(props: SwitchProps) {
44
47
  'aria-labelledby',
45
48
  'aria-describedby',
46
49
  'aria-invalid',
50
+ 'aria-errormessage',
47
51
  'aria-busy',
52
+ 'id',
53
+ 'inputId',
48
54
  'title',
49
55
  ])
56
+ const field = useFormFieldControl('control', {
57
+ id: local.inputId ?? local.id,
58
+ 'aria-label': local['aria-label'],
59
+ 'aria-labelledby': local['aria-labelledby'],
60
+ 'aria-describedby': local['aria-describedby'],
61
+ 'aria-invalid': local['aria-invalid'],
62
+ 'aria-errormessage': local['aria-errormessage'],
63
+ })
50
64
 
51
65
  return (
52
66
  <KobalteSwitch
67
+ id={field ? undefined : local.id}
53
68
  title={local.title}
54
69
  class={cn('group/switch flex items-center gap-2.5', local.class)}
55
70
  {...rest}
56
71
  >
57
72
  {/* The name belongs on the input; see Checkbox for why. */}
58
73
  <KobalteSwitch.Input
74
+ id={field?.id ?? local.inputId}
59
75
  aria-label={local['aria-label']}
60
- aria-labelledby={local['aria-labelledby']}
61
- aria-describedby={local['aria-describedby']}
62
- aria-invalid={local['aria-invalid']}
76
+ aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
77
+ aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}
78
+ aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
79
+ aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
63
80
  aria-busy={local['aria-busy']}
64
81
  title={local.title}
65
82
  />
@@ -59,7 +59,7 @@ export function TabsTrigger(props: TabsTriggerProps) {
59
59
  'transition-colors ease-out outline-none select-none',
60
60
  'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
61
61
  'disabled:pointer-events-none disabled:opacity-50',
62
- 'text-muted-foreground hover:text-foreground data-[selected]:text-foreground',
62
+ 'text-foreground',
63
63
  '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-4',
64
64
  local.appearance === 'segmented'
65
65
  ? 'h-control-sm rounded-md px-control-sm data-[selected]:bg-card data-[selected]:shadow-xs'
@@ -0,0 +1,2 @@
1
+ // TextLink: inline navigation for prose and content.
2
+ export { TextLink } from './text-link'
@@ -0,0 +1,28 @@
1
+ import type { ComponentProps } from 'solid-js'
2
+ import { splitProps } from 'solid-js'
3
+ import { cn } from '#lib/utils'
4
+
5
+ /**
6
+ * TextLink.
7
+ *
8
+ * An inline, underlined link for prose and content navigation. Use ActionButton
9
+ * or ListRow when the link is a compact action or a row-sized target.
10
+ */
11
+ export function TextLink(props: ComponentProps<'a'>) {
12
+ const [local, rest] = splitProps(props, ['class', 'tabIndex'])
13
+
14
+ return (
15
+ <a
16
+ data-slot="textlink"
17
+ class={cn(
18
+ 'rounded-sm text-foreground underline decoration-primary underline-offset-4',
19
+ 'transition-colors ease-out hover:decoration-foreground outline-none',
20
+ 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
21
+ 'motion-reduce:transition-none',
22
+ local.class
23
+ )}
24
+ tabIndex={local.tabIndex ?? 0}
25
+ {...rest}
26
+ />
27
+ )
28
+ }
@@ -1,5 +1,6 @@
1
1
  import type { ComponentProps } from 'solid-js'
2
2
  import { splitProps } from 'solid-js'
3
+ import { useFormFieldControl } from '#lib/form-field'
3
4
  import { cn } from '#lib/utils'
4
5
 
5
6
  /**
@@ -14,11 +15,33 @@ import { cn } from '#lib/utils'
14
15
  * remains the fallback so an unsupported engine still gets a usable height.
15
16
  */
16
17
  export function Textarea(props: ComponentProps<'textarea'>) {
17
- const [local, rest] = splitProps(props, ['class'])
18
+ const [local, rest] = splitProps(props, [
19
+ 'class',
20
+ 'id',
21
+ 'aria-label',
22
+ 'aria-labelledby',
23
+ 'aria-describedby',
24
+ 'aria-invalid',
25
+ 'aria-errormessage',
26
+ ])
27
+ const field = useFormFieldControl('control', {
28
+ id: local.id,
29
+ 'aria-label': local['aria-label'],
30
+ 'aria-labelledby': local['aria-labelledby'],
31
+ 'aria-describedby': local['aria-describedby'],
32
+ 'aria-invalid': local['aria-invalid'],
33
+ 'aria-errormessage': local['aria-errormessage'],
34
+ })
18
35
 
19
36
  return (
20
37
  <textarea
21
38
  data-slot="textarea"
39
+ id={field?.id ?? local.id}
40
+ aria-label={local['aria-label']}
41
+ aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
42
+ aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}
43
+ aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
44
+ aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
22
45
  class={cn(
23
46
  'flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-control-md py-2 text-sm',
24
47
  'transition-[color,box-shadow,border-color] ease-out outline-none',
package/src/index.ts CHANGED
@@ -69,6 +69,7 @@ export * from './components/ui/switch'
69
69
  export * from './components/ui/table'
70
70
  export * from './components/ui/tabs'
71
71
  export * from './components/ui/textarea'
72
+ export * from './components/ui/text-link'
72
73
  export * from './components/ui/toast'
73
74
  export * from './components/ui/toggle'
74
75
  export * from './components/ui/toggle-group'
@@ -79,6 +80,7 @@ export * from './components/motion'
79
80
 
80
81
  /* --- Layout -------------------------------------------------------------- */
81
82
  export * from './components/layout/split-layout'
83
+ export * from './components/layout/floating-preview'
82
84
  export * from './components/layout/orbit-layout'
83
85
  export * from './components/layout/virtual-window'
84
86
  export * from './components/layout/app-shell'
@@ -138,6 +140,7 @@ export {
138
140
  type ThemeVariant,
139
141
  } from './lib/themes'
140
142
  export { keyedRows, type KeyedRow } from './lib/keyed-rows'
143
+ export { writeClipboardText } from './lib/clipboard'
141
144
  export { formatBytes, formatReleaseDate, plainTextFromMarkdown } from './lib/version-notes'
142
145
  export {
143
146
  accentPresets,
@@ -0,0 +1,41 @@
1
+ const CLIPBOARD_UNAVAILABLE = 'Clipboard is unavailable in this WebView'
2
+
3
+ /** Writes text with the async Clipboard API, falling back for older WebViews. */
4
+ export async function writeClipboardText(value: string): Promise<void> {
5
+ if (typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function') {
6
+ await navigator.clipboard.writeText(value)
7
+ return
8
+ }
9
+
10
+ if (typeof document === 'undefined' || !document.body) {
11
+ throw new Error(CLIPBOARD_UNAVAILABLE)
12
+ }
13
+
14
+ const previousFocus = document.activeElement
15
+ const textarea = document.createElement('textarea')
16
+ textarea.value = value
17
+ textarea.setAttribute('readonly', '')
18
+ textarea.setAttribute('aria-hidden', 'true')
19
+ textarea.tabIndex = -1
20
+ textarea.style.position = 'fixed'
21
+ textarea.style.opacity = '0'
22
+ document.body.appendChild(textarea)
23
+
24
+ let copied = false
25
+ try {
26
+ textarea.focus({ preventScroll: true })
27
+ textarea.select()
28
+ copied = typeof document.execCommand === 'function' && document.execCommand('copy')
29
+ } finally {
30
+ textarea.remove()
31
+ if (previousFocus instanceof HTMLElement && previousFocus.isConnected) {
32
+ try {
33
+ previousFocus.focus({ preventScroll: true })
34
+ } catch {
35
+ // A host may revoke focus during the copy; preserve the clipboard outcome.
36
+ }
37
+ }
38
+ }
39
+
40
+ if (!copied) throw new Error(CLIPBOARD_UNAVAILABLE)
41
+ }
@@ -0,0 +1,29 @@
1
+ /** Download a Blob through a temporary, non-focusable anchor. */
2
+ export function downloadBlob(blob: Blob, filename: string): void {
3
+ const objectUrl = URL.createObjectURL(blob)
4
+ const previousFocus = document.activeElement as HTMLElement | null
5
+ let anchor: HTMLAnchorElement | undefined
6
+
7
+ try {
8
+ anchor = document.createElement('a')
9
+ anchor.href = objectUrl
10
+ anchor.download = filename
11
+ anchor.hidden = true
12
+ anchor.tabIndex = -1
13
+ anchor.setAttribute('aria-hidden', 'true')
14
+ document.body.append(anchor)
15
+ anchor.click()
16
+ } finally {
17
+ try {
18
+ anchor?.remove()
19
+ } finally {
20
+ try {
21
+ if (previousFocus?.isConnected && document.activeElement !== previousFocus) {
22
+ previousFocus.focus({ preventScroll: true })
23
+ }
24
+ } finally {
25
+ setTimeout(() => URL.revokeObjectURL(objectUrl), 0)
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,93 @@
1
+ import type { ComponentProps } from 'solid-js'
2
+ import { createContext, createSignal, useContext } from 'solid-js'
3
+
4
+ export type FormFieldControlKind = 'control' | 'group'
5
+
6
+ export type FormFieldContextValue = {
7
+ group: () => boolean
8
+ labelId: string
9
+ controlId: () => string
10
+ registerControlId: (requestedId?: string) => string | undefined
11
+ describedBy: () => string | undefined
12
+ errorId: () => string | undefined
13
+ invalid: () => boolean
14
+ }
15
+
16
+ export const FormFieldContext = createContext<FormFieldContextValue>()
17
+
18
+ function mergeIds(...values: Array<string | undefined>) {
19
+ return (
20
+ [...new Set(values.flatMap((value) => value?.split(/\s+/).filter(Boolean) ?? []))].join(' ') ||
21
+ undefined
22
+ )
23
+ }
24
+
25
+ export function createFormFieldContext(props: {
26
+ controlId?: string
27
+ defaultControlId: string
28
+ labelId: string
29
+ descriptionId?: string
30
+ errorId?: string
31
+ hint: () => string | undefined
32
+ error: () => string | undefined
33
+ group: () => boolean
34
+ }): FormFieldContextValue {
35
+ const [controlId, setControlId] = createSignal(props.controlId ?? props.defaultControlId)
36
+
37
+ return {
38
+ group: props.group,
39
+ labelId: props.labelId,
40
+ controlId,
41
+ registerControlId(requestedId) {
42
+ if (props.group() && !requestedId) return undefined
43
+ if (requestedId) {
44
+ setControlId(requestedId)
45
+ return requestedId
46
+ }
47
+ return props.group() ? undefined : controlId()
48
+ },
49
+ describedBy: () =>
50
+ [props.hint() ? props.descriptionId : undefined, props.error() ? props.errorId : undefined]
51
+ .filter((id): id is string => Boolean(id))
52
+ .join(' ') || undefined,
53
+ errorId: () => (props.error() ? props.errorId : undefined),
54
+ invalid: () => Boolean(props.error()),
55
+ }
56
+ }
57
+
58
+ export function useFormFieldControl(
59
+ kind: FormFieldControlKind,
60
+ props: {
61
+ id?: string
62
+ 'aria-label'?: string
63
+ 'aria-labelledby'?: string
64
+ 'aria-describedby'?: string
65
+ 'aria-invalid'?: ComponentProps<'input'>['aria-invalid']
66
+ 'aria-errormessage'?: string
67
+ } = {}
68
+ ) {
69
+ const field = useContext(FormFieldContext)
70
+ if (!field) return null
71
+
72
+ const groupedDescendant = field.group() && kind !== 'group'
73
+ const id = groupedDescendant ? props.id : field.registerControlId(props.id)
74
+ const labelledBy =
75
+ props['aria-label'] || props['aria-labelledby']
76
+ ? props['aria-labelledby']
77
+ : groupedDescendant
78
+ ? undefined
79
+ : field.labelId
80
+ return {
81
+ id,
82
+ 'aria-labelledby': labelledBy,
83
+ get 'aria-describedby'() {
84
+ return mergeIds(props['aria-describedby'], field.describedBy())
85
+ },
86
+ get 'aria-invalid'() {
87
+ return props['aria-invalid'] ?? (field.invalid() || undefined)
88
+ },
89
+ get 'aria-errormessage'() {
90
+ return props['aria-errormessage'] ?? field.errorId()
91
+ },
92
+ }
93
+ }