mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-07 07:48:16 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ee254eace | ||
|
|
3415280302 | ||
|
|
26ece25e1c |
No files matched your search
@@ -56,6 +56,71 @@ test("tab strip keeps draft tabs as wide as session tabs and navigates on mouse
|
||||
await expectPath(page, sessionHref(b.id))
|
||||
})
|
||||
|
||||
test("inactive session attention dots share the close control without marking the avatar or active tab", async ({
|
||||
page,
|
||||
}) => {
|
||||
await mockWorkspace(page, {
|
||||
name: "Tabs",
|
||||
sessions: [a, b],
|
||||
sessionPermissions: {
|
||||
[b.id]: [
|
||||
{ id: "permission-tab-b", sessionID: b.id, action: "shell", resources: ["git status"], metadata: {}, save: [] },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(sessionHref(a.id))
|
||||
const tabA = page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(a.id)}"])`)
|
||||
const tabB = page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(b.id)}"])`)
|
||||
const dot = tabB.locator('[data-slot="tab-unread-dot"]')
|
||||
const close = tabB.getByRole("button", { name: "Close tab", exact: true })
|
||||
await expect(tabA.locator('[data-slot="tab-title"]')).toHaveText(a.title)
|
||||
await expect(tabB.locator('[data-slot="tab-title"]')).toHaveText(b.title)
|
||||
await page.mouse.move(0, 0)
|
||||
await expect(dot).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toBeHidden()
|
||||
await expect(close).toHaveCSS("opacity", "0")
|
||||
await expect(tabB.locator('[data-slot="project-avatar-unread-dot"]')).toHaveCount(0)
|
||||
await expect
|
||||
.poll(() =>
|
||||
dot.evaluate((element) => {
|
||||
const dot = element.getBoundingClientRect()
|
||||
const slot = element.parentElement!.getBoundingClientRect()
|
||||
|
||||
return (
|
||||
Math.abs(dot.x + dot.width / 2 - slot.x - slot.width / 2) +
|
||||
Math.abs(dot.y + dot.height / 2 - slot.y - slot.height / 2)
|
||||
)
|
||||
}),
|
||||
)
|
||||
.toBeLessThan(1)
|
||||
|
||||
await tabB.hover()
|
||||
await expect(dot).toBeHidden()
|
||||
await expect(close).toHaveCSS("opacity", "1")
|
||||
await page.mouse.move(0, 0)
|
||||
await tabB.locator("[data-titlebar-tab-link]").focus()
|
||||
await page.keyboard.press("Tab")
|
||||
await expect(close).toBeFocused()
|
||||
await expect(dot).toBeHidden()
|
||||
await expect(close).toHaveCSS("opacity", "1")
|
||||
await close.blur()
|
||||
await expect(dot).toBeVisible()
|
||||
|
||||
await tabB.locator("[data-titlebar-tab-link]").click()
|
||||
await expectPath(page, sessionHref(b.id))
|
||||
await expect(tabB).toHaveAttribute("data-active", "true")
|
||||
await expect(dot).toHaveCount(0)
|
||||
await tabA.locator("[data-titlebar-tab-link]").click()
|
||||
await expectPath(page, sessionHref(a.id))
|
||||
await page.mouse.move(0, 0)
|
||||
await expect(dot).toBeVisible()
|
||||
await tabB.hover()
|
||||
await close.click()
|
||||
await expect(tabB).toHaveCount(0)
|
||||
await expectPath(page, sessionHref(a.id))
|
||||
})
|
||||
|
||||
test("a tab does not reopen its title editor while a rename is saving", async ({ page }) => {
|
||||
await mockWorkspace(page, { name: "Tabs", sessions: [a, b] })
|
||||
await page.goto(sessionHref(a.id))
|
||||
@@ -98,43 +163,112 @@ test("keyboard navigation follows the visible tab order and skips unresolved tab
|
||||
})
|
||||
|
||||
for (const row of [
|
||||
{ tabLayout: "horizontal", width: 360 },
|
||||
{ tabLayout: "vertical", width: 390 },
|
||||
{ tabLayout: "horizontal", width: 360, hasTouch: false, direction: "ltr" },
|
||||
{ tabLayout: "vertical", width: 390, hasTouch: true, direction: "rtl" },
|
||||
]) {
|
||||
test(`mobile drawer exposes close controls and navigates between tabs (${row.tabLayout})`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: row.width, height: 720 })
|
||||
await mockWorkspace(page, {
|
||||
name: "Tabs",
|
||||
sessions: [a, b, c],
|
||||
seed: { settings: { appearance: { tabLayout: row.tabLayout } } },
|
||||
test.describe(`mobile drawer (${row.tabLayout})`, () => {
|
||||
test.use({ hasTouch: row.hasTouch })
|
||||
test(`mobile drawer exposes close controls and navigates between tabs (${row.tabLayout})`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: row.width, height: 720 })
|
||||
await mockWorkspace(page, {
|
||||
name: "Tabs",
|
||||
sessions: [a, b, c],
|
||||
sessionPermissions: {
|
||||
[b.id]: [
|
||||
{
|
||||
id: "permission-mobile-b",
|
||||
sessionID: b.id,
|
||||
action: "shell",
|
||||
resources: ["git status"],
|
||||
metadata: {},
|
||||
save: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
seed: { settings: { appearance: { tabLayout: row.tabLayout } } },
|
||||
})
|
||||
await page.goto(sessionHref(a.id))
|
||||
await page.locator("html").evaluate((element, direction) => element.setAttribute("dir", direction), row.direction)
|
||||
await page.getByRole("button", { name: "Tabs", exact: true }).click()
|
||||
|
||||
const drawer = page.locator('[data-slot="mobile-tabs-drawer"]')
|
||||
const tabA = drawer.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(a.id)}"])`)
|
||||
const tabB = drawer.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(b.id)}"])`)
|
||||
await expect(tabA).toHaveAttribute("data-active", "true")
|
||||
await expect(tabA.locator('[data-slot="tab-close"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-close"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-unread-dot"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-unread-slot"]')).toHaveCSS("width", "20px")
|
||||
await expect(tabB.locator('[data-slot="tab-unread-slot"]')).toHaveCSS("height", "20px")
|
||||
await expect
|
||||
.poll(() =>
|
||||
tabB.evaluate((element) => {
|
||||
const dot = element.querySelector('[data-slot="tab-unread-slot"]')!.getBoundingClientRect()
|
||||
const close = element.querySelector('[data-slot="tab-close"]')!.getBoundingClientRect()
|
||||
|
||||
return dot.right <= close.left || dot.left >= close.right
|
||||
}),
|
||||
)
|
||||
.toBe(true)
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toBeHidden()
|
||||
await expect(tabB.getByRole("button", { name: "Close tab", exact: true })).toHaveCSS("opacity", "1")
|
||||
await expect(page.locator('[data-slot="vertical-tabs-sidebar"]')).toHaveCount(0)
|
||||
|
||||
await tabB.locator(`a[href="${sessionHref(b.id)}"]`).click()
|
||||
|
||||
await expectPath(page, sessionHref(b.id))
|
||||
await expect(page.getByRole("dialog", { name: "Tabs", exact: true })).toBeHidden()
|
||||
|
||||
if (row.tabLayout !== "vertical") return
|
||||
await page.setViewportSize({ width: 1280, height: 720 })
|
||||
await expect(
|
||||
page
|
||||
.locator('[data-slot="vertical-tabs-sidebar"]')
|
||||
.locator(`[data-titlebar-tab-link][href="${sessionHref(b.id)}"]`),
|
||||
).toBeVisible()
|
||||
await expect(page.locator('[data-slot="titlebar-tabs"]')).toHaveCount(0)
|
||||
})
|
||||
await page.goto(sessionHref(a.id))
|
||||
await page.getByRole("button", { name: "Tabs", exact: true }).click()
|
||||
|
||||
const drawer = page.locator('[data-slot="mobile-tabs-drawer"]')
|
||||
const tabA = drawer.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(a.id)}"])`)
|
||||
const tabB = drawer.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(b.id)}"])`)
|
||||
await expect(tabA).toHaveAttribute("data-active", "true")
|
||||
await expect(tabA.locator('[data-slot="tab-close"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-close"]')).toBeVisible()
|
||||
await expect(page.locator('[data-slot="vertical-tabs-sidebar"]')).toHaveCount(0)
|
||||
|
||||
await tabB.locator(`a[href="${sessionHref(b.id)}"]`).click()
|
||||
|
||||
await expectPath(page, sessionHref(b.id))
|
||||
await expect(page.getByRole("dialog", { name: "Tabs", exact: true })).toBeHidden()
|
||||
|
||||
if (row.tabLayout !== "vertical") return
|
||||
await page.setViewportSize({ width: 1280, height: 720 })
|
||||
await expect(
|
||||
page
|
||||
.locator('[data-slot="vertical-tabs-sidebar"]')
|
||||
.locator(`[data-titlebar-tab-link][href="${sessionHref(b.id)}"]`),
|
||||
).toBeVisible()
|
||||
await expect(page.locator('[data-slot="titlebar-tabs"]')).toHaveCount(0)
|
||||
})
|
||||
}
|
||||
|
||||
test("icon-only inactive tabs retain their avatar and attention badge instead of showing a close overlay", async ({
|
||||
page,
|
||||
}) => {
|
||||
await mockWorkspace(page, {
|
||||
name: "CompactTabs",
|
||||
sessions: [
|
||||
a,
|
||||
b,
|
||||
...Array.from({ length: 30 }, (_, index) => ({ id: `ses_compact_${index}`, title: `Compact ${index}` })),
|
||||
],
|
||||
sessionPermissions: {
|
||||
[b.id]: [
|
||||
{
|
||||
id: "permission-compact-b",
|
||||
sessionID: b.id,
|
||||
action: "shell",
|
||||
resources: ["git status"],
|
||||
metadata: {},
|
||||
save: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
await page.goto(sessionHref(a.id))
|
||||
const tabB = page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(b.id)}"])`)
|
||||
await expect(tabB.locator('[data-slot="tab-title"]')).toBeHidden()
|
||||
await expect(tabB.locator('[data-component="project-avatar-v2"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toHaveCSS("width", "6px")
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toHaveCSS("height", "6px")
|
||||
await expect(tabB.locator('[data-slot="tab-close"]')).toBeHidden()
|
||||
await tabB.hover()
|
||||
await expect(tabB.locator('[data-slot="tab-close"]')).toBeHidden()
|
||||
await tabB.locator("[data-titlebar-tab-link]").click()
|
||||
await expectPath(page, sessionHref(b.id))
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test("vertical tabs resize, scroll, show shortcut hints, and navigate", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 480 })
|
||||
const directory = "C:/OpenCode/Tabs"
|
||||
|
||||
@@ -9,6 +9,45 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[data-titlebar-tab] :is([data-slot="tab-unread-dot"], [data-slot="tab-avatar-unread-dot"]) {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--v2-background-bg-accent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-titlebar-tab] [data-slot="project-avatar-slot"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-titlebar-tab] [data-slot="tab-avatar-unread-dot"] {
|
||||
display: none;
|
||||
inset-inline-end: -3px;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
[data-titlebar-tab] [data-slot="tab-unread-slot"] {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-end: 0;
|
||||
display: flex;
|
||||
margin-block: auto;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
[data-titlebar-tab]:not(:where([data-slot="mobile-tabs-drawer"] *)):is(:hover, :has(:focus-visible))
|
||||
[data-slot="tab-unread-slot"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-titlebar-tab],
|
||||
[data-titlebar-tab-action] {
|
||||
--tab-base: var(--v2-background-bg-deep);
|
||||
@@ -77,7 +116,8 @@
|
||||
--tab-overlay: var(--v2-overlay-simple-overlay-pressed);
|
||||
}
|
||||
|
||||
[data-titlebar-tab]:is(:hover, [data-state="pressed"]) [data-slot="tab-close"] {
|
||||
[data-titlebar-tab]:is(:hover, :has(:focus-visible), [data-state="pressed"], [data-unread="true"])
|
||||
[data-slot="tab-close"] {
|
||||
background: linear-gradient(var(--tab-overlay), var(--tab-overlay)), var(--tab-base);
|
||||
}
|
||||
|
||||
@@ -132,7 +172,10 @@
|
||||
}
|
||||
|
||||
[data-titlebar-tab][data-title-overflow="true"]:not([data-editing="true"]) [data-slot="tab-link"],
|
||||
[data-titlebar-tab]:is(:hover, [data-active="true"]):not([data-editing="true"]) [data-slot="tab-link"] {
|
||||
[data-titlebar-tab]:is(:hover, :has(:focus-visible), [data-active="true"], [data-unread="true"]):not(
|
||||
[data-editing="true"]
|
||||
)
|
||||
[data-slot="tab-link"] {
|
||||
--tab-title-fade-offset: 4px;
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to right,
|
||||
@@ -151,7 +194,10 @@
|
||||
}
|
||||
|
||||
[data-titlebar-tab][data-title-overflow="true"]:not([data-editing="true"]):dir(rtl) [data-slot="tab-link"],
|
||||
[data-titlebar-tab]:is(:hover, [data-active="true"]):not([data-editing="true"]):dir(rtl) [data-slot="tab-link"] {
|
||||
[data-titlebar-tab]:is(:hover, :has(:focus-visible), [data-active="true"], [data-unread="true"]):not(
|
||||
[data-editing="true"]
|
||||
):dir(rtl)
|
||||
[data-slot="tab-link"] {
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to left,
|
||||
black 0,
|
||||
@@ -168,7 +214,10 @@
|
||||
);
|
||||
}
|
||||
|
||||
[data-titlebar-tab]:is(:hover, [data-active="true"]):not([data-editing="true"]) [data-slot="tab-link"] {
|
||||
[data-titlebar-tab]:is(:hover, :has(:focus-visible), [data-active="true"], [data-unread="true"]):not(
|
||||
[data-editing="true"]
|
||||
)
|
||||
[data-slot="tab-link"] {
|
||||
--tab-title-fade-offset: 24px;
|
||||
}
|
||||
|
||||
@@ -196,6 +245,10 @@
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
[data-titlebar-tab] [data-slot="tab-unread-slot"] {
|
||||
inset-inline-end: 20px;
|
||||
}
|
||||
|
||||
[data-titlebar-tab] [data-slot="tab-close"] {
|
||||
background: linear-gradient(var(--tab-overlay), var(--tab-overlay)), var(--tab-base);
|
||||
}
|
||||
@@ -223,22 +276,53 @@
|
||||
[data-titlebar-tab]:dir(rtl) [data-slot="tab-link"] {
|
||||
--tab-title-fade-direction: to left;
|
||||
}
|
||||
|
||||
[data-titlebar-tab][data-unread="true"]:not([data-editing="true"]) [data-slot="tab-link"] {
|
||||
--tab-title-fade-offset: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
@container (44px < width <= 64px) {
|
||||
[data-titlebar-tab]:not([data-orientation="vertical"]):not([data-editing="true"]):is(:hover, [data-active="true"])
|
||||
[data-titlebar-tab]:not([data-orientation="vertical"]):not([data-editing="true"]):is(
|
||||
:hover,
|
||||
:has(:focus-visible),
|
||||
[data-active="true"],
|
||||
[data-unread="true"]
|
||||
)
|
||||
[data-slot="tab-link"] {
|
||||
-webkit-mask-image: none;
|
||||
mask-image: none;
|
||||
}
|
||||
|
||||
[data-titlebar-tab]:not([data-orientation="vertical"]):not([data-editing="true"]):is(:hover, [data-active="true"])
|
||||
[data-titlebar-tab]:not([data-orientation="vertical"]):not([data-editing="true"]):is(
|
||||
:hover,
|
||||
:has(:focus-visible),
|
||||
[data-active="true"],
|
||||
[data-unread="true"]
|
||||
)
|
||||
[data-titlebar-tab-title] {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 44px) {
|
||||
[data-titlebar-tab]:not([data-orientation="vertical"]) [data-slot="tab-avatar-unread-dot"] {
|
||||
display: block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
[data-titlebar-tab][data-unread="true"]:not([data-orientation="vertical"]) [data-slot="project-avatar-surface"] {
|
||||
-webkit-mask-image: radial-gradient(circle 4.5px at 100% 0, transparent 4px, black 5px);
|
||||
mask-image: radial-gradient(circle 4.5px at 100% 0, transparent 4px, black 5px);
|
||||
}
|
||||
|
||||
[data-titlebar-tab][data-unread="true"]:not([data-orientation="vertical"]):dir(rtl)
|
||||
[data-slot="project-avatar-surface"] {
|
||||
-webkit-mask-image: radial-gradient(circle 4.5px at 0 0, transparent 4px, black 5px);
|
||||
mask-image: radial-gradient(circle 4.5px at 0 0, transparent 4px, black 5px);
|
||||
}
|
||||
|
||||
[data-titlebar-tab]:not([data-orientation="vertical"]) [data-titlebar-tab-link] {
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
|
||||
@@ -11,7 +11,8 @@ import { useServerCtx } from "@/runtime/server/runtime"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { ServerConnection, serverName, useServers } from "@/runtime/server/registry"
|
||||
import { displayName } from "@opencode/ui/project-avatar"
|
||||
import { SessionTabAvatar } from "@/shell/layout/session-tab-avatar"
|
||||
import { SessionTabAvatarView } from "@/shell/layout/session-tab-avatar"
|
||||
import { useSessionTabAvatarState } from "@/shell/layout/project-avatar-state"
|
||||
import { SessionProgressIndicatorV2 } from "@opencode/session-ui/v2/session-progress-indicator-v2"
|
||||
import type { SessionInfo } from "@opencode/client/promise"
|
||||
import { sessionTabTitle } from "./tab-title"
|
||||
@@ -55,6 +56,12 @@ export function TabNavItem(props: {
|
||||
|
||||
const servers = useServers()
|
||||
const serverCtx = useServerCtx(() => servers.list.find((item) => ServerConnection.key(item) === props.server))
|
||||
const avatar = useSessionTabAvatarState(
|
||||
() => props.server,
|
||||
() => props.session?.id ?? "",
|
||||
() => true,
|
||||
)
|
||||
const unread = () => !!props.session && !props.active && avatar.unread() && !avatar.loading()
|
||||
|
||||
const project = createMemo(() => {
|
||||
const session = props.session
|
||||
@@ -232,6 +239,7 @@ export function TabNavItem(props: {
|
||||
class="group relative flex h-7 w-full min-w-0 select-none flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] px-1.5 [container-type:inline-size]"
|
||||
classList={{ invisible: props.hidden }}
|
||||
data-active={props.active}
|
||||
data-unread={unread()}
|
||||
data-dragging={props.dragging}
|
||||
data-state={props.active || props.pressed ? "pressed" : undefined}
|
||||
onMouseDown={(event) => {
|
||||
@@ -298,14 +306,17 @@ export function TabNavItem(props: {
|
||||
}
|
||||
>
|
||||
{(session) => (
|
||||
<SessionTabAvatar
|
||||
<SessionTabAvatarView
|
||||
project={project()}
|
||||
directory={session.location.directory}
|
||||
sessionId={session.id}
|
||||
server={props.server}
|
||||
unread={false}
|
||||
loading={avatar.loading()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={unread()}>
|
||||
<span data-slot="tab-avatar-unread-dot" aria-hidden="true" />
|
||||
</Show>
|
||||
</span>
|
||||
<span
|
||||
ref={(el) => {
|
||||
@@ -350,10 +361,15 @@ export function TabNavItem(props: {
|
||||
</Menu.Context.Trigger>
|
||||
|
||||
<div data-slot="tab-close">
|
||||
<Show when={unread()}>
|
||||
<span data-slot="tab-unread-slot" aria-hidden="true">
|
||||
<span data-slot="tab-unread-dot" />
|
||||
</span>
|
||||
</Show>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="ghost-muted"
|
||||
class="hover-reveal relative z-10 group-hover:opacity-100 group-data-[active=true]:opacity-100 group-data-[editing=true]:opacity-100"
|
||||
class="hover-reveal relative z-10 group-hover:opacity-100 group-has-[:focus-visible]:opacity-100 group-data-[active=true]:opacity-100 group-data-[editing=true]:opacity-100"
|
||||
onPointerDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@@ -70,12 +70,20 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-slot="mobile-tabs-drawer"] [data-titlebar-tab] [data-slot="tab-unread-slot"] {
|
||||
inset-inline-end: 44px;
|
||||
}
|
||||
|
||||
[data-slot="mobile-tabs-drawer"] [data-titlebar-tab] [data-slot="tab-link"] {
|
||||
padding-inline-end: 38px;
|
||||
-webkit-mask-image: none;
|
||||
mask-image: none;
|
||||
}
|
||||
|
||||
[data-slot="mobile-tabs-drawer"] [data-titlebar-tab][data-unread="true"] [data-slot="tab-link"] {
|
||||
padding-inline-end: 58px;
|
||||
}
|
||||
|
||||
[data-slot="mobile-tabs-drawer"] [data-titlebar-tab] [data-slot="tab-title"] {
|
||||
color: var(--v2-text-text-base);
|
||||
font-weight: 530;
|
||||
|
||||
Reference in new issue
Block a user