@adea-ai/ui 0.88.0 → 0.89.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/components/composites/settings/index.d.ts +2 -0
- package/dist/components/composites/settings/index.d.ts.map +1 -1
- package/dist/components/composites/settings/index.js +3 -1
- package/dist/components/composites/settings/settings-layout.d.ts +28 -0
- package/dist/components/composites/settings/settings-layout.d.ts.map +1 -0
- package/dist/components/composites/settings/settings-layout.js +62 -0
- package/dist/components/composites/settings/settings-layout.js.map +1 -0
- package/dist/components/composites/settings/settings-navigation.d.ts +41 -0
- package/dist/components/composites/settings/settings-navigation.d.ts.map +1 -0
- package/dist/components/composites/settings/settings-navigation.js +102 -0
- package/dist/components/composites/settings/settings-navigation.js.map +1 -0
- package/dist/components/layout/top-bar/top-bar.d.ts +3 -1
- package/dist/components/layout/top-bar/top-bar.d.ts.map +1 -1
- package/dist/components/layout/top-bar/top-bar.js +4 -4
- package/dist/components/layout/top-bar/top-bar.js.map +1 -1
- package/dist/components/ui/tabs/tabs.d.ts.map +1 -1
- package/dist/components/ui/tabs/tabs.js +70 -13
- package/dist/components/ui/tabs/tabs.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/r/registry.json +12 -0
- package/dist/r/settings.json +12 -0
- package/dist/r/src/components/composites/settings/index.ts +7 -0
- package/dist/r/src/components/composites/settings/settings-layout.tsx +65 -0
- package/dist/r/src/components/composites/settings/settings-navigation.tsx +135 -0
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +9 -5
- package/dist/r/src/components/ui/tabs/tabs.tsx +80 -10
- package/package.json +2 -1
- package/registry.json +12 -0
- package/src/components/composites/settings/index.ts +7 -0
- package/src/components/composites/settings/settings-layout.tsx +65 -0
- package/src/components/composites/settings/settings-navigation.tsx +135 -0
- package/src/components/layout/top-bar/top-bar.tsx +9 -5
- package/src/components/ui/tabs/tabs.tsx +80 -10
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js'
|
|
2
|
+
import { createEffect, createUniqueId, onCleanup, splitProps } from 'solid-js'
|
|
3
|
+
import { cn } from '#lib/utils'
|
|
4
|
+
import { sidebarNavItemClass, sidebarNavItemStateClass } from '../../layout/sidebar-nav/sidebar-nav'
|
|
5
|
+
import { TabsList, TabsTrigger, type TabsListProps } from '../../ui/tabs'
|
|
6
|
+
|
|
7
|
+
/** One host-owned destination in a settings tab list. Values must be unique. */
|
|
8
|
+
export type SettingsNavigationItem = {
|
|
9
|
+
value: string
|
|
10
|
+
label: string
|
|
11
|
+
icon?: JSX.Element
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A visible, accessibly named group of settings destinations. */
|
|
16
|
+
export type SettingsNavigationGroup = {
|
|
17
|
+
label: string
|
|
18
|
+
items: readonly SettingsNavigationItem[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SettingsNavigationProps = Omit<
|
|
22
|
+
TabsListProps,
|
|
23
|
+
'appearance' | 'children' | 'orientation'
|
|
24
|
+
> & {
|
|
25
|
+
/** Groups are presentation only; the host owns the tab values and panels. */
|
|
26
|
+
groups: readonly SettingsNavigationGroup[]
|
|
27
|
+
/** The same controlled value supplied to the enclosing `Tabs`. */
|
|
28
|
+
value: string
|
|
29
|
+
/** Called when the already-selected destination is activated again. */
|
|
30
|
+
onReselect?: (value: string) => void
|
|
31
|
+
/** Reveal the selected row on value changes and window resizes. Defaults to nearest scroll. */
|
|
32
|
+
revealSelected?: boolean | ((trigger: HTMLButtonElement | undefined) => void)
|
|
33
|
+
/** Every tab list needs a label independent of its visible group headings. */
|
|
34
|
+
'aria-label': string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* SettingsNavigation.
|
|
39
|
+
*
|
|
40
|
+
* A grouped vertical tab list for sibling settings panels. It composes the
|
|
41
|
+
* shared Tabs behaviour with the shared sidebar row recipe; the host supplies
|
|
42
|
+
* the controlled value, panel content, icons and any persistence or routing.
|
|
43
|
+
* Use ordinary tabs for a short, ungrouped row of view choices.
|
|
44
|
+
*
|
|
45
|
+
* Kobalte owns roving focus, arrow keys, selection and tab/panel ARIA. This
|
|
46
|
+
* component adds no URL, storage or host-specific state. Set
|
|
47
|
+
* `revealSelected={false}` when the surrounding layout does not use an internal
|
|
48
|
+
* scrolling viewport.
|
|
49
|
+
*/
|
|
50
|
+
export function SettingsNavigation(props: SettingsNavigationProps) {
|
|
51
|
+
const [local, rest] = splitProps(props, [
|
|
52
|
+
'class',
|
|
53
|
+
'aria-label',
|
|
54
|
+
'groups',
|
|
55
|
+
'value',
|
|
56
|
+
'onReselect',
|
|
57
|
+
'revealSelected',
|
|
58
|
+
])
|
|
59
|
+
const triggers = new Map<string, HTMLButtonElement>()
|
|
60
|
+
const groupIdPrefix = `settings-navigation-${createUniqueId()}`
|
|
61
|
+
|
|
62
|
+
createEffect(() => {
|
|
63
|
+
const value = local.value
|
|
64
|
+
const reveal = local.revealSelected
|
|
65
|
+
if (reveal === false) return
|
|
66
|
+
|
|
67
|
+
const revealCurrent = () => {
|
|
68
|
+
const trigger = triggers.get(value)
|
|
69
|
+
if (typeof reveal === 'function') reveal(trigger)
|
|
70
|
+
else trigger?.scrollIntoView({ block: 'nearest', inline: 'nearest' })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
revealCurrent()
|
|
74
|
+
window.addEventListener('resize', revealCurrent)
|
|
75
|
+
onCleanup(() => window.removeEventListener('resize', revealCurrent))
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<TabsList
|
|
80
|
+
data-slot="settings-navigation"
|
|
81
|
+
aria-label={local['aria-label']}
|
|
82
|
+
orientation="vertical"
|
|
83
|
+
class={cn(
|
|
84
|
+
'flex max-h-full min-h-0 min-w-0 shrink-0 flex-col items-stretch justify-start gap-2 overflow-x-hidden overflow-y-auto px-2 py-2',
|
|
85
|
+
local.class
|
|
86
|
+
)}
|
|
87
|
+
{...rest}
|
|
88
|
+
>
|
|
89
|
+
{local.groups.map((group, groupIndex) => {
|
|
90
|
+
const groupId = `${groupIdPrefix}-group-${groupIndex}`
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
role="none"
|
|
94
|
+
data-slot="settings-navigation-group"
|
|
95
|
+
class="flex min-w-0 flex-col gap-0.5"
|
|
96
|
+
>
|
|
97
|
+
<span id={groupId} class="sr-only">
|
|
98
|
+
{group.label}
|
|
99
|
+
</span>
|
|
100
|
+
<span
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
class="px-2 py-1.5 text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase"
|
|
103
|
+
>
|
|
104
|
+
{group.label}
|
|
105
|
+
</span>
|
|
106
|
+
{group.items.map((item) => (
|
|
107
|
+
<TabsTrigger
|
|
108
|
+
data-slot="settings-navigation-trigger"
|
|
109
|
+
orientation="vertical"
|
|
110
|
+
aria-describedby={groupId}
|
|
111
|
+
value={item.value}
|
|
112
|
+
disabled={item.disabled}
|
|
113
|
+
ref={(element: HTMLButtonElement | undefined) => {
|
|
114
|
+
if (element) triggers.set(item.value, element)
|
|
115
|
+
else triggers.delete(item.value)
|
|
116
|
+
}}
|
|
117
|
+
onClick={() => {
|
|
118
|
+
if (local.value === item.value) local.onReselect?.(item.value)
|
|
119
|
+
}}
|
|
120
|
+
class={cn(
|
|
121
|
+
sidebarNavItemClass,
|
|
122
|
+
sidebarNavItemStateClass.idle,
|
|
123
|
+
'h-row-lg w-full justify-start border-0 px-3 py-2 text-left data-[selected]:bg-primary-subtle data-[selected]:text-foreground'
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{item.icon}
|
|
127
|
+
<span class="min-w-0 truncate">{item.label}</span>
|
|
128
|
+
</TabsTrigger>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
})}
|
|
133
|
+
</TabsList>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
@@ -41,7 +41,7 @@ export function TopBar(props: TopBarProps) {
|
|
|
41
41
|
<header
|
|
42
42
|
data-slot="top-bar"
|
|
43
43
|
class={cn(
|
|
44
|
-
'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[
|
|
44
|
+
'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 border-b border-border bg-card text-card-foreground px-3 md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',
|
|
45
45
|
local.glass && 'glass-panel',
|
|
46
46
|
local.draggable && 'window-drag',
|
|
47
47
|
local.macosInset && 'window-inset-macos',
|
|
@@ -61,7 +61,7 @@ export function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' |
|
|
|
61
61
|
<div
|
|
62
62
|
data-slot="top-bar-section"
|
|
63
63
|
class={cn(
|
|
64
|
-
'flex min-w-0 items-center gap-2 overflow-hidden',
|
|
64
|
+
'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',
|
|
65
65
|
local.align === 'end' && 'justify-end',
|
|
66
66
|
local.align !== 'end' && 'justify-start',
|
|
67
67
|
local.class
|
|
@@ -71,12 +71,16 @@ export function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' |
|
|
|
71
71
|
)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
export function TopBarTitle(props: ComponentProps<'div'>) {
|
|
75
|
-
const [local, rest] = splitProps(props, ['class'])
|
|
74
|
+
export function TopBarTitle(props: ComponentProps<'div'> & { align?: 'start' | 'center' }) {
|
|
75
|
+
const [local, rest] = splitProps(props, ['class', 'align'])
|
|
76
76
|
return (
|
|
77
77
|
<div
|
|
78
78
|
data-slot="top-bar-title"
|
|
79
|
-
class={cn(
|
|
79
|
+
class={cn(
|
|
80
|
+
'truncate text-sm font-medium',
|
|
81
|
+
local.align === 'center' && 'text-center',
|
|
82
|
+
local.class
|
|
83
|
+
)}
|
|
80
84
|
{...rest}
|
|
81
85
|
/>
|
|
82
86
|
)
|
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
import { Tabs as KobalteTabs } from '@kobalte/core/tabs'
|
|
2
|
-
import type { ComponentProps } from 'solid-js'
|
|
3
|
-
import {
|
|
2
|
+
import type { Accessor, ComponentProps } from 'solid-js'
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
createRenderEffect,
|
|
6
|
+
createSignal,
|
|
7
|
+
createUniqueId,
|
|
8
|
+
onCleanup,
|
|
9
|
+
splitProps,
|
|
10
|
+
useContext,
|
|
11
|
+
} from 'solid-js'
|
|
4
12
|
import { cn } from '#lib/utils'
|
|
5
13
|
|
|
14
|
+
type TabsIdContextValue = {
|
|
15
|
+
id: Accessor<string>
|
|
16
|
+
triggerIds: Accessor<ReadonlyMap<string, string>>
|
|
17
|
+
registerTriggerId: (value: string, id: string) => void
|
|
18
|
+
unregisterTriggerId: (value: string, id: string) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const TabsIdContext = createContext<TabsIdContextValue>()
|
|
22
|
+
|
|
23
|
+
function useTabsId() {
|
|
24
|
+
const tabs = useContext(TabsIdContext)
|
|
25
|
+
if (!tabs) throw new Error('[adea-ui]: Tabs parts must be used within Tabs.')
|
|
26
|
+
return tabs
|
|
27
|
+
}
|
|
28
|
+
|
|
6
29
|
/**
|
|
7
30
|
* Tabs.
|
|
8
31
|
*
|
|
@@ -14,12 +37,37 @@ import { cn } from '#lib/utils'
|
|
|
14
37
|
* where a full-width underline would collide with the panel's own header rule.
|
|
15
38
|
*/
|
|
16
39
|
export function Tabs(props: ComponentProps<typeof KobalteTabs>) {
|
|
17
|
-
const [local, rest] = splitProps(props, ['class'])
|
|
40
|
+
const [local, rest] = splitProps(props, ['class', 'id'])
|
|
41
|
+
const generatedId = `tabs-${createUniqueId()}`
|
|
42
|
+
const id = () => local.id ?? generatedId
|
|
43
|
+
const [triggerIds, setTriggerIds] = createSignal<ReadonlyMap<string, string>>(new Map())
|
|
44
|
+
const tabs: TabsIdContextValue = {
|
|
45
|
+
id,
|
|
46
|
+
triggerIds,
|
|
47
|
+
registerTriggerId: (value, triggerId) => {
|
|
48
|
+
setTriggerIds((current) => new Map(current).set(value, triggerId))
|
|
49
|
+
},
|
|
50
|
+
unregisterTriggerId: (value, triggerId) => {
|
|
51
|
+
setTriggerIds((current) => {
|
|
52
|
+
if (current.get(value) !== triggerId) return current
|
|
53
|
+
const next = new Map(current)
|
|
54
|
+
next.delete(value)
|
|
55
|
+
return next
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
18
60
|
return (
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
<TabsIdContext.Provider value={tabs}>
|
|
62
|
+
<KobalteTabs
|
|
63
|
+
id={id()}
|
|
64
|
+
class={cn(
|
|
65
|
+
'flex min-h-0 min-w-0 flex-col gap-2 data-[orientation=vertical]:flex-row',
|
|
66
|
+
local.class
|
|
67
|
+
)}
|
|
68
|
+
{...rest}
|
|
69
|
+
/>
|
|
70
|
+
</TabsIdContext.Provider>
|
|
23
71
|
)
|
|
24
72
|
}
|
|
25
73
|
|
|
@@ -50,10 +98,21 @@ export type TabsTriggerProps = ComponentProps<typeof KobalteTabs.Trigger> & {
|
|
|
50
98
|
}
|
|
51
99
|
|
|
52
100
|
export function TabsTrigger(props: TabsTriggerProps) {
|
|
53
|
-
const [local, rest] = splitProps(props, ['class', 'appearance'])
|
|
101
|
+
const [local, rest] = splitProps(props, ['class', 'appearance', 'id'])
|
|
102
|
+
const tabs = useTabsId()
|
|
103
|
+
const triggerId = () => local.id ?? `${tabs.id()}-trigger-${rest.value}`
|
|
104
|
+
|
|
105
|
+
// Register before panels render, including SSR where createEffect is skipped.
|
|
106
|
+
createRenderEffect(() => {
|
|
107
|
+
const value = rest.value
|
|
108
|
+
const id = triggerId()
|
|
109
|
+
tabs.registerTriggerId(value, id)
|
|
110
|
+
onCleanup(() => tabs.unregisterTriggerId(value, id))
|
|
111
|
+
})
|
|
54
112
|
|
|
55
113
|
return (
|
|
56
114
|
<KobalteTabs.Trigger
|
|
115
|
+
id={triggerId()}
|
|
57
116
|
class={cn(
|
|
58
117
|
'inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium',
|
|
59
118
|
'transition-colors ease-out outline-none select-none',
|
|
@@ -72,6 +131,17 @@ export function TabsTrigger(props: TabsTriggerProps) {
|
|
|
72
131
|
}
|
|
73
132
|
|
|
74
133
|
export function TabsContent(props: ComponentProps<typeof KobalteTabs.Content>) {
|
|
75
|
-
const [local, rest] = splitProps(props, ['class'])
|
|
76
|
-
|
|
134
|
+
const [local, rest] = splitProps(props, ['class', 'aria-labelledby'])
|
|
135
|
+
const tabs = useTabsId()
|
|
136
|
+
return (
|
|
137
|
+
<KobalteTabs.Content
|
|
138
|
+
aria-labelledby={
|
|
139
|
+
local['aria-labelledby'] ??
|
|
140
|
+
tabs.triggerIds().get(rest.value) ??
|
|
141
|
+
`${tabs.id()}-trigger-${rest.value}`
|
|
142
|
+
}
|
|
143
|
+
class={cn('min-h-0 min-w-0 flex-1 outline-none', local.class)}
|
|
144
|
+
{...rest}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
77
147
|
}
|