chore: merge dev into v2 (#36770)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: James Long <longster@gmail.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Jay <air@live.ca> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: James Long <jlongster@users.noreply.github.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com> Co-authored-by: Victor Navarro <vn4varro@gmail.com> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Nabs <nabil@instafork.com> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: AidenGeunGeun <eastlandwyvern@gmail.com> Co-authored-by: Mark <geraint0923@users.noreply.github.com>
This commit is contained in:
co-authored by
opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Aiden Cline
usrnk1
Aarav Sareen
Luke Parker
Brendan Allan
Kit Langton
James Long
Adam
opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Jay
Simon Klee
Jay
Jack
David Hill
Brendan Allan
opencode
Aiden Cline
James Long
Dustin Deus
冯基魁
Frank
Aiden Cline
Victor Navarro
Dax Raad
Dax
Nabs
Vladimir Glafirov
AidenGeunGeun
Mark
parent
26e27c7a7f
commit
634386fe0f
@@ -698,3 +698,104 @@ body[data-new-layout] #terminal-panel [data-component="tabs"][data-variant="norm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body[data-new-layout] #review-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"] {
|
||||
[data-slot="tabs-list"] {
|
||||
height: 52px;
|
||||
padding-right: 12px;
|
||||
gap: 8px;
|
||||
--tabs-review-fade: 8px;
|
||||
}
|
||||
|
||||
[data-slot="tabs-trigger-wrapper"] {
|
||||
height: 28px;
|
||||
padding-inline: 10px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-slot="tabs-trigger"] {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
&:has([data-slot="tabs-trigger-close-button"]) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&:has([data-selected]) {
|
||||
background-color: var(--v2-background-bg-layer-02);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="tabs-trigger"] {
|
||||
font-family: Inter, sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-muted);
|
||||
font-variation-settings: "slnt" 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
.tab-fileicon-color,
|
||||
.tab-fileicon-mono,
|
||||
[data-slot="icon-svg"] {
|
||||
margin-left: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Preview/temporary tabs: beat trigger + .text-14-medium upright defaults */
|
||||
[data-slot="tabs-trigger"] .italic {
|
||||
font-style: italic !important;
|
||||
font-variation-settings: "slnt" -10 !important;
|
||||
}
|
||||
}
|
||||
|
||||
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--border-weaker-base, var(--v2-border-border-weak));
|
||||
background-color: var(--v2-background-bg-base);
|
||||
}
|
||||
|
||||
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar [data-slot="tabs-list"],
|
||||
body[data-new-layout]
|
||||
#review-panel
|
||||
[data-component="tabs"][data-variant="normal"][data-orientation="horizontal"]
|
||||
.session-review-v2-tabs-bar
|
||||
[data-slot="tabs-list"] {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
body[data-new-layout]
|
||||
#review-panel
|
||||
[data-component="tabs"]
|
||||
.session-review-v2-tabs-bar
|
||||
[data-slot="tabs-list"]
|
||||
> .sticky {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app-slot {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { Marked } from "marked"
|
||||
import { markedCodeSpanBoundary } from "./marked-code-span"
|
||||
|
||||
test("preserves code spans adjacent to tildes", async () => {
|
||||
const marked = new Marked(markedCodeSpanBoundary)
|
||||
|
||||
expect(await marked.parse("~`0.1576` to measurement-window-only `0.00092`")).toBe(
|
||||
"<p>~<code>0.1576</code> to measurement-window-only <code>0.00092</code></p>\n",
|
||||
)
|
||||
expect(await marked.parse("`before`~`after`")).toBe("<p><code>before</code>~<code>after</code></p>\n")
|
||||
expect(await marked.parse("~~`deleted code`~~")).toBe("<p><del><code>deleted code</code></del></p>\n")
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MarkedExtension } from "marked"
|
||||
|
||||
// Keep adjacent tilde and backtick runs separate until markedjs/marked#4011 is released.
|
||||
export const markedCodeSpanBoundary = {
|
||||
tokenizer: {
|
||||
inlineText(src) {
|
||||
const match = /^(`+(?=~)|~+(?=`))/.exec(src)
|
||||
if (!match) return false
|
||||
return {
|
||||
type: "text",
|
||||
raw: match[0],
|
||||
text: match[0],
|
||||
escaped: this.lexer.state.inRawBlock,
|
||||
}
|
||||
},
|
||||
},
|
||||
} satisfies MarkedExtension
|
||||
@@ -3,6 +3,7 @@ import markedShiki from "marked-shiki"
|
||||
import katex from "katex"
|
||||
import { bundledLanguages, type BundledLanguage } from "shiki"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { markedCodeSpanBoundary } from "./marked-code-span"
|
||||
import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/diffs"
|
||||
|
||||
export const OpenCodeTheme = {
|
||||
@@ -521,6 +522,7 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext(
|
||||
name: "Marked",
|
||||
init: (props: { nativeParser?: NativeMarkdownParser }) => {
|
||||
const jsParser = marked.use(
|
||||
markedCodeSpanBoundary,
|
||||
{
|
||||
renderer: {
|
||||
link({ href, title, text }) {
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
padding-right: 8px;
|
||||
overflow: visible;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background-color: transparent;
|
||||
color: var(--v2-text-text-muted);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
scroll-margin-block: 8px;
|
||||
transition:
|
||||
background-color 120ms ease,
|
||||
color 120ms ease;
|
||||
@@ -42,6 +44,21 @@
|
||||
background-color: var(--v2-overlay-simple-overlay-pressed);
|
||||
}
|
||||
|
||||
[data-component="file-tree-v2"] [data-slot="file-tree-v2-guide"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-top: -2px;
|
||||
height: calc(100% + 2px);
|
||||
width: 1px;
|
||||
pointer-events: none;
|
||||
background-color: var(--v2-border-border-muted);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-component="file-tree-v2"]:hover [data-slot="file-tree-v2-guide"] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
[data-component="file-tree-v2"] .filetree-icon--mono {
|
||||
color: var(--v2-icon-icon-muted);
|
||||
}
|
||||
@@ -108,7 +125,7 @@
|
||||
}
|
||||
|
||||
[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="modified"] {
|
||||
opacity: 0;
|
||||
color: var(--v2-state-fg-info);
|
||||
}
|
||||
|
||||
[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="added"] {
|
||||
|
||||
@@ -89,6 +89,10 @@ const icons = {
|
||||
viewBox: "0 0 16 16",
|
||||
body: `<path d="M4.25 11.75L11.75 4.25M11.75 11.75L4.25 4.25" stroke="currentColor"/>`,
|
||||
},
|
||||
"outline-xmark": {
|
||||
viewBox: "0 0 16 16",
|
||||
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"/>`,
|
||||
},
|
||||
"outline-chevron-down": {
|
||||
viewBox: "0 0 16 16",
|
||||
body: `<path d="M5 6.5L8 9.5L11 6.5" stroke="currentColor"/>`,
|
||||
@@ -129,6 +133,10 @@ const icons = {
|
||||
viewBox: "0 0 16 16",
|
||||
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"/>`,
|
||||
},
|
||||
"outline-share": {
|
||||
viewBox: "0 0 16 16",
|
||||
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"/>`,
|
||||
},
|
||||
reset: {
|
||||
viewBox: "0 0 20 20",
|
||||
body: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
|
||||
@@ -203,3 +203,56 @@
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-list"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
max-height: 180px;
|
||||
overflow: auto;
|
||||
margin-top: 8px;
|
||||
padding: 4px;
|
||||
border: 0.5px solid var(--v2-border-border-muted);
|
||||
border-radius: 6px;
|
||||
background: var(--v2-background-bg-layer-02);
|
||||
}
|
||||
|
||||
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 6px 8px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--v2-text-text-base);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"][data-active] {
|
||||
background: var(--v2-background-bg-layer-03);
|
||||
}
|
||||
|
||||
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-path"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-dir"] {
|
||||
min-width: 0;
|
||||
color: var(--v2-text-text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-file"] {
|
||||
color: var(--v2-text-text-base);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { type ComponentProps, type JSX, Show, onMount, splitProps } from "solid-js"
|
||||
import { For, Show, createSignal, onMount, splitProps, type ComponentProps, type JSX } from "solid-js"
|
||||
import { FileIcon } from "../../components/file-icon"
|
||||
import { useFilteredList } from "../../hooks"
|
||||
import { ButtonV2 } from "./button-v2"
|
||||
import "./line-comment-v2.css"
|
||||
|
||||
@@ -53,6 +55,10 @@ export function LineCommentV2(props: LineCommentV2Props) {
|
||||
)
|
||||
}
|
||||
|
||||
export type LineCommentEditorV2Mention = {
|
||||
items: (query: string) => string[] | Promise<string[]>
|
||||
}
|
||||
|
||||
export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "children" | "onInput" | "onSubmit"> {
|
||||
/** Visible field label above the textarea (default: “Comment”). */
|
||||
heading?: JSX.Element | string
|
||||
@@ -66,10 +72,22 @@ export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "c
|
||||
cancelLabel?: string
|
||||
submitLabel?: string
|
||||
autofocus?: boolean
|
||||
mention?: LineCommentEditorV2Mention
|
||||
}
|
||||
|
||||
function pathFilename(path: string) {
|
||||
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
||||
return index === -1 ? path : path.slice(index + 1)
|
||||
}
|
||||
|
||||
function pathDirectory(path: string) {
|
||||
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
||||
return index === -1 ? "" : path.slice(0, index + 1)
|
||||
}
|
||||
|
||||
export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
||||
let textareaRef: HTMLTextAreaElement | undefined
|
||||
const [mentionOpen, setMentionOpen] = createSignal(false)
|
||||
|
||||
const [local, rest] = splitProps(props, [
|
||||
"heading",
|
||||
@@ -83,6 +101,7 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
||||
"cancelLabel",
|
||||
"submitLabel",
|
||||
"autofocus",
|
||||
"mention",
|
||||
"class",
|
||||
"classList",
|
||||
])
|
||||
@@ -90,6 +109,78 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
||||
const heading = () => local.heading ?? "Comment"
|
||||
const canSubmit = () => local.value.trim().length > 0
|
||||
|
||||
const closeMention = () => {
|
||||
setMentionOpen(false)
|
||||
mention.clear()
|
||||
}
|
||||
|
||||
const currentMention = () => {
|
||||
const textarea = textareaRef
|
||||
if (!textarea) return
|
||||
if (!local.mention) return
|
||||
if (textarea.selectionStart !== textarea.selectionEnd) return
|
||||
|
||||
const end = textarea.selectionStart
|
||||
const match = textarea.value.slice(0, end).match(/@(\S*)$/)
|
||||
if (!match) return
|
||||
|
||||
return {
|
||||
query: match[1] ?? "",
|
||||
start: end - match[0].length,
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
||||
function selectMention(item: { path: string } | undefined) {
|
||||
if (!item) return
|
||||
|
||||
const textarea = textareaRef
|
||||
const query = currentMention()
|
||||
if (!textarea || !query) return
|
||||
|
||||
const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}`
|
||||
const cursor = query.start + item.path.length + 2
|
||||
|
||||
local.onInput(value)
|
||||
closeMention()
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
textarea.focus()
|
||||
textarea.setSelectionRange(cursor, cursor)
|
||||
})
|
||||
}
|
||||
|
||||
const mention = useFilteredList<{ path: string }>({
|
||||
items: async (query) => {
|
||||
if (!local.mention) return []
|
||||
if (!query.trim()) return []
|
||||
const paths = await local.mention.items(query)
|
||||
return paths.map((path) => ({ path }))
|
||||
},
|
||||
key: (item) => item.path,
|
||||
filterKeys: ["path"],
|
||||
skipFilter: () => true,
|
||||
onSelect: selectMention,
|
||||
})
|
||||
|
||||
const syncMention = () => {
|
||||
const item = currentMention()
|
||||
if (!item) {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
|
||||
setMentionOpen(true)
|
||||
mention.onInput(item.query)
|
||||
}
|
||||
|
||||
const selectActiveMention = () => {
|
||||
const items = mention.flat()
|
||||
if (items.length === 0) return
|
||||
const active = mention.active()
|
||||
selectMention(items.find((item) => item.path === active) ?? items[0])
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
const v = local.value.trim()
|
||||
if (!v) return
|
||||
@@ -122,9 +213,39 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
||||
rows={local.rows ?? 3}
|
||||
placeholder={local.placeholder ?? "Add context for this change"}
|
||||
value={local.value}
|
||||
onInput={(e) => local.onInput(e.currentTarget.value)}
|
||||
onInput={(e) => {
|
||||
local.onInput(e.currentTarget.value)
|
||||
syncMention()
|
||||
}}
|
||||
onClick={() => syncMention()}
|
||||
onSelect={() => syncMention()}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.isComposing || e.keyCode === 229) return
|
||||
|
||||
if (mentionOpen()) {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === "Tab") {
|
||||
if (mention.flat().length === 0) return
|
||||
e.preventDefault()
|
||||
selectActiveMention()
|
||||
return
|
||||
}
|
||||
|
||||
const nav = e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter"
|
||||
const ctrlNav = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && (e.key === "n" || e.key === "p")
|
||||
if ((nav || ctrlNav) && mention.flat().length > 0) {
|
||||
mention.onKeyDown(e)
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
e.currentTarget.blur()
|
||||
@@ -137,6 +258,34 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Show when={mentionOpen() && mention.flat().length > 0}>
|
||||
<div data-slot="line-comment-v2-mention-list">
|
||||
<For each={mention.flat().slice(0, 10)}>
|
||||
{(item) => {
|
||||
const directory = item.path.endsWith("/") ? item.path : pathDirectory(item.path)
|
||||
const name = item.path.endsWith("/") ? "" : pathFilename(item.path)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-slot="line-comment-v2-mention-item"
|
||||
data-active={mention.active() === item.path ? "" : undefined}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onMouseEnter={() => mention.setActive(item.path)}
|
||||
onClick={() => selectMention(item)}
|
||||
>
|
||||
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
|
||||
<div data-slot="line-comment-v2-mention-path">
|
||||
<span data-slot="line-comment-v2-mention-dir">{directory}</span>
|
||||
<Show when={name}>
|
||||
<span data-slot="line-comment-v2-mention-file">{name}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div data-slot="line-comment-v2-footer">
|
||||
<div data-slot="line-comment-v2-footer-meta">{local.selection}</div>
|
||||
|
||||
@@ -38,7 +38,6 @@ export interface ProjectAvatarProps extends ComponentProps<"div"> {
|
||||
|
||||
export function ProjectAvatar(props: ProjectAvatarProps) {
|
||||
const [split, rest] = splitProps(props, ["fallback", "src", "variant", "unread", "class", "classList", "style"])
|
||||
const src = split.src
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
@@ -53,9 +52,9 @@ export function ProjectAvatar(props: ProjectAvatarProps) {
|
||||
<div
|
||||
data-slot="project-avatar-surface"
|
||||
data-variant={split.variant ?? "gray"}
|
||||
data-has-image={src ? "" : undefined}
|
||||
data-has-image={split.src ? "" : undefined}
|
||||
>
|
||||
<Show when={src} fallback={first(split.fallback)}>
|
||||
<Show when={split.src} fallback={first(split.fallback)}>
|
||||
{(value) => <img src={value()} draggable={false} data-slot="project-avatar-image" />}
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
[data-component="split-button-v2"] {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
flex-shrink: 0;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2"]:is(:hover, :has([data-component="split-button-v2-menu-trigger"][data-expanded])) {
|
||||
box-shadow: inset 0 0 0 1px var(--v2-border-border-muted);
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"],
|
||||
[data-component="split-button-v2-menu-trigger"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 28px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--v2-icon-icon-base);
|
||||
cursor: default;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"] {
|
||||
width: 28px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"]:hover:not(:disabled) {
|
||||
background-color: var(--v2-overlay-simple-overlay-hover);
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"]:is(:active, [data-pressed]):not(:disabled) {
|
||||
background-color: var(--v2-overlay-simple-overlay-pressed);
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"]:is(:focus-visible, [data-focus]) {
|
||||
outline: 2px solid var(--v2-border-border-focus);
|
||||
outline-offset: -2px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"]:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-menu-trigger"] {
|
||||
width: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-menu-trigger"]:is(:hover, [data-expanded]):not(:disabled) {
|
||||
background-color: var(--v2-overlay-simple-overlay-hover);
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-menu-trigger"]:is(:active, [data-pressed]):not(:disabled) {
|
||||
background-color: var(--v2-overlay-simple-overlay-pressed);
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-menu-trigger"]:is(:focus-visible, [data-focus]) {
|
||||
outline: 2px solid var(--v2-border-border-focus);
|
||||
outline-offset: -2px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-menu-trigger"]:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
[data-component="split-button-v2-action"] [data-component="app-icon"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
[data-component="menu-v2-content"].open-in-app-v2-menu [data-component="app-icon"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-component="menu-v2-content"].open-in-app-v2-menu [data-component="icon"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-component="menu-v2-content"].open-in-app-v2-menu [data-component="icon"] [data-slot="icon-svg"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { splitProps, type ComponentProps, type ParentProps } from "solid-js"
|
||||
import "./split-button-v2.css"
|
||||
|
||||
export function SplitButtonV2(props: ParentProps<ComponentProps<"div">>) {
|
||||
const [split, rest] = splitProps(props, ["class", "classList", "children"])
|
||||
return (
|
||||
<div
|
||||
data-component="split-button-v2"
|
||||
classList={{
|
||||
...split.classList,
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{split.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SplitButtonV2Action(props: ComponentProps<"button">) {
|
||||
const [split, rest] = splitProps(props, ["class", "classList"])
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-component="split-button-v2-action"
|
||||
classList={{
|
||||
...split.classList,
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SplitButtonV2MenuTrigger(props: ComponentProps<"button">) {
|
||||
const [split, rest] = splitProps(props, ["class", "classList"])
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-component="split-button-v2-menu-trigger"
|
||||
classList={{
|
||||
...split.classList,
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user