Fusion UI LogoFusion UI Logo
Fusion UI LogoFusion UI Logo
/
Getting Started
Concepts
Components
Example Apps
Getting Started
Concepts
Components
Example Apps
Fusion UI LogoFusion UI Logo
Getting Started
Concepts
Components
Example Apps
Overview
Example Apps
Analytics Dashboard
Customer Management
Account Settings
Support Inbox
Subscription Checkout

Account Settings

A settings page with annotated sections, many form controls and a danger zone.

Preview

/preview/settings

File: app.tsx

"use client";
import React, {useState} from "react";
import {
AnnotatedSection,
Button,
ButtonGroup,
Checkbox,
Container,
FormField,
FormSpacing,
IconErrorWarning,
IconNotification3,
IconShieldUser,
IconUserSettings,
InlineMessage,
Input,
MainNavView,
Modal,
ModalActions,
ModalTitle,
PageHeaderView,
Radio,
RadioGroup,
Select,
SideMenuItem,
SideMenuSection,
SideMenuView,
StackLayout,
Switch,
Tile,
TileContent,
useSnackbar,
VisualSelect,
VisualSelectOption,
} from "@brynlabs/fusion-ui";
type Profile = {name: string; email: string; language: string; timezone: string};
const savedProfile: Profile = {
name: "Jane Doe",
email: "jane@example.com",
language: "English",
timezone: "Europe/Berlin",
};
const themes = [
{value: "light", label: "Light", background: "#ffffff", bar: "#f6f8fa"},
{value: "dark", label: "Dark", background: "#242526", bar: "#18191a"},
{
value: "system",
label: "System",
background: "linear-gradient(135deg, #fff 50%, #242526 50%)",
bar: "transparent",
},
];
const ThemePreview = ({background, bar}: {background: string; bar: string}) => (
<div
style={{width: "100%", height: "100%", background, display: "flex", flexDirection: "column"}}>
<div style={{height: "22%", background: bar}} />
</div>
);
export const SettingsApp = () => {
const {showSnackbar} = useSnackbar();
const [profile, setProfile] = useState(savedProfile);
const [saved, setSaved] = useState(savedProfile);
const [theme, setTheme] = useState<string | null>("system");
const [density, setDensity] = useState("comfortable");
const [emailNotifications, setEmailNotifications] = useState(true);
const [pushNotifications, setPushNotifications] = useState(false);
const [weeklyReport, setWeeklyReport] = useState(true);
const [deleteOpen, setDeleteOpen] = useState(false);
const [confirmed, setConfirmed] = useState(false);
const dirty = JSON.stringify(profile) !== JSON.stringify(saved);
const emailInvalid = !/^\S+@\S+\.\S+$/.test(profile.email);
// Switches take effect right away, so they confirm with a snackbar instead of a save button
const toggle = (label: string, setter: (value: boolean) => void) => (value: boolean) => {
setter(value);
showSnackbar({message: `${label} turned ${value ? "on" : "off"}`, durationInSeconds: 3});
};
return (
<MainNavView
logo={<span className={"heading-4-font"}>Acme</span>}
menuItems={[
{title: "Projects", href: "#projects"},
{title: "Reports", href: "#reports"},
{title: "Settings", href: "/preview/settings"},
]}>
<SideMenuView
menuItems={
<SideMenuSection sectionTitle={"Settings"}>
<SideMenuItem
menuItem={{title: "Account", href: "/preview/settings", icon: <IconUserSettings />}}
/>
<SideMenuItem
menuItem={{
title: "Notifications",
href: "#notifications",
icon: <IconNotification3 />,
}}
/>
<SideMenuItem
menuItem={{title: "Security", href: "#security", icon: <IconShieldUser />}}
/>
</SideMenuSection>
}>
<PageHeaderView
pageHeaderTitle={"Account"}
subtitle={"Manage your profile and preferences"}>
<Container direction={"both"}>
<StackLayout>
<Tile>
<TileContent>
<AnnotatedSection
sectionTitle={"Profile"}
annotation={"Your name and email are visible to the members of your team."}>
<FormSpacing>
<Input
label={"Name"}
required
value={profile.name}
onChange={name => setProfile(p => ({...p, name}))}
/>
<Input
label={"Email"}
type={"email"}
required
value={profile.email}
invalid={emailInvalid}
errorMessage={emailInvalid ? "Enter a valid email address" : undefined}
hint={"We send a confirmation to the new address."}
onChange={email => setProfile(p => ({...p, email}))}
/>
<FormField label={"Language"}>
<Select
style={"FULL_SIZE"}
values={["English", "Deutsch", "Français"]}
value={profile.language}
placeholder={"Choose a language"}
onChange={language => language && setProfile(p => ({...p, language}))}
/>
</FormField>
<FormField label={"Time zone"}>
<Select
style={"FULL_SIZE"}
values={[
"Europe/Berlin",
"Europe/London",
"America/New_York",
"Asia/Tokyo",
]}
value={profile.timezone}
placeholder={"Choose a time zone"}
onChange={timezone => timezone && setProfile(p => ({...p, timezone}))}
/>
</FormField>
{dirty && (
<ButtonGroup>
<Button
disabled={emailInvalid}
onClick={() => {
setSaved(profile);
showSnackbar({message: "Profile saved"});
}}>
Save profile
</Button>
<Button color={"neutral"} onClick={() => setProfile(saved)}>
Discard
</Button>
</ButtonGroup>
)}
</FormSpacing>
</AnnotatedSection>
</TileContent>
</Tile>
<Tile>
<TileContent>
<AnnotatedSection
sectionTitle={"Appearance"}
annotation={
"Applies to this device only. System follows the setting of your operating system."
}>
<FormSpacing>
<VisualSelect
label={"Theme"}
value={theme}
onChange={setTheme}
aspectRatio={"4 / 3"}>
{themes.map(({value, label, background, bar}) => (
<VisualSelectOption key={value} value={value} label={label}>
<ThemePreview background={background} bar={bar} />
</VisualSelectOption>
))}
</VisualSelect>
<RadioGroup label={"Density"} defaultValue={density} onChange={setDensity}>
<Radio value={"comfortable"}>
Comfortable — more space, larger targets
</Radio>
<Radio value={"compact"}>Compact — more content on the screen</Radio>
</RadioGroup>
</FormSpacing>
</AnnotatedSection>
</TileContent>
</Tile>
<Tile>
<TileContent>
<AnnotatedSection
sectionTitle={"Notifications"}
annotation={"Changes take effect immediately."}>
<FormSpacing>
<Switch
label={"Email notifications"}
value={emailNotifications}
onChange={toggle("Email notifications", setEmailNotifications)}
/>
<Switch
label={"Push notifications"}
value={pushNotifications}
onChange={toggle("Push notifications", setPushNotifications)}
/>
<Switch
label={"Weekly report"}
value={weeklyReport}
onChange={toggle("Weekly report", setWeeklyReport)}
/>
</FormSpacing>
</AnnotatedSection>
</TileContent>
</Tile>
<Tile>
<TileContent>
<AnnotatedSection sectionTitle={"Danger zone"}>
<FormSpacing>
<InlineMessage
inlineMessageStyle={"critical"}
inlineMessageTitle={"Delete account"}>
Deleting your account removes all your data. This can’t be undone.
</InlineMessage>
<div>
<Button
color={"critical"}
buttonStyle={"outline"}
onClick={() => setDeleteOpen(true)}>
Delete account
</Button>
</div>
</FormSpacing>
</AnnotatedSection>
</TileContent>
</Tile>
</StackLayout>
</Container>
</PageHeaderView>
<Modal
open={deleteOpen}
size={"small"}
onAbort={() => setDeleteOpen(false)}
modalTitle={
<ModalTitle
icon={
<span style={{color: "var(--color--critical-500)"}}>
<IconErrorWarning />
</span>
}
onAbort={() => setDeleteOpen(false)}>
Delete your account?
</ModalTitle>
}
actions={
<ModalActions>
<Button
color={"critical"}
disabled={!confirmed}
onClick={() => {
setDeleteOpen(false);
setConfirmed(false);
showSnackbar({message: "This is a demo, nothing was deleted", style: "warn"});
}}>
Delete account
</Button>
<Button color={"neutral"} onClick={() => setDeleteOpen(false)}>
Keep account
</Button>
</ModalActions>
}>
<StackLayout>
<p>All projects, reports and invoices of this account are deleted permanently.</p>
<Checkbox
value={confirmed}
onChange={setConfirmed}
label={"I understand that this can’t be undone"}
/>
</StackLayout>
</Modal>
</SideMenuView>
</MainNavView>
);
};

What it shows

  • Annotated sections explaining each group of settings
  • Switches with immediate effect, radio buttons and a visual select for the theme
  • Form actions that only appear while there are unsaved changes
  • A critical inline message and a confirmation modal for deleting the account

Components used

Read more about each component and when to use it.

Main NavSide MenuAnnotated SectionFormInputSwitchRadioVisual SelectSelectCheckboxInline MessageModalButton
On this page
  • Preview
  • What it shows
  • Components used