A draft saved together
Change the greeting: the save bar appears. Save checks the fields first (clear the greeting to see it refuse).
Agent settings
How the front desk agent talks to callers.
import { useState } from 'react';
import { SettingRow, SettingToggle, SettingsPage, TextField } from '@arbol-ai/ui';
const SAVED_GREETING = 'Hi, this is Clínica Norte. How can I help?';
const AgentSettings = () => {
const [saved, setSaved] = useState({ greeting: SAVED_GREETING, endSilentCalls: true });
const [draft, setDraft] = useState(saved);
const [isSaved, setIsSaved] = useState(false);
return (
<SettingsPage
className="px-0 md:px-0"
description="How the front desk agent talks to callers."
label="Agent settings"
save={{
idle: isSaved ? 'All changes saved.' : undefined,
isDirty: draft.greeting !== saved.greeting || draft.endSilentCalls !== saved.endSilentCalls,
onDiscard: () => {
setDraft(saved);
},
onSave: () => {
setSaved(draft);
setIsSaved(true);
},
}}
sections={[
{
id: 'greeting',
title: 'Greeting',
description: 'What callers hear first.',
content: (
<SettingRow description="Said when the agent answers." label="Greeting" layout="stacked">
<TextField
aria-label="Greeting"
isRequired
multiline
onChange={(greeting) => {
setDraft({ ...draft, greeting });
setIsSaved(false);
}}
value={draft.greeting}
/>
</SettingRow>
),
},
{
id: 'calls',
title: 'Calls',
content: (
<SettingToggle
description="The agent hangs up after 30 seconds of silence."
isSelected={draft.endSilentCalls}
label="End silent calls"
onChange={(endSilentCalls) => {
setDraft({ ...draft, endSilentCalls });
setIsSaved(false);
}}
/>
),
},
]}
title="Agent settings"
/>
);
};
<AgentSettings />Settings that apply at once
No save: each control applies as it changes.
Profile
Preferences
How this browser shows Account.
import { useState } from 'react';
import { SegmentedControl, SettingRow, SettingsPage } from '@arbol-ai/ui';
const Preferences = () => {
const [language, setLanguage] = useState('es');
return (
<SettingsPage
className="px-0 md:px-0"
sections={[
{
id: 'preferences',
title: 'Preferences',
description: 'How this browser shows Account.',
content: (
<SettingRow label="Language" layout="responsive">
<SegmentedControl
label="Language"
onChange={setLanguage}
options={[
{ label: 'Español', value: 'es' },
{ label: 'English', value: 'en' },
]}
size="sm"
value={language}
/>
</SettingRow>
),
},
]}
title="Profile"
/>
);
};
<Preferences />An area with tabs and a preview
The area’s pages as tabs, a notice first, and what the settings change beside them.
import { UserPlusIcon } from '@phosphor-icons/react';
import { Notice, SettingRow, SettingsPage, TextField } from '@arbol-ai/ui';
<SettingsPage
className="px-0 md:px-0"
preview={
<div className="rounded-card bg-card text-muted-foreground grid place-items-center border p-8 text-sm">
Welcome to Clínica Norte
</div>
}
primaryAction={{ label: 'Invite people', icon: <UserPlusIcon />, href: '#/settings-page' }}
sections={[
{
id: 'identity',
title: 'Identity',
description: 'How the organization appears in every app.',
content: (
<SettingRow label="Name" layout="stacked">
<TextField aria-label="Name" defaultValue="Clínica Norte" />
</SettingRow>
),
},
]}
summary={<Notice tone="info">Only administrators change these settings.</Notice>}
tabs={{
items: [
{ id: 'organization', label: 'Organization', href: '#/settings-page' },
{ id: 'team', label: 'Team', href: '#/settings-page' },
{ id: 'billing', label: 'Billing', href: '#/settings-page' },
],
label: 'Settings pages',
value: 'organization',
}}
title="Organization"
/>Source
The examples above are src/blocks/settings-page/settings-page.examples.tsx, which the library’s tests render too.