@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.
Files changed (82) hide show
  1. package/dist/data-display/accordion/accordion.d.ts +3 -1
  2. package/dist/feedback/toast/toast.d.ts +3 -2
  3. package/dist/i18n/en.d.ts +5 -0
  4. package/dist/icons/catalog.d.ts +257 -0
  5. package/dist/icons/icon/additional-icons.d.ts +1 -0
  6. package/dist/icons/icon/icon.d.ts +1 -237
  7. package/package.json +5 -1
  8. package/src/actions/button/button.tsx +1 -0
  9. package/src/actions/icon-button/icon-button.tsx +1 -0
  10. package/src/actions/split-button/split-button.tsx +3 -0
  11. package/src/components/animated-number.tsx +13 -0
  12. package/src/components/app-icon.tsx +2 -0
  13. package/src/components/card.tsx +21 -0
  14. package/src/components/collapsible.tsx +1 -0
  15. package/src/components/context-menu.tsx +34 -0
  16. package/src/components/dock-surface.tsx +3 -0
  17. package/src/components/file-icon.tsx +12 -0
  18. package/src/components/image-preview.tsx +1 -0
  19. package/src/components/list.tsx +32 -0
  20. package/src/components/motion-spring.tsx +5 -0
  21. package/src/components/popover.tsx +12 -0
  22. package/src/components/provider-icon.tsx +1 -0
  23. package/src/components/resize-handle.tsx +8 -0
  24. package/src/components/scroll-view.tsx +39 -0
  25. package/src/components/spinner.tsx +2 -0
  26. package/src/components/text-field.tsx +6 -0
  27. package/src/components/text-reveal.tsx +19 -0
  28. package/src/components/text-strikethrough.tsx +8 -0
  29. package/src/components/typewriter.tsx +4 -0
  30. package/src/context/dialog.tsx +12 -0
  31. package/src/context/file.tsx +1 -0
  32. package/src/context/helper.tsx +4 -0
  33. package/src/context/i18n.tsx +20 -0
  34. package/src/context/index.ts +3 -0
  35. package/src/context/marked-base.tsx +7 -0
  36. package/src/context/marked-parser.tsx +7 -0
  37. package/src/context/marked.tsx +3 -0
  38. package/src/context/worker-pool.tsx +2 -0
  39. package/src/data-display/accordion/accordion.tsx +16 -2
  40. package/src/data-display/avatar/avatar.tsx +4 -0
  41. package/src/data-display/badge/badge.tsx +1 -0
  42. package/src/data-display/diff-changes/diff-changes.tsx +2 -0
  43. package/src/data-display/keybind/keybind.tsx +1 -0
  44. package/src/data-display/line-comment/line-comment.tsx +22 -0
  45. package/src/data-display/project-avatar/project-avatar.tsx +10 -0
  46. package/src/feedback/loader/loader.tsx +1 -0
  47. package/src/feedback/toast/description.ts +1 -0
  48. package/src/feedback/toast/toast.tsx +32 -6
  49. package/src/forms/checkbox/checkbox.tsx +1 -0
  50. package/src/forms/field/field.tsx +8 -0
  51. package/src/forms/inline-input/inline-input.tsx +4 -0
  52. package/src/forms/radio/radio.tsx +2 -0
  53. package/src/forms/select/select.tsx +11 -0
  54. package/src/forms/switch/switch.tsx +1 -0
  55. package/src/forms/text-input/text-input.tsx +3 -0
  56. package/src/hooks/create-auto-scroll.tsx +34 -0
  57. package/src/hooks/index.ts +1 -0
  58. package/src/hooks/use-filtered-list.tsx +14 -0
  59. package/src/i18n/en.ts +10 -0
  60. package/src/i18n/no.ts +1 -0
  61. package/src/icons/catalog.ts +273 -0
  62. package/src/icons/icon/additional-icons.ts +1 -0
  63. package/src/icons/icon/icon.tsx +10 -250
  64. package/src/layout/divider/divider.tsx +1 -0
  65. package/src/navigation/menu/menu.tsx +13 -0
  66. package/src/navigation/segmented-control/segmented-control.tsx +16 -0
  67. package/src/navigation/tab-state-indicator.tsx +1 -0
  68. package/src/navigation/tabs/tabs.css +6 -72
  69. package/src/navigation/tabs/tabs.tsx +18 -0
  70. package/src/overlays/dialog/dialog.tsx +3 -0
  71. package/src/overlays/tooltip/tooltip.tsx +15 -0
  72. package/src/storybook/scaffold.tsx +3 -0
  73. package/src/styles/theme.css +1 -1
  74. package/src/theme/color.ts +27 -0
  75. package/src/theme/context.tsx +58 -0
  76. package/src/theme/default-themes.ts +35 -0
  77. package/src/theme/index.ts +3 -0
  78. package/src/theme/loader.ts +11 -0
  79. package/src/theme/resolve.ts +28 -0
  80. package/src/theme/v2/foreground.ts +8 -0
  81. package/src/theme/v2/resolve.ts +10 -0
  82. package/src/typography/text-shimmer/text-shimmer.tsx +1 -0
@@ -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
  >
@@ -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
 
@@ -1,2 +1,3 @@
1
1
  export * from "./use-filtered-list"
2
+
2
3
  export * from "./create-auto-scroll"
@@ -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/en.ts CHANGED
@@ -108,6 +108,7 @@ const source = {
108
108
  "ui.messagePart.questions.dismissed": "Questions dismissed",
109
109
  "ui.messagePart.compaction": "Session compacted",
110
110
  "ui.messagePart.compaction.started": "Session compaction started",
111
+ "ui.messagePart.compaction.queued": "Session compaction queued",
111
112
  "ui.messagePart.compaction.running": "Compacting",
112
113
  "ui.messagePart.compaction.failed": "Session compaction failed",
113
114
  "ui.messagePart.compaction.cancelled": "Session compaction cancelled",
@@ -242,12 +243,16 @@ const source = {
242
243
  "ui.message.copyMessage": "Copy message",
243
244
  "ui.message.forkMessage": "Fork to new session",
244
245
  "ui.message.revertMessage": "Revert message",
246
+ "ui.message.moveToQueue": "Move to queue",
247
+ "ui.message.pending": "Pending",
248
+ "ui.message.deletePending": "Delete",
245
249
  "ui.message.copyResponse": "Copy response",
246
250
  "ui.message.copied": "Copied",
247
251
  "ui.message.thought": "Thought",
248
252
  "ui.message.duration.seconds": "{{count}}s",
249
253
  "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
250
254
  "ui.message.interrupted": "Interrupted",
255
+ "ui.message.modelVariant": "{{model}} ({{variant}})",
251
256
  "ui.sessionTimeline.notice.model": "Model",
252
257
  "ui.sessionTimeline.notice.modelSwitched": "Switched to {{model}}",
253
258
  "ui.sessionTimeline.notice.agentChanged": "Agent changed",
@@ -275,10 +280,15 @@ const source = {
275
280
  } satisfies Record<string, string>
276
281
 
277
282
  export type Key = keyof typeof source
283
+
278
284
  export type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other"
285
+
279
286
  export type PluralKey = {
280
287
  [Entry in Key]: Entry extends `${infer Base}.other` ? (`${Base}.one` extends Key ? Base : never) : never
281
288
  }[Key]
289
+
282
290
  export type PluralLookupKey = `${PluralKey}.${PluralCategory}`
291
+
283
292
  export type LocaleKey = Key | PluralLookupKey
293
+
284
294
  export const dict: typeof source & Record<string, string> = source
package/src/i18n/no.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { dict as en } from "./en"
2
+
2
3
  type Keys = keyof typeof en
3
4
 
4
5
  export const dict: Record<Keys, string> = {