@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.
- package/README.md +17 -1
- package/dist/NOTICE +34 -0
- package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
- package/dist/components/composites/action-button/action-button.js +2 -2
- package/dist/components/composites/action-button/action-button.js.map +1 -1
- package/dist/components/composites/list-row/list-row.d.ts +6 -3
- package/dist/components/composites/list-row/list-row.d.ts.map +1 -1
- package/dist/components/composites/list-row/list-row.js +69 -7
- package/dist/components/composites/list-row/list-row.js.map +1 -1
- package/dist/components/composites/settings/settings.d.ts +9 -11
- package/dist/components/composites/settings/settings.d.ts.map +1 -1
- package/dist/components/composites/settings/settings.js +48 -33
- package/dist/components/composites/settings/settings.js.map +1 -1
- package/dist/components/conversation/message-composer.js +1 -1
- package/dist/components/conversation/scroll-follow.d.ts.map +1 -1
- package/dist/components/conversation/scroll-follow.js +95 -15
- package/dist/components/conversation/scroll-follow.js.map +1 -1
- package/dist/components/layout/app-shell/index.d.ts +1 -0
- package/dist/components/layout/app-shell/index.d.ts.map +1 -1
- package/dist/components/layout/app-shell/index.js +2 -1
- package/dist/components/layout/app-shell/skip-link.d.ts +11 -0
- package/dist/components/layout/app-shell/skip-link.d.ts.map +1 -0
- package/dist/components/layout/app-shell/skip-link.js +44 -0
- package/dist/components/layout/app-shell/skip-link.js.map +1 -0
- package/dist/components/layout/floating-preview/floating-preview.d.ts +12 -0
- package/dist/components/layout/floating-preview/floating-preview.d.ts.map +1 -0
- package/dist/components/layout/floating-preview/floating-preview.js +283 -0
- package/dist/components/layout/floating-preview/floating-preview.js.map +1 -0
- package/dist/components/layout/floating-preview/index.d.ts +3 -0
- package/dist/components/layout/floating-preview/index.d.ts.map +1 -0
- package/dist/components/layout/floating-preview/index.js +3 -0
- package/dist/components/layout/floating-preview/model.d.ts +74 -0
- package/dist/components/layout/floating-preview/model.d.ts.map +1 -0
- package/dist/components/layout/floating-preview/model.js +146 -0
- package/dist/components/layout/floating-preview/model.js.map +1 -0
- package/dist/components/layout/sidebar-nav/index.d.ts +1 -1
- package/dist/components/layout/sidebar-nav/index.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts +18 -2
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js +42 -11
- package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
- package/dist/components/theme/index.d.ts +1 -0
- package/dist/components/theme/index.d.ts.map +1 -1
- package/dist/components/theme/index.js +2 -1
- package/dist/components/theme/theme-mode-toggle.d.ts +10 -0
- package/dist/components/theme/theme-mode-toggle.d.ts.map +1 -0
- package/dist/components/theme/theme-mode-toggle.js +82 -0
- package/dist/components/theme/theme-mode-toggle.js.map +1 -0
- package/dist/components/ui/checkbox/checkbox.d.ts +2 -0
- package/dist/components/ui/checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/ui/checkbox/checkbox.js +24 -3
- package/dist/components/ui/checkbox/checkbox.js.map +1 -1
- package/dist/components/ui/combobox/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox/combobox.js +41 -4
- package/dist/components/ui/combobox/combobox.js.map +1 -1
- package/dist/components/ui/combobox/index.d.ts +1 -0
- package/dist/components/ui/combobox/index.d.ts.map +1 -1
- package/dist/components/ui/combobox/index.js +2 -1
- package/dist/components/ui/combobox/value-combobox.d.ts +19 -0
- package/dist/components/ui/combobox/value-combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox/value-combobox.js +42 -0
- package/dist/components/ui/combobox/value-combobox.js.map +1 -0
- package/dist/components/ui/empty/empty-state.d.ts +15 -0
- package/dist/components/ui/empty/empty-state.d.ts.map +1 -0
- package/dist/components/ui/empty/empty-state.js +96 -0
- package/dist/components/ui/empty/empty-state.js.map +1 -0
- package/dist/components/ui/empty/index.d.ts +1 -0
- package/dist/components/ui/empty/index.d.ts.map +1 -1
- package/dist/components/ui/empty/index.js +2 -1
- package/dist/components/ui/entity-icon/entity-icon.d.ts +4 -1
- package/dist/components/ui/entity-icon/entity-icon.d.ts.map +1 -1
- package/dist/components/ui/entity-icon/entity-icon.js +37 -13
- package/dist/components/ui/entity-icon/entity-icon.js.map +1 -1
- package/dist/components/ui/field/field.d.ts +36 -13
- package/dist/components/ui/field/field.d.ts.map +1 -1
- package/dist/components/ui/field/field.js +146 -24
- package/dist/components/ui/field/field.js.map +1 -1
- package/dist/components/ui/field/index.d.ts +3 -1
- package/dist/components/ui/field/index.d.ts.map +1 -1
- package/dist/components/ui/field/index.js +3 -2
- package/dist/components/ui/field/number-field.d.ts +19 -0
- package/dist/components/ui/field/number-field.d.ts.map +1 -0
- package/dist/components/ui/field/number-field.js +76 -0
- package/dist/components/ui/field/number-field.js.map +1 -0
- package/dist/components/ui/input/index.d.ts +1 -0
- package/dist/components/ui/input/index.d.ts.map +1 -1
- package/dist/components/ui/input/index.js +2 -1
- package/dist/components/ui/input/input-control.d.ts +11 -0
- package/dist/components/ui/input/input-control.d.ts.map +1 -0
- package/dist/components/ui/input/input-control.js +24 -0
- package/dist/components/ui/input/input-control.js.map +1 -0
- package/dist/components/ui/input/input.d.ts.map +1 -1
- package/dist/components/ui/input/input.js +56 -28
- package/dist/components/ui/input/input.js.map +1 -1
- package/dist/components/ui/input-group/index.d.ts +1 -0
- package/dist/components/ui/input-group/index.d.ts.map +1 -1
- package/dist/components/ui/input-group/index.js +2 -1
- package/dist/components/ui/input-group/input-group.d.ts.map +1 -1
- package/dist/components/ui/input-group/input-group.js +42 -5
- package/dist/components/ui/input-group/input-group.js.map +1 -1
- package/dist/components/ui/input-group/secret-input-group.d.ts +11 -0
- package/dist/components/ui/input-group/secret-input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group/secret-input-group.js +67 -0
- package/dist/components/ui/input-group/secret-input-group.js.map +1 -0
- package/dist/components/ui/native-select/native-select.d.ts.map +1 -1
- package/dist/components/ui/native-select/native-select.js +39 -4
- package/dist/components/ui/native-select/native-select.js.map +1 -1
- package/dist/components/ui/progress/progress.d.ts.map +1 -1
- package/dist/components/ui/progress/progress.js +30 -18
- package/dist/components/ui/progress/progress.js.map +1 -1
- package/dist/components/ui/radio-group/radio-group.d.ts.map +1 -1
- package/dist/components/ui/radio-group/radio-group.js +41 -4
- package/dist/components/ui/radio-group/radio-group.js.map +1 -1
- package/dist/components/ui/switch/switch.d.ts +2 -0
- package/dist/components/ui/switch/switch.d.ts.map +1 -1
- package/dist/components/ui/switch/switch.js +24 -3
- package/dist/components/ui/switch/switch.js.map +1 -1
- package/dist/components/ui/tabs/tabs.js +1 -1
- package/dist/components/ui/tabs/tabs.js.map +1 -1
- package/dist/components/ui/text-link/index.d.ts +2 -0
- package/dist/components/ui/text-link/index.d.ts.map +1 -0
- package/dist/components/ui/text-link/index.js +2 -0
- package/dist/components/ui/text-link/text-link.d.ts +9 -0
- package/dist/components/ui/text-link/text-link.d.ts.map +1 -0
- package/dist/components/ui/text-link/text-link.js +30 -0
- package/dist/components/ui/text-link/text-link.js.map +1 -0
- package/dist/components/ui/textarea/textarea.d.ts.map +1 -1
- package/dist/components/ui/textarea/textarea.js +41 -4
- package/dist/components/ui/textarea/textarea.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -7
- package/dist/lib/clipboard.d.ts +3 -0
- package/dist/lib/clipboard.d.ts.map +1 -0
- package/dist/lib/clipboard.js +35 -0
- package/dist/lib/clipboard.js.map +1 -0
- package/dist/lib/download.d.ts +3 -0
- package/dist/lib/download.d.ts.map +1 -0
- package/dist/lib/download.js +31 -0
- package/dist/lib/download.js.map +1 -0
- package/dist/lib/form-field.d.ts +37 -0
- package/dist/lib/form-field.d.ts.map +1 -0
- package/dist/lib/form-field.js +47 -0
- package/dist/lib/form-field.js.map +1 -0
- package/dist/r/app-shell.json +5 -0
- package/dist/r/combobox.json +5 -0
- package/dist/r/empty.json +10 -1
- package/dist/r/field.json +7 -0
- package/dist/r/floating-preview.json +30 -0
- package/dist/r/input-group.json +6 -0
- package/dist/r/input.json +5 -0
- package/dist/r/lib.json +15 -0
- package/dist/r/list-row.json +1 -0
- package/dist/r/registry.json +112 -1
- package/dist/r/src/components/composites/action-button/action-button.tsx +5 -1
- package/dist/r/src/components/composites/list-row/list-row.tsx +81 -8
- package/dist/r/src/components/composites/settings/settings.tsx +67 -37
- package/dist/r/src/components/conversation/scroll-follow.ts +109 -15
- package/dist/r/src/components/layout/app-shell/index.ts +1 -0
- package/dist/r/src/components/layout/app-shell/skip-link.tsx +35 -0
- package/dist/r/src/components/layout/floating-preview/floating-preview.tsx +260 -0
- package/dist/r/src/components/layout/floating-preview/index.ts +2 -0
- package/dist/r/src/components/layout/floating-preview/model.ts +285 -0
- package/dist/r/src/components/layout/sidebar-nav/index.ts +2 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +71 -16
- package/dist/r/src/components/theme/index.ts +1 -0
- package/dist/r/src/components/theme/theme-mode-toggle.tsx +56 -0
- package/dist/r/src/components/ui/checkbox/checkbox.tsx +20 -3
- package/dist/r/src/components/ui/combobox/combobox.tsx +24 -1
- package/dist/r/src/components/ui/combobox/index.ts +1 -0
- package/dist/r/src/components/ui/combobox/value-combobox.tsx +38 -0
- package/dist/r/src/components/ui/empty/empty-state.tsx +71 -0
- package/dist/r/src/components/ui/empty/index.ts +1 -0
- package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +33 -2
- package/dist/r/src/components/ui/field/field.tsx +102 -5
- package/dist/r/src/components/ui/field/index.ts +5 -0
- package/dist/r/src/components/ui/field/number-field.tsx +71 -0
- package/dist/r/src/components/ui/input/index.ts +1 -0
- package/dist/r/src/components/ui/input/input-control.tsx +38 -0
- package/dist/r/src/components/ui/input/input.tsx +32 -20
- package/dist/r/src/components/ui/input-group/index.ts +1 -0
- package/dist/r/src/components/ui/input-group/input-group.tsx +25 -2
- package/dist/r/src/components/ui/input-group/secret-input-group.tsx +58 -0
- package/dist/r/src/components/ui/native-select/native-select.tsx +27 -1
- package/dist/r/src/components/ui/progress/progress.tsx +12 -8
- package/dist/r/src/components/ui/radio-group/radio-group.tsx +30 -2
- package/dist/r/src/components/ui/switch/switch.tsx +20 -3
- package/dist/r/src/components/ui/tabs/tabs.tsx +1 -1
- package/dist/r/src/components/ui/text-link/index.ts +2 -0
- package/dist/r/src/components/ui/text-link/text-link.tsx +28 -0
- package/dist/r/src/components/ui/textarea/textarea.tsx +24 -1
- package/dist/r/src/lib/clipboard.ts +41 -0
- package/dist/r/src/lib/download.ts +29 -0
- package/dist/r/src/lib/form-field.ts +93 -0
- package/dist/r/text-link.json +22 -0
- package/dist/r/theming.json +6 -0
- package/package.json +1 -1
- package/registry.json +112 -1
- package/src/components/composites/action-button/action-button.tsx +5 -1
- package/src/components/composites/list-row/list-row.tsx +81 -8
- package/src/components/composites/settings/settings.tsx +67 -37
- package/src/components/conversation/scroll-follow.ts +109 -15
- package/src/components/layout/app-shell/index.ts +1 -0
- package/src/components/layout/app-shell/skip-link.tsx +35 -0
- package/src/components/layout/floating-preview/floating-preview.tsx +260 -0
- package/src/components/layout/floating-preview/index.ts +2 -0
- package/src/components/layout/floating-preview/model.ts +285 -0
- package/src/components/layout/sidebar-nav/index.ts +2 -0
- package/src/components/layout/sidebar-nav/sidebar-nav.tsx +71 -16
- package/src/components/theme/index.ts +1 -0
- package/src/components/theme/theme-mode-toggle.tsx +56 -0
- package/src/components/ui/checkbox/checkbox.tsx +20 -3
- package/src/components/ui/combobox/combobox.tsx +24 -1
- package/src/components/ui/combobox/index.ts +1 -0
- package/src/components/ui/combobox/value-combobox.tsx +38 -0
- package/src/components/ui/empty/empty-state.tsx +71 -0
- package/src/components/ui/empty/index.ts +1 -0
- package/src/components/ui/entity-icon/entity-icon.tsx +33 -2
- package/src/components/ui/field/field.tsx +102 -5
- package/src/components/ui/field/index.ts +5 -0
- package/src/components/ui/field/number-field.tsx +71 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/input/input-control.tsx +38 -0
- package/src/components/ui/input/input.tsx +32 -20
- package/src/components/ui/input-group/index.ts +1 -0
- package/src/components/ui/input-group/input-group.tsx +25 -2
- package/src/components/ui/input-group/secret-input-group.tsx +58 -0
- package/src/components/ui/native-select/native-select.tsx +27 -1
- package/src/components/ui/progress/progress.tsx +12 -8
- package/src/components/ui/radio-group/radio-group.tsx +30 -2
- package/src/components/ui/switch/switch.tsx +20 -3
- package/src/components/ui/tabs/tabs.tsx +1 -1
- package/src/components/ui/text-link/index.ts +2 -0
- package/src/components/ui/text-link/text-link.tsx +28 -0
- package/src/components/ui/textarea/textarea.tsx +24 -1
- package/src/index.ts +3 -0
- package/src/lib/clipboard.ts +41 -0
- package/src/lib/download.ts +29 -0
- package/src/lib/form-field.ts +93 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Search } from 'lucide-solid'
|
|
2
|
+
import type { ComponentProps, JSX } from 'solid-js'
|
|
3
|
+
import { Show, splitProps } from 'solid-js'
|
|
4
|
+
import { ActionButton } from '../../composites/action-button'
|
|
5
|
+
import { Spinner } from '../spinner'
|
|
6
|
+
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from './empty'
|
|
7
|
+
|
|
8
|
+
export type EmptyStateProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'aria-live'> & {
|
|
9
|
+
title: string
|
|
10
|
+
detail: string
|
|
11
|
+
busy?: boolean
|
|
12
|
+
icon?: JSX.Element
|
|
13
|
+
announceAs?: 'alert' | 'status'
|
|
14
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6
|
|
15
|
+
action?: () => void
|
|
16
|
+
actionLabel?: string
|
|
17
|
+
actionTooltip?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A semantic empty, loading, or error state built from the shared empty parts. */
|
|
21
|
+
export function EmptyState(props: EmptyStateProps) {
|
|
22
|
+
const [local, rest] = splitProps(props, [
|
|
23
|
+
'title',
|
|
24
|
+
'detail',
|
|
25
|
+
'busy',
|
|
26
|
+
'icon',
|
|
27
|
+
'announceAs',
|
|
28
|
+
'headingLevel',
|
|
29
|
+
'action',
|
|
30
|
+
'actionLabel',
|
|
31
|
+
'actionTooltip',
|
|
32
|
+
'class',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Empty
|
|
37
|
+
{...rest}
|
|
38
|
+
class={local.class}
|
|
39
|
+
role={local.announceAs}
|
|
40
|
+
aria-live={local.announceAs === 'status' ? 'polite' : undefined}
|
|
41
|
+
aria-busy={local.busy || undefined}
|
|
42
|
+
>
|
|
43
|
+
<EmptyHeader>
|
|
44
|
+
<Show when={local.busy || local.icon}>
|
|
45
|
+
<EmptyMedia variant="icon">
|
|
46
|
+
{local.busy ? (
|
|
47
|
+
<Spinner label={false} aria-hidden="true" />
|
|
48
|
+
) : (
|
|
49
|
+
(local.icon ?? <Search aria-hidden="true" />)
|
|
50
|
+
)}
|
|
51
|
+
</EmptyMedia>
|
|
52
|
+
</Show>
|
|
53
|
+
<EmptyTitle role="heading" aria-level={local.headingLevel ?? 1}>
|
|
54
|
+
{local.title}
|
|
55
|
+
</EmptyTitle>
|
|
56
|
+
<EmptyDescription>{local.detail}</EmptyDescription>
|
|
57
|
+
</EmptyHeader>
|
|
58
|
+
<Show when={local.action}>
|
|
59
|
+
<EmptyContent>
|
|
60
|
+
<ActionButton
|
|
61
|
+
tooltip={local.actionTooltip ?? local.actionLabel ?? 'Try again'}
|
|
62
|
+
aria-label={local.actionLabel ?? 'Try again'}
|
|
63
|
+
onClick={local.action}
|
|
64
|
+
>
|
|
65
|
+
{local.actionLabel ?? 'Try again'}
|
|
66
|
+
</ActionButton>
|
|
67
|
+
</EmptyContent>
|
|
68
|
+
</Show>
|
|
69
|
+
</Empty>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createSignal,
|
|
3
|
+
Show,
|
|
4
|
+
splitProps,
|
|
5
|
+
type Component,
|
|
6
|
+
type ComponentProps,
|
|
7
|
+
type JSX,
|
|
8
|
+
} from 'solid-js'
|
|
2
9
|
import { Dynamic } from 'solid-js/web'
|
|
3
10
|
import { cva, type VariantProps } from '#lib/variants'
|
|
4
11
|
import { cn } from '#lib/utils'
|
|
@@ -16,7 +23,8 @@ import { cn } from '#lib/utils'
|
|
|
16
23
|
*
|
|
17
24
|
* 1. **A glyph is always present.** A brand with no logo gets a monogram, not an
|
|
18
25
|
* empty box. `fallback` takes the name and returns the glyph, so a caller can
|
|
19
|
-
* choose initials, a domain letter, or a kind icon.
|
|
26
|
+
* choose initials, a domain letter, or a kind icon. When `src` is supplied,
|
|
27
|
+
* its contained image overlays that glyph and failure reveals the glyph below.
|
|
20
28
|
* 2. **The tile owns its own contrast.** `tone` tints the background while
|
|
21
29
|
* monogram text keeps the body foreground; vector glyphs retain the status
|
|
22
30
|
* colour. A caller never picks a colour pair or ships a glyph that disappears.
|
|
@@ -67,6 +75,8 @@ export type EntityIconProps = Omit<ComponentProps<'span'>, 'children'> &
|
|
|
67
75
|
EntityIconVariantProps & {
|
|
68
76
|
/** The entity's name. Used for the accessible label and, via `fallback`, the monogram. */
|
|
69
77
|
name: string
|
|
78
|
+
/** Optional image URL. The image is decorative; `name` remains the accessible label. */
|
|
79
|
+
src?: string
|
|
70
80
|
/**
|
|
71
81
|
* A logo or icon — a Lucide icon reference (`icon={Wrench}`) or an element
|
|
72
82
|
* (`icon={<Wrench />}`). Both are accepted because both are what a caller
|
|
@@ -108,9 +118,27 @@ function Glyph(props: { glyph: Glyph }) {
|
|
|
108
118
|
)
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
function EntityIconImage(props: { src: string }) {
|
|
122
|
+
const [failed, setFailed] = createSignal(false)
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Show when={!failed()}>
|
|
126
|
+
<img
|
|
127
|
+
data-slot="entity-icon-image"
|
|
128
|
+
src={props.src}
|
|
129
|
+
alt=""
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
class="pointer-events-none absolute inset-0 size-full object-contain"
|
|
132
|
+
onError={() => setFailed(true)}
|
|
133
|
+
/>
|
|
134
|
+
</Show>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
export function EntityIcon(props: EntityIconProps) {
|
|
112
139
|
const [local, rest] = splitProps(props, [
|
|
113
140
|
'name',
|
|
141
|
+
'src',
|
|
114
142
|
'icon',
|
|
115
143
|
'fallback',
|
|
116
144
|
'badge',
|
|
@@ -146,6 +174,9 @@ export function EntityIcon(props: EntityIconProps) {
|
|
|
146
174
|
>
|
|
147
175
|
{(value) => <Glyph glyph={value()} />}
|
|
148
176
|
</Show>
|
|
177
|
+
<Show when={local.src} keyed>
|
|
178
|
+
{(src) => <EntityIconImage src={src} />}
|
|
179
|
+
</Show>
|
|
149
180
|
<Show when={local.badge}>
|
|
150
181
|
<span class="absolute right-0 bottom-0 translate-x-1/4 translate-y-1/4">{local.badge}</span>
|
|
151
182
|
</Show>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TextField as KobalteTextField } from '@kobalte/core/text-field'
|
|
2
|
-
import type { ComponentProps } from 'solid-js'
|
|
3
|
-
import { Show, splitProps } from 'solid-js'
|
|
2
|
+
import type { ComponentProps, JSX } from 'solid-js'
|
|
3
|
+
import { createUniqueId, Show, splitProps } from 'solid-js'
|
|
4
|
+
import { Label } from '../label/label'
|
|
5
|
+
import { createFormFieldContext, FormFieldContext } from '#lib/form-field'
|
|
4
6
|
import { cn } from '#lib/utils'
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -9,21 +11,110 @@ import { cn } from '#lib/utils'
|
|
|
9
11
|
* The wrapper that makes a form control describable and validatable: a label,
|
|
10
12
|
* a description, an error message, and the ARIA relationships between them.
|
|
11
13
|
*
|
|
14
|
+
* This Kobalte TextField composition is for its own `FieldLabel`, `FieldInput`
|
|
15
|
+
* and `FieldTextArea` parts. Use `FormField` to wire the standalone shared
|
|
16
|
+
* controls, including checkboxes, switches and comboboxes.
|
|
17
|
+
*
|
|
12
18
|
* It is built on Kobalte's TextField specifically so `aria-describedby`,
|
|
13
19
|
* `aria-invalid` and `aria-errormessage` are wired by construction. Those three
|
|
14
20
|
* attributes are the difference between "this field is required and empty" and
|
|
15
21
|
* a red border that only a sighted user can perceive — and they are also the
|
|
16
22
|
* first thing a hand-rolled form drops.
|
|
17
23
|
*
|
|
18
|
-
* `Field` owns the description and the error slot, so
|
|
19
|
-
*
|
|
20
|
-
* Compose it with any control: `Field` wraps, `Label`/`Input`/`Select` fill it.
|
|
24
|
+
* `Field` owns the description and the error slot, so its controls line up and
|
|
25
|
+
* its messages land in the same place regardless of which TextField part is used.
|
|
21
26
|
*/
|
|
22
27
|
export function Field(props: ComponentProps<typeof KobalteTextField>) {
|
|
23
28
|
const [local, rest] = splitProps(props, ['class'])
|
|
24
29
|
return <KobalteTextField class={cn('flex flex-col gap-1.5', local.class)} {...rest} />
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
export type FormFieldProps = Omit<ComponentProps<'div'>, 'children' | 'ref'> & {
|
|
33
|
+
label: string
|
|
34
|
+
hint?: string
|
|
35
|
+
error?: string
|
|
36
|
+
controlId?: string
|
|
37
|
+
group?: boolean
|
|
38
|
+
children: JSX.Element
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* FormField.
|
|
43
|
+
*
|
|
44
|
+
* A shared label, help and validation wrapper for any one shared control. Its
|
|
45
|
+
* context is consumed by Input, Textarea, NativeSelect, Checkbox, Switch,
|
|
46
|
+
* RadioGroup and ComboboxInput, so the actual focusable element receives the
|
|
47
|
+
* generated ID and description relationships. Set `group` when the label names
|
|
48
|
+
* a set of controls; the wrapper then uses fieldset and legend semantics.
|
|
49
|
+
*/
|
|
50
|
+
export function FormField(props: FormFieldProps) {
|
|
51
|
+
const [local, rest] = splitProps(props, [
|
|
52
|
+
'class',
|
|
53
|
+
'label',
|
|
54
|
+
'hint',
|
|
55
|
+
'error',
|
|
56
|
+
'controlId',
|
|
57
|
+
'group',
|
|
58
|
+
'children',
|
|
59
|
+
])
|
|
60
|
+
const fieldId = createUniqueId()
|
|
61
|
+
const labelId = `${fieldId}-label`
|
|
62
|
+
const descriptionId = `${fieldId}-description`
|
|
63
|
+
const errorId = `${fieldId}-error`
|
|
64
|
+
const field = createFormFieldContext({
|
|
65
|
+
controlId: local.controlId,
|
|
66
|
+
defaultControlId: fieldId,
|
|
67
|
+
labelId,
|
|
68
|
+
descriptionId,
|
|
69
|
+
errorId,
|
|
70
|
+
hint: () => local.hint,
|
|
71
|
+
error: () => local.error,
|
|
72
|
+
group: () => local.group ?? false,
|
|
73
|
+
})
|
|
74
|
+
const help = () => (
|
|
75
|
+
<>
|
|
76
|
+
<Show when={local.hint}>
|
|
77
|
+
<p id={descriptionId} class="text-muted-foreground text-sm">
|
|
78
|
+
{local.hint}
|
|
79
|
+
</p>
|
|
80
|
+
</Show>
|
|
81
|
+
<Show when={local.error}>
|
|
82
|
+
<p id={errorId} role="alert" class="text-foreground text-sm">
|
|
83
|
+
{local.error}
|
|
84
|
+
</p>
|
|
85
|
+
</Show>
|
|
86
|
+
</>
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<FormFieldContext.Provider value={field}>
|
|
91
|
+
<div class={cn('flex flex-col gap-1.5', local.class)} {...rest}>
|
|
92
|
+
{local.group ? (
|
|
93
|
+
<FieldSet
|
|
94
|
+
class="flex flex-col gap-1.5"
|
|
95
|
+
aria-describedby={field.describedBy()}
|
|
96
|
+
aria-invalid={field.invalid() || undefined}
|
|
97
|
+
>
|
|
98
|
+
<FieldLegend id={labelId} variant="label">
|
|
99
|
+
{local.label}
|
|
100
|
+
</FieldLegend>
|
|
101
|
+
{local.children}
|
|
102
|
+
{help()}
|
|
103
|
+
</FieldSet>
|
|
104
|
+
) : (
|
|
105
|
+
<>
|
|
106
|
+
<Label id={labelId} for={field.controlId()}>
|
|
107
|
+
{local.label}
|
|
108
|
+
</Label>
|
|
109
|
+
{local.children}
|
|
110
|
+
{help()}
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</FormFieldContext.Provider>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
27
118
|
export function FieldLabel(props: ComponentProps<typeof KobalteTextField.Label>) {
|
|
28
119
|
const [local, rest] = splitProps(props, ['class'])
|
|
29
120
|
return (
|
|
@@ -96,6 +187,12 @@ export function FieldSet(props: ComponentProps<'fieldset'>) {
|
|
|
96
187
|
return <fieldset class={cn('flex flex-col gap-4', local.class)} {...rest} />
|
|
97
188
|
}
|
|
98
189
|
|
|
190
|
+
/** A visual stack for related controls that does not imply fieldset semantics. */
|
|
191
|
+
export function FieldGroup(props: ComponentProps<'div'>) {
|
|
192
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
193
|
+
return <div data-slot="field-group" class={cn('flex flex-col gap-4', local.class)} {...rest} />
|
|
194
|
+
}
|
|
195
|
+
|
|
99
196
|
export function FieldLegend(props: ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
|
|
100
197
|
const [local, rest] = splitProps(props, ['class', 'variant'])
|
|
101
198
|
return (
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
export {
|
|
2
2
|
Field,
|
|
3
|
+
FormField,
|
|
3
4
|
FieldDescription,
|
|
4
5
|
FieldError,
|
|
6
|
+
FieldGroup,
|
|
5
7
|
FieldInput,
|
|
6
8
|
FieldLabel,
|
|
7
9
|
FieldLegend,
|
|
8
10
|
FieldSeparator,
|
|
9
11
|
FieldSet,
|
|
10
12
|
FieldTextArea,
|
|
13
|
+
type FormFieldProps,
|
|
11
14
|
} from './field'
|
|
15
|
+
export { NumberField } from './number-field'
|
|
16
|
+
export type { NumberFieldProps } from './number-field'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createEffect, createSignal, type JSX } from 'solid-js'
|
|
2
|
+
import { FormField } from './field'
|
|
3
|
+
import { Input } from '../input/input'
|
|
4
|
+
|
|
5
|
+
export type NumberFieldProps = {
|
|
6
|
+
label: string
|
|
7
|
+
hint?: string
|
|
8
|
+
value: number
|
|
9
|
+
min: number
|
|
10
|
+
max: number
|
|
11
|
+
integer?: boolean
|
|
12
|
+
onChange: (value: number) => void
|
|
13
|
+
class?: string
|
|
14
|
+
onBlur?: JSX.EventHandler<HTMLInputElement, FocusEvent>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A controlled numeric field that keeps an editable draft while validating
|
|
19
|
+
* range and integer constraints. Invalid drafts are announced and restored
|
|
20
|
+
* to the latest controlled value when focus leaves the input.
|
|
21
|
+
*/
|
|
22
|
+
export function NumberField(props: NumberFieldProps) {
|
|
23
|
+
const [draft, setDraft] = createSignal(String(props.value))
|
|
24
|
+
const [error, setError] = createSignal('')
|
|
25
|
+
const integer = () => props.integer ?? true
|
|
26
|
+
|
|
27
|
+
createEffect(() => {
|
|
28
|
+
setDraft(String(props.value))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const validate = (raw: string) => {
|
|
32
|
+
if (!raw) return `${props.label} is required.`
|
|
33
|
+
const next = Number(raw)
|
|
34
|
+
if (!Number.isFinite(next)) return `${props.label} must be a valid number.`
|
|
35
|
+
if (integer() && !Number.isInteger(next)) {
|
|
36
|
+
return `${props.label} must be a whole number.`
|
|
37
|
+
}
|
|
38
|
+
if (next < props.min || next > props.max) {
|
|
39
|
+
return `${props.label} must be between ${props.min} and ${props.max}.`
|
|
40
|
+
}
|
|
41
|
+
return ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<FormField label={props.label} hint={props.hint} error={error()} class={props.class}>
|
|
46
|
+
<Input
|
|
47
|
+
type="number"
|
|
48
|
+
value={draft()}
|
|
49
|
+
min={props.min}
|
|
50
|
+
max={props.max}
|
|
51
|
+
step={integer() ? 1 : 'any'}
|
|
52
|
+
required
|
|
53
|
+
onInput={(event) => {
|
|
54
|
+
const raw = event.currentTarget.value
|
|
55
|
+
setDraft(raw)
|
|
56
|
+
const nextError = validate(raw)
|
|
57
|
+
setError(nextError)
|
|
58
|
+
if (!nextError) props.onChange(Number(raw))
|
|
59
|
+
}}
|
|
60
|
+
onBlur={(event) => {
|
|
61
|
+
const nextError = validate(draft())
|
|
62
|
+
if (nextError) {
|
|
63
|
+
setDraft(String(props.value))
|
|
64
|
+
setError('')
|
|
65
|
+
}
|
|
66
|
+
props.onBlur?.(event)
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
</FormField>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ComponentProps } from 'solid-js'
|
|
2
|
+
import { splitProps } from 'solid-js'
|
|
3
|
+
import { cn } from '#lib/utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* InputControl.
|
|
7
|
+
*
|
|
8
|
+
* A styled native input with no optional suggestion-list behavior. Use this
|
|
9
|
+
* when a consumer needs only the control; `Input` adds native datalist
|
|
10
|
+
* suggestions for forms that need them.
|
|
11
|
+
*/
|
|
12
|
+
export type InputControlProps = ComponentProps<'input'>
|
|
13
|
+
|
|
14
|
+
export function InputControl(props: InputControlProps) {
|
|
15
|
+
const [local, rest] = splitProps(props, ['class', 'type'])
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<input
|
|
19
|
+
data-slot="input"
|
|
20
|
+
type={local.type ?? 'text'}
|
|
21
|
+
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',
|
|
23
|
+
'transition-[color,box-shadow,border-color] ease-out outline-none',
|
|
24
|
+
'placeholder:text-muted-foreground',
|
|
25
|
+
'selection:bg-primary selection:text-primary-foreground',
|
|
26
|
+
'file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',
|
|
27
|
+
/* A pointer click on a text field is a typing affordance, so the ring
|
|
28
|
+
appears where the user agent already matches :focus-visible. */
|
|
29
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
|
|
30
|
+
'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive-subtle',
|
|
31
|
+
'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
32
|
+
'read-only:bg-surface-hover',
|
|
33
|
+
local.class
|
|
34
|
+
)}
|
|
35
|
+
{...rest}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ComponentProps } from 'solid-js'
|
|
2
2
|
import { createUniqueId, For, Show, splitProps } from 'solid-js'
|
|
3
|
-
import {
|
|
3
|
+
import { useFormFieldControl } from '#lib/form-field'
|
|
4
|
+
import { InputControl } from './input-control'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Input.
|
|
@@ -20,31 +21,42 @@ export type InputProps = ComponentProps<'input'> & {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function Input(props: InputProps) {
|
|
23
|
-
const [local, rest] = splitProps(props, [
|
|
24
|
+
const [local, rest] = splitProps(props, [
|
|
25
|
+
'class',
|
|
26
|
+
'type',
|
|
27
|
+
'id',
|
|
28
|
+
'list',
|
|
29
|
+
'suggestions',
|
|
30
|
+
'aria-label',
|
|
31
|
+
'aria-labelledby',
|
|
32
|
+
'aria-describedby',
|
|
33
|
+
'aria-invalid',
|
|
34
|
+
'aria-errormessage',
|
|
35
|
+
])
|
|
36
|
+
const field = useFormFieldControl('control', {
|
|
37
|
+
id: local.id,
|
|
38
|
+
'aria-label': local['aria-label'],
|
|
39
|
+
'aria-labelledby': local['aria-labelledby'],
|
|
40
|
+
'aria-describedby': local['aria-describedby'],
|
|
41
|
+
'aria-invalid': local['aria-invalid'],
|
|
42
|
+
'aria-errormessage': local['aria-errormessage'],
|
|
43
|
+
})
|
|
24
44
|
const suggestionsId = createUniqueId()
|
|
25
45
|
const listId = () => local.list ?? `input-suggestions-${suggestionsId}`
|
|
26
46
|
|
|
27
47
|
return (
|
|
28
48
|
<>
|
|
29
|
-
<
|
|
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
|
|
40
|
-
appears where the user agent already matches :focus-visible. */
|
|
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
|
-
)}
|
|
49
|
+
<InputControl
|
|
47
50
|
{...rest}
|
|
51
|
+
class={local.class}
|
|
52
|
+
type={local.type}
|
|
53
|
+
id={field?.id ?? local.id}
|
|
54
|
+
list={local.suggestions ? listId() : local.list}
|
|
55
|
+
aria-label={local['aria-label']}
|
|
56
|
+
aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
|
|
57
|
+
aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}
|
|
58
|
+
aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
|
|
59
|
+
aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
|
|
48
60
|
/>
|
|
49
61
|
<Show when={local.suggestions}>
|
|
50
62
|
{(suggestions) => (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentProps, JSX } 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
|
/**
|
|
@@ -26,7 +27,7 @@ export function InputGroup(props: ComponentProps<'div'>) {
|
|
|
26
27
|
'transition-[color,box-shadow,border-color] ease-out',
|
|
27
28
|
'focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle',
|
|
28
29
|
'has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:ring-3 has-[[aria-invalid=true]]:ring-destructive-subtle',
|
|
29
|
-
'has-[input:disabled]:
|
|
30
|
+
'has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50',
|
|
30
31
|
local.class
|
|
31
32
|
)}
|
|
32
33
|
{...rest}
|
|
@@ -35,11 +36,33 @@ export function InputGroup(props: ComponentProps<'div'>) {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export function InputGroupInput(props: ComponentProps<'input'>) {
|
|
38
|
-
const [local, rest] = splitProps(props, [
|
|
39
|
+
const [local, rest] = splitProps(props, [
|
|
40
|
+
'class',
|
|
41
|
+
'id',
|
|
42
|
+
'aria-label',
|
|
43
|
+
'aria-labelledby',
|
|
44
|
+
'aria-describedby',
|
|
45
|
+
'aria-invalid',
|
|
46
|
+
'aria-errormessage',
|
|
47
|
+
])
|
|
48
|
+
const field = useFormFieldControl('control', {
|
|
49
|
+
id: local.id,
|
|
50
|
+
'aria-label': local['aria-label'],
|
|
51
|
+
'aria-labelledby': local['aria-labelledby'],
|
|
52
|
+
'aria-describedby': local['aria-describedby'],
|
|
53
|
+
'aria-invalid': local['aria-invalid'],
|
|
54
|
+
'aria-errormessage': local['aria-errormessage'],
|
|
55
|
+
})
|
|
39
56
|
|
|
40
57
|
return (
|
|
41
58
|
<input
|
|
42
59
|
data-slot="input-group-input"
|
|
60
|
+
id={field?.id ?? local.id}
|
|
61
|
+
aria-label={local['aria-label']}
|
|
62
|
+
aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
|
|
63
|
+
aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}
|
|
64
|
+
aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
|
|
65
|
+
aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
|
|
43
66
|
class={cn(
|
|
44
67
|
'h-full min-w-0 flex-1 bg-transparent text-sm outline-none',
|
|
45
68
|
'placeholder:text-muted-foreground',
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { splitProps, useContext, type ComponentProps, type JSX } from 'solid-js'
|
|
2
|
+
import { ActionButton } from '../../composites/action-button'
|
|
3
|
+
import { FormFieldContext } from '#lib/form-field'
|
|
4
|
+
import { InputGroup, InputGroupInput } from './input-group'
|
|
5
|
+
|
|
6
|
+
export type SecretInputGroupProps = {
|
|
7
|
+
value: string
|
|
8
|
+
disabled: boolean
|
|
9
|
+
onChange: JSX.EventHandler<HTMLInputElement, Event>
|
|
10
|
+
onClear?: () => void
|
|
11
|
+
class?: string
|
|
12
|
+
} & Pick<ComponentProps<'input'>, 'id' | 'aria-label' | 'aria-describedby' | 'aria-invalid'>
|
|
13
|
+
|
|
14
|
+
/** A password input with an optional keyboard-accessible clear action. */
|
|
15
|
+
export function SecretInputGroup(props: SecretInputGroupProps) {
|
|
16
|
+
const field = useContext(FormFieldContext)
|
|
17
|
+
const [local, rest] = splitProps(props, [
|
|
18
|
+
'value',
|
|
19
|
+
'disabled',
|
|
20
|
+
'onChange',
|
|
21
|
+
'onClear',
|
|
22
|
+
'aria-label',
|
|
23
|
+
'class',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
const input = (
|
|
27
|
+
<InputGroupInput
|
|
28
|
+
{...rest}
|
|
29
|
+
aria-label={local['aria-label'] ?? (field ? undefined : 'New API key')}
|
|
30
|
+
type="password"
|
|
31
|
+
autocomplete="new-password"
|
|
32
|
+
value={local.value}
|
|
33
|
+
disabled={local.disabled}
|
|
34
|
+
onInput={local.onChange}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if (!local.onClear) return <InputGroup class={local.class}>{input}</InputGroup>
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<InputGroup class={local.class}>
|
|
42
|
+
{input}
|
|
43
|
+
<ActionButton
|
|
44
|
+
data-slot="input-group-button"
|
|
45
|
+
variant="ghost"
|
|
46
|
+
size="xs"
|
|
47
|
+
class="-me-1 h-6 min-w-0 shrink-0 px-2"
|
|
48
|
+
disabled={local.disabled}
|
|
49
|
+
tooltip={local.disabled ? 'This API key is managed by policy' : 'Clear API key'}
|
|
50
|
+
tooltipSide="top"
|
|
51
|
+
aria-label="Clear API key"
|
|
52
|
+
onClick={() => local.onClear?.()}
|
|
53
|
+
>
|
|
54
|
+
Clear
|
|
55
|
+
</ActionButton>
|
|
56
|
+
</InputGroup>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChevronDownIcon } from 'lucide-solid'
|
|
2
2
|
import type { ComponentProps } from 'solid-js'
|
|
3
3
|
import { createEffect, For, onMount, Show, splitProps } from 'solid-js'
|
|
4
|
+
import { useFormFieldControl } from '#lib/form-field'
|
|
4
5
|
import { cn } from '#lib/utils'
|
|
5
6
|
|
|
6
7
|
export type NativeSelectProps = ComponentProps<'select'> & {
|
|
@@ -59,7 +60,26 @@ export type NativeSelectOption = Readonly<{
|
|
|
59
60
|
}>
|
|
60
61
|
|
|
61
62
|
export function NativeSelect(props: NativeSelectProps) {
|
|
62
|
-
const [local, rest] = splitProps(props, [
|
|
63
|
+
const [local, rest] = splitProps(props, [
|
|
64
|
+
'class',
|
|
65
|
+
'children',
|
|
66
|
+
'defaultValue',
|
|
67
|
+
'options',
|
|
68
|
+
'id',
|
|
69
|
+
'aria-label',
|
|
70
|
+
'aria-labelledby',
|
|
71
|
+
'aria-describedby',
|
|
72
|
+
'aria-invalid',
|
|
73
|
+
'aria-errormessage',
|
|
74
|
+
])
|
|
75
|
+
const field = useFormFieldControl('control', {
|
|
76
|
+
id: local.id,
|
|
77
|
+
'aria-label': local['aria-label'],
|
|
78
|
+
'aria-labelledby': local['aria-labelledby'],
|
|
79
|
+
'aria-describedby': local['aria-describedby'],
|
|
80
|
+
'aria-invalid': local['aria-invalid'],
|
|
81
|
+
'aria-errormessage': local['aria-errormessage'],
|
|
82
|
+
})
|
|
63
83
|
let select: HTMLSelectElement | undefined
|
|
64
84
|
|
|
65
85
|
// Set selection after native options exist; setting select.value before its
|
|
@@ -81,6 +101,12 @@ export function NativeSelect(props: NativeSelectProps) {
|
|
|
81
101
|
select = element
|
|
82
102
|
}}
|
|
83
103
|
data-slot="native-select"
|
|
104
|
+
id={field?.id ?? local.id}
|
|
105
|
+
aria-label={local['aria-label']}
|
|
106
|
+
aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
|
|
107
|
+
aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}
|
|
108
|
+
aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
|
|
109
|
+
aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
|
|
84
110
|
class={cn(
|
|
85
111
|
'h-control-md min-w-0 appearance-none rounded-md border border-input bg-transparent px-control-md pr-9 text-sm',
|
|
86
112
|
'transition-[color,box-shadow,border-color] ease-out outline-none',
|
|
@@ -40,14 +40,18 @@ export function Progress(props: ProgressProps) {
|
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<KobalteProgress class={cn('flex w-full flex-col gap-1.5', local.class)} {...rest}>
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
{local.label}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
<Show when={local.label || !local.hideValue}>
|
|
44
|
+
<div class="flex items-center justify-between gap-3">
|
|
45
|
+
<Show when={local.label}>
|
|
46
|
+
<KobalteProgress.Label class="text-muted-foreground text-sm">
|
|
47
|
+
{local.label}
|
|
48
|
+
</KobalteProgress.Label>
|
|
49
|
+
</Show>
|
|
50
|
+
<Show when={!local.hideValue}>
|
|
51
|
+
<KobalteProgress.ValueLabel class="text-muted-foreground text-sm tabular-nums" />
|
|
52
|
+
</Show>
|
|
53
|
+
</div>
|
|
54
|
+
</Show>
|
|
51
55
|
<KobalteProgress.Track
|
|
52
56
|
class={cn('bg-input relative h-1.5 w-full overflow-hidden rounded-full', local.trackClass)}
|
|
53
57
|
>
|