@opencode/ui 0.0.0-dev-20607 → 0.0.0-dev-20610
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
package/src/context/i18n.tsx
CHANGED
|
@@ -4,14 +4,21 @@ import { dict as en } from "../i18n/en"
|
|
|
4
4
|
import type { Key, LocaleKey, PluralCategory, PluralKey, PluralLookupKey } from "../i18n/en"
|
|
5
5
|
|
|
6
6
|
export type UiI18nKey = Key
|
|
7
|
+
|
|
7
8
|
export type UiI18nPluralKey = PluralKey
|
|
9
|
+
|
|
8
10
|
export type UiPluralCategory = PluralCategory
|
|
11
|
+
|
|
9
12
|
export type UiI18nPluralLookupKey = PluralLookupKey
|
|
13
|
+
|
|
10
14
|
export type UiI18nLocaleKey = LocaleKey
|
|
15
|
+
|
|
11
16
|
type UiTranslationKey<Value extends string> = Value extends UiI18nPluralLookupKey ? never : Value
|
|
17
|
+
|
|
12
18
|
export type UiI18nOrdinaryKey = UiTranslationKey<UiI18nKey>
|
|
13
19
|
|
|
14
20
|
export type UiI18nParams = Record<string, string | number | boolean>
|
|
21
|
+
|
|
15
22
|
export type UiTranslate = <Value extends string>(key: UiTranslationKey<Value>, params?: UiI18nParams) => string
|
|
16
23
|
|
|
17
24
|
export type UiI18nSource = {
|
|
@@ -33,10 +40,13 @@ const rules = new Map<string, Intl.PluralRules>()
|
|
|
33
40
|
|
|
34
41
|
export function pluralCategory(locale: string, count: number): UiPluralCategory {
|
|
35
42
|
const cached = rules.get(locale)
|
|
43
|
+
|
|
36
44
|
if (cached) return cached.select(count)
|
|
37
45
|
const next = new Intl.PluralRules(locale)
|
|
46
|
+
|
|
38
47
|
if (rules.size >= 32) rules.delete(rules.keys().next().value!)
|
|
39
48
|
rules.set(locale, next)
|
|
49
|
+
|
|
40
50
|
return next.select(count)
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -46,13 +56,17 @@ export function pluralKey(key: UiI18nPluralKey, category: UiPluralCategory) {
|
|
|
46
56
|
|
|
47
57
|
export function localizedListSeparator(locale: string, index: number, count: number) {
|
|
48
58
|
if (index <= 0 || index >= count) return ""
|
|
59
|
+
|
|
49
60
|
const parts = new Intl.ListFormat(locale, { style: "long", type: "conjunction" }).formatToParts(
|
|
50
61
|
Array.from({ length: count }, (_, item) => String(item)),
|
|
51
62
|
)
|
|
63
|
+
|
|
52
64
|
const current = parts.findIndex((part) => part.type === "element" && part.value === String(index))
|
|
65
|
+
|
|
53
66
|
const previous = parts.findLastIndex(
|
|
54
67
|
(part, partIndex) => partIndex < current && part.type === "element" && part.value === String(index - 1),
|
|
55
68
|
)
|
|
69
|
+
|
|
56
70
|
return parts
|
|
57
71
|
.slice(previous + 1, current)
|
|
58
72
|
.map((part) => part.value)
|
|
@@ -61,9 +75,11 @@ export function localizedListSeparator(locale: string, index: number, count: num
|
|
|
61
75
|
|
|
62
76
|
function resolveTemplate(text: string, params?: UiI18nParams) {
|
|
63
77
|
if (!params) return text
|
|
78
|
+
|
|
64
79
|
return text.replace(/{{\s*([^}]+?)\s*}}/g, (_, rawKey) => {
|
|
65
80
|
const key = String(rawKey)
|
|
66
81
|
const value = params[key]
|
|
82
|
+
|
|
67
83
|
return value === undefined ? "" : String(value)
|
|
68
84
|
})
|
|
69
85
|
}
|
|
@@ -91,6 +107,7 @@ const fallbackSource: UiI18nSource = {
|
|
|
91
107
|
locale: () => "en",
|
|
92
108
|
t: (key, params) => {
|
|
93
109
|
const value = en[key as UiI18nKey] ?? String(key)
|
|
110
|
+
|
|
94
111
|
return resolveTemplate(value, params)
|
|
95
112
|
},
|
|
96
113
|
plural: (key, count, params) =>
|
|
@@ -98,15 +115,18 @@ const fallbackSource: UiI18nSource = {
|
|
|
98
115
|
pluralForm: (key, category, params) => {
|
|
99
116
|
const values = en as Partial<Record<UiI18nLocaleKey, string>>
|
|
100
117
|
const value = values[pluralKey(key, category)] ?? values[`${key}.other`] ?? `${key}.other`
|
|
118
|
+
|
|
101
119
|
return resolveTemplate(value, params)
|
|
102
120
|
},
|
|
103
121
|
}
|
|
122
|
+
|
|
104
123
|
const fallback = createUiI18n(fallbackSource)
|
|
105
124
|
|
|
106
125
|
const Context = createContext<UiI18n>(fallback)
|
|
107
126
|
|
|
108
127
|
function UiI18nProvider(props: ParentProps<{ value: UiI18nSource }>) {
|
|
109
128
|
const value = createUiI18n(props.value)
|
|
129
|
+
|
|
110
130
|
return (
|
|
111
131
|
<I18nProvider locale={(value.layoutLocale ?? value.locale)()}>
|
|
112
132
|
<Context.Provider value={value}>{props.children}</Context.Provider>
|
package/src/context/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export function createMarkdownBase() {
|
|
|
5
5
|
renderer: {
|
|
6
6
|
link({ href, title, text }) {
|
|
7
7
|
const titleAttr = title ? ` title="${title}"` : ""
|
|
8
|
+
|
|
8
9
|
return `<a href="${href}"${titleAttr} class="external-link" target="_blank" rel="noopener noreferrer">${text}</a>`
|
|
9
10
|
},
|
|
10
11
|
},
|
|
@@ -16,6 +17,7 @@ let smallParser: Marked | undefined
|
|
|
16
17
|
export function parseSmallMarkdown(text: string) {
|
|
17
18
|
// Any possible KaTeX delimiter stays on the worker, including escaped ones.
|
|
18
19
|
if (text.length > 1024 || text.includes("\\(") || text.includes("$$")) return
|
|
20
|
+
|
|
19
21
|
// Ordinary prose does not need the block/inline lexer's cold regular expressions.
|
|
20
22
|
// Keep every possible Markdown construct, autolink, and hard break on that lexer.
|
|
21
23
|
if (
|
|
@@ -30,8 +32,10 @@ export function parseSmallMarkdown(text: string) {
|
|
|
30
32
|
.map((paragraph) => `<p>${paragraph.replace(/["']/g, (value) => (value === '"' ? """ : "'"))}</p>\n`)
|
|
31
33
|
.join("")
|
|
32
34
|
}
|
|
35
|
+
|
|
33
36
|
const parser = (smallParser ??= createMarkdownBase())
|
|
34
37
|
const paragraphs = text.replace(/\r\n?/g, "\n")
|
|
38
|
+
|
|
35
39
|
// Inline formatting in ordinary paragraphs does not need the block lexer.
|
|
36
40
|
// Any possible block opener, HTML, table, or reference definition stays on it.
|
|
37
41
|
if (
|
|
@@ -45,11 +49,14 @@ export function parseSmallMarkdown(text: string) {
|
|
|
45
49
|
.map((paragraph) => `<p>${parser.parseInline(paragraph, { async: false })}</p>\n`)
|
|
46
50
|
.join("")
|
|
47
51
|
}
|
|
52
|
+
|
|
48
53
|
const tokens = parser.lexer(text)
|
|
49
54
|
let code = false
|
|
50
55
|
parser.walkTokens(tokens, (token) => {
|
|
51
56
|
if (token.type === "code") code = true
|
|
52
57
|
})
|
|
58
|
+
|
|
53
59
|
if (code) return
|
|
60
|
+
|
|
54
61
|
return parser.parser(tokens)
|
|
55
62
|
}
|
|
@@ -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",
|
|
@@ -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
|
}
|
|
@@ -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}
|
|
@@ -17,13 +17,17 @@ function groupOptions<T>(options: T[], groupBy?: (x: T) => string): { category:
|
|
|
17
17
|
if (!groupBy) {
|
|
18
18
|
return [{ category: "", options }]
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
const map = new Map<string, T[]>()
|
|
22
|
+
|
|
21
23
|
for (const opt of options) {
|
|
22
24
|
const key = groupBy(opt)
|
|
23
25
|
const arr = map.get(key)
|
|
26
|
+
|
|
24
27
|
if (arr) arr.push(opt)
|
|
25
28
|
else map.set(key, [opt])
|
|
26
29
|
}
|
|
30
|
+
|
|
27
31
|
return [...map.entries()].map(([category, opts]) => ({ category, options: opts }))
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -110,11 +114,15 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
110
114
|
|
|
111
115
|
const move = (item: T | undefined) => {
|
|
112
116
|
if (!local.onHighlight) return
|
|
117
|
+
|
|
113
118
|
if (!item) {
|
|
114
119
|
stop()
|
|
120
|
+
|
|
115
121
|
return
|
|
116
122
|
}
|
|
123
|
+
|
|
117
124
|
const key = keyFor(item)
|
|
125
|
+
|
|
118
126
|
if (state.key === key) return
|
|
119
127
|
state.cleanup?.()
|
|
120
128
|
state.cleanup = local.onHighlight(item)
|
|
@@ -178,6 +186,7 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
178
186
|
}}
|
|
179
187
|
onOpenChange={(open) => {
|
|
180
188
|
local.onOpenChange?.(open)
|
|
189
|
+
|
|
181
190
|
if (!open) stop()
|
|
182
191
|
}}
|
|
183
192
|
>
|
|
@@ -200,7 +209,9 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
200
209
|
<Value<T> data-slot="select-v2-value-text" class={local.valueClass}>
|
|
201
210
|
{(st) => {
|
|
202
211
|
const selected = st.selectedOption()
|
|
212
|
+
|
|
203
213
|
if (local.label && selected != null) return local.label(selected)
|
|
214
|
+
|
|
204
215
|
return selected != null ? (selected as string) : ""
|
|
205
216
|
}}
|
|
206
217
|
</Value>
|
|
@@ -12,6 +12,7 @@ export interface SwitchProps extends ParentProps<ComponentProps<typeof Root>> {
|
|
|
12
12
|
|
|
13
13
|
export function Switch(props: SwitchProps) {
|
|
14
14
|
const [local, others] = splitProps(props, ["children", "class", "appearance", "hideLabel", "description"])
|
|
15
|
+
|
|
15
16
|
return (
|
|
16
17
|
<Root {...others} class={local.class} data-component="switch" data-appearance={local.appearance ?? "compact"}>
|
|
17
18
|
<Input data-slot="switch-input" />
|
|
@@ -29,6 +29,7 @@ export interface TextInputProps extends Omit<ComponentProps<"input">, "type"> {
|
|
|
29
29
|
|
|
30
30
|
export function TextInput(props: TextInputProps) {
|
|
31
31
|
const i18n = useI18n()
|
|
32
|
+
|
|
32
33
|
const [local, inputProps] = splitProps(props, [
|
|
33
34
|
"class",
|
|
34
35
|
"classList",
|
|
@@ -89,8 +90,10 @@ export function TextInput(props: TextInputProps) {
|
|
|
89
90
|
onClick={(event) => {
|
|
90
91
|
if (local.showClearButton) {
|
|
91
92
|
local.onClearClick?.(event)
|
|
93
|
+
|
|
92
94
|
return
|
|
93
95
|
}
|
|
96
|
+
|
|
94
97
|
local.onCopyClick?.(event)
|
|
95
98
|
}}
|
|
96
99
|
>
|