@opencode-ai/ui 1.17.18 → 1.17.20

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.
@@ -88,6 +88,10 @@ declare const icons: {
88
88
  viewBox: string;
89
89
  body: string;
90
90
  };
91
+ "outline-xmark": {
92
+ viewBox: string;
93
+ body: string;
94
+ };
91
95
  "outline-chevron-down": {
92
96
  viewBox: string;
93
97
  body: string;
@@ -128,6 +132,10 @@ declare const icons: {
128
132
  viewBox: string;
129
133
  body: string;
130
134
  };
135
+ "outline-share": {
136
+ viewBox: string;
137
+ body: string;
138
+ };
131
139
  reset: {
132
140
  viewBox: string;
133
141
  body: string;
@@ -11,6 +11,9 @@ export interface LineCommentV2Props extends ComponentProps<"div"> {
11
11
  actions?: JSX.Element;
12
12
  }
13
13
  export declare function LineCommentV2(props: LineCommentV2Props): JSX.Element;
14
+ export type LineCommentEditorV2Mention = {
15
+ items: (query: string) => string[] | Promise<string[]>;
16
+ };
14
17
  export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "children" | "onInput" | "onSubmit"> {
15
18
  /** Visible field label above the textarea (default: “Comment”). */
16
19
  heading?: JSX.Element | string;
@@ -24,5 +27,6 @@ export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "c
24
27
  cancelLabel?: string;
25
28
  submitLabel?: string;
26
29
  autofocus?: boolean;
30
+ mention?: LineCommentEditorV2Mention;
27
31
  }
28
32
  export declare function LineCommentEditorV2(props: LineCommentEditorV2Props): JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { type ComponentProps, type ParentProps } from "solid-js";
2
+ import "./split-button-v2.css";
3
+ export declare function SplitButtonV2(props: ParentProps<ComponentProps<"div">>): import("solid-js").JSX.Element;
4
+ export declare function SplitButtonV2Action(props: ComponentProps<"button">): import("solid-js").JSX.Element;
5
+ export declare function SplitButtonV2MenuTrigger(props: ComponentProps<"button">): import("solid-js").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode-ai/ui",
3
- "version": "1.17.18",
3
+ "version": "1.17.20",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -679,3 +679,123 @@
679
679
  [data-component="tabs-drag-preview"] > * {
680
680
  position: relative;
681
681
  }
682
+
683
+ body[data-new-layout] #review-panel [data-component="tabs"],
684
+ body[data-new-layout] #terminal-panel [data-component="tabs"],
685
+ body[data-new-layout] #review-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"],
686
+ body[data-new-layout] #terminal-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"] {
687
+ background-color: var(--v2-background-bg-base);
688
+
689
+ [data-slot="tabs-list"] {
690
+ background-color: var(--v2-background-bg-base);
691
+
692
+ > .sticky {
693
+ background-color: var(--v2-background-bg-base);
694
+
695
+ &::before {
696
+ background: linear-gradient(90deg, transparent, var(--v2-background-bg-base));
697
+ }
698
+ }
699
+ }
700
+ }
701
+
702
+ body[data-new-layout] #review-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"] {
703
+ [data-slot="tabs-list"] {
704
+ height: 52px;
705
+ padding-right: 12px;
706
+ gap: 8px;
707
+ --tabs-review-fade: 8px;
708
+ }
709
+
710
+ [data-slot="tabs-trigger-wrapper"] {
711
+ height: 28px;
712
+ padding-inline: 10px;
713
+ border: 0;
714
+ background-color: transparent;
715
+ box-shadow: none;
716
+
717
+ &::after {
718
+ display: none;
719
+ }
720
+
721
+ [data-slot="tabs-trigger"] {
722
+ padding: 0 !important;
723
+ }
724
+
725
+ &:has([data-slot="tabs-trigger-close-button"]) {
726
+ padding-right: 10px;
727
+ }
728
+
729
+ &:has([data-selected]) {
730
+ background-color: var(--v2-background-bg-layer-02);
731
+ box-shadow: none;
732
+ }
733
+ }
734
+
735
+ [data-slot="tabs-trigger"] {
736
+ font-family: Inter, sans-serif;
737
+ font-style: normal;
738
+ font-weight: 440;
739
+ font-size: 13px;
740
+ line-height: 16px;
741
+ letter-spacing: -0.04px;
742
+ color: var(--v2-text-text-muted);
743
+ font-variation-settings: "slnt" 0;
744
+ font-variant-numeric: tabular-nums;
745
+
746
+ &[data-selected] {
747
+ color: var(--v2-text-text-base);
748
+ }
749
+
750
+ .tab-fileicon-color,
751
+ .tab-fileicon-mono,
752
+ [data-slot="icon-svg"] {
753
+ margin-left: -2px;
754
+ }
755
+ }
756
+
757
+ /* Preview/temporary tabs: beat trigger + .text-14-medium upright defaults */
758
+ [data-slot="tabs-trigger"] .italic {
759
+ font-style: italic !important;
760
+ font-variation-settings: "slnt" -10 !important;
761
+ }
762
+ }
763
+
764
+ body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar {
765
+ width: 100%;
766
+ min-width: 0;
767
+ gap: 8px;
768
+ box-sizing: border-box;
769
+ border-bottom: 1px solid var(--border-weaker-base, var(--v2-border-border-weak));
770
+ background-color: var(--v2-background-bg-base);
771
+ }
772
+
773
+ body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar [data-slot="tabs-list"],
774
+ body[data-new-layout]
775
+ #review-panel
776
+ [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"]
777
+ .session-review-v2-tabs-bar
778
+ [data-slot="tabs-list"] {
779
+ flex: 1 1 0;
780
+ min-width: 0;
781
+ width: auto;
782
+ border-bottom: none;
783
+ }
784
+
785
+ body[data-new-layout]
786
+ #review-panel
787
+ [data-component="tabs"]
788
+ .session-review-v2-tabs-bar
789
+ [data-slot="tabs-list"]
790
+ > .sticky {
791
+ padding-right: 0;
792
+ }
793
+
794
+ body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app-slot {
795
+ flex-shrink: 0;
796
+ margin-left: auto;
797
+ }
798
+
799
+ body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app {
800
+ flex-shrink: 0;
801
+ }
@@ -14,12 +14,14 @@
14
14
  justify-content: flex-start;
15
15
  gap: 6px;
16
16
  padding-right: 8px;
17
+ overflow: visible;
17
18
  border: none;
18
19
  border-radius: 6px;
19
20
  background-color: transparent;
20
21
  color: var(--v2-text-text-muted);
21
22
  text-align: left;
22
23
  cursor: pointer;
24
+ scroll-margin-block: 8px;
23
25
  transition:
24
26
  background-color 120ms ease,
25
27
  color 120ms ease;
@@ -42,6 +44,21 @@
42
44
  background-color: var(--v2-overlay-simple-overlay-pressed);
43
45
  }
44
46
 
47
+ [data-component="file-tree-v2"] [data-slot="file-tree-v2-guide"] {
48
+ position: absolute;
49
+ top: 0;
50
+ margin-top: -2px;
51
+ height: calc(100% + 2px);
52
+ width: 1px;
53
+ pointer-events: none;
54
+ background-color: var(--v2-border-border-muted);
55
+ opacity: 0;
56
+ }
57
+
58
+ [data-component="file-tree-v2"]:hover [data-slot="file-tree-v2-guide"] {
59
+ opacity: 0.5;
60
+ }
61
+
45
62
  [data-component="file-tree-v2"] .filetree-icon--mono {
46
63
  color: var(--v2-icon-icon-muted);
47
64
  }
@@ -108,7 +125,7 @@
108
125
  }
109
126
 
110
127
  [data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="modified"] {
111
- opacity: 0;
128
+ color: var(--v2-state-fg-info);
112
129
  }
113
130
 
114
131
  [data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="added"] {
@@ -112,8 +112,8 @@
112
112
  color: var(--v2-text-text-base);
113
113
  }
114
114
 
115
- [data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"], [data-expanded]):not(
116
- :disabled
115
+ [data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not(
116
+ [data-expanded]
117
117
  ) {
118
118
  background-color: var(--v2-overlay-simple-overlay-hover);
119
119
  }
@@ -122,6 +122,10 @@
122
122
  background-color: var(--v2-overlay-simple-overlay-pressed);
123
123
  }
124
124
 
125
+ [data-component="icon-button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) {
126
+ background-color: var(--v2-overlay-simple-overlay-pressed);
127
+ }
128
+
125
129
  [data-component="icon-button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) {
126
130
  opacity: 0.5;
127
131
  cursor: not-allowed;
@@ -133,8 +137,8 @@
133
137
  color: var(--v2-icon-icon-muted);
134
138
  }
135
139
 
136
- [data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"], [data-expanded]):not(
137
- :disabled
140
+ [data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not(
141
+ [data-expanded]
138
142
  ) {
139
143
  background-color: var(--v2-overlay-simple-overlay-hover);
140
144
  }
@@ -89,6 +89,10 @@ const icons = {
89
89
  viewBox: "0 0 16 16",
90
90
  body: `<path d="M4.25 11.75L11.75 4.25M11.75 11.75L4.25 4.25" stroke="currentColor"/>`,
91
91
  },
92
+ "outline-xmark": {
93
+ viewBox: "0 0 16 16",
94
+ body: `<path fill-rule="evenodd" clip-rule="evenodd" d="M4.99487 5.70186L7.29297 7.99995L4.99487 10.2981L5.70198 11.0052L8.00008 8.70706L10.2982 11.0052L11.0053 10.2981L8.70718 7.99995L11.0053 5.70186L10.2982 4.99475L8.00008 7.29285L5.70198 4.99475L4.99487 5.70186Z" fill="currentColor"/>`,
95
+ },
92
96
  "outline-chevron-down": {
93
97
  viewBox: "0 0 16 16",
94
98
  body: `<path d="M5 6.5L8 9.5L11 6.5" stroke="currentColor"/>`,
@@ -129,6 +133,10 @@ const icons = {
129
133
  viewBox: "0 0 16 16",
130
134
  body: `<path d="M13.5555 6.66656V2.44434H9.33326M13.5555 2.44434L7.99993 7.99989M13.5555 9.33324V13.5555C13.5555 13.5555 12.7599 13.5555 11.7777 13.5555H2.44438C2.44438 13.5555 2.44438 12.7599 2.44438 11.7777V4.22213C2.44438 3.2399 2.44434 2.44435 2.44434 2.44435H6.66661" stroke="currentColor"/>`,
131
135
  },
136
+ "outline-share": {
137
+ viewBox: "0 0 16 16",
138
+ body: `<path d="M13.5554 10.4445V13.5556C13.5554 13.5556 12.7599 13.5556 11.7777 13.5556H4.22211C3.23989 13.5556 2.44434 13.5556 2.44434 13.5556V10.4445M4.88878 5.55557L7.99989 2.44446L11.111 5.55557M7.99989 2.44446L7.99989 9.11112" stroke="currentColor"/>`,
139
+ },
132
140
  reset: {
133
141
  viewBox: "0 0 20 20",
134
142
  body: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`,
@@ -6,6 +6,7 @@
6
6
 
7
7
  [data-component="line-comment-v2"] {
8
8
  box-sizing: border-box;
9
+ font-family: var(--v2-font-family-sans);
9
10
  font-variant-numeric: tabular-nums;
10
11
  min-width: 0;
11
12
  width: 100%;
@@ -202,3 +203,56 @@
202
203
  gap: 8px;
203
204
  flex-shrink: 0;
204
205
  }
206
+
207
+ [data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-list"] {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 4px;
211
+ max-height: 180px;
212
+ overflow: auto;
213
+ margin-top: 8px;
214
+ padding: 4px;
215
+ border: 0.5px solid var(--v2-border-border-muted);
216
+ border-radius: 6px;
217
+ background: var(--v2-background-bg-layer-02);
218
+ }
219
+
220
+ [data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"] {
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 8px;
224
+ width: 100%;
225
+ min-width: 0;
226
+ padding: 6px 8px;
227
+ border: 0;
228
+ border-radius: 4px;
229
+ background: transparent;
230
+ color: var(--v2-text-text-base);
231
+ text-align: left;
232
+ cursor: pointer;
233
+ }
234
+
235
+ [data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"][data-active] {
236
+ background: var(--v2-background-bg-layer-03);
237
+ }
238
+
239
+ [data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-path"] {
240
+ display: flex;
241
+ align-items: center;
242
+ min-width: 0;
243
+ font-size: 12px;
244
+ line-height: 16px;
245
+ }
246
+
247
+ [data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-dir"] {
248
+ min-width: 0;
249
+ color: var(--v2-text-text-muted);
250
+ white-space: nowrap;
251
+ overflow: hidden;
252
+ text-overflow: ellipsis;
253
+ }
254
+
255
+ [data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-file"] {
256
+ color: var(--v2-text-text-base);
257
+ white-space: nowrap;
258
+ }
@@ -1,4 +1,6 @@
1
- import { type ComponentProps, type JSX, Show, onMount, splitProps } from "solid-js"
1
+ import { For, Show, createSignal, onMount, splitProps, type ComponentProps, type JSX } from "solid-js"
2
+ import { FileIcon } from "../../components/file-icon"
3
+ import { useFilteredList } from "../../hooks"
2
4
  import { ButtonV2 } from "./button-v2"
3
5
  import "./line-comment-v2.css"
4
6
 
@@ -53,6 +55,10 @@ export function LineCommentV2(props: LineCommentV2Props) {
53
55
  )
54
56
  }
55
57
 
58
+ export type LineCommentEditorV2Mention = {
59
+ items: (query: string) => string[] | Promise<string[]>
60
+ }
61
+
56
62
  export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "children" | "onInput" | "onSubmit"> {
57
63
  /** Visible field label above the textarea (default: “Comment”). */
58
64
  heading?: JSX.Element | string
@@ -66,10 +72,22 @@ export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "c
66
72
  cancelLabel?: string
67
73
  submitLabel?: string
68
74
  autofocus?: boolean
75
+ mention?: LineCommentEditorV2Mention
76
+ }
77
+
78
+ function pathFilename(path: string) {
79
+ const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
80
+ return index === -1 ? path : path.slice(index + 1)
81
+ }
82
+
83
+ function pathDirectory(path: string) {
84
+ const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
85
+ return index === -1 ? "" : path.slice(0, index + 1)
69
86
  }
70
87
 
71
88
  export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
72
89
  let textareaRef: HTMLTextAreaElement | undefined
90
+ const [mentionOpen, setMentionOpen] = createSignal(false)
73
91
 
74
92
  const [local, rest] = splitProps(props, [
75
93
  "heading",
@@ -83,6 +101,7 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
83
101
  "cancelLabel",
84
102
  "submitLabel",
85
103
  "autofocus",
104
+ "mention",
86
105
  "class",
87
106
  "classList",
88
107
  ])
@@ -90,6 +109,78 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
90
109
  const heading = () => local.heading ?? "Comment"
91
110
  const canSubmit = () => local.value.trim().length > 0
92
111
 
112
+ const closeMention = () => {
113
+ setMentionOpen(false)
114
+ mention.clear()
115
+ }
116
+
117
+ const currentMention = () => {
118
+ const textarea = textareaRef
119
+ if (!textarea) return
120
+ if (!local.mention) return
121
+ if (textarea.selectionStart !== textarea.selectionEnd) return
122
+
123
+ const end = textarea.selectionStart
124
+ const match = textarea.value.slice(0, end).match(/@(\S*)$/)
125
+ if (!match) return
126
+
127
+ return {
128
+ query: match[1] ?? "",
129
+ start: end - match[0].length,
130
+ end,
131
+ }
132
+ }
133
+
134
+ function selectMention(item: { path: string } | undefined) {
135
+ if (!item) return
136
+
137
+ const textarea = textareaRef
138
+ const query = currentMention()
139
+ if (!textarea || !query) return
140
+
141
+ const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}`
142
+ const cursor = query.start + item.path.length + 2
143
+
144
+ local.onInput(value)
145
+ closeMention()
146
+
147
+ requestAnimationFrame(() => {
148
+ textarea.focus()
149
+ textarea.setSelectionRange(cursor, cursor)
150
+ })
151
+ }
152
+
153
+ const mention = useFilteredList<{ path: string }>({
154
+ items: async (query) => {
155
+ if (!local.mention) return []
156
+ if (!query.trim()) return []
157
+ const paths = await local.mention.items(query)
158
+ return paths.map((path) => ({ path }))
159
+ },
160
+ key: (item) => item.path,
161
+ filterKeys: ["path"],
162
+ skipFilter: () => true,
163
+ onSelect: selectMention,
164
+ })
165
+
166
+ const syncMention = () => {
167
+ const item = currentMention()
168
+ if (!item) {
169
+ closeMention()
170
+ return
171
+ }
172
+
173
+ setMentionOpen(true)
174
+ mention.onInput(item.query)
175
+ }
176
+
177
+ const selectActiveMention = () => {
178
+ const items = mention.flat()
179
+ if (items.length === 0) return
180
+ const active = mention.active()
181
+ selectMention(items.find((item) => item.path === active) ?? items[0])
182
+ }
183
+
93
184
  const submit = () => {
94
185
  const v = local.value.trim()
95
186
  if (!v) return
@@ -122,9 +213,39 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
122
213
  rows={local.rows ?? 3}
123
214
  placeholder={local.placeholder ?? "Add context for this change"}
124
215
  value={local.value}
125
- onInput={(e) => local.onInput(e.currentTarget.value)}
216
+ onInput={(e) => {
217
+ local.onInput(e.currentTarget.value)
218
+ syncMention()
219
+ }}
220
+ onClick={() => syncMention()}
221
+ onSelect={() => syncMention()}
126
222
  onKeyDown={(e) => {
127
223
  e.stopPropagation()
224
+ if (e.isComposing || e.keyCode === 229) return
225
+
226
+ if (mentionOpen()) {
227
+ if (e.key === "Escape") {
228
+ e.preventDefault()
229
+ closeMention()
230
+ return
231
+ }
232
+
233
+ if (e.key === "Tab") {
234
+ if (mention.flat().length === 0) return
235
+ e.preventDefault()
236
+ selectActiveMention()
237
+ return
238
+ }
239
+
240
+ const nav = e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter"
241
+ const ctrlNav = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && (e.key === "n" || e.key === "p")
242
+ if ((nav || ctrlNav) && mention.flat().length > 0) {
243
+ mention.onKeyDown(e)
244
+ e.preventDefault()
245
+ return
246
+ }
247
+ }
248
+
128
249
  if (e.key === "Escape") {
129
250
  e.preventDefault()
130
251
  e.currentTarget.blur()
@@ -137,6 +258,34 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
137
258
  }
138
259
  }}
139
260
  />
261
+ <Show when={mentionOpen() && mention.flat().length > 0}>
262
+ <div data-slot="line-comment-v2-mention-list">
263
+ <For each={mention.flat().slice(0, 10)}>
264
+ {(item) => {
265
+ const directory = item.path.endsWith("/") ? item.path : pathDirectory(item.path)
266
+ const name = item.path.endsWith("/") ? "" : pathFilename(item.path)
267
+ return (
268
+ <button
269
+ type="button"
270
+ data-slot="line-comment-v2-mention-item"
271
+ data-active={mention.active() === item.path ? "" : undefined}
272
+ onMouseDown={(event) => event.preventDefault()}
273
+ onMouseEnter={() => mention.setActive(item.path)}
274
+ onClick={() => selectMention(item)}
275
+ >
276
+ <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
277
+ <div data-slot="line-comment-v2-mention-path">
278
+ <span data-slot="line-comment-v2-mention-dir">{directory}</span>
279
+ <Show when={name}>
280
+ <span data-slot="line-comment-v2-mention-file">{name}</span>
281
+ </Show>
282
+ </div>
283
+ </button>
284
+ )
285
+ }}
286
+ </For>
287
+ </div>
288
+ </Show>
140
289
  </div>
141
290
  <div data-slot="line-comment-v2-footer">
142
291
  <div data-slot="line-comment-v2-footer-meta">{local.selection}</div>
@@ -38,7 +38,6 @@ export interface ProjectAvatarProps extends ComponentProps<"div"> {
38
38
 
39
39
  export function ProjectAvatar(props: ProjectAvatarProps) {
40
40
  const [split, rest] = splitProps(props, ["fallback", "src", "variant", "unread", "class", "classList", "style"])
41
- const src = split.src
42
41
  return (
43
42
  <div
44
43
  {...rest}
@@ -53,9 +52,9 @@ export function ProjectAvatar(props: ProjectAvatarProps) {
53
52
  <div
54
53
  data-slot="project-avatar-surface"
55
54
  data-variant={split.variant ?? "gray"}
56
- data-has-image={src ? "" : undefined}
55
+ data-has-image={split.src ? "" : undefined}
57
56
  >
58
- <Show when={src} fallback={first(split.fallback)}>
57
+ <Show when={split.src} fallback={first(split.fallback)}>
59
58
  {(value) => <img src={value()} draggable={false} data-slot="project-avatar-image" />}
60
59
  </Show>
61
60
  </div>
@@ -65,6 +65,7 @@
65
65
  box-shadow: var(--v2-elevation-button-neutral);
66
66
  flex: none;
67
67
  align-self: stretch;
68
+ user-select: none;
68
69
  transition:
69
70
  background 85ms ease-out,
70
71
  outline-color 85ms ease-out,
@@ -0,0 +1,99 @@
1
+ [data-component="split-button-v2"] {
2
+ display: inline-flex;
3
+ align-items: stretch;
4
+ flex-shrink: 0;
5
+ height: 28px;
6
+ border-radius: 6px;
7
+ background-color: transparent;
8
+ box-shadow: none;
9
+ overflow: hidden;
10
+ }
11
+
12
+ [data-component="split-button-v2"]:is(:hover, :has([data-component="split-button-v2-menu-trigger"][data-expanded])) {
13
+ box-shadow: inset 0 0 0 1px var(--v2-border-border-muted);
14
+ }
15
+
16
+ [data-component="split-button-v2-action"],
17
+ [data-component="split-button-v2-menu-trigger"] {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ height: 28px;
22
+ margin: 0;
23
+ padding: 0;
24
+ border: none;
25
+ background: transparent;
26
+ color: var(--v2-icon-icon-base);
27
+ cursor: default;
28
+ outline: none;
29
+ }
30
+
31
+ [data-component="split-button-v2-action"] {
32
+ width: 28px;
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ [data-component="split-button-v2-action"]:hover:not(:disabled) {
37
+ background-color: var(--v2-overlay-simple-overlay-hover);
38
+ }
39
+
40
+ [data-component="split-button-v2-action"]:is(:active, [data-pressed]):not(:disabled) {
41
+ background-color: var(--v2-overlay-simple-overlay-pressed);
42
+ }
43
+
44
+ [data-component="split-button-v2-action"]:is(:focus-visible, [data-focus]) {
45
+ outline: 2px solid var(--v2-border-border-focus);
46
+ outline-offset: -2px;
47
+ z-index: 1;
48
+ }
49
+
50
+ [data-component="split-button-v2-action"]:disabled {
51
+ opacity: 0.5;
52
+ cursor: not-allowed;
53
+ }
54
+
55
+ [data-component="split-button-v2-menu-trigger"] {
56
+ width: 20px;
57
+ flex-shrink: 0;
58
+ }
59
+
60
+ [data-component="split-button-v2-menu-trigger"]:is(:hover, [data-expanded]):not(:disabled) {
61
+ background-color: var(--v2-overlay-simple-overlay-hover);
62
+ }
63
+
64
+ [data-component="split-button-v2-menu-trigger"]:is(:active, [data-pressed]):not(:disabled) {
65
+ background-color: var(--v2-overlay-simple-overlay-pressed);
66
+ }
67
+
68
+ [data-component="split-button-v2-menu-trigger"]:is(:focus-visible, [data-focus]) {
69
+ outline: 2px solid var(--v2-border-border-focus);
70
+ outline-offset: -2px;
71
+ z-index: 1;
72
+ }
73
+
74
+ [data-component="split-button-v2-menu-trigger"]:disabled {
75
+ opacity: 0.5;
76
+ cursor: not-allowed;
77
+ }
78
+
79
+ [data-component="split-button-v2-action"] [data-component="app-icon"] {
80
+ width: 16px;
81
+ height: 16px;
82
+ }
83
+
84
+ [data-component="menu-v2-content"].open-in-app-v2-menu [data-component="app-icon"] {
85
+ width: 16px;
86
+ height: 16px;
87
+ flex-shrink: 0;
88
+ }
89
+
90
+ [data-component="menu-v2-content"].open-in-app-v2-menu [data-component="icon"] {
91
+ width: 16px;
92
+ height: 16px;
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ [data-component="menu-v2-content"].open-in-app-v2-menu [data-component="icon"] [data-slot="icon-svg"] {
97
+ width: 16px;
98
+ height: 16px;
99
+ }
@@ -0,0 +1,48 @@
1
+ import { splitProps, type ComponentProps, type ParentProps } from "solid-js"
2
+ import "./split-button-v2.css"
3
+
4
+ export function SplitButtonV2(props: ParentProps<ComponentProps<"div">>) {
5
+ const [split, rest] = splitProps(props, ["class", "classList", "children"])
6
+ return (
7
+ <div
8
+ data-component="split-button-v2"
9
+ classList={{
10
+ ...split.classList,
11
+ [split.class ?? ""]: !!split.class,
12
+ }}
13
+ {...rest}
14
+ >
15
+ {split.children}
16
+ </div>
17
+ )
18
+ }
19
+
20
+ export function SplitButtonV2Action(props: ComponentProps<"button">) {
21
+ const [split, rest] = splitProps(props, ["class", "classList"])
22
+ return (
23
+ <button
24
+ type="button"
25
+ data-component="split-button-v2-action"
26
+ classList={{
27
+ ...split.classList,
28
+ [split.class ?? ""]: !!split.class,
29
+ }}
30
+ {...rest}
31
+ />
32
+ )
33
+ }
34
+
35
+ export function SplitButtonV2MenuTrigger(props: ComponentProps<"button">) {
36
+ const [split, rest] = splitProps(props, ["class", "classList"])
37
+ return (
38
+ <button
39
+ type="button"
40
+ data-component="split-button-v2-menu-trigger"
41
+ classList={{
42
+ ...split.classList,
43
+ [split.class ?? ""]: !!split.class,
44
+ }}
45
+ {...rest}
46
+ />
47
+ )
48
+ }
@@ -192,6 +192,7 @@
192
192
  color: var(--v2-text-text-muted);
193
193
  font-size: 12px;
194
194
  font-weight: 500;
195
+ user-select: none;
195
196
  }
196
197
 
197
198
  [data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-trigger-wrapper"] {