@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.
- package/README.md +41 -8
- package/dist/components/composites/account-menu/account-menu.d.ts +5 -0
- package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
- package/dist/components/composites/account-menu/account-menu.js +122 -70
- package/dist/components/composites/account-menu/account-menu.js.map +1 -1
- package/dist/components/composites/action-button/action-button.d.ts +27 -0
- package/dist/components/composites/action-button/action-button.d.ts.map +1 -0
- package/dist/components/composites/action-button/action-button.js +121 -0
- package/dist/components/composites/action-button/action-button.js.map +1 -0
- package/dist/components/composites/action-button/index.d.ts +3 -0
- package/dist/components/composites/action-button/index.d.ts.map +1 -0
- package/dist/components/composites/action-button/index.js +2 -0
- package/dist/components/conversation/thread-panel.d.ts +2 -0
- package/dist/components/conversation/thread-panel.d.ts.map +1 -1
- package/dist/components/conversation/thread-panel.js +36 -31
- package/dist/components/conversation/thread-panel.js.map +1 -1
- 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 +6 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js +66 -62
- package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
- package/dist/components/ui/board/board.d.ts.map +1 -1
- package/dist/components/ui/board/board.js +54 -7
- package/dist/components/ui/board/board.js.map +1 -1
- package/dist/components/ui/command/command.d.ts +5 -1
- package/dist/components/ui/command/command.d.ts.map +1 -1
- package/dist/components/ui/command/command.js +8 -3
- package/dist/components/ui/command/command.js.map +1 -1
- package/dist/components/ui/input/index.d.ts +1 -1
- package/dist/components/ui/input/index.d.ts.map +1 -1
- package/dist/components/ui/input/input.d.ts +5 -1
- package/dist/components/ui/input/input.d.ts.map +1 -1
- package/dist/components/ui/input/input.js +36 -17
- package/dist/components/ui/input/input.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/r/account-menu.json +1 -0
- package/dist/r/action-button.json +24 -0
- package/dist/r/conversation.json +1 -0
- package/dist/r/registry.json +26 -0
- package/dist/r/src/components/composites/account-menu/account-menu.tsx +100 -44
- package/dist/r/src/components/composites/action-button/action-button.tsx +139 -0
- package/dist/r/src/components/composites/action-button/index.ts +2 -0
- package/dist/r/src/components/conversation/thread-panel.tsx +22 -12
- package/dist/r/src/components/layout/sidebar-nav/index.ts +1 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +29 -21
- package/dist/r/src/components/ui/board/board.tsx +74 -6
- package/dist/r/src/components/ui/command/command.tsx +15 -4
- package/dist/r/src/components/ui/input/index.ts +1 -1
- package/dist/r/src/components/ui/input/input.tsx +38 -21
- package/package.json +1 -1
- package/registry.json +26 -0
- package/src/components/composites/account-menu/account-menu.tsx +100 -44
- package/src/components/composites/action-button/action-button.tsx +139 -0
- package/src/components/composites/action-button/index.ts +2 -0
- package/src/components/conversation/thread-panel.tsx +22 -12
- package/src/components/layout/sidebar-nav/index.ts +1 -0
- package/src/components/layout/sidebar-nav/sidebar-nav.tsx +29 -21
- package/src/components/ui/board/board.tsx +74 -6
- package/src/components/ui/command/command.tsx +15 -4
- package/src/components/ui/input/index.ts +1 -1
- package/src/components/ui/input/input.tsx +38 -21
- package/src/index.ts +1 -0
|
@@ -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
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1649,6 +1649,7 @@
|
|
|
1649
1649
|
"registryDependencies": [
|
|
1650
1650
|
"@adea-ai/ui/button",
|
|
1651
1651
|
"@adea-ai/ui/dropdown-menu",
|
|
1652
|
+
"@adea-ai/ui/tooltip",
|
|
1652
1653
|
"lib"
|
|
1653
1654
|
],
|
|
1654
1655
|
"files": [
|
|
@@ -1664,6 +1665,30 @@
|
|
|
1664
1665
|
}
|
|
1665
1666
|
]
|
|
1666
1667
|
},
|
|
1668
|
+
{
|
|
1669
|
+
"name": "action-button",
|
|
1670
|
+
"type": "registry:ui",
|
|
1671
|
+
"title": "Action Button",
|
|
1672
|
+
"description": "A shared Button with optional tooltip and busy-state feedback.",
|
|
1673
|
+
"dependencies": [],
|
|
1674
|
+
"registryDependencies": [
|
|
1675
|
+
"@adea-ai/ui/button",
|
|
1676
|
+
"@adea-ai/ui/spinner",
|
|
1677
|
+
"@adea-ai/ui/tooltip"
|
|
1678
|
+
],
|
|
1679
|
+
"files": [
|
|
1680
|
+
{
|
|
1681
|
+
"path": "src/components/composites/action-button/action-button.tsx",
|
|
1682
|
+
"type": "registry:ui",
|
|
1683
|
+
"target": "components/composites/action-button/action-button.tsx"
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
"path": "src/components/composites/action-button/index.ts",
|
|
1687
|
+
"type": "registry:ui",
|
|
1688
|
+
"target": "components/composites/action-button/index.ts"
|
|
1689
|
+
}
|
|
1690
|
+
]
|
|
1691
|
+
},
|
|
1667
1692
|
{
|
|
1668
1693
|
"name": "appearance-editor",
|
|
1669
1694
|
"type": "registry:ui",
|
|
@@ -1853,6 +1878,7 @@
|
|
|
1853
1878
|
"@adea-ai/ui/kbd",
|
|
1854
1879
|
"@adea-ai/ui/spinner",
|
|
1855
1880
|
"@adea-ai/ui/textarea",
|
|
1881
|
+
"@adea-ai/ui/tooltip",
|
|
1856
1882
|
"lib"
|
|
1857
1883
|
],
|
|
1858
1884
|
"files": [
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
Smartphone,
|
|
10
10
|
UserRound,
|
|
11
11
|
} from 'lucide-solid'
|
|
12
|
-
import type { JSX } from 'solid-js'
|
|
13
|
-
import { For, Show, splitProps } from 'solid-js'
|
|
12
|
+
import type { ComponentProps, JSX } from 'solid-js'
|
|
13
|
+
import { For, Show, createSignal, onCleanup, splitProps } from 'solid-js'
|
|
14
14
|
import { cn } from '#lib/utils'
|
|
15
|
-
import { Button } from '../../ui/button/button'
|
|
15
|
+
import { Button, type ButtonProps } from '../../ui/button/button'
|
|
16
16
|
import {
|
|
17
17
|
DropdownMenu,
|
|
18
18
|
DropdownMenuContent,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
DropdownMenuSeparator,
|
|
22
22
|
DropdownMenuTrigger,
|
|
23
23
|
} from '../../ui/dropdown-menu/dropdown-menu'
|
|
24
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip/tooltip'
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* AccountMenu.
|
|
@@ -49,10 +50,14 @@ export type AccountMenuItem = {
|
|
|
49
50
|
/** Render only on this platform. Omit to always render. */
|
|
50
51
|
platform?: 'desktop' | 'web'
|
|
51
52
|
onSelect?: () => void
|
|
53
|
+
/** Run after the menu's close-focus cycle, with its trigger as a stable opener. */
|
|
54
|
+
onSelectAfterClose?: (trigger: HTMLButtonElement | undefined) => void
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export type AccountMenuProps = {
|
|
55
58
|
class?: string
|
|
59
|
+
/** The trigger size. Icon-only menus default to the standard icon size. */
|
|
60
|
+
size?: ButtonProps['size']
|
|
56
61
|
/** The entries above the separator. */
|
|
57
62
|
items: readonly AccountMenuItem[]
|
|
58
63
|
/** Which platform this is rendered on, for items that declare one. */
|
|
@@ -81,6 +86,7 @@ const FALLBACK_ICONS: Record<string, typeof Settings2> = {
|
|
|
81
86
|
export function AccountMenu(props: AccountMenuProps) {
|
|
82
87
|
const [local, rest] = splitProps(props, [
|
|
83
88
|
'class',
|
|
89
|
+
'size',
|
|
84
90
|
'items',
|
|
85
91
|
'platform',
|
|
86
92
|
'authenticated',
|
|
@@ -91,55 +97,105 @@ export function AccountMenu(props: AccountMenuProps) {
|
|
|
91
97
|
'onIntent',
|
|
92
98
|
])
|
|
93
99
|
|
|
100
|
+
let trigger: HTMLButtonElement | undefined
|
|
101
|
+
let pendingAfterClose:
|
|
102
|
+
| { callback: NonNullable<AccountMenuItem['onSelectAfterClose']> }
|
|
103
|
+
| undefined
|
|
104
|
+
let scheduledAfterClose:
|
|
105
|
+
| { callback: NonNullable<AccountMenuItem['onSelectAfterClose']> }
|
|
106
|
+
| undefined
|
|
107
|
+
const [menuOpen, setMenuOpen] = createSignal(false)
|
|
108
|
+
const [tooltipOpen, setTooltipOpen] = createSignal(false)
|
|
109
|
+
|
|
110
|
+
onCleanup(() => {
|
|
111
|
+
pendingAfterClose = undefined
|
|
112
|
+
scheduledAfterClose = undefined
|
|
113
|
+
})
|
|
114
|
+
|
|
94
115
|
const visible = () =>
|
|
95
116
|
local.items.filter((item) => !item.platform || item.platform === (local.platform ?? 'desktop'))
|
|
96
117
|
|
|
97
118
|
return (
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
onFocus={() => local.onIntent?.()}
|
|
106
|
-
onPointerEnter={() => local.onIntent?.()}
|
|
107
|
-
{...rest}
|
|
119
|
+
<Tooltip open={tooltipOpen()} onOpenChange={(open) => setTooltipOpen(open && !menuOpen())}>
|
|
120
|
+
<DropdownMenu
|
|
121
|
+
modal={false}
|
|
122
|
+
onOpenChange={(open) => {
|
|
123
|
+
setMenuOpen(open)
|
|
124
|
+
if (open) setTooltipOpen(false)
|
|
125
|
+
}}
|
|
108
126
|
>
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
<TooltipTrigger
|
|
128
|
+
as={AccountMenuButton}
|
|
129
|
+
ref={(element: HTMLButtonElement) => (trigger = element)}
|
|
130
|
+
variant="ghost"
|
|
131
|
+
size={local.size ?? 'icon-md'}
|
|
132
|
+
class={cn('text-muted-foreground', local.class)}
|
|
133
|
+
aria-label={local.label ?? 'Account and settings'}
|
|
134
|
+
onFocus={() => local.onIntent?.()}
|
|
135
|
+
onPointerEnter={() => local.onIntent?.()}
|
|
136
|
+
{...rest}
|
|
137
|
+
>
|
|
138
|
+
<UserRound aria-hidden="true" />
|
|
139
|
+
</TooltipTrigger>
|
|
140
|
+
<TooltipContent placement="top">{local.label ?? 'Account and settings'}</TooltipContent>
|
|
141
|
+
<DropdownMenuContent
|
|
142
|
+
placement="top-start"
|
|
143
|
+
class="min-w-56 max-h-(--kb-popper-available-height) overflow-x-hidden overflow-y-auto"
|
|
144
|
+
onCloseAutoFocus={(event) => {
|
|
145
|
+
const selection = pendingAfterClose
|
|
146
|
+
pendingAfterClose = undefined
|
|
147
|
+
if (!selection) return
|
|
148
|
+
|
|
149
|
+
event.preventDefault()
|
|
150
|
+
scheduledAfterClose = selection
|
|
151
|
+
queueMicrotask(() => {
|
|
152
|
+
if (scheduledAfterClose !== selection) return
|
|
153
|
+
scheduledAfterClose = undefined
|
|
154
|
+
selection.callback(trigger)
|
|
155
|
+
})
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
<DropdownMenuGroup>
|
|
159
|
+
<For each={visible()}>
|
|
160
|
+
{(item) => (
|
|
161
|
+
<DropdownMenuItem
|
|
162
|
+
disabled={item.disabled}
|
|
163
|
+
onSelect={() => {
|
|
164
|
+
pendingAfterClose = item.onSelectAfterClose
|
|
165
|
+
? { callback: item.onSelectAfterClose }
|
|
166
|
+
: undefined
|
|
167
|
+
item.onSelect?.()
|
|
168
|
+
}}
|
|
169
|
+
shortcut={item.shortcut}
|
|
170
|
+
>
|
|
171
|
+
{item.icon ?? renderFallbackIcon(item.id)}
|
|
172
|
+
<span>{item.label}</span>
|
|
173
|
+
</DropdownMenuItem>
|
|
174
|
+
)}
|
|
175
|
+
</For>
|
|
176
|
+
</DropdownMenuGroup>
|
|
177
|
+
<DropdownMenuSeparator />
|
|
178
|
+
<DropdownMenuGroup>
|
|
179
|
+
<DropdownMenuItem
|
|
180
|
+
disabled={local.busy}
|
|
181
|
+
onSelect={() => (local.authenticated ? local.onSignOut?.() : local.onSignIn?.())}
|
|
182
|
+
>
|
|
183
|
+
<Show when={local.authenticated} fallback={<LogIn aria-hidden="true" />}>
|
|
184
|
+
<LogOut aria-hidden="true" />
|
|
185
|
+
</Show>
|
|
186
|
+
<span>{local.authenticated ? 'Sign out' : 'Sign in'}</span>
|
|
187
|
+
</DropdownMenuItem>
|
|
188
|
+
</DropdownMenuGroup>
|
|
189
|
+
</DropdownMenuContent>
|
|
190
|
+
</DropdownMenu>
|
|
191
|
+
</Tooltip>
|
|
140
192
|
)
|
|
141
193
|
}
|
|
142
194
|
|
|
195
|
+
function AccountMenuButton(props: ComponentProps<typeof Button>) {
|
|
196
|
+
return <DropdownMenuTrigger as={Button} {...props} />
|
|
197
|
+
}
|
|
198
|
+
|
|
143
199
|
/**
|
|
144
200
|
* A known item id gets its conventional glyph, so a caller that only wants the
|
|
145
201
|
* standard menu does not have to import six icons to get it.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Show, splitProps, type ComponentProps, type JSX, type ValidComponent } from 'solid-js'
|
|
2
|
+
import { Button, type ButtonProps } from '../../ui/button'
|
|
3
|
+
import { Spinner } from '../../ui/spinner'
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ActionButton.
|
|
8
|
+
*
|
|
9
|
+
* A Button with optional explanatory tooltip and busy feedback. Use it for a
|
|
10
|
+
* simple action that needs these states; actions with product-specific rules
|
|
11
|
+
* should compose Button directly.
|
|
12
|
+
*
|
|
13
|
+
* The tooltip trigger renders the polymorphic Button itself. A disabled native
|
|
14
|
+
* control cannot receive pointer or keyboard events. When a tooltip is set,
|
|
15
|
+
* disabled actions therefore use focusable `aria-disabled` semantics and guard
|
|
16
|
+
* activation; without a tooltip, native buttons retain native disabling.
|
|
17
|
+
* Custom polymorphic components must forward aria, event, and href props for
|
|
18
|
+
* these guarantees to apply.
|
|
19
|
+
*/
|
|
20
|
+
export type ActionButtonProps<T extends ValidComponent = 'button'> = ButtonProps<T> & {
|
|
21
|
+
/** A short explanation, also shown on keyboard focus. The Button still needs its own name. */
|
|
22
|
+
tooltip?: string
|
|
23
|
+
tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
|
|
24
|
+
/** Disables the action and announces the busy label through a polite status region. */
|
|
25
|
+
busy?: boolean
|
|
26
|
+
/** The activity announced while busy. The Button's accessible name stays unchanged. */
|
|
27
|
+
busyLabel?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function guardedClick(handler: unknown, disabled: () => boolean) {
|
|
31
|
+
return (event: MouseEvent) => {
|
|
32
|
+
if (disabled()) {
|
|
33
|
+
event.preventDefault()
|
|
34
|
+
event.stopImmediatePropagation()
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(handler)) {
|
|
39
|
+
const [callback, data] = handler as [(data: unknown, event: MouseEvent) => void, unknown]
|
|
40
|
+
callback(data, event)
|
|
41
|
+
} else if (typeof handler === 'function') {
|
|
42
|
+
const callback = handler as (event: MouseEvent) => void
|
|
43
|
+
callback(event)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type ButtonTargetProps<T extends ValidComponent = 'button'> = Omit<ButtonProps<T>, 'as'> & {
|
|
49
|
+
actionAs?: T
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Adapter keeps TooltipTrigger's `as` separate from Button's polymorphic `as`. */
|
|
53
|
+
function ButtonTarget<T extends ValidComponent = 'button'>(props: ButtonTargetProps<T>) {
|
|
54
|
+
const [local, rest] = splitProps(props as ButtonTargetProps, ['actionAs'])
|
|
55
|
+
const PolymorphicButton = Button as (targetProps: ButtonProps<T>) => JSX.Element
|
|
56
|
+
return <PolymorphicButton as={(local.actionAs ?? 'button') as T} {...(rest as ButtonProps<T>)} />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ActionButton<T extends ValidComponent = 'button'>(props: ActionButtonProps<T>) {
|
|
60
|
+
const [local, rest] = splitProps(props as ActionButtonProps, [
|
|
61
|
+
'as',
|
|
62
|
+
'variant',
|
|
63
|
+
'size',
|
|
64
|
+
'class',
|
|
65
|
+
'children',
|
|
66
|
+
'disabled',
|
|
67
|
+
'aria-busy',
|
|
68
|
+
'tooltip',
|
|
69
|
+
'tooltipSide',
|
|
70
|
+
'busy',
|
|
71
|
+
'busyLabel',
|
|
72
|
+
])
|
|
73
|
+
const busy = () => local.busy ?? false
|
|
74
|
+
const disabled = () => busy() || !!local.disabled
|
|
75
|
+
const busyStatus = () => local.busyLabel ?? 'Working'
|
|
76
|
+
const onClickCapture = (rest as { onClickCapture?: unknown }).onClickCapture
|
|
77
|
+
const PolymorphicButton = Button as (buttonProps: ButtonProps<T>) => JSX.Element
|
|
78
|
+
const PolymorphicButtonTarget = ButtonTarget as (targetProps: ButtonTargetProps<T>) => JSX.Element
|
|
79
|
+
|
|
80
|
+
const buttonProps = () => ({
|
|
81
|
+
...rest,
|
|
82
|
+
variant: local.variant,
|
|
83
|
+
size: local.size,
|
|
84
|
+
class: [local.class, local.tooltip && disabled() && 'aria-disabled:pointer-events-auto']
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.join(' '),
|
|
87
|
+
disabled: disabled() && !local.tooltip,
|
|
88
|
+
'aria-disabled': disabled() ? true : rest['aria-disabled'],
|
|
89
|
+
tabIndex: local.as === 'a' && disabled() ? (local.tooltip ? 0 : -1) : rest.tabIndex,
|
|
90
|
+
role: local.as === 'a' && disabled() ? 'link' : rest.role,
|
|
91
|
+
href: local.as === 'a' && disabled() ? undefined : (rest as { href?: string }).href,
|
|
92
|
+
'aria-busy': busy() || local['aria-busy'],
|
|
93
|
+
onClick: guardedClick(rest.onClick, disabled),
|
|
94
|
+
onClickCapture: onClickCapture ? guardedClick(onClickCapture, disabled) : undefined,
|
|
95
|
+
'on:click': guardedClick((rest as Record<string, unknown>)['on:click'], disabled),
|
|
96
|
+
'oncapture:click': guardedClick((rest as Record<string, unknown>)['oncapture:click'], disabled),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const contents = () => (
|
|
100
|
+
<>
|
|
101
|
+
<Show when={busy()}>
|
|
102
|
+
<Spinner size="sm" label={false} aria-hidden="true" data-icon="inline-start" />
|
|
103
|
+
</Show>
|
|
104
|
+
{local.children}
|
|
105
|
+
</>
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<>
|
|
110
|
+
<Show
|
|
111
|
+
when={local.tooltip}
|
|
112
|
+
fallback={
|
|
113
|
+
<PolymorphicButton
|
|
114
|
+
as={(local.as ?? 'button') as T}
|
|
115
|
+
{...(buttonProps() as unknown as ButtonProps<T>)}
|
|
116
|
+
>
|
|
117
|
+
{contents()}
|
|
118
|
+
</PolymorphicButton>
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
<Tooltip>
|
|
122
|
+
<TooltipTrigger
|
|
123
|
+
as={PolymorphicButtonTarget}
|
|
124
|
+
{...(buttonProps() as unknown as ComponentProps<typeof PolymorphicButtonTarget>)}
|
|
125
|
+
actionAs={(local.as ?? 'button') as T}
|
|
126
|
+
>
|
|
127
|
+
{contents()}
|
|
128
|
+
</TooltipTrigger>
|
|
129
|
+
<TooltipContent side={local.tooltipSide}>{local.tooltip}</TooltipContent>
|
|
130
|
+
</Tooltip>
|
|
131
|
+
</Show>
|
|
132
|
+
<Show when={busy()}>
|
|
133
|
+
<span role="status" aria-live="polite" class="visually-hidden">
|
|
134
|
+
{busyStatus()}
|
|
135
|
+
</span>
|
|
136
|
+
</Show>
|
|
137
|
+
</>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { ComponentProps, JSX } from 'solid-js'
|
|
|
3
3
|
import { Show, splitProps } from 'solid-js'
|
|
4
4
|
import { cn } from '#lib/utils'
|
|
5
5
|
import { Button } from '../ui/button/button'
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip/tooltip'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* ThreadPanel.
|
|
@@ -26,6 +27,8 @@ export type ThreadPanelProps = ComponentProps<'aside'> & {
|
|
|
26
27
|
root?: JSX.Element
|
|
27
28
|
/** The composer for a reply. */
|
|
28
29
|
composer?: JSX.Element
|
|
30
|
+
/** Host actions, such as marking the thread unread, before the close control. */
|
|
31
|
+
headerActions?: JSX.Element
|
|
29
32
|
onClose?: () => void
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -36,6 +39,7 @@ export function ThreadPanel(props: ThreadPanelProps) {
|
|
|
36
39
|
'count',
|
|
37
40
|
'root',
|
|
38
41
|
'composer',
|
|
42
|
+
'headerActions',
|
|
39
43
|
'onClose',
|
|
40
44
|
'children',
|
|
41
45
|
])
|
|
@@ -45,7 +49,7 @@ export function ThreadPanel(props: ThreadPanelProps) {
|
|
|
45
49
|
data-slot="thread-panel"
|
|
46
50
|
aria-label={`Thread: ${local.label}`}
|
|
47
51
|
class={cn(
|
|
48
|
-
'bg-surface flex h-full w-96 shrink-0 flex-col border-s border-border',
|
|
52
|
+
'bg-surface flex h-full w-96 max-w-full shrink-0 flex-col border-s border-border',
|
|
49
53
|
local.class
|
|
50
54
|
)}
|
|
51
55
|
{...rest}
|
|
@@ -59,17 +63,23 @@ export function ThreadPanel(props: ThreadPanelProps) {
|
|
|
59
63
|
</span>
|
|
60
64
|
</Show>
|
|
61
65
|
</div>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
<div class="ms-auto flex shrink-0 items-center gap-1">
|
|
67
|
+
{local.headerActions}
|
|
68
|
+
<Show when={local.onClose}>
|
|
69
|
+
<Tooltip>
|
|
70
|
+
<TooltipTrigger
|
|
71
|
+
as={Button}
|
|
72
|
+
variant="ghost"
|
|
73
|
+
size="icon-sm"
|
|
74
|
+
aria-label="Close thread"
|
|
75
|
+
onClick={() => local.onClose?.()}
|
|
76
|
+
>
|
|
77
|
+
<X />
|
|
78
|
+
</TooltipTrigger>
|
|
79
|
+
<TooltipContent>Close thread</TooltipContent>
|
|
80
|
+
</Tooltip>
|
|
81
|
+
</Show>
|
|
82
|
+
</div>
|
|
73
83
|
</header>
|
|
74
84
|
|
|
75
85
|
<Show when={local.root}>
|
|
@@ -56,13 +56,19 @@ export function SidebarNavHeader(props: ComponentProps<'div'>) {
|
|
|
56
56
|
)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export
|
|
60
|
-
|
|
59
|
+
export type SidebarNavTitleProps<T extends ValidComponent = 'div'> = PolymorphicProps<
|
|
60
|
+
T,
|
|
61
|
+
{ class?: string }
|
|
62
|
+
>
|
|
63
|
+
|
|
64
|
+
export function SidebarNavTitle<T extends ValidComponent = 'div'>(props: SidebarNavTitleProps<T>) {
|
|
65
|
+
const [local, rest] = splitProps(props as SidebarNavTitleProps, ['class'])
|
|
61
66
|
return (
|
|
62
|
-
<
|
|
67
|
+
<Polymorphic
|
|
68
|
+
as="div"
|
|
63
69
|
data-slot="sidebar-nav-title"
|
|
64
70
|
class={cn('truncate text-sm font-semibold tracking-tight', local.class)}
|
|
65
|
-
{...rest}
|
|
71
|
+
{...(rest as ComponentProps<'div'>)}
|
|
66
72
|
/>
|
|
67
73
|
)
|
|
68
74
|
}
|
|
@@ -105,6 +111,8 @@ export function SidebarNavSection(
|
|
|
105
111
|
count?: number
|
|
106
112
|
/** A control at the trailing edge of the heading row. */
|
|
107
113
|
action?: JSX.Element
|
|
114
|
+
/** Semantic heading element for a host's section hierarchy. */
|
|
115
|
+
headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
108
116
|
}
|
|
109
117
|
) {
|
|
110
118
|
const [local, rest] = splitProps(props, [
|
|
@@ -114,6 +122,7 @@ export function SidebarNavSection(
|
|
|
114
122
|
'defaultOpen',
|
|
115
123
|
'count',
|
|
116
124
|
'action',
|
|
125
|
+
'headingAs',
|
|
117
126
|
'children',
|
|
118
127
|
])
|
|
119
128
|
const [open, setOpen] = createSignal(local.defaultOpen ?? true)
|
|
@@ -138,25 +147,24 @@ export function SidebarNavSection(
|
|
|
138
147
|
return (
|
|
139
148
|
<div data-slot="sidebar-nav-section" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>
|
|
140
149
|
<div class="group/section-header flex items-center gap-1 rounded-md px-2 py-1.5 hover:bg-sidebar-accent/60">
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{heading}
|
|
146
|
-
</div>
|
|
147
|
-
}
|
|
150
|
+
<Polymorphic
|
|
151
|
+
as={local.headingAs ?? 'div'}
|
|
152
|
+
aria-label={local.headingAs ? local.label : undefined}
|
|
153
|
+
class="flex min-w-0 flex-1 items-center gap-1.5 text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase"
|
|
148
154
|
>
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
<Show when={local.collapsible} fallback={heading}>
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
aria-expanded={open()}
|
|
159
|
+
onClick={() => setOpen((value) => !value)}
|
|
160
|
+
class="flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
|
|
161
|
+
>
|
|
162
|
+
{heading}
|
|
163
|
+
</button>
|
|
164
|
+
</Show>
|
|
165
|
+
</Polymorphic>
|
|
158
166
|
<Show when={local.action}>
|
|
159
|
-
<span class="shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100">
|
|
167
|
+
<span class="shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100">
|
|
160
168
|
{local.action}
|
|
161
169
|
</span>
|
|
162
170
|
</Show>
|