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 @@...@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
1import { ChevronRightIcon } from "lucide-react";2import { ChevronRightIcon } from "lucide-react";
2import { type ReactNode, useState } from "react";3import { type ReactNode, useState } from "react";
34
...@@ -35,11 +36,7 @@ export function FoldedSettingsSection({...@@ -35,11 +36,7 @@ export function FoldedSettingsSection({
3536
36 return (37 return (
37 <section id={id} ref={targetRef} tabIndex={-1} className="outline-none">38 <section id={id} ref={targetRef} tabIndex={-1} className="outline-none">
38 <Collapsible39 <Collapsible open={open} onOpenChange={setOpen} render={<SettingsGroup divided={false} />}>
39 open={open}
40 onOpenChange={setOpen}
41 className="rounded-xl border border-border/60 bg-card/40 text-foreground shadow-xs/5"
42 >
43 <div className="flex items-center gap-4 px-3 sm:px-4">40 <div className="flex items-center gap-4 px-3 sm:px-4">
44 <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">41 <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">
45 <ChevronRightIcon42 <ChevronRightIcon
apps/web/src/components/settings/ProviderSettingsPanel.tsx+10-13
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
1import { RefreshIcon } from "~/components/ui/refresh-icon";2import { RefreshIcon } from "~/components/ui/refresh-icon";
2import { useAtomValue } from "@effect/atom-react";3import { useAtomValue } from "@effect/atom-react";
3import { connectionStatusTitle } from "@t3tools/client-runtime/connection";4import { connectionStatusTitle } from "@t3tools/client-runtime/connection";
...@@ -171,7 +172,6 @@ function providerEnvironmentDetail(environment: EnvironmentPresentation): string...@@ -171,7 +172,6 @@ function providerEnvironmentDetail(environment: EnvironmentPresentation): string
171 return environment.displayUrl ?? "Remote device";172 return environment.displayUrl ?? "Remote device";
172}173}
173174
174const providerCardClassName = "rounded-xl border border-border/60 bg-card/40 shadow-xs/5";
175// Shared by the editor grid and the placeholder states so switching devices175// Shared by the editor grid and the placeholder states so switching devices
176// never changes the card's footprint.176// never changes the card's footprint.
177const providerCardHeightClassName =177const providerCardHeightClassName =
...@@ -199,12 +199,9 @@ function ProviderSettingsPlaceholder({...@@ -199,12 +199,9 @@ function ProviderSettingsPlaceholder({
199 {deviceTabs ? (199 {deviceTabs ? (
200 <div className="flex min-h-11 min-w-0 items-center px-3 sm:px-4">{deviceTabs}</div>200 <div className="flex min-h-11 min-w-0 items-center px-3 sm:px-4">{deviceTabs}</div>
201 ) : null}201 ) : null}
202 <div202 <SettingsGroup
203 className={cn(203 divided={false}
204 providerCardClassName,204 className={cn(providerCardHeightClassName, "flex overflow-x-hidden overflow-y-auto")}
205 providerCardHeightClassName,
206 "flex overflow-x-hidden overflow-y-auto",
207 )}
208 >205 >
209 <Empty className="min-h-88">206 <Empty className="min-h-88">
210 <EmptyMedia variant="icon">{icon}</EmptyMedia>207 <EmptyMedia variant="icon">{icon}</EmptyMedia>
...@@ -214,7 +211,7 @@ function ProviderSettingsPlaceholder({...@@ -214,7 +211,7 @@ function ProviderSettingsPlaceholder({
214 </EmptyHeader>211 </EmptyHeader>
215 {children ? <EmptyContent className="max-w-xl">{children}</EmptyContent> : null}212 {children ? <EmptyContent className="max-w-xl">{children}</EmptyContent> : null}
216 </Empty>213 </Empty>
217 </div>214 </SettingsGroup>
218 </SettingsSection>215 </SettingsSection>
219 );216 );
220}217}
...@@ -1044,16 +1041,16 @@ export function EnvironmentProviderSettings({...@@ -1044,16 +1041,16 @@ export function EnvironmentProviderSettings({
1044 </div>1041 </div>
1045 </div>1042 </div>
1046 {readOnly ? (1043 {readOnly ? (
1047 <div className={cn(providerCardClassName, "overflow-hidden")}>1044 <SettingsGroup divided={false} className="overflow-hidden">
1048 <SettingsRow1045 <SettingsRow
1049 title="Limited permissions"1046 title="Limited permissions"
1050 description={`This session can view ${environmentLabel}'s providers but can't change their settings.`}1047 description={`This session can view ${environmentLabel}'s providers but can't change their settings.`}
1051 />1048 />
1052 </div>1049 </SettingsGroup>
1053 ) : null}1050 ) : null}
1054 <div1051 <SettingsGroup
1052 divided={false}
1055 className={cn(1053 className={cn(
1056 providerCardClassName,
1057 providerCardHeightClassName,1054 providerCardHeightClassName,
1058 "overflow-hidden @min-[48rem]/providers:grid @min-[48rem]/providers:grid-cols-[17rem_minmax(0,1fr)]",1055 "overflow-hidden @min-[48rem]/providers:grid @min-[48rem]/providers:grid-cols-[17rem_minmax(0,1fr)]",
1059 )}1056 )}
...@@ -1083,7 +1080,7 @@ export function EnvironmentProviderSettings({...@@ -1083,7 +1080,7 @@ export function EnvironmentProviderSettings({
1083 </div>1080 </div>
1084 )}1081 )}
1085 </div>1082 </div>
1086 </div>1083 </SettingsGroup>
1087 </SettingsSection>1084 </SettingsSection>
10881085
1089 <UsageProviderSettings1086 <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 @@...@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
1import { Spinner } from "~/components/ui/spinner";2import { Spinner } from "~/components/ui/spinner";
2import { NotificationSettings } from "./NotificationSettings";3import { NotificationSettings } from "./NotificationSettings";
3import { ArchiveIcon, ArchiveX, CheckIcon, ChevronRightIcon, SettingsIcon } from "lucide-react";4import { ArchiveIcon, ArchiveX, CheckIcon, ChevronRightIcon, SettingsIcon } from "lucide-react";
...@@ -2047,7 +2048,7 @@ function LegacyFeaturesSection() {...@@ -2047,7 +2048,7 @@ function LegacyFeaturesSection() {
2047 <ChevronRightIcon className="size-4 text-muted-foreground transition-transform duration-200 group-data-panel-open:rotate-90" />2048 <ChevronRightIcon className="size-4 text-muted-foreground transition-transform duration-200 group-data-panel-open:rotate-90" />
2048 </CollapsibleTrigger>2049 </CollapsibleTrigger>
2049 <CollapsiblePanel>2050 <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>
2051 <SettingsRow2052 <SettingsRow
2052 {...searchableSetting("legacy-plan-mode")}2053 {...searchableSetting("legacy-plan-mode")}
2053 description="Restore Build/Plan, /plan, /default, and Shift+Tab. Off uses build mode."2054 description="Restore Build/Plan, /plan, /default, and Shift+Tab. Off uses build mode."
...@@ -2087,7 +2088,7 @@ function LegacyFeaturesSection() {...@@ -2087,7 +2088,7 @@ function LegacyFeaturesSection() {
2087 />2088 />
2088 }2089 }
2089 />2090 />
2090 </div>2091 </SettingsGroup>
2091 </CollapsiblePanel>2092 </CollapsiblePanel>
2092 </Collapsible>2093 </Collapsible>
2093 </section>2094 </section>
apps/web/src/components/settings/settingsLayout.tsx+3-10
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { SettingsGroup } from "./SettingsGroup";
1import { InfoIcon, Undo2Icon } from "lucide-react";2import { InfoIcon, Undo2Icon } from "lucide-react";
2import { DEFAULT_SERVER_SETTINGS, type ServerSettings } from "@t3tools/contracts";3import { DEFAULT_SERVER_SETTINGS, type ServerSettings } from "@t3tools/contracts";
3import * as Equal from "effect/Equal";4import * as Equal from "effect/Equal";
...@@ -215,17 +216,9 @@ export function SettingsSection({...@@ -215,17 +216,9 @@ export function SettingsSection({
215 <div className="flex min-h-7 min-w-7 items-center justify-end">{headerAction}</div>216 <div className="flex min-h-7 min-w-7 items-center justify-end">{headerAction}</div>
216 </div>217 </div>
217 )}218 )}
218 <div219 <SettingsGroup data-settings-scroll-target={hideTitle ? "" : undefined} variant={variant}>
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 >
227 {children}220 {children}
228 </div>221 </SettingsGroup>
229 </section>222 </section>
230 );223 );
231}224}