@opencode/ui 2.0.22 → 2.0.24
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/dist/data-display/accordion/accordion.d.ts +3 -1
- package/dist/feedback/toast/toast.d.ts +3 -2
- package/dist/i18n/en.d.ts +5 -0
- package/dist/icons/catalog.d.ts +257 -0
- package/dist/icons/icon/additional-icons.d.ts +1 -0
- package/dist/icons/icon/icon.d.ts +1 -237
- package/package.json +5 -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/accordion/accordion.tsx +16 -2
- 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/feedback/toast/toast.tsx +32 -6
- 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/en.ts +10 -0
- package/src/i18n/no.ts +1 -0
- package/src/icons/catalog.ts +273 -0
- package/src/icons/icon/additional-icons.ts +1 -0
- package/src/icons/icon/icon.tsx +10 -250
- 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.css +6 -72
- 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/styles/theme.css +1 -1
- 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
|
@@ -8,6 +8,7 @@ export function createMarkdownParser(highlight: (code: string, language: string)
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const inlineMathRegex = /^\\\(((?:\\.|[^\\\n])*?)\\\)/
|
|
11
|
+
|
|
11
12
|
const blockMathRegex = /^\$\$\n([\s\S]+?)\n\$\$(?:\n|$)/
|
|
12
13
|
|
|
13
14
|
const katexExtension: MarkedExtension = {
|
|
@@ -17,12 +18,16 @@ const katexExtension: MarkedExtension = {
|
|
|
17
18
|
level: "inline",
|
|
18
19
|
start(src) {
|
|
19
20
|
const index = src.indexOf("\\(")
|
|
21
|
+
|
|
20
22
|
if (index === -1) return
|
|
23
|
+
|
|
21
24
|
return index
|
|
22
25
|
},
|
|
23
26
|
tokenizer(src) {
|
|
24
27
|
const match = src.match(inlineMathRegex)
|
|
28
|
+
|
|
25
29
|
if (!match) return
|
|
30
|
+
|
|
26
31
|
return {
|
|
27
32
|
type: "inlineKatex",
|
|
28
33
|
raw: match[0],
|
|
@@ -37,7 +42,9 @@ const katexExtension: MarkedExtension = {
|
|
|
37
42
|
level: "block",
|
|
38
43
|
tokenizer(src) {
|
|
39
44
|
const match = src.match(blockMathRegex)
|
|
45
|
+
|
|
40
46
|
if (!match) return
|
|
47
|
+
|
|
41
48
|
return {
|
|
42
49
|
type: "blockKatex",
|
|
43
50
|
raw: match[0],
|
package/src/context/marked.tsx
CHANGED
|
@@ -17,8 +17,11 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext(
|
|
|
17
17
|
langs: [],
|
|
18
18
|
preferredHighlighter: "shiki-wasm",
|
|
19
19
|
})
|
|
20
|
+
|
|
20
21
|
const name = language in bundledLanguages ? language : "text"
|
|
22
|
+
|
|
21
23
|
if (!highlighter.getLoadedLanguages().includes(name)) await highlighter.loadLanguage(name as BundledLanguage)
|
|
24
|
+
|
|
22
25
|
return highlighter.codeToHtml(code, {
|
|
23
26
|
lang: name,
|
|
24
27
|
theme: "OpenCode",
|
|
@@ -2,17 +2,27 @@ import { Content, Header, Item, Root, Trigger } from "@kobalte/core/accordion"
|
|
|
2
2
|
import { splitProps } from "solid-js"
|
|
3
3
|
import type { ComponentProps, ParentProps } from "solid-js"
|
|
4
4
|
|
|
5
|
-
export interface AccordionProps extends ComponentProps<typeof Root> {
|
|
5
|
+
export interface AccordionProps extends Omit<ComponentProps<typeof Root>, "value"> {
|
|
6
|
+
/** Controlled expanded items. Kobalte reads this iterable into its own selection; it never mutates this array. */
|
|
7
|
+
value?: readonly string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export interface AccordionItemProps extends ComponentProps<typeof Item> {}
|
|
11
|
+
|
|
7
12
|
export interface AccordionHeaderProps extends ComponentProps<typeof Header> {}
|
|
13
|
+
|
|
8
14
|
export interface AccordionTriggerProps extends ComponentProps<typeof Trigger> {}
|
|
15
|
+
|
|
9
16
|
export interface AccordionContentProps extends ComponentProps<typeof Content> {}
|
|
10
17
|
|
|
11
18
|
function AccordionRoot(props: AccordionProps) {
|
|
12
|
-
const [split, rest] = splitProps(props, ["class", "classList"])
|
|
19
|
+
const [split, rest] = splitProps(props, ["class", "classList", "value"])
|
|
20
|
+
|
|
21
|
+
// SAFETY: Kobalte's createMultipleSelectionState copies selectedKeys into a Selection before changing it.
|
|
13
22
|
return (
|
|
14
23
|
<Root
|
|
15
24
|
{...rest}
|
|
25
|
+
value={split.value as string[] | undefined}
|
|
16
26
|
data-component="accordion"
|
|
17
27
|
classList={{
|
|
18
28
|
...split.classList,
|
|
@@ -24,6 +34,7 @@ function AccordionRoot(props: AccordionProps) {
|
|
|
24
34
|
|
|
25
35
|
function AccordionItem(props: AccordionItemProps) {
|
|
26
36
|
const [split, rest] = splitProps(props, ["class", "classList"])
|
|
37
|
+
|
|
27
38
|
return (
|
|
28
39
|
<Item
|
|
29
40
|
{...rest}
|
|
@@ -38,6 +49,7 @@ function AccordionItem(props: AccordionItemProps) {
|
|
|
38
49
|
|
|
39
50
|
function AccordionHeader(props: ParentProps<AccordionHeaderProps>) {
|
|
40
51
|
const [split, rest] = splitProps(props, ["class", "classList", "children"])
|
|
52
|
+
|
|
41
53
|
return (
|
|
42
54
|
<Header
|
|
43
55
|
{...rest}
|
|
@@ -54,6 +66,7 @@ function AccordionHeader(props: ParentProps<AccordionHeaderProps>) {
|
|
|
54
66
|
|
|
55
67
|
function AccordionTrigger(props: ParentProps<AccordionTriggerProps>) {
|
|
56
68
|
const [split, rest] = splitProps(props, ["class", "classList", "children"])
|
|
69
|
+
|
|
57
70
|
return (
|
|
58
71
|
<Trigger
|
|
59
72
|
{...rest}
|
|
@@ -70,6 +83,7 @@ function AccordionTrigger(props: ParentProps<AccordionTriggerProps>) {
|
|
|
70
83
|
|
|
71
84
|
function AccordionContent(props: ParentProps<AccordionContentProps>) {
|
|
72
85
|
const [split, rest] = splitProps(props, ["class", "classList", "children"])
|
|
86
|
+
|
|
73
87
|
return (
|
|
74
88
|
<Content
|
|
75
89
|
{...rest}
|
|
@@ -8,7 +8,9 @@ const segmenter =
|
|
|
8
8
|
|
|
9
9
|
function first(value: string) {
|
|
10
10
|
if (!value) return ""
|
|
11
|
+
|
|
11
12
|
if (!segmenter) return Array.from(value)[0] ?? ""
|
|
13
|
+
|
|
12
14
|
return segmenter.segment(value)[Symbol.iterator]().next().value?.segment ?? Array.from(value)[0] ?? ""
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -33,7 +35,9 @@ export function Avatar(props: AvatarProps) {
|
|
|
33
35
|
"classList",
|
|
34
36
|
"style",
|
|
35
37
|
])
|
|
38
|
+
|
|
36
39
|
const src = split.src // did this so i can zero it out to test fallback
|
|
40
|
+
|
|
37
41
|
return (
|
|
38
42
|
<div
|
|
39
43
|
{...rest}
|
|
@@ -11,11 +11,13 @@ export function DiffChanges(props: {
|
|
|
11
11
|
? props.changes.reduce((acc, diff) => acc + (diff.additions ?? 0), 0)
|
|
12
12
|
: props.changes.additions,
|
|
13
13
|
)
|
|
14
|
+
|
|
14
15
|
const deletions = createMemo(() =>
|
|
15
16
|
Array.isArray(props.changes)
|
|
16
17
|
? props.changes.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0)
|
|
17
18
|
: props.changes.deletions,
|
|
18
19
|
)
|
|
20
|
+
|
|
19
21
|
const total = createMemo(() => (additions() ?? 0) + (deletions() ?? 0))
|
|
20
22
|
|
|
21
23
|
return (
|
|
@@ -35,6 +35,7 @@ export interface LineCommentProps extends ComponentProps<"div"> {
|
|
|
35
35
|
|
|
36
36
|
export function LineComment(props: LineCommentProps) {
|
|
37
37
|
const [local, rest] = splitProps(props, ["comment", "selection", "actions", "class", "classList"])
|
|
38
|
+
|
|
38
39
|
return (
|
|
39
40
|
<div
|
|
40
41
|
{...rest}
|
|
@@ -78,11 +79,13 @@ export interface LineCommentEditorProps extends Omit<ComponentProps<"div">, "chi
|
|
|
78
79
|
|
|
79
80
|
function pathFilename(path: string) {
|
|
80
81
|
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
|
82
|
+
|
|
81
83
|
return index === -1 ? path : path.slice(index + 1)
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
function pathDirectory(path: string) {
|
|
85
87
|
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
|
88
|
+
|
|
86
89
|
return index === -1 ? "" : path.slice(0, index + 1)
|
|
87
90
|
}
|
|
88
91
|
|
|
@@ -118,12 +121,16 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
118
121
|
|
|
119
122
|
const currentMention = () => {
|
|
120
123
|
const textarea = textareaRef
|
|
124
|
+
|
|
121
125
|
if (!textarea) return
|
|
126
|
+
|
|
122
127
|
if (!local.mention) return
|
|
128
|
+
|
|
123
129
|
if (textarea.selectionStart !== textarea.selectionEnd) return
|
|
124
130
|
|
|
125
131
|
const end = textarea.selectionStart
|
|
126
132
|
const match = textarea.value.slice(0, end).match(/@(\S*)$/)
|
|
133
|
+
|
|
127
134
|
if (!match) return
|
|
128
135
|
|
|
129
136
|
return {
|
|
@@ -138,6 +145,7 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
138
145
|
|
|
139
146
|
const textarea = textareaRef
|
|
140
147
|
const query = currentMention()
|
|
148
|
+
|
|
141
149
|
if (!textarea || !query) return
|
|
142
150
|
|
|
143
151
|
const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}`
|
|
@@ -155,8 +163,10 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
155
163
|
const mention = useFilteredList<{ path: string }>({
|
|
156
164
|
items: async (query) => {
|
|
157
165
|
if (!local.mention) return []
|
|
166
|
+
|
|
158
167
|
if (!query.trim()) return []
|
|
159
168
|
const paths = await local.mention.items(query)
|
|
169
|
+
|
|
160
170
|
return paths.map((path) => ({ path }))
|
|
161
171
|
},
|
|
162
172
|
key: (item) => item.path,
|
|
@@ -167,8 +177,10 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
167
177
|
|
|
168
178
|
const syncMention = () => {
|
|
169
179
|
const item = currentMention()
|
|
180
|
+
|
|
170
181
|
if (!item) {
|
|
171
182
|
closeMention()
|
|
183
|
+
|
|
172
184
|
return
|
|
173
185
|
}
|
|
174
186
|
|
|
@@ -178,6 +190,7 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
178
190
|
|
|
179
191
|
const selectActiveMention = () => {
|
|
180
192
|
const items = mention.flat()
|
|
193
|
+
|
|
181
194
|
if (items.length === 0) return
|
|
182
195
|
const active = mention.active()
|
|
183
196
|
selectMention(items.find((item) => item.path === active) ?? items[0])
|
|
@@ -185,6 +198,7 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
185
198
|
|
|
186
199
|
const submit = () => {
|
|
187
200
|
const v = local.value.trim()
|
|
201
|
+
|
|
188
202
|
if (!v) return
|
|
189
203
|
local.onSubmit(v)
|
|
190
204
|
}
|
|
@@ -225,12 +239,14 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
225
239
|
onSelect={() => syncMention()}
|
|
226
240
|
onKeyDown={(e) => {
|
|
227
241
|
e.stopPropagation()
|
|
242
|
+
|
|
228
243
|
if (e.isComposing || e.keyCode === 229) return
|
|
229
244
|
|
|
230
245
|
if (mentionOpen()) {
|
|
231
246
|
if (e.key === "Escape") {
|
|
232
247
|
e.preventDefault()
|
|
233
248
|
closeMention()
|
|
249
|
+
|
|
234
250
|
return
|
|
235
251
|
}
|
|
236
252
|
|
|
@@ -238,14 +254,17 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
238
254
|
if (mention.flat().length === 0) return
|
|
239
255
|
e.preventDefault()
|
|
240
256
|
selectActiveMention()
|
|
257
|
+
|
|
241
258
|
return
|
|
242
259
|
}
|
|
243
260
|
|
|
244
261
|
const nav = e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter"
|
|
245
262
|
const ctrlNav = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && (e.key === "n" || e.key === "p")
|
|
263
|
+
|
|
246
264
|
if ((nav || ctrlNav) && mention.flat().length > 0) {
|
|
247
265
|
mention.onKeyDown(e)
|
|
248
266
|
e.preventDefault()
|
|
267
|
+
|
|
249
268
|
return
|
|
250
269
|
}
|
|
251
270
|
}
|
|
@@ -254,8 +273,10 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
254
273
|
e.preventDefault()
|
|
255
274
|
e.currentTarget.blur()
|
|
256
275
|
local.onCancel()
|
|
276
|
+
|
|
257
277
|
return
|
|
258
278
|
}
|
|
279
|
+
|
|
259
280
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
260
281
|
e.preventDefault()
|
|
261
282
|
submit()
|
|
@@ -268,6 +289,7 @@ export function LineCommentEditor(props: LineCommentEditorProps) {
|
|
|
268
289
|
{(item) => {
|
|
269
290
|
const directory = item.path.endsWith("/") ? item.path : pathDirectory(item.path)
|
|
270
291
|
const name = item.path.endsWith("/") ? "" : pathFilename(item.path)
|
|
292
|
+
|
|
271
293
|
return (
|
|
272
294
|
<button
|
|
273
295
|
type="button"
|
|
@@ -8,7 +8,9 @@ const segmenter =
|
|
|
8
8
|
|
|
9
9
|
function first(value: string) {
|
|
10
10
|
if (!value) return ""
|
|
11
|
+
|
|
11
12
|
if (!segmenter) return Array.from(value)[0] ?? ""
|
|
13
|
+
|
|
12
14
|
return segmenter.segment(value)[Symbol.iterator]().next().value?.segment ?? Array.from(value)[0] ?? ""
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -33,14 +35,19 @@ const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
|
|
|
33
35
|
|
|
34
36
|
export function getProjectAvatarSource(id?: string, icon?: { color?: string; url?: string; override?: string }) {
|
|
35
37
|
if (id === OPENCODE_PROJECT_ID) return "https://opencode.ai/favicon.svg"
|
|
38
|
+
|
|
36
39
|
if (icon?.override) return icon.override
|
|
40
|
+
|
|
37
41
|
if (icon?.color) return undefined
|
|
42
|
+
|
|
38
43
|
return icon?.url
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export function getProjectAvatarVariant(key?: string): ProjectAvatarVariant {
|
|
42
47
|
if (key === "mint") return "cyan"
|
|
48
|
+
|
|
43
49
|
if (key === "lime") return "green"
|
|
50
|
+
|
|
44
51
|
if (
|
|
45
52
|
key === "orange" ||
|
|
46
53
|
key === "yellow" ||
|
|
@@ -53,12 +60,14 @@ export function getProjectAvatarVariant(key?: string): ProjectAvatarVariant {
|
|
|
53
60
|
key === "gray"
|
|
54
61
|
)
|
|
55
62
|
return key
|
|
63
|
+
|
|
56
64
|
return "gray"
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
export const displayName = (project: { name?: string; worktree: string }) => {
|
|
60
68
|
const trimmed = project.worktree.replace(/[/\\]+$/, "")
|
|
61
69
|
const filename = trimmed.slice(Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")) + 1)
|
|
70
|
+
|
|
62
71
|
return project.name || filename || project.worktree
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -71,6 +80,7 @@ export interface ProjectAvatarProps extends ComponentProps<"div"> {
|
|
|
71
80
|
|
|
72
81
|
export function ProjectAvatar(props: ProjectAvatarProps) {
|
|
73
82
|
const [split, rest] = splitProps(props, ["fallback", "src", "variant", "unread", "class", "classList", "style"])
|
|
83
|
+
|
|
74
84
|
return (
|
|
75
85
|
<div
|
|
76
86
|
{...rest}
|
|
@@ -5,5 +5,6 @@ export const toastDescriptionLimit = 2000
|
|
|
5
5
|
|
|
6
6
|
export function boundToastDescription<T extends { description?: string }>(options: T): T {
|
|
7
7
|
if (!options.description || options.description.length <= toastDescriptionLimit) return options
|
|
8
|
+
|
|
8
9
|
return { ...options, description: `${options.description.slice(0, toastDescriptionLimit)}…` }
|
|
9
10
|
}
|
|
@@ -21,9 +21,12 @@ function ToastRegion(props: ToastRegionProps) {
|
|
|
21
21
|
element.tabIndex = hidden ? -1 : 0
|
|
22
22
|
})
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
let connected = false
|
|
26
|
+
|
|
25
27
|
const connect = () => {
|
|
26
28
|
const regions = document.querySelectorAll(".toast-v2-region")
|
|
29
|
+
|
|
27
30
|
if (!regions.length) return
|
|
28
31
|
observer.disconnect()
|
|
29
32
|
regions.forEach((region) => {
|
|
@@ -37,14 +40,17 @@ function ToastRegion(props: ToastRegionProps) {
|
|
|
37
40
|
connected = true
|
|
38
41
|
sync()
|
|
39
42
|
}
|
|
43
|
+
|
|
40
44
|
const observer = new MutationObserver(() => {
|
|
41
45
|
if (!connected) connect()
|
|
42
46
|
else sync()
|
|
43
47
|
})
|
|
48
|
+
|
|
44
49
|
observer.observe(document.body, { subtree: true, childList: true })
|
|
45
50
|
queueMicrotask(connect)
|
|
46
51
|
onCleanup(() => observer.disconnect())
|
|
47
52
|
})
|
|
53
|
+
|
|
48
54
|
return (
|
|
49
55
|
<Portal>
|
|
50
56
|
<Toaster
|
|
@@ -55,7 +61,7 @@ function ToastRegion(props: ToastRegionProps) {
|
|
|
55
61
|
duration={5000}
|
|
56
62
|
swipeDirections={["bottom"]}
|
|
57
63
|
className={["toast-v2-region", local.className, local.class].filter(Boolean).join(" ")}
|
|
58
|
-
style={{ "--width": "320px", ...local.style }
|
|
64
|
+
style={{ "--width": "320px", ...local.style }}
|
|
59
65
|
toastOptions={{
|
|
60
66
|
...local.toastOptions,
|
|
61
67
|
unstyled: true,
|
|
@@ -103,6 +109,7 @@ function ToastCloseButton(props: ComponentProps<"button">) {
|
|
|
103
109
|
const i18n = useI18n()
|
|
104
110
|
const toastId = useContext(ToastContext)
|
|
105
111
|
const [local, rest] = splitProps(props, ["children", "onClick"])
|
|
112
|
+
|
|
106
113
|
return (
|
|
107
114
|
<button
|
|
108
115
|
type="button"
|
|
@@ -110,7 +117,8 @@ function ToastCloseButton(props: ComponentProps<"button">) {
|
|
|
110
117
|
aria-label={i18n.t("ui.common.dismiss")}
|
|
111
118
|
{...rest}
|
|
112
119
|
onClick={(event) => {
|
|
113
|
-
if (
|
|
120
|
+
if (local.onClick instanceof Function) local.onClick(event)
|
|
121
|
+
|
|
114
122
|
if (!event.defaultPrevented && toastId !== undefined) toaster.dismiss(toastId)
|
|
115
123
|
}}
|
|
116
124
|
>
|
|
@@ -147,7 +155,7 @@ export const toaster = {
|
|
|
147
155
|
duration?: number
|
|
148
156
|
persistent?: boolean
|
|
149
157
|
/** Reactive value the toast's height depends on; sonner re-measures when it changes. */
|
|
150
|
-
resize?: () =>
|
|
158
|
+
resize?: () => void
|
|
151
159
|
},
|
|
152
160
|
) {
|
|
153
161
|
const toastId = --toastV2Id
|
|
@@ -162,10 +170,12 @@ export const toaster = {
|
|
|
162
170
|
description: options?.resize
|
|
163
171
|
? (): JSX.Element => {
|
|
164
172
|
options.resize?.()
|
|
173
|
+
|
|
165
174
|
return undefined
|
|
166
175
|
}
|
|
167
176
|
: undefined,
|
|
168
177
|
})
|
|
178
|
+
|
|
169
179
|
return toastId
|
|
170
180
|
},
|
|
171
181
|
dismiss(toastId?: number) {
|
|
@@ -175,6 +185,7 @@ export const toaster = {
|
|
|
175
185
|
} else {
|
|
176
186
|
releaseToast(activeToastById.get(toastId))
|
|
177
187
|
}
|
|
188
|
+
|
|
178
189
|
return toast.dismiss(toastId)
|
|
179
190
|
},
|
|
180
191
|
}
|
|
@@ -188,7 +199,8 @@ export interface ToastAction {
|
|
|
188
199
|
export interface ToastOptions {
|
|
189
200
|
title?: string
|
|
190
201
|
description?: string
|
|
191
|
-
|
|
202
|
+
/** An element, or a render function the toast calls under its own owner, for icons created from imperative code. */
|
|
203
|
+
icon?: JSX.Element | (() => JSX.Element)
|
|
192
204
|
variant?: "default" | "success" | "error" | "loading"
|
|
193
205
|
duration?: number
|
|
194
206
|
persistent?: boolean
|
|
@@ -203,10 +215,14 @@ interface ActiveToast {
|
|
|
203
215
|
}
|
|
204
216
|
|
|
205
217
|
const activeToastByKey = new Map<string, ActiveToast>()
|
|
218
|
+
|
|
206
219
|
const activeToastById = new Map<number, ActiveToast>()
|
|
207
220
|
|
|
208
221
|
export function showToast(options: ToastOptions | string) {
|
|
222
|
+
// SAFETY: `options` is declared `ToastOptions | string`; `typeof` separates the string shorthand from the object.
|
|
223
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- see SAFETY above
|
|
209
224
|
const opts = boundToastDescription(typeof options === "string" ? { description: options } : options)
|
|
225
|
+
|
|
210
226
|
const key = JSON.stringify({
|
|
211
227
|
title: opts.title,
|
|
212
228
|
description: opts.description,
|
|
@@ -215,6 +231,7 @@ export function showToast(options: ToastOptions | string) {
|
|
|
215
231
|
persistent: opts.persistent,
|
|
216
232
|
actions: opts.actions?.map((action) => [action.label, action.variant]),
|
|
217
233
|
})
|
|
234
|
+
|
|
218
235
|
const active = activeToastByKey.get(key)
|
|
219
236
|
const toasts = toast.getToasts()
|
|
220
237
|
|
|
@@ -222,6 +239,7 @@ export function showToast(options: ToastOptions | string) {
|
|
|
222
239
|
active.options = opts
|
|
223
240
|
publishToast(active)
|
|
224
241
|
pulseToast(active.id)
|
|
242
|
+
|
|
225
243
|
return active.id
|
|
226
244
|
}
|
|
227
245
|
|
|
@@ -233,6 +251,7 @@ export function showToast(options: ToastOptions | string) {
|
|
|
233
251
|
activeToastByKey.set(key, entry)
|
|
234
252
|
activeToastById.set(entry.id, entry)
|
|
235
253
|
publishToast(entry)
|
|
254
|
+
|
|
236
255
|
return entry.id
|
|
237
256
|
}
|
|
238
257
|
|
|
@@ -241,7 +260,9 @@ function publishToast(entry: ActiveToast) {
|
|
|
241
260
|
toast(entry.options.title ?? "", {
|
|
242
261
|
id: entry.id,
|
|
243
262
|
description: entry.options.description,
|
|
244
|
-
|
|
263
|
+
// SAFETY: solid-sonner inserts `icon` as a JSX child, and Solid calls a function child under the toast's render
|
|
264
|
+
// owner; solid-sonner's type omits function children.
|
|
265
|
+
icon: entry.options.icon as JSX.Element,
|
|
245
266
|
action: entry.actions,
|
|
246
267
|
closeButton: true,
|
|
247
268
|
duration: entry.options.persistent ? Number.POSITIVE_INFINITY : entry.options.duration,
|
|
@@ -255,6 +276,7 @@ function publishToast(entry: ActiveToast) {
|
|
|
255
276
|
|
|
256
277
|
function createToastActions(entry: ActiveToast) {
|
|
257
278
|
if (!entry.options.actions?.length) return undefined
|
|
279
|
+
|
|
258
280
|
return (
|
|
259
281
|
<Toast.Actions>
|
|
260
282
|
{/* Static map, not <For>: this JSX is created imperatively outside any Solid
|
|
@@ -268,7 +290,8 @@ function createToastActions(entry: ActiveToast) {
|
|
|
268
290
|
data-action-variant={action.variant ?? "primary"}
|
|
269
291
|
onClick={() => {
|
|
270
292
|
const onClick = entry.options.actions?.[index]?.onClick
|
|
271
|
-
|
|
293
|
+
|
|
294
|
+
if (onClick && onClick !== "dismiss") onClick()
|
|
272
295
|
toaster.dismiss(entry.id)
|
|
273
296
|
}}
|
|
274
297
|
>
|
|
@@ -283,6 +306,7 @@ function pulseToast(toastId: number) {
|
|
|
283
306
|
if (typeof document === "undefined" || typeof requestAnimationFrame === "undefined") return
|
|
284
307
|
requestAnimationFrame(() => {
|
|
285
308
|
const element = document.querySelector<HTMLElement>(`[data-testid="toast-v2-${toastId}"]`)
|
|
309
|
+
|
|
286
310
|
if (!element || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return
|
|
287
311
|
element.animate([{ scale: 1 }, { scale: 1.025 }, { scale: 1 }], { duration: 160, easing: "ease-out" })
|
|
288
312
|
})
|
|
@@ -290,7 +314,9 @@ function pulseToast(toastId: number) {
|
|
|
290
314
|
|
|
291
315
|
function releaseToast(entry: ActiveToast | undefined) {
|
|
292
316
|
if (!entry) return
|
|
317
|
+
|
|
293
318
|
if (activeToastByKey.get(entry.key) === entry) activeToastByKey.delete(entry.key)
|
|
319
|
+
|
|
294
320
|
if (activeToastById.get(entry.id) === entry) activeToastById.delete(entry.id)
|
|
295
321
|
}
|
|
296
322
|
|
|
@@ -10,6 +10,7 @@ export interface CheckboxProps extends ParentProps<ComponentProps<typeof Root>>
|
|
|
10
10
|
|
|
11
11
|
export function Checkbox(props: CheckboxProps) {
|
|
12
12
|
const [local, others] = splitProps(props, ["children", "class", "label", "hideLabel", "description", "icon"])
|
|
13
|
+
|
|
13
14
|
return (
|
|
14
15
|
<Root {...others} data-component="checkbox">
|
|
15
16
|
<Input data-slot="checkbox-checkbox-input" />
|
|
@@ -31,9 +31,11 @@ const FieldContext = createContext<FieldContextValue>()
|
|
|
31
31
|
|
|
32
32
|
function useField() {
|
|
33
33
|
const ctx = useContext(FieldContext)
|
|
34
|
+
|
|
34
35
|
if (!ctx) {
|
|
35
36
|
throw new Error("Field subcomponents must be used within <Field>")
|
|
36
37
|
}
|
|
38
|
+
|
|
37
39
|
return ctx
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -72,17 +74,22 @@ function FieldRoot(props: ParentProps<FieldProps>) {
|
|
|
72
74
|
unregisterSuffix: () => setSuffixCount((n) => Math.max(0, n - 1)),
|
|
73
75
|
getDescribedBy: () => {
|
|
74
76
|
const ids: string[] = []
|
|
77
|
+
|
|
75
78
|
if (prefixCount() > 0) ids.push(prefixId)
|
|
79
|
+
|
|
76
80
|
if (suffixCount() > 0) ids.push(suffixId)
|
|
81
|
+
|
|
77
82
|
return ids.length > 0 ? ids.join(" ") : undefined
|
|
78
83
|
},
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
const syncControlA11y = () => {
|
|
82
87
|
const root = rootRef
|
|
88
|
+
|
|
83
89
|
if (!root) return
|
|
84
90
|
|
|
85
91
|
const control = root.querySelector(CONTROL_SELECTOR) as HTMLInputElement | HTMLTextAreaElement | null
|
|
92
|
+
|
|
86
93
|
if (!control) return
|
|
87
94
|
|
|
88
95
|
const shell = control.closest(
|
|
@@ -93,6 +100,7 @@ function FieldRoot(props: ParentProps<FieldProps>) {
|
|
|
93
100
|
control.setAttribute("aria-labelledby", labelId)
|
|
94
101
|
|
|
95
102
|
const describedBy = ctx.getDescribedBy()
|
|
103
|
+
|
|
96
104
|
if (describedBy) {
|
|
97
105
|
control.setAttribute("aria-describedby", describedBy)
|
|
98
106
|
} else {
|
|
@@ -10,11 +10,15 @@ export function InlineInput(props: InlineInputProps) {
|
|
|
10
10
|
|
|
11
11
|
const style = () => {
|
|
12
12
|
if (!local.style) return { width: local.width }
|
|
13
|
+
|
|
13
14
|
if (typeof local.style === "string") {
|
|
14
15
|
if (!local.width) return local.style
|
|
16
|
+
|
|
15
17
|
return `${local.style};width:${local.width}`
|
|
16
18
|
}
|
|
19
|
+
|
|
17
20
|
if (!local.width) return local.style
|
|
21
|
+
|
|
18
22
|
return { ...local.style, width: local.width }
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -21,6 +21,7 @@ export interface RadioGroupProps extends ParentProps<ComponentProps<typeof Root>
|
|
|
21
21
|
|
|
22
22
|
export function RadioGroup(props: RadioGroupProps) {
|
|
23
23
|
const [local, others] = splitProps(props, ["class", "classList", "children", "label", "description", "hideLabel"])
|
|
24
|
+
|
|
24
25
|
return (
|
|
25
26
|
<Root
|
|
26
27
|
{...others}
|
|
@@ -54,6 +55,7 @@ export interface RadioItemProps extends ComponentProps<typeof Item> {
|
|
|
54
55
|
|
|
55
56
|
export function RadioItem(props: RadioItemProps) {
|
|
56
57
|
const [local, others] = splitProps(props, ["class", "classList", "label", "description", "hideLabel"])
|
|
58
|
+
|
|
57
59
|
return (
|
|
58
60
|
<Item
|
|
59
61
|
{...others}
|