authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-17 20:19:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-17 20:19:36-07:00
log4aa841a8d45ccd611a809351db0b693faba6ba13
tree9a74a709fdf177b124ff461aeb5a66e890b81c8e
parent14337401bc801d6239dd10e34997b4481081cf1d
signature Signed by PGP key B5690EEEBB952194

refactor(web): share settings group surfaces (#12360)


5 files changed, 48 insertions(+), 30 deletions(-)

apps/web/src/components/settings/FoldedSettingsSection.tsx+2-5
......@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
12import { ChevronRightIcon } from "lucide-react";
23import { type ReactNode, useState } from "react";
34
......@@ -35,11 +36,7 @@ export function FoldedSettingsSection({
3536
3637 return (
3738 <section id={id} ref={targetRef} tabIndex={-1} className="outline-none">
38 <Collapsible
39 open={open}
40 onOpenChange={setOpen}
41 className="rounded-xl border border-border/60 bg-card/40 text-foreground shadow-xs/5"
42 >
39 <Collapsible open={open} onOpenChange={setOpen} render={<SettingsGroup divided={false} />}>
4340 <div className="flex items-center gap-4 px-3 sm:px-4">
4441 <CollapsibleTrigger className="flex min-h-11 min-w-0 flex-1 items-center gap-2 py-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md">
4542 <ChevronRightIcon
apps/web/src/components/settings/ProviderSettingsPanel.tsx+10-13
......@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
12import { RefreshIcon } from "~/components/ui/refresh-icon";
23import { useAtomValue } from "@effect/atom-react";
34import { connectionStatusTitle } from "@t3tools/client-runtime/connection";
......@@ -171,7 +172,6 @@ function providerEnvironmentDetail(environment: EnvironmentPresentation): string
171172 return environment.displayUrl ?? "Remote device";
172173}
173174
174const providerCardClassName = "rounded-xl border border-border/60 bg-card/40 shadow-xs/5";
175175// Shared by the editor grid and the placeholder states so switching devices
176176// never changes the card's footprint.
177177const providerCardHeightClassName =
......@@ -199,12 +199,9 @@ function ProviderSettingsPlaceholder({
199199 {deviceTabs ? (
200200 <div className="flex min-h-11 min-w-0 items-center px-3 sm:px-4">{deviceTabs}</div>
201201 ) : null}
202 <div
203 className={cn(
204 providerCardClassName,
205 providerCardHeightClassName,
206 "flex overflow-x-hidden overflow-y-auto",
207 )}
202 <SettingsGroup
203 divided={false}
204 className={cn(providerCardHeightClassName, "flex overflow-x-hidden overflow-y-auto")}
208205 >
209206 <Empty className="min-h-88">
210207 <EmptyMedia variant="icon">{icon}</EmptyMedia>
......@@ -214,7 +211,7 @@ function ProviderSettingsPlaceholder({
214211 </EmptyHeader>
215212 {children ? <EmptyContent className="max-w-xl">{children}</EmptyContent> : null}
216213 </Empty>
217 </div>
214 </SettingsGroup>
218215 </SettingsSection>
219216 );
220217}
......@@ -1044,16 +1041,16 @@ export function EnvironmentProviderSettings({
10441041 </div>
10451042 </div>
10461043 {readOnly ? (
1047 <div className={cn(providerCardClassName, "overflow-hidden")}>
1044 <SettingsGroup divided={false} className="overflow-hidden">
10481045 <SettingsRow
10491046 title="Limited permissions"
10501047 description={`This session can view ${environmentLabel}'s providers but can't change their settings.`}
10511048 />
1052 </div>
1049 </SettingsGroup>
10531050 ) : null}
1054 <div
1051 <SettingsGroup
1052 divided={false}
10551053 className={cn(
1056 providerCardClassName,
10571054 providerCardHeightClassName,
10581055 "overflow-hidden @min-[48rem]/providers:grid @min-[48rem]/providers:grid-cols-[17rem_minmax(0,1fr)]",
10591056 )}
......@@ -1083,7 +1080,7 @@ export function EnvironmentProviderSettings({
10831080 </div>
10841081 )}
10851082 </div>
1086 </div>
1083 </SettingsGroup>
10871084 </SettingsSection>
10881085
10891086 <UsageProviderSettings
apps/web/src/components/settings/SettingsGroup.tsx created+30
......@@ -0,0 +1,30 @@
1import type { ComponentProps } from "react";
2
3import { cn } from "../../lib/utils";
4
5/** Shared settings card surface, with optional separators between rows. */
6export function SettingsGroup({
7 variant = "grouped",
8 divided = true,
9 className,
10 ...props
11}: ComponentProps<"div"> & {
12 variant?: "grouped" | "plain";
13 divided?: boolean;
14}) {
15 return (
16 <div
17 {...props}
18 className={cn(
19 "relative overflow-visible text-foreground",
20 variant === "grouped"
21 ? "rounded-xl border border-border/60 bg-card/40 shadow-xs/5"
22 : "space-y-1",
23 variant === "grouped" &&
24 divided &&
25 "[&>*+*]:border-t [&>*+*]:border-border/50 [&>[data-slot=settings-row]]:rounded-none",
26 className,
27 )}
28 />
29 );
30}
apps/web/src/components/settings/SettingsPanels.tsx+3-2
......@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
12import { Spinner } from "~/components/ui/spinner";
23import { NotificationSettings } from "./NotificationSettings";
34import { ArchiveIcon, ArchiveX, CheckIcon, ChevronRightIcon, SettingsIcon } from "lucide-react";
......@@ -2047,7 +2048,7 @@ function LegacyFeaturesSection() {
20472048 <ChevronRightIcon className="size-4 text-muted-foreground transition-transform duration-200 group-data-panel-open:rotate-90" />
20482049 </CollapsibleTrigger>
20492050 <CollapsiblePanel>
2050 <div className="relative overflow-visible rounded-xl border border-border/60 bg-card/40 text-foreground shadow-xs/5 [&>*+*]:border-t [&>*+*]:border-border/50 [&>[data-slot=settings-row]]:rounded-none">
2051 <SettingsGroup>
20512052 <SettingsRow
20522053 {...searchableSetting("legacy-plan-mode")}
20532054 description="Restore Build/Plan, /plan, /default, and Shift+Tab. Off uses build mode."
......@@ -2087,7 +2088,7 @@ function LegacyFeaturesSection() {
20872088 />
20882089 }
20892090 />
2090 </div>
2091 </SettingsGroup>
20912092 </CollapsiblePanel>
20922093 </Collapsible>
20932094 </section>
apps/web/src/components/settings/settingsLayout.tsx+3-10
......@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
12import { InfoIcon, Undo2Icon } from "lucide-react";
23import { DEFAULT_SERVER_SETTINGS, type ServerSettings } from "@t3tools/contracts";
34import * as Equal from "effect/Equal";
......@@ -215,17 +216,9 @@ export function SettingsSection({
215216 <div className="flex min-h-7 min-w-7 items-center justify-end">{headerAction}</div>
216217 </div>
217218 )}
218 <div
219 data-settings-scroll-target={hideTitle ? "" : undefined}
220 className={cn(
221 "relative overflow-visible text-foreground",
222 variant === "grouped"
223 ? "rounded-xl border border-border/60 bg-card/40 shadow-xs/5 [&>*+*]:border-t [&>*+*]:border-border/50 [&>[data-slot=settings-row]]:rounded-none"
224 : "space-y-1",
225 )}
226 >
219 <SettingsGroup data-settings-scroll-target={hideTitle ? "" : undefined} variant={variant}>
227220 {children}
228 </div>
221 </SettingsGroup>
229222 </section>
230223 );
231224}