@opencode/ui 0.0.0-dev-20607 → 0.0.0-dev-20609
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/package.json +1 -1
- package/src/actions/button/button.tsx +1 -0
- package/src/actions/icon-button/icon-button.tsx +1 -0
- package/src/actions/split-button/split-button.tsx +3 -0
- package/src/components/animated-number.tsx +13 -0
- package/src/components/app-icon.tsx +2 -0
- package/src/components/card.tsx +21 -0
- package/src/components/collapsible.tsx +1 -0
- package/src/components/context-menu.tsx +34 -0
- package/src/components/dock-surface.tsx +3 -0
- package/src/components/file-icon.tsx +12 -0
- package/src/components/image-preview.tsx +1 -0
- package/src/components/list.tsx +32 -0
- package/src/components/motion-spring.tsx +5 -0
- package/src/components/popover.tsx +12 -0
- package/src/components/provider-icon.tsx +1 -0
- package/src/components/resize-handle.tsx +8 -0
- package/src/components/scroll-view.tsx +39 -0
- package/src/components/spinner.tsx +2 -0
- package/src/components/text-field.tsx +6 -0
- package/src/components/text-reveal.tsx +19 -0
- package/src/components/text-strikethrough.tsx +8 -0
- package/src/components/typewriter.tsx +4 -0
- package/src/context/dialog.tsx +12 -0
- package/src/context/file.tsx +1 -0
- package/src/context/helper.tsx +4 -0
- package/src/context/i18n.tsx +20 -0
- package/src/context/index.ts +3 -0
- package/src/context/marked-base.tsx +7 -0
- package/src/context/marked-parser.tsx +7 -0
- package/src/context/marked.tsx +3 -0
- package/src/context/worker-pool.tsx +2 -0
- package/src/data-display/avatar/avatar.tsx +4 -0
- package/src/data-display/badge/badge.tsx +1 -0
- package/src/data-display/diff-changes/diff-changes.tsx +2 -0
- package/src/data-display/keybind/keybind.tsx +1 -0
- package/src/data-display/line-comment/line-comment.tsx +22 -0
- package/src/data-display/project-avatar/project-avatar.tsx +10 -0
- package/src/feedback/loader/loader.tsx +1 -0
- package/src/feedback/toast/description.ts +1 -0
- package/src/forms/checkbox/checkbox.tsx +1 -0
- package/src/forms/field/field.tsx +8 -0
- package/src/forms/inline-input/inline-input.tsx +4 -0
- package/src/forms/radio/radio.tsx +2 -0
- package/src/forms/select/select.tsx +11 -0
- package/src/forms/switch/switch.tsx +1 -0
- package/src/forms/text-input/text-input.tsx +3 -0
- package/src/hooks/create-auto-scroll.tsx +34 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-filtered-list.tsx +14 -0
- package/src/i18n/no.ts +1 -0
- package/src/layout/divider/divider.tsx +1 -0
- package/src/navigation/menu/menu.tsx +13 -0
- package/src/navigation/segmented-control/segmented-control.tsx +16 -0
- package/src/navigation/tab-state-indicator.tsx +1 -0
- package/src/navigation/tabs/tabs.tsx +18 -0
- package/src/overlays/dialog/dialog.tsx +3 -0
- package/src/overlays/tooltip/tooltip.tsx +15 -0
- package/src/storybook/scaffold.tsx +3 -0
- package/src/theme/color.ts +27 -0
- package/src/theme/context.tsx +58 -0
- package/src/theme/default-themes.ts +35 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/loader.ts +11 -0
- package/src/theme/resolve.ts +28 -0
- package/src/theme/v2/foreground.ts +8 -0
- package/src/theme/v2/resolve.ts +10 -0
- package/src/typography/text-shimmer/text-shimmer.tsx +1 -0
|
@@ -53,10 +53,12 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
53
53
|
|
|
54
54
|
const isAuto = (el: HTMLElement) => {
|
|
55
55
|
const a = auto
|
|
56
|
+
|
|
56
57
|
if (!a) return false
|
|
57
58
|
|
|
58
59
|
if (Date.now() - a.time > 1500) {
|
|
59
60
|
auto = undefined
|
|
61
|
+
|
|
60
62
|
return false
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -65,10 +67,13 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
65
67
|
|
|
66
68
|
const scrollToBottomNow = (behavior: ScrollBehavior) => {
|
|
67
69
|
const el = store.scrollRef
|
|
70
|
+
|
|
68
71
|
if (!el) return
|
|
69
72
|
markAuto(el)
|
|
73
|
+
|
|
70
74
|
if (behavior === "smooth") {
|
|
71
75
|
el.scrollTo({ top: el.scrollHeight, behavior })
|
|
76
|
+
|
|
72
77
|
return
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -82,13 +87,16 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
82
87
|
if (force && store.userScrolled) setStore("userScrolled", false)
|
|
83
88
|
|
|
84
89
|
const el = store.scrollRef
|
|
90
|
+
|
|
85
91
|
if (!el) return
|
|
86
92
|
|
|
87
93
|
if (!force && store.userScrolled) return
|
|
88
94
|
|
|
89
95
|
const distance = distanceFromBottom(el)
|
|
96
|
+
|
|
90
97
|
if (distance < 2) {
|
|
91
98
|
markAuto(el)
|
|
99
|
+
|
|
92
100
|
return
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -99,11 +107,15 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
99
107
|
|
|
100
108
|
const stop = () => {
|
|
101
109
|
const el = store.scrollRef
|
|
110
|
+
|
|
102
111
|
if (!el) return
|
|
112
|
+
|
|
103
113
|
if (!canScroll(el)) {
|
|
104
114
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
115
|
+
|
|
105
116
|
return
|
|
106
117
|
}
|
|
118
|
+
|
|
107
119
|
if (store.userScrolled) return
|
|
108
120
|
|
|
109
121
|
setStore("userScrolled", true)
|
|
@@ -118,27 +130,32 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
118
130
|
const el = store.scrollRef
|
|
119
131
|
const target = e.target instanceof Element ? e.target : undefined
|
|
120
132
|
const nested = target?.closest("[data-scrollable]")
|
|
133
|
+
|
|
121
134
|
if (el && nested && nested !== el) return
|
|
122
135
|
stop()
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
const handleScroll = () => {
|
|
126
139
|
const el = store.scrollRef
|
|
140
|
+
|
|
127
141
|
if (!el) return
|
|
128
142
|
|
|
129
143
|
if (!canScroll(el)) {
|
|
130
144
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
145
|
+
|
|
131
146
|
return
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
if (distanceFromBottom(el) < threshold()) {
|
|
135
150
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
151
|
+
|
|
136
152
|
return
|
|
137
153
|
}
|
|
138
154
|
|
|
139
155
|
// Ignore scroll events triggered by our own scrollToBottom calls.
|
|
140
156
|
if (!store.userScrolled && isAuto(el)) {
|
|
141
157
|
scrollToBottom(false)
|
|
158
|
+
|
|
142
159
|
return
|
|
143
160
|
}
|
|
144
161
|
|
|
@@ -148,6 +165,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
148
165
|
const handleInteraction = () => {
|
|
149
166
|
if (!active()) return
|
|
150
167
|
const selection = window.getSelection()
|
|
168
|
+
|
|
151
169
|
if (selection && selection.toString().length > 0) {
|
|
152
170
|
stop()
|
|
153
171
|
}
|
|
@@ -158,11 +176,13 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
158
176
|
|
|
159
177
|
if (mode === "none") {
|
|
160
178
|
el.style.overflowAnchor = "none"
|
|
179
|
+
|
|
161
180
|
return
|
|
162
181
|
}
|
|
163
182
|
|
|
164
183
|
if (mode === "auto") {
|
|
165
184
|
el.style.overflowAnchor = "auto"
|
|
185
|
+
|
|
166
186
|
return
|
|
167
187
|
}
|
|
168
188
|
|
|
@@ -173,11 +193,15 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
173
193
|
() => store.contentRef,
|
|
174
194
|
() => {
|
|
175
195
|
const el = store.scrollRef
|
|
196
|
+
|
|
176
197
|
if (el && !canScroll(el)) {
|
|
177
198
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
199
|
+
|
|
178
200
|
return
|
|
179
201
|
}
|
|
202
|
+
|
|
180
203
|
if (!active()) return
|
|
204
|
+
|
|
181
205
|
if (store.userScrolled) return
|
|
182
206
|
// ResizeObserver fires after layout, before paint.
|
|
183
207
|
// Keep the bottom locked in the same frame to avoid visible
|
|
@@ -188,20 +212,26 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
188
212
|
|
|
189
213
|
createEffect(() => {
|
|
190
214
|
const el = store.scrollRef
|
|
215
|
+
|
|
191
216
|
if (!el) return
|
|
192
217
|
const root = el.closest("main")
|
|
218
|
+
|
|
193
219
|
if (!root) return
|
|
194
220
|
let connected = el.isConnected
|
|
195
221
|
const contains = (node: Node) => node === el || (node instanceof Element && node.contains(el))
|
|
222
|
+
|
|
196
223
|
const observer = new MutationObserver((records) => {
|
|
197
224
|
records.forEach((record) => {
|
|
198
225
|
if ([...record.removedNodes].some(contains)) connected = false
|
|
226
|
+
|
|
199
227
|
if (!record.addedNodes.length || ![...record.addedNodes].some(contains)) return
|
|
200
228
|
const reattached = !connected
|
|
201
229
|
connected = true
|
|
230
|
+
|
|
202
231
|
if (reattached) scrollToBottom(false)
|
|
203
232
|
})
|
|
204
233
|
})
|
|
234
|
+
|
|
205
235
|
observer.observe(root, { childList: true })
|
|
206
236
|
onCleanup(() => observer.disconnect())
|
|
207
237
|
})
|
|
@@ -209,11 +239,13 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
209
239
|
createEffect(
|
|
210
240
|
on(options.working, (working: boolean) => {
|
|
211
241
|
settling = false
|
|
242
|
+
|
|
212
243
|
if (settleTimer) clearTimeout(settleTimer)
|
|
213
244
|
settleTimer = undefined
|
|
214
245
|
|
|
215
246
|
if (working) {
|
|
216
247
|
if (!store.userScrolled) scrollToBottom(true)
|
|
248
|
+
|
|
217
249
|
return
|
|
218
250
|
}
|
|
219
251
|
|
|
@@ -229,6 +261,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
229
261
|
// update overflow anchoring once the element exists.
|
|
230
262
|
store.userScrolled
|
|
231
263
|
const el = store.scrollRef
|
|
264
|
+
|
|
232
265
|
if (!el) return
|
|
233
266
|
updateOverflowAnchor(el)
|
|
234
267
|
})
|
|
@@ -237,6 +270,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
237
270
|
|
|
238
271
|
onCleanup(() => {
|
|
239
272
|
if (settleTimer) clearTimeout(settleTimer)
|
|
273
|
+
|
|
240
274
|
if (autoTimer) clearTimeout(autoTimer)
|
|
241
275
|
})
|
|
242
276
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
21
21
|
const [store, setStore] = createStore<{ filter: string }>({ filter: "" })
|
|
22
22
|
|
|
23
23
|
type Group = { category: string; items: [T, ...T[]] }
|
|
24
|
+
|
|
24
25
|
const empty: Group[] = []
|
|
25
26
|
|
|
26
27
|
const [grouped, { refetch }] = createResource(
|
|
@@ -32,6 +33,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
32
33
|
const query = filter ?? ""
|
|
33
34
|
const needle = query.toLowerCase()
|
|
34
35
|
const all = (await Promise.resolve(items)) || []
|
|
36
|
+
|
|
35
37
|
const result = pipe(
|
|
36
38
|
all,
|
|
37
39
|
(x) => {
|
|
@@ -39,10 +41,12 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
39
41
|
const skipFilter = props.skipFilter
|
|
40
42
|
const filterable = skipFilter ? x.filter((item) => !skipFilter(item)) : x
|
|
41
43
|
const skipped = skipFilter ? x.filter(skipFilter) : []
|
|
44
|
+
|
|
42
45
|
const filtered =
|
|
43
46
|
!props.filterKeys && Array.isArray(filterable) && filterable.every((e) => typeof e === "string")
|
|
44
47
|
? (fuzzysort.go(needle, filterable).map((x) => x.target) as T[])
|
|
45
48
|
: fuzzysort.go(needle, filterable, { keys: props.filterKeys! }).map((x) => x.obj)
|
|
49
|
+
|
|
46
50
|
return skipped.length ? [...filtered, ...skipped] : filtered
|
|
47
51
|
},
|
|
48
52
|
groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
|
|
@@ -50,6 +54,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
50
54
|
map(([k, v]) => ({ category: k, items: props.sortBy ? v.sort(props.sortBy) : v })),
|
|
51
55
|
(groups) => (props.sortGroupsBy ? groups.sort(props.sortGroupsBy) : groups),
|
|
52
56
|
)
|
|
57
|
+
|
|
53
58
|
return result
|
|
54
59
|
},
|
|
55
60
|
{ initialValue: empty },
|
|
@@ -64,10 +69,13 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
64
69
|
|
|
65
70
|
function initialActive() {
|
|
66
71
|
if (props.noInitialSelection) return ""
|
|
72
|
+
|
|
67
73
|
if (props.current) return props.key(props.current)
|
|
68
74
|
|
|
69
75
|
const items = flat()
|
|
76
|
+
|
|
70
77
|
if (items.length === 0) return ""
|
|
78
|
+
|
|
71
79
|
return props.key(items[0])
|
|
72
80
|
}
|
|
73
81
|
|
|
@@ -80,9 +88,12 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
80
88
|
const reset = () => {
|
|
81
89
|
if (props.noInitialSelection) {
|
|
82
90
|
list.setActive("")
|
|
91
|
+
|
|
83
92
|
return
|
|
84
93
|
}
|
|
94
|
+
|
|
85
95
|
const all = flat()
|
|
96
|
+
|
|
86
97
|
if (all.length === 0) return
|
|
87
98
|
list.setActive(props.key(all[0]))
|
|
88
99
|
}
|
|
@@ -92,14 +103,17 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
92
103
|
event.preventDefault()
|
|
93
104
|
const selectedIndex = flat().findIndex((x) => props.key(x) === list.active())
|
|
94
105
|
const selected = flat()[selectedIndex]
|
|
106
|
+
|
|
95
107
|
if (selected) props.onSelect?.(selected, selectedIndex)
|
|
96
108
|
} else if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
|
|
97
109
|
if (event.key === "n" || event.key === "p") {
|
|
98
110
|
event.preventDefault()
|
|
111
|
+
|
|
99
112
|
const navEvent = new KeyboardEvent("keydown", {
|
|
100
113
|
key: event.key === "n" ? "ArrowDown" : "ArrowUp",
|
|
101
114
|
bubbles: true,
|
|
102
115
|
})
|
|
116
|
+
|
|
103
117
|
list.onKeyDown(navEvent)
|
|
104
118
|
}
|
|
105
119
|
} else {
|
package/src/i18n/no.ts
CHANGED
|
@@ -47,7 +47,9 @@ const MenuAppearanceContext = createContext<{ appearance: Accessor<MenuAppearanc
|
|
|
47
47
|
|
|
48
48
|
function useMenuContext() {
|
|
49
49
|
const ctx = useContext(MenuAppearanceContext)
|
|
50
|
+
|
|
50
51
|
if (!ctx) throw new Error("Menu components must be used inside Menu or Menu.Context")
|
|
52
|
+
|
|
51
53
|
return ctx
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -76,6 +78,7 @@ export interface MenuItemProps extends ComponentProps<typeof DropdownMenu.Item>
|
|
|
76
78
|
function MenuItem(props: ParentProps<MenuItemProps>) {
|
|
77
79
|
const ctx = useMenuContext()
|
|
78
80
|
const [s, r] = splitProps(props, ["class", "classList", "children", "shortcut", "badge"])
|
|
81
|
+
|
|
79
82
|
return (
|
|
80
83
|
<DropdownMenu.Item
|
|
81
84
|
{...r}
|
|
@@ -98,6 +101,7 @@ export interface MenuCheckboxItemProps extends ComponentProps<typeof DropdownMen
|
|
|
98
101
|
function MenuCheckboxItem(props: ParentProps<MenuCheckboxItemProps>) {
|
|
99
102
|
const ctx = useMenuContext()
|
|
100
103
|
const [s, r] = splitProps(props, ["class", "classList", "children", "shortcut", "badge"])
|
|
104
|
+
|
|
101
105
|
return (
|
|
102
106
|
<DropdownMenu.CheckboxItem
|
|
103
107
|
{...r}
|
|
@@ -128,6 +132,7 @@ export interface MenuRadioItemProps extends ComponentProps<typeof DropdownMenu.R
|
|
|
128
132
|
function MenuRadioItem(props: ParentProps<MenuRadioItemProps>) {
|
|
129
133
|
const ctx = useMenuContext()
|
|
130
134
|
const [s, r] = splitProps(props, ["class", "classList", "children", "shortcut", "badge"])
|
|
135
|
+
|
|
131
136
|
return (
|
|
132
137
|
<DropdownMenu.RadioItem
|
|
133
138
|
{...r}
|
|
@@ -178,6 +183,7 @@ function MenuSub(props: MenuSubProps) {
|
|
|
178
183
|
function MenuSubTrigger(props: ParentProps<MenuSubTriggerProps>) {
|
|
179
184
|
const ctx = useMenuContext()
|
|
180
185
|
const [s, r] = splitProps(props, ["class", "classList", "children", "shortcut", "badge"])
|
|
186
|
+
|
|
181
187
|
return (
|
|
182
188
|
<DropdownMenu.SubTrigger
|
|
183
189
|
{...r}
|
|
@@ -195,6 +201,7 @@ function MenuSubTrigger(props: ParentProps<MenuSubTriggerProps>) {
|
|
|
195
201
|
function MenuSubContent(props: ComponentProps<typeof DropdownMenu.SubContent>) {
|
|
196
202
|
const ctx = useMenuContext()
|
|
197
203
|
const [s, r] = splitProps(props, ["class", "classList"])
|
|
204
|
+
|
|
198
205
|
return (
|
|
199
206
|
<DropdownMenu.SubContent
|
|
200
207
|
{...r}
|
|
@@ -208,6 +215,7 @@ function MenuSubContent(props: ComponentProps<typeof DropdownMenu.SubContent>) {
|
|
|
208
215
|
function MenuGroupLabel(props: ComponentProps<typeof DropdownMenu.GroupLabel>) {
|
|
209
216
|
const ctx = useMenuContext()
|
|
210
217
|
const [s, r] = splitProps(props, ["class", "classList"])
|
|
218
|
+
|
|
211
219
|
return (
|
|
212
220
|
<DropdownMenu.GroupLabel
|
|
213
221
|
{...r}
|
|
@@ -221,6 +229,7 @@ function MenuGroupLabel(props: ComponentProps<typeof DropdownMenu.GroupLabel>) {
|
|
|
221
229
|
function MenuSeparator(props: ComponentProps<typeof DropdownMenu.Separator>) {
|
|
222
230
|
const ctx = useMenuContext()
|
|
223
231
|
const [s, r] = splitProps(props, ["class", "classList"])
|
|
232
|
+
|
|
224
233
|
return (
|
|
225
234
|
<DropdownMenu.Separator
|
|
226
235
|
{...r}
|
|
@@ -234,6 +243,7 @@ function MenuSeparator(props: ComponentProps<typeof DropdownMenu.Separator>) {
|
|
|
234
243
|
function MenuContent(props: ComponentProps<typeof DropdownMenu.Content>) {
|
|
235
244
|
const ctx = useMenuContext()
|
|
236
245
|
const [s, r] = splitProps(props, ["class", "classList"])
|
|
246
|
+
|
|
237
247
|
return (
|
|
238
248
|
<DropdownMenu.Content
|
|
239
249
|
{...r}
|
|
@@ -251,6 +261,7 @@ export interface MenuProps extends ComponentProps<typeof DropdownMenu> {
|
|
|
251
261
|
function MenuRoot(props: MenuProps) {
|
|
252
262
|
const [local, rest] = splitProps(props, ["appearance", "children"])
|
|
253
263
|
const appearance = () => local.appearance ?? "compact"
|
|
264
|
+
|
|
254
265
|
return (
|
|
255
266
|
<MenuAppearanceContext.Provider value={{ appearance }}>
|
|
256
267
|
<DropdownMenu {...rest}>{local.children}</DropdownMenu>
|
|
@@ -265,6 +276,7 @@ export interface MenuContextProps extends ComponentProps<typeof ContextMenu> {
|
|
|
265
276
|
function MenuContextRoot(props: MenuContextProps) {
|
|
266
277
|
const [local, rest] = splitProps(props, ["appearance", "children"])
|
|
267
278
|
const appearance = () => local.appearance ?? "compact"
|
|
279
|
+
|
|
268
280
|
return (
|
|
269
281
|
<MenuAppearanceContext.Provider value={{ appearance }}>
|
|
270
282
|
<ContextMenu {...rest}>{local.children}</ContextMenu>
|
|
@@ -275,6 +287,7 @@ function MenuContextRoot(props: MenuContextProps) {
|
|
|
275
287
|
function MenuContextContent(props: ComponentProps<typeof ContextMenu.Content>) {
|
|
276
288
|
const ctx = useMenuContext()
|
|
277
289
|
const [s, r] = splitProps(props, ["class", "classList"])
|
|
290
|
+
|
|
278
291
|
return (
|
|
279
292
|
<ContextMenu.Content
|
|
280
293
|
{...r}
|
|
@@ -26,7 +26,9 @@ const SegmentedControlContext = createContext<SegmentedControlContextValue>()
|
|
|
26
26
|
|
|
27
27
|
function useSegmentedControlContext() {
|
|
28
28
|
const ctx = useContext(SegmentedControlContext)
|
|
29
|
+
|
|
29
30
|
if (!ctx) throw new Error("SegmentedControlItem must be used inside SegmentedControl")
|
|
31
|
+
|
|
30
32
|
return ctx
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -45,6 +47,7 @@ export type SegmentedControlProps = Omit<ComponentProps<"div">, "onChange"> &
|
|
|
45
47
|
export function SegmentedControl(props: SegmentedControlProps) {
|
|
46
48
|
const isControlled = createMemo(() => Object.hasOwn(props as object, "value"))
|
|
47
49
|
const merged = mergeProps({ allowDeselect: false, disabled: false }, props)
|
|
50
|
+
|
|
48
51
|
const [local, rest] = splitProps(merged, [
|
|
49
52
|
"class",
|
|
50
53
|
"classList",
|
|
@@ -77,10 +80,13 @@ export function SegmentedControl(props: SegmentedControlProps) {
|
|
|
77
80
|
|
|
78
81
|
const focusNext = (from: HTMLButtonElement, direction: 1 | -1) => {
|
|
79
82
|
const root = from.closest(`[data-slot="segmented-control-v2"]`)
|
|
83
|
+
|
|
80
84
|
if (!root) return
|
|
85
|
+
|
|
81
86
|
const buttons = Array.from(
|
|
82
87
|
root.querySelectorAll<HTMLButtonElement>(`button[data-slot="segmented-control-v2-item"]`),
|
|
83
88
|
).filter((b) => !b.disabled)
|
|
89
|
+
|
|
84
90
|
const i = buttons.indexOf(from)
|
|
85
91
|
const next = buttons[i + direction]
|
|
86
92
|
next?.focus()
|
|
@@ -96,6 +102,7 @@ export function SegmentedControl(props: SegmentedControlProps) {
|
|
|
96
102
|
|
|
97
103
|
const assignRef = (el: HTMLDivElement | undefined) => {
|
|
98
104
|
const r = local.ref
|
|
105
|
+
|
|
99
106
|
if (typeof r === "function") (r as (el: HTMLDivElement | undefined) => void)(el)
|
|
100
107
|
else if (r != null && typeof r === "object" && "value" in r) (r as { value: HTMLDivElement | undefined }).value = el
|
|
101
108
|
}
|
|
@@ -134,6 +141,7 @@ function invokeButtonHandler<E extends Event>(
|
|
|
134
141
|
|
|
135
142
|
export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
136
143
|
const merged = mergeProps({ disabled: false }, props)
|
|
144
|
+
|
|
137
145
|
const [local, rest] = splitProps(merged, [
|
|
138
146
|
"class",
|
|
139
147
|
"classList",
|
|
@@ -143,6 +151,7 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
143
151
|
"onClick",
|
|
144
152
|
"onKeyDown",
|
|
145
153
|
])
|
|
154
|
+
|
|
146
155
|
const ctx = useSegmentedControlContext()
|
|
147
156
|
|
|
148
157
|
const pressed = createMemo(() => ctx.selected() === local.value)
|
|
@@ -150,13 +159,16 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
150
159
|
|
|
151
160
|
const onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> = (e) => {
|
|
152
161
|
invokeButtonHandler(local.onClick, e)
|
|
162
|
+
|
|
153
163
|
if (e.defaultPrevented || disabled()) return
|
|
164
|
+
|
|
154
165
|
if (pressed()) ctx.clearIfAllowed(local.value)
|
|
155
166
|
else ctx.select(local.value)
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
const onKeyDown: JSX.EventHandlerUnion<HTMLButtonElement, KeyboardEvent> = (e) => {
|
|
159
170
|
invokeButtonHandler(local.onKeyDown, e)
|
|
171
|
+
|
|
160
172
|
if (e.defaultPrevented || disabled()) return
|
|
161
173
|
const t = e.currentTarget
|
|
162
174
|
const horizontal = t.closest(`[data-slot="segmented-control-v2"]`)?.matches(":dir(rtl)") ? -1 : 1
|
|
@@ -173,16 +185,20 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
173
185
|
else if (e.key === "Home") {
|
|
174
186
|
e.preventDefault()
|
|
175
187
|
const root = t.closest(`[data-slot="segmented-control-v2"]`)
|
|
188
|
+
|
|
176
189
|
const first = root?.querySelector<HTMLButtonElement>(
|
|
177
190
|
`button[data-slot="segmented-control-v2-item"]:not(:disabled)`,
|
|
178
191
|
)
|
|
192
|
+
|
|
179
193
|
first?.focus()
|
|
180
194
|
} else if (e.key === "End") {
|
|
181
195
|
e.preventDefault()
|
|
182
196
|
const root = t.closest(`[data-slot="segmented-control-v2"]`)
|
|
197
|
+
|
|
183
198
|
const buttons = root?.querySelectorAll<HTMLButtonElement>(
|
|
184
199
|
`button[data-slot="segmented-control-v2-item"]:not(:disabled)`,
|
|
185
200
|
)
|
|
201
|
+
|
|
186
202
|
const last = buttons?.[buttons.length - 1]
|
|
187
203
|
last?.focus()
|
|
188
204
|
}
|
|
@@ -10,7 +10,9 @@ export interface TabsProps extends ComponentProps<typeof Root> {
|
|
|
10
10
|
variant?: TabsVariant
|
|
11
11
|
orientation?: "horizontal" | "vertical"
|
|
12
12
|
}
|
|
13
|
+
|
|
13
14
|
export interface TabsListProps extends ComponentProps<typeof List> {}
|
|
15
|
+
|
|
14
16
|
export interface TabsTriggerProps extends ComponentProps<typeof Trigger> {
|
|
15
17
|
classes?: { button?: string }
|
|
16
18
|
closeButton?: JSX.Element
|
|
@@ -18,7 +20,9 @@ export interface TabsTriggerProps extends ComponentProps<typeof Trigger> {
|
|
|
18
20
|
onMiddleClick?: () => void
|
|
19
21
|
subtext?: JSX.Element | string
|
|
20
22
|
}
|
|
23
|
+
|
|
21
24
|
export interface TabsCloseButtonProps extends ComponentProps<"button"> {}
|
|
25
|
+
|
|
22
26
|
export interface TabsContentProps extends ComponentProps<typeof Content> {}
|
|
23
27
|
|
|
24
28
|
const TabsContext = createContext<{ current: () => boolean }>({ current: () => false })
|
|
@@ -27,12 +31,17 @@ function TabsRoot(props: TabsProps) {
|
|
|
27
31
|
const [local, rest] = splitProps(props, ["class", "classList", "variant", "orientation", "children"])
|
|
28
32
|
const variant = () => local.variant ?? "panel"
|
|
29
33
|
const current = () => variant() === "line" || variant() === "pill" || variant() === "settings"
|
|
34
|
+
|
|
30
35
|
const dataVariant = () => {
|
|
31
36
|
if (variant() === "panel" || variant() === "line") return "normal"
|
|
37
|
+
|
|
32
38
|
if (variant() === "underline") return "alt"
|
|
39
|
+
|
|
33
40
|
if (variant() === "surface") return "pill"
|
|
41
|
+
|
|
34
42
|
return variant()
|
|
35
43
|
}
|
|
44
|
+
|
|
36
45
|
return (
|
|
37
46
|
<TabsContext.Provider value={{ current }}>
|
|
38
47
|
<Root
|
|
@@ -55,6 +64,7 @@ function TabsRoot(props: TabsProps) {
|
|
|
55
64
|
function TabsList(props: TabsListProps) {
|
|
56
65
|
const ctx = useContext(TabsContext)
|
|
57
66
|
const [local, rest] = splitProps(props, ["class", "classList"])
|
|
67
|
+
|
|
58
68
|
return (
|
|
59
69
|
<List
|
|
60
70
|
{...rest}
|
|
@@ -69,6 +79,7 @@ function TabsList(props: TabsListProps) {
|
|
|
69
79
|
|
|
70
80
|
function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
|
|
71
81
|
const ctx = useContext(TabsContext)
|
|
82
|
+
|
|
72
83
|
const [local, rest] = splitProps(props, [
|
|
73
84
|
"class",
|
|
74
85
|
"classList",
|
|
@@ -80,9 +91,11 @@ function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
|
|
|
80
91
|
"subtext",
|
|
81
92
|
"dir",
|
|
82
93
|
])
|
|
94
|
+
|
|
83
95
|
const wrapperSlot = () => (ctx.current() ? "tabs-v2-trigger-wrapper" : "tabs-trigger-wrapper")
|
|
84
96
|
const triggerSlot = () => (ctx.current() ? "tabs-v2-trigger" : "tabs-trigger")
|
|
85
97
|
const closeSlot = () => (ctx.current() ? "tabs-v2-trigger-close-button" : "tabs-trigger-close-button")
|
|
98
|
+
|
|
86
99
|
return (
|
|
87
100
|
<div
|
|
88
101
|
data-slot={wrapperSlot()}
|
|
@@ -135,6 +148,7 @@ function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
|
|
|
135
148
|
function TabsCloseButton(props: TabsCloseButtonProps) {
|
|
136
149
|
const i18n = useI18n()
|
|
137
150
|
const [local, rest] = splitProps(props, ["class", "classList", "onClick", "onPointerDown", "aria-label"])
|
|
151
|
+
|
|
138
152
|
return (
|
|
139
153
|
<button
|
|
140
154
|
type="button"
|
|
@@ -147,11 +161,13 @@ function TabsCloseButton(props: TabsCloseButtonProps) {
|
|
|
147
161
|
}}
|
|
148
162
|
onPointerDown={(event) => {
|
|
149
163
|
event.stopPropagation()
|
|
164
|
+
|
|
150
165
|
if (typeof local.onPointerDown === "function") local.onPointerDown(event)
|
|
151
166
|
}}
|
|
152
167
|
onClick={(event) => {
|
|
153
168
|
event.preventDefault()
|
|
154
169
|
event.stopPropagation()
|
|
170
|
+
|
|
155
171
|
if (typeof local.onClick === "function") local.onClick(event)
|
|
156
172
|
}}
|
|
157
173
|
>
|
|
@@ -166,6 +182,7 @@ function TabsCloseButton(props: TabsCloseButtonProps) {
|
|
|
166
182
|
function TabsContent(props: ParentProps<TabsContentProps>) {
|
|
167
183
|
const ctx = useContext(TabsContext)
|
|
168
184
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
|
185
|
+
|
|
169
186
|
return (
|
|
170
187
|
<Content
|
|
171
188
|
{...rest}
|
|
@@ -182,6 +199,7 @@ function TabsContent(props: ParentProps<TabsContentProps>) {
|
|
|
182
199
|
|
|
183
200
|
const TabsSectionTitle: Component<ParentProps> = (props) => {
|
|
184
201
|
const ctx = useContext(TabsContext)
|
|
202
|
+
|
|
185
203
|
return <div data-slot={ctx.current() ? "tabs-v2-section-title" : "tabs-section-title"}>{props.children}</div>
|
|
186
204
|
}
|
|
187
205
|
|
|
@@ -31,6 +31,7 @@ export function DialogFooter(props: ParentProps) {
|
|
|
31
31
|
|
|
32
32
|
export function DialogBody(props: ParentProps & { class?: ComponentProps<"div">["class"] }) {
|
|
33
33
|
const [local] = splitProps(props, ["class", "children"])
|
|
34
|
+
|
|
34
35
|
return (
|
|
35
36
|
<div data-slot="dialog-body" class={local.class}>
|
|
36
37
|
{local.children}
|
|
@@ -96,6 +97,7 @@ export function Dialog(props: DialogProps) {
|
|
|
96
97
|
"onCloseAutoFocus",
|
|
97
98
|
"preventBackdropDismiss",
|
|
98
99
|
])
|
|
100
|
+
|
|
99
101
|
const layer = useDialogLayer()
|
|
100
102
|
createEffect(() => layer?.setBackdropDismiss(!local.preventBackdropDismiss))
|
|
101
103
|
|
|
@@ -120,6 +122,7 @@ export function Dialog(props: DialogProps) {
|
|
|
120
122
|
onOpenAutoFocus={(e) => {
|
|
121
123
|
const target = e.currentTarget as HTMLElement | null
|
|
122
124
|
const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null
|
|
125
|
+
|
|
123
126
|
if (autofocusEl) {
|
|
124
127
|
e.preventDefault()
|
|
125
128
|
autofocusEl.focus({ preventScroll: true })
|
|
@@ -17,11 +17,13 @@ export interface TooltipProps extends ComponentProps<typeof Root> {
|
|
|
17
17
|
|
|
18
18
|
export function Tooltip(props: TooltipProps) {
|
|
19
19
|
let ref: HTMLDivElement | undefined
|
|
20
|
+
|
|
20
21
|
const [state, setState] = createStore({
|
|
21
22
|
open: false,
|
|
22
23
|
block: false,
|
|
23
24
|
expand: false,
|
|
24
25
|
})
|
|
26
|
+
|
|
25
27
|
const [local, others] = splitProps(props, [
|
|
26
28
|
"children",
|
|
27
29
|
"appearance",
|
|
@@ -40,13 +42,17 @@ export function Tooltip(props: TooltipProps) {
|
|
|
40
42
|
|
|
41
43
|
const inside = () => {
|
|
42
44
|
const active = document.activeElement
|
|
45
|
+
|
|
43
46
|
if (!ref || !active) return false
|
|
47
|
+
|
|
44
48
|
return ref.contains(active)
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
const drop = (expand = state.expand) => {
|
|
48
52
|
if (expand || !state.block) return
|
|
53
|
+
|
|
49
54
|
if (ref?.matches(":hover")) return
|
|
55
|
+
|
|
50
56
|
if (inside()) return
|
|
51
57
|
setState("block", false)
|
|
52
58
|
}
|
|
@@ -54,11 +60,14 @@ export function Tooltip(props: TooltipProps) {
|
|
|
54
60
|
const sync = () => {
|
|
55
61
|
const expand = !!ref?.querySelector('[aria-expanded="true"], [data-expanded]')
|
|
56
62
|
setState("expand", expand)
|
|
63
|
+
|
|
57
64
|
if (expand) {
|
|
58
65
|
setState("block", true)
|
|
59
66
|
close()
|
|
67
|
+
|
|
60
68
|
return
|
|
61
69
|
}
|
|
70
|
+
|
|
62
71
|
drop(expand)
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -101,11 +110,15 @@ export function Tooltip(props: TooltipProps) {
|
|
|
101
110
|
open={controlled() ? local.forceOpen : state.open}
|
|
102
111
|
onOpenChange={(open) => {
|
|
103
112
|
if (controlled()) return
|
|
113
|
+
|
|
104
114
|
if (state.block && open) return
|
|
115
|
+
|
|
105
116
|
if (justClickedTrigger) {
|
|
106
117
|
justClickedTrigger = false
|
|
118
|
+
|
|
107
119
|
return
|
|
108
120
|
}
|
|
121
|
+
|
|
109
122
|
setState("open", open)
|
|
110
123
|
}}
|
|
111
124
|
>
|
|
@@ -129,6 +142,7 @@ export function Tooltip(props: TooltipProps) {
|
|
|
129
142
|
<Content
|
|
130
143
|
ref={(el) => {
|
|
131
144
|
const theme = ref?.closest("[data-theme]")?.getAttribute("data-theme")
|
|
145
|
+
|
|
132
146
|
if (theme) el.setAttribute("data-theme", theme)
|
|
133
147
|
}}
|
|
134
148
|
data-component="tooltip-v2"
|
|
@@ -141,6 +155,7 @@ export function Tooltip(props: TooltipProps) {
|
|
|
141
155
|
if (ref === e.target || (e.target instanceof Node && ref?.contains(e.target))) {
|
|
142
156
|
justClickedTrigger = true
|
|
143
157
|
}
|
|
158
|
+
|
|
144
159
|
e.preventDefault()
|
|
145
160
|
}}
|
|
146
161
|
>
|