1- import type { Interpolation } from "@emotion/react" ;
2- import CircularProgress from "@mui/material/CircularProgress" ;
3- import FormControl from "@mui/material/FormControl" ;
4- import FormControlLabel from "@mui/material/FormControlLabel" ;
5- import Radio from "@mui/material/Radio" ;
6- import RadioGroup from "@mui/material/RadioGroup" ;
7- import { visuallyHidden } from "@mui/utils" ;
81import {
92type TerminalFontName ,
103TerminalFontNames ,
114type UpdateUserAppearanceSettingsRequest ,
125} from "api/typesGenerated" ;
136import { ErrorAlert } from "components/Alert/ErrorAlert" ;
147import { PreviewBadge } from "components/Badges/Badges" ;
15- import { Stack } from "components/Stack/Stack" ;
16- import { ThemeOverride } from "contexts/ThemeProvider" ;
8+ import { Label } from "components/Label/Label" ;
9+ import { RadioGroup , RadioGroupItem } from "components/RadioGroup/RadioGroup" ;
10+ import { Spinner } from "components/Spinner/Spinner" ;
1711import type { FC } from "react" ;
18- import themes , { DEFAULT_THEME , type Theme } from "theme" ;
12+ import { DEFAULT_THEME } from "theme" ;
1913import {
2014DEFAULT_TERMINAL_FONT ,
2115terminalFontLabels ,
@@ -67,67 +61,65 @@ export const AppearanceForm: FC<AppearanceFormProps> = ({
6761
6862< Section
6963title = {
70- < Stack direction = " row" alignItems = " center">
64+ < div className = "flex flex- row items- center gap-2 ">
7165< span > Theme</ span >
72- { isUpdating && < CircularProgress size = { 16 } /> }
73- </ Stack >
66+ < Spinner loading = { isUpdating } size = "sm" />
67+ </ div >
7468}
7569layout = "fluid"
70+ className = "mb-12"
7671>
77- < Stack direction = " row" wrap = "wrap ">
72+ < div className = "flex flex- row flex- wrap gap-4 ">
7873< AutoThemePreviewButton
7974displayName = "Auto"
8075active = { currentTheme === "auto" }
81- themes = { [ themes . dark , themes . light ] }
76+ themes = { [ " dark" , " light" ] }
8277onSelect = { ( ) => onChangeTheme ( "auto" ) }
8378/>
8479< ThemePreviewButton
8580displayName = "Dark"
8681active = { currentTheme === "dark" }
87- theme = { themes . dark }
82+ theme = " dark"
8883onSelect = { ( ) => onChangeTheme ( "dark" ) }
8984/>
9085< ThemePreviewButton
9186displayName = "Light"
9287active = { currentTheme === "light" }
93- theme = { themes . light }
88+ theme = " light"
9489onSelect = { ( ) => onChangeTheme ( "light" ) }
9590/>
96- </ Stack >
91+ </ div >
9792</ Section >
98- < div css = { { marginBottom :48 } } > </ div >
9993< Section
10094title = {
101- < Stack direction = " row" alignItems = " center">
102- < span > Terminal Font</ span >
103- { isUpdating && < CircularProgress size = { 16 } /> }
104- </ Stack >
95+ < div className = "flex flex- row items- center gap-2 ">
96+ < span id = "fonts-radio-buttons-group-label" > Terminal Font</ span >
97+ < Spinner loading = { isUpdating } size = "sm" />
98+ </ div >
10599}
106100layout = "fluid"
107101>
108- < FormControl >
109- < RadioGroup
110- aria-labelledby = "fonts-radio-buttons-group-label"
111- defaultValue = { currentTerminalFont }
112- name = "fonts-radio-buttons-group"
113- onChange = { ( _ , value ) =>
114- onChangeTerminalFont ( toTerminalFontName ( value ) )
115- }
116- >
117- { TerminalFontNames . filter ( ( name ) => name !== "" ) . map ( ( name ) => (
118- < FormControlLabel
119- key = { name }
120- value = { name }
121- control = { < Radio /> }
122- label = {
123- < div css = { { fontFamily :terminalFonts [ name ] } } >
124- { terminalFontLabels [ name ] }
125- </ div >
126- }
127- />
128- ) ) }
129- </ RadioGroup >
130- </ FormControl >
102+ < RadioGroup
103+ aria-labelledby = "fonts-radio-buttons-group-label"
104+ defaultValue = { currentTerminalFont }
105+ name = "fonts-radio-buttons-group"
106+ onValueChange = { ( value ) =>
107+ onChangeTerminalFont ( toTerminalFontName ( value ) )
108+ }
109+ >
110+ { TerminalFontNames . filter ( ( name ) => name !== "" ) . map ( ( name ) => (
111+ < div key = { name } className = "flex items-center space-x-2" >
112+ < RadioGroupItem value = { name } id = { name } />
113+ < Label
114+ htmlFor = { name }
115+ className = "cursor-pointer font-normal"
116+ style = { { fontFamily :terminalFonts [ name ] } }
117+ >
118+ { terminalFontLabels [ name ] }
119+ </ Label >
120+ </ div >
121+ ) ) }
122+ </ RadioGroup >
131123</ Section >
132124</ form >
133125) ;
@@ -139,8 +131,10 @@ function toTerminalFontName(value: string): TerminalFontName {
139131:"" ;
140132}
141133
134+ type ThemeMode = "dark" | "light" ;
135+
142136interface AutoThemePreviewButtonProps extends Omit < ThemePreviewProps , "theme" > {
143- themes :[ Theme , Theme ] ;
137+ themes :[ ThemeMode , ThemeMode ] ;
144138onSelect ?:( ) => void ;
145139}
146140
@@ -163,13 +157,15 @@ const AutoThemePreviewButton: FC<AutoThemePreviewButtonProps> = ({
163157value = { displayName }
164158checked = { active }
165159onChange = { onSelect }
166- css = { { ... visuallyHidden } }
160+ className = "sr-only"
167161/>
168- < label htmlFor = { displayName } className = { cn ( "relative" , className ) } >
162+ < label
163+ htmlFor = { displayName }
164+ className = { cn ( "relative cursor-pointer" , className ) }
165+ >
169166< ThemePreview
170- css = { {
171- // This half is absolute to not advance the layout (which would offset the second half)
172- position :"absolute" ,
167+ className = "absolute"
168+ style = { {
173169// Slightly past the bounding box to avoid cutting off the outline
174170clipPath :"polygon(-5% -5%, 50% -5%, 50% 105%, -5% 105%)" ,
175171} }
@@ -210,9 +206,9 @@ const ThemePreviewButton: FC<ThemePreviewButtonProps> = ({
210206value = { displayName }
211207checked = { active }
212208onChange = { onSelect }
213- css = { { ... visuallyHidden } }
209+ className = "sr-only"
214210/>
215- < label htmlFor = { displayName } className = { className } >
211+ < label htmlFor = { displayName } className = { cn ( "cursor-pointer" , className ) } >
216212< ThemePreview
217213active = { active }
218214preview = { preview }
@@ -228,152 +224,65 @@ interface ThemePreviewProps {
228224active ?:boolean ;
229225preview ?:boolean ;
230226className ?:string ;
227+ style ?:React . CSSProperties ;
231228displayName :string ;
232- theme :Theme ;
229+ theme :ThemeMode ;
233230}
234231
235232const ThemePreview :FC < ThemePreviewProps > = ( {
236233active,
237234preview,
238235className,
236+ style,
239237displayName,
240238theme,
241239} ) => {
242240return (
243- < ThemeOverride theme = { theme } >
241+ < div className = { theme } >
244242< div
245- css = { [ styles . container , active && styles . containerActive ] }
246- className = { className }
243+ className = { cn (
244+ "w-56 overflow-clip rounded-md border border-border border-solid bg-surface-primary text-content-primary select-none" ,
245+ active && "outline outline-2 outline-content-link" ,
246+ className ,
247+ ) }
248+ style = { style }
247249>
248- < div css = { styles . page } >
249- < div css = { styles . header } >
250- < div css = { styles . headerLinks } >
251- < div css = { [ styles . headerLink , styles . activeHeaderLink ] } />
252- < div css = { styles . headerLink } />
253- < div css = { styles . headerLink } />
250+ < div className = "bg-surface-primary text-content-primary" >
251+ < div className = "bg-surface-secondary flex items-center justify-between px-2.5 py-1.5 mb-2 border-0 border-b border-border border-solid" >
252+ < div className = "flex items-center gap-1.5" >
253+ < div className = "bg-content-primary h-1.5 w-5 rounded" />
254+ < div className = "bg-content-secondary h-1.5 w-5 rounded" />
255+ < div className = "bg-content-secondary h-1.5 w-5 rounded" />
254256</ div >
255- < div css = { styles . headerLinks } >
256- < div css = { styles . proxy } />
257- < div css = { styles . user } />
257+ < div className = "flex items-center gap-1.5" >
258+ < div className = "bg-green-400 h-1.5 w-3 rounded" />
259+ < div className = "bg-content-primary h-2 w-2 rounded-full" />
258260</ div >
259261</ div >
260- < div css = { styles . body } >
261- < div css = { styles . title } />
262- < div css = { styles . table } >
263- < div css = { styles . tableHeader } />
264- < div css = { styles . workspace } />
265- < div css = { styles . workspace } />
266- < div css = { styles . workspace } />
267- < div css = { styles . workspace } />
262+ < div className = "w-32 mx-auto" >
263+ < div className = "bg-content-primary h-2 w-11 rounded mb-1.5" />
264+ < div className = "border border-solid rounded-t overflow-clip" >
265+ < div className = "bg-surface-secondary h-2.5 -m-px" />
266+ < div className = "h-4 border-0 border-t border-border border-solid" >
267+ < div className = "bg-content-disabled h-1.5 w-8 rounded mt-1 ml-1" />
268+ </ div >
269+ < div className = "h-4 border-0 border-t border-border border-solid" >
270+ < div className = "bg-content-disabled h-1.5 w-8 rounded mt-1 ml-1" />
271+ </ div >
272+ < div className = "h-4 border-0 border-t border-border border-solid" >
273+ < div className = "bg-content-disabled h-1.5 w-8 rounded mt-1 ml-1" />
274+ </ div >
275+ < div className = "h-4 border-0 border-t border-border border-solid" >
276+ < div className = "bg-content-disabled h-1.5 w-8 rounded mt-1 ml-1" />
277+ </ div >
268278</ div >
269279</ div >
270280</ div >
271- < div css = { styles . label } >
281+ < div className = "flex items-center justify-between border-0 border-t border-border border-solid px-3 py-1 text-sm" >
272282< span > { displayName } </ span >
273283{ preview && < PreviewBadge /> }
274284</ div >
275285</ div >
276- </ ThemeOverride >
286+ </ div >
277287) ;
278288} ;
279-
280- const styles = {
281- container :( theme ) => ( {
282- backgroundColor :theme . palette . background . default ,
283- border :`1px solid${ theme . palette . divider } ` ,
284- width :220 ,
285- color :theme . palette . text . primary ,
286- borderRadius :6 ,
287- overflow :"clip" ,
288- userSelect :"none" ,
289- } ) ,
290- containerActive :( theme ) => ( {
291- outline :`2px solid${ theme . roles . active . outline } ` ,
292- } ) ,
293- page :( theme ) => ( {
294- backgroundColor :theme . palette . background . default ,
295- color :theme . palette . text . primary ,
296- } ) ,
297- header :( theme ) => ( {
298- backgroundColor :theme . palette . background . paper ,
299- display :"flex" ,
300- alignItems :"center" ,
301- justifyContent :"space-between" ,
302- padding :"6px 10px" ,
303- marginBottom :8 ,
304- borderBottom :`1px solid${ theme . palette . divider } ` ,
305- } ) ,
306- headerLinks :{
307- display :"flex" ,
308- alignItems :"center" ,
309- gap :6 ,
310- } ,
311- headerLink :( theme ) => ( {
312- backgroundColor :theme . palette . text . secondary ,
313- height :6 ,
314- width :20 ,
315- borderRadius :3 ,
316- } ) ,
317- activeHeaderLink :( theme ) => ( {
318- backgroundColor :theme . palette . text . primary ,
319- } ) ,
320- proxy :( theme ) => ( {
321- backgroundColor :theme . palette . success . light ,
322- height :6 ,
323- width :12 ,
324- borderRadius :3 ,
325- } ) ,
326- user :( theme ) => ( {
327- backgroundColor :theme . palette . text . primary ,
328- height :8 ,
329- width :8 ,
330- borderRadius :4 ,
331- float :"right" ,
332- } ) ,
333- body :{
334- width :120 ,
335- margin :"auto" ,
336- } ,
337- title :( theme ) => ( {
338- backgroundColor :theme . palette . text . primary ,
339- height :8 ,
340- width :45 ,
341- borderRadius :4 ,
342- marginBottom :6 ,
343- } ) ,
344- table :( theme ) => ( {
345- border :`1px solid${ theme . palette . divider } ` ,
346- borderBottom :"none" ,
347- borderTopLeftRadius :3 ,
348- borderTopRightRadius :3 ,
349- overflow :"clip" ,
350- } ) ,
351- tableHeader :( theme ) => ( {
352- backgroundColor :theme . palette . background . paper ,
353- height :10 ,
354- margin :- 1 ,
355- } ) ,
356- label :( theme ) => ( {
357- display :"flex" ,
358- alignItems :"center" ,
359- justifyContent :"space-between" ,
360- borderTop :`1px solid${ theme . palette . divider } ` ,
361- padding :"4px 12px" ,
362- fontSize :14 ,
363- } ) ,
364- workspace :( theme ) => ( {
365- borderTop :`1px solid${ theme . palette . divider } ` ,
366- height :15 ,
367-
368- "&::after" :{
369- content :'""' ,
370- display :"block" ,
371- marginTop :4 ,
372- marginLeft :4 ,
373- backgroundColor :theme . palette . text . disabled ,
374- height :6 ,
375- width :30 ,
376- borderRadius :3 ,
377- } ,
378- } ) ,
379- } satisfies Record < string , Interpolation < Theme > > ;