Skip to content

Commit 4635fe0

Browse files
shivamhwpT3 Code Test
authored andcommitted
follow-up normalization after pingdotgg#4700. (pingdotgg#4498)
Co-authored-by: T3 Code Test <t3code-test@example.com>
1 parent 2a44968 commit 4635fe0

12 files changed

Lines changed: 181 additions & 23 deletions
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { ProjectScript, ResolvedKeybindingsConfig } from "@t3tools/contracts";
2+
import { renderToStaticMarkup } from "react-dom/server";
3+
import { describe, expect, it } from "vite-plus/test";
4+
5+
import ProjectScriptsControl from "./ProjectScriptsControl";
6+
7+
const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = [];
8+
const PRIMARY_SCRIPT: ProjectScript = {
9+
id: "dev",
10+
name: "Dev",
11+
command: "vp dev",
12+
icon: "play",
13+
runOnWorktreeCreate: false,
14+
};
15+
16+
function renderControl(scripts: ReadonlyArray<ProjectScript>) {
17+
return renderToStaticMarkup(
18+
<ProjectScriptsControl
19+
scripts={scripts}
20+
keybindings={EMPTY_KEYBINDINGS}
21+
onRunScript={() => {}}
22+
onAddScript={async () => undefined as never}
23+
onUpdateScript={async () => undefined as never}
24+
onDeleteScript={async () => undefined as never}
25+
/>,
26+
);
27+
}
28+
29+
function buttonTag(html: string, ariaLabel: string) {
30+
return html.match(new RegExp(`<button[^>]*aria-label="${ariaLabel}"[^>]*>`))?.[0];
31+
}
32+
33+
function expectResponsiveXsControl(markup: string | undefined) {
34+
expect(markup).toBeDefined();
35+
expect(markup).toContain("h-7");
36+
expect(markup).toContain("gap-1");
37+
expect(markup).toContain("text-sm");
38+
expect(markup).toContain("sm:h-6");
39+
expect(markup).toContain("sm:text-xs");
40+
expect(markup).toContain("w-7");
41+
expect(markup).toContain("px-0");
42+
expect(markup).toContain("sm:w-6");
43+
expect(markup).toContain("@3xl/header-actions:w-auto!");
44+
expect(markup).toContain("@3xl/header-actions:px-[calc(--spacing(2)-1px)]");
45+
}
46+
47+
describe("ProjectScriptsControl compact controls", () => {
48+
it("keeps the primary Run control compact and expands it with its label", () => {
49+
const html = renderControl([PRIMARY_SCRIPT]);
50+
51+
expectResponsiveXsControl(buttonTag(html, "Run Dev"));
52+
expect(html).toContain(
53+
'class="sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5"',
54+
);
55+
});
56+
57+
it("keeps the standalone Add control compact and expands it with its label", () => {
58+
const html = renderControl([]);
59+
60+
expectResponsiveXsControl(buttonTag(html, "Add action"));
61+
expect(html).toContain(
62+
'class="sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5"',
63+
);
64+
});
65+
});

apps/web/src/components/ProjectScriptsControl.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ export default function ProjectScriptsControl({
345345
<Button
346346
size="xs"
347347
variant="outline"
348+
className="w-7 px-0 sm:w-6 @3xl/header-actions:w-auto! @3xl/header-actions:px-[calc(--spacing(2)-1px)]"
348349
aria-label={`Run ${primaryScript.name}`}
349350
onClick={() => onRunScript(primaryScript)}
350351
/>
@@ -445,7 +446,13 @@ export default function ProjectScriptsControl({
445446
<Tooltip>
446447
<TooltipTrigger
447448
render={
448-
<Button size="xs" variant="outline" aria-label="Add action" onClick={openAddDialog} />
449+
<Button
450+
size="xs"
451+
variant="outline"
452+
className="w-7 px-0 sm:w-6 @3xl/header-actions:w-auto! @3xl/header-actions:px-[calc(--spacing(2)-1px)]"
453+
aria-label="Add action"
454+
onClick={openAddDialog}
455+
/>
449456
}
450457
>
451458
<PlusIcon className="size-3.5" />

apps/web/src/components/SidebarV2.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,9 @@ function SidebarV2ThreadTooltip({
266266
align="start"
267267
sideOffset={4}
268268
variant="glass"
269-
className="max-w-80 text-left whitespace-normal"
269+
className="max-w-80 text-left whitespace-normal [&_[data-slot=tooltip-viewport]]:p-0"
270270
>
271-
<div className="flex min-w-0 max-w-80 flex-col gap-2 px-0.5 py-1.5">
271+
<div className="flex min-w-0 max-w-80 flex-col gap-2 p-[var(--floating-content-inset)]">
272272
<div className="min-w-0 truncate text-xs leading-none font-medium text-foreground">
273273
{thread.title}
274274
</div>
@@ -923,7 +923,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
923923
/>
924924
}
925925
>
926-
<div className="relative z-10 h-[4.875rem] px-2.5 py-2">
926+
<div className="relative z-10 h-[4.875rem] px-[var(--sidebar-row-content-inset)] py-[var(--sidebar-content-inset)]">
927927
<div className="flex h-5 min-w-0 items-center gap-1.5">
928928
<ProjectFavicon
929929
environmentId={thread.environmentId}
@@ -2464,7 +2464,7 @@ export default function SidebarV2() {
24642464
<SidebarContent
24652465
className="gap-0"
24662466
fixedHeader={
2467-
<SidebarGroup className="gap-1 p-2">
2467+
<SidebarGroup className="gap-1 p-[var(--sidebar-content-inset)]">
24682468
<div className="flex items-center gap-1">
24692469
<div className="min-w-0 flex-1">
24702470
<CommandDialogTrigger
@@ -2612,7 +2612,7 @@ export default function SidebarV2() {
26122612
</SidebarGroup>
26132613
}
26142614
>
2615-
<SidebarGroup className="px-2 pb-1 pt-0">
2615+
<SidebarGroup className="px-[var(--sidebar-content-inset)] pb-1 pt-0">
26162616
<TooltipProvider
26172617
key="sidebar-thread-tooltips-150"
26182618
delay={150}

apps/web/src/components/settings/SettingsSidebarNav.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
7575
return (
7676
<>
7777
<SidebarContent className="overflow-x-hidden">
78-
<SidebarGroup className="p-2">
78+
<SidebarGroup className="p-[var(--sidebar-content-inset)]">
7979
<SidebarMenu>
8080
{SETTINGS_NAV_ITEMS.map((item) => {
8181
const Icon = item.icon;
@@ -95,7 +95,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
9595
</SidebarMenu>
9696
</SidebarGroup>
9797
</SidebarContent>
98-
<SidebarFooter className="p-2">
98+
<SidebarFooter className="p-[var(--sidebar-content-inset)]">
9999
<T3ConnectSidebarSignIn />
100100
<div className="flex items-center gap-1">
101101
<SidebarMenu className="min-w-0 flex-1">

apps/web/src/components/sidebar/SidebarChrome.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
119119
}, [isMobile, navigate, setOpenMobile]);
120120

121121
return (
122-
<SidebarFooter className="p-2">
122+
<SidebarFooter className="p-[var(--sidebar-content-inset)]">
123123
<SidebarProviderUpdatePill />
124124
<SidebarUpdatePill />
125125
<SidebarMenu>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { renderToStaticMarkup } from "react-dom/server";
2+
import { describe, expect, it } from "vite-plus/test";
3+
import { FlaskConicalIcon } from "lucide-react";
4+
5+
import { Button } from "./button";
6+
7+
describe("button geometry tokens", () => {
8+
it("uses the shared control radius and an opaque semantic icon color", () => {
9+
const html = renderToStaticMarkup(
10+
<Button size="icon-xs" variant="outline" aria-label="Run tests">
11+
<FlaskConicalIcon />
12+
</Button>,
13+
);
14+
15+
expect(html).toContain("rounded-[var(--control-radius)]");
16+
expect(html).toContain("[--control-icon-color:var(--muted-foreground)]");
17+
expect(html).toContain("text-[var(--control-icon-color)]");
18+
expect(html).not.toContain("opacity-80");
19+
});
20+
21+
it("keeps compact icon buttons square at every breakpoint", () => {
22+
const html = renderToStaticMarkup(
23+
<Button size="icon-xs" aria-label="Add action">
24+
<span>+</span>
25+
</Button>,
26+
);
27+
28+
expect(html).toContain("size-7");
29+
expect(html).toContain("sm:size-6");
30+
});
31+
});

apps/web/src/components/ui/button.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type * as React from "react";
88
import { cn } from "~/lib/utils";
99

1010
const buttonVariants = cva(
11-
"[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
11+
"[--control-icon-color:currentColor] [&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--control-radius)] border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--control-radius)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='text-'])]:text-[var(--control-icon-color)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
1212
{
1313
defaultVariants: {
1414
size: "default",
@@ -23,11 +23,11 @@ const buttonVariants = cva(
2323
"icon-xl":
2424
"size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
2525
"icon-xs":
26-
"size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
26+
"size-7 sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
2727
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
2828
sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7",
2929
xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
30-
xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5",
30+
xs: "h-7 gap-1 px-[calc(--spacing(2)-1px)] text-sm sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5",
3131
},
3232
variant: {
3333
default:
@@ -37,10 +37,10 @@ const buttonVariants = cva(
3737
"destructive-outline":
3838
"border-input bg-popover not-dark:bg-clip-padding text-destructive-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4",
3939
ghost:
40-
"border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [&_svg:not([class*='text-'])]:text-muted-foreground",
40+
"[--control-icon-color:var(--muted-foreground)] border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent",
4141
link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline",
4242
outline:
43-
"border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64 [&_svg:not([class*='text-'])]:text-muted-foreground",
43+
"[--control-icon-color:var(--muted-foreground)] border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64",
4444
secondary:
4545
"border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90",
4646
},
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { renderToStaticMarkup } from "react-dom/server";
2+
import { describe, expect, it } from "vite-plus/test";
3+
4+
import { Command, CommandFooter, CommandInput } from "./command";
5+
6+
describe("command compact geometry", () => {
7+
it("keeps shell selectors on the wrapper and direct-input padding on AutocompleteInput", () => {
8+
const html = renderToStaticMarkup(
9+
<Command>
10+
<CommandInput placeholder="Search commands" />
11+
</Command>,
12+
);
13+
const shellClass = html.match(/class="([^"]*px-\[var\(--command-shell-inset\)[^"]*)"/)?.[1];
14+
const inputClass = html.match(/class="([^"]*has-focus-visible:ring-0[^"]*)"/)?.[1];
15+
16+
expect(shellClass).toContain(
17+
"[&amp;_[data-slot=autocomplete-start-addon]]:ps-[calc(var(--command-shell-inset)+0.0625rem)]",
18+
);
19+
expect(shellClass).not.toContain("sm:*:data-[slot=autocomplete-input]");
20+
expect(inputClass).toContain(
21+
"sm:*:data-[slot=autocomplete-input]:ps-[calc(var(--command-shell-inset)+1.5rem)]!",
22+
);
23+
});
24+
25+
it("uses the semantic footer inset without changing compact vertical padding", () => {
26+
const html = renderToStaticMarkup(<CommandFooter>Shortcuts</CommandFooter>);
27+
28+
expect(html).toContain("px-[var(--command-content-inset)]");
29+
expect(html).toContain("py-2.5");
30+
});
31+
});

apps/web/src/components/ui/command.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,16 @@ function CommandInput({
103103
wrapperClassName?: string | undefined;
104104
}) {
105105
return (
106-
<div className={cn("px-1.25 py-1.5", wrapperClassName)}>
106+
<div
107+
className={cn(
108+
"px-[var(--command-shell-inset)] py-1.5 [&_[data-slot=autocomplete-start-addon]]:ps-[calc(var(--command-shell-inset)+0.0625rem)]",
109+
wrapperClassName,
110+
)}
111+
>
107112
<AutocompleteInput
108113
autoFocus
109114
className={cn(
110-
"border-transparent! bg-transparent! shadow-none before:hidden has-focus-visible:ring-0 placeholder:text-muted-foreground/80 *:data-[slot=autocomplete-input]:ps-9! sm:*:data-[slot=autocomplete-input]:ps-8.5!",
115+
"border-transparent! bg-transparent! shadow-none before:hidden has-focus-visible:ring-0 placeholder:text-muted-foreground/80 *:data-[slot=autocomplete-input]:ps-9! sm:*:data-[slot=autocomplete-input]:ps-[calc(var(--command-shell-inset)+1.5rem)]!",
111116
className,
112117
)}
113118
placeholder={placeholder}
@@ -211,7 +216,7 @@ function CommandFooter({ className, ...props }: React.ComponentProps<"div">) {
211216
return (
212217
<div
213218
className={cn(
214-
"relative flex items-center justify-between gap-2 rounded-b-[calc(var(--radius-2xl)-1px)] bg-foreground/[0.025] px-4 py-2.5 font-medium text-sm text-muted-foreground [&_[data-slot=kbd-group]]:font-sans [&_[data-slot=kbd]]:bg-foreground/[0.08] [&_[data-slot=kbd]]:text-foreground [&_[data-slot=kbd]]:ring-0",
219+
"relative flex items-center justify-between gap-2 rounded-b-[calc(var(--radius-2xl)-1px)] bg-foreground/[0.025] px-[var(--command-content-inset)] py-2.5 font-medium text-sm text-muted-foreground [&_[data-slot=kbd-group]]:font-sans [&_[data-slot=kbd]]:bg-foreground/[0.08] [&_[data-slot=kbd]]:text-foreground [&_[data-slot=kbd]]:ring-0",
215220
className,
216221
)}
217222
data-slot="command-footer"

apps/web/src/components/ui/sidebar.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ describe("sidebar interactive cursors", () => {
5555

5656
expect(html).toContain('data-slot="sidebar-menu-button"');
5757
expect(html).toContain("h-8");
58-
expect(html).toContain("rounded-md");
59-
expect(html).toContain("px-2");
58+
expect(html).toContain("rounded-[var(--control-radius)]");
59+
expect(html).toContain("px-[var(--sidebar-row-content-inset)]");
6060
expect(html).toContain("py-1.5");
6161
expect(html).toContain("]:size-4");
6262
expect(html).toContain("]:shrink-0");
6363
expect(html).toContain("cursor-pointer");
64+
expect(html).toContain("gap-[var(--sidebar-control-gap)]");
65+
expect(html).toContain("text-[var(--sidebar-icon-color)]");
66+
expect(html).not.toContain("[&amp;&gt;svg]:opacity-60");
6467
});
6568

6669
it("applies the shared default treatment to icon-only menu buttons", () => {

0 commit comments

Comments
 (0)