Settings Kit
This package provides components to help users scaffold out settings pages quickly. All components in this package adhere to the Settings Figma file.
Settings Cards
The settings kit provides a SettingsCard component to provide structure when displaying one or more settings sections on a page. The card has two sections: content and actions.
Card Content
Card content, displayed on the left side of the card when viewed at full width, includes the card title, description, and optional statuses.
Minimal Example
import { SettingsCard } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section A"
description="This is the description for this settings section."
/>
)
}
Example with Statuses
import { SettingsCard } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section A"
description="This is the description for this settings section."
statuses={[
{
content: 'Enabled message...',
variant: 'enabled',
},
{
content: 'Disabled message...',
variant: 'disabled',
},
{
content: 'Error message...',
variant: 'error',
},
{
content: 'Info message...',
variant: 'info',
},
{
content: 'Warning message...',
variant: 'warning',
},
]}
/>
)
}
Example with Description Component
import { SettingsCard } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section A"
description={
<>
This is the description for this settings section.{' '}
<Link href="/settings/example/full-page" variant="alternative">
This is a link to a site with more information
</Link>
</>
}
/>
)
}
Example with Skeletons
import { SettingsCard } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section A"
description="This is the description for this settings section."
isLoading={true}
/>
)
}
Card Actions
Card actions, displayed on the right side of the card when viewed at full width, can be either simple or disclosed. Simple actions perform their action immediately when clicked. These include buttons that open modals, toggles, and links to full page settings layouts. Disclosed actions are displayed inline after clicking an "Edit" button. The settings kit exports actions to handle all of these use cases.
Simple Actions
ButtonAction
The ButtonAction is used to open a modal on the current page.
import { SettingsCard, ButtonAction } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section With Modal"
description="This is the description for this settings section."
actionElement={<ButtonAction onClick={() => alert('Open modal')} />}
/>
)
}
If necessary, the default text can be overridden using the text prop.
LinkAction
The LinkAction is used when linking to another full page layout settings page.
import { SettingsCard, LinkAction } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section With Link"
description="This is the description for this settings section."
actionElement={<LinkAction to="/full-page-settings" />}
/>
)
}
If necessary, the default text can be overridden using the text prop.
ToggleAction
The ToggleAction is used when allowing the user to toggle a boolean settings value.
import { SettingsCard, ToggleAction } from '@shared/settings-kit'
export function SettingsSection() {
const [isEnabled, toggleEnabled] = useReducer((state) => !state, false)
return (
<SettingsCard
title="Section With Toggle"
description="This is the description for this settings section."
actionElement={<ToggleAction onClick={toggleEnabled} value={isEnabled} />}
/>
)
}
Disclosed Actions
ActionDisclosure
Disclosed actions are wrapped in an ActionDisclosure component. This component is responsible for displaying an initial button to toggle the underlying actions visibility. It also provides Save and Cancel buttons that display when the disclosed action is showing.
import { SettingsCard, ActionDisclosure } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SettingsCard
title="Section With Disclosed Action"
description="This is the description for this settings section."
actionElement={
<ActionDisclosure
onSave={(callback) => {
setTimeout(() => {
successToast('Setting saved')
callback(true)
}, 1500)
}}
>
{/* Disclosed action */}
</ActionDisclosure>
}
/>
)
}
In the above example, the ActionDisclosure will first show an Edit button. When clicked, it will hide the Edit button and instead display the disclosed action, a Save button, and a Cancel button. The disclosed action can be either a SelectAction or TextFieldAction. The user can interact with those then click either Save or Cancel. Clicking Cancel hides the disclosed action and displays the Edit button. Clicking Save causes multiple things to happen.
Clicking Save invokes the onSave handler and disables the Save and Cancel buttons. The onSave handler will be passed a callback that must be invoked to re-enable user interaction. The callback requires a boolean value. Passing true closes the disclosure to show the Edit button again. Passing false will keep the disclosed action showing and enabled the Save and Cancel buttons again. A more complete example of an onSave handler is shown below:
async function onSaveHandler(callback) {
try {
await executeNetworkRequest()
successToast('Save succeeded')
callback(true) // Show Edit button, hide disclosed action
} catch {
errorToast('Save failed')
callback(false) // Enable Save and Cancel buttons, keep disclosed action visible
}
}
SelectAction
The SelectAction component is a thin wrapper around the Select component from @rhythm/inputs. Refer to that component's documentation for all available options.
import {
SettingsCard,
ActionDisclosure,
SelectAction,
} from '@shared/settings-kit'
export function SettingsSection() {
const [inputValue, setInputValue] = useState()
return (
<SettingsCard
title="Section With Select Action"
description="This is the description for this settings section."
actionElement={
<ActionDisclosure
onSave={async (callback) => {
await executeNetworkRequest(inputValue)
callback(true)
}}
>
<SelectAction
onChange={(newValue) => setInputValue(newValue)}
options={[
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b' },
]}
value={inputValue}
/>
</ActionDisclosure>
}
/>
)
}
Note how in the above example, the current value for SelectAction is kept in the inputValue local state variable outside the SelectAction or ActionDisclosure components. This is intentional. Once the user clicks the Save button encapsulated in ActionDisclosure, the onSave callback is invoked and one can access the current value of inputValue to save to the appropriate persistence layer.
TextFieldAction
The TextFieldAction component is a thin wrapper around the TextField component from @rhythm/inputs. Refer to that component's documentation for all available options.
import {
SettingsCard,
ActionDisclosure,
TextFieldAction,
} from '@shared/settings-kit'
export function SettingsSection() {
const [inputValue, setInputValue] = useState()
return (
<SettingsCard
title="Section With Text Field Action"
description="This is the description for this settings section."
actionElement={
<ActionDisclosure
onSave={async (callback) => {
await executeNetworkRequest(inputValue)
callback(true)
}}
>
<TextFieldAction
onChange={(newValue) => setInputValue(newValue)}
value={inputValue}
/>
</ActionDisclosure>
}
/>
)
}
Note how in the above example, the current value for TextFieldAction is kept in the inputValue local state variable outside the TextFieldAction or ActionDisclosure components. This is intentional. Once the user clicks the Save button encapsulated in ActionDisclosure, the onSave callback is invoked and one can access the current value of inputValue to save to the appropriate persistence layer.
Segmented Settings Cards
If you need to include multiple segments of settings within the same card, you can use SegmentedSettingsCard. The props normally passed to SettingsCard are passed as objects in a segments prop array.
import { SegmentedSettingsCard } from '@shared/settings-kit'
export function SettingsSection() {
return (
<SegmentedSettingsCard
segments={[
{
title: 'Segment A',
description:
'This is the description for this settings segment within the same card.',
},
{
title: 'Segment B',
description:
'This is the description for this settings section within the same card.',
},
{
title: 'Segment C',
description:
'This is the description for this settings section within the same card.',
},
]}
/>
)
}
Common Save Button
The SaveButton component has styling pre-applied and can be used everywhere a Save button is required.
import { SaveButton } from '@shared/settings-kit'
function FormFooter({ loading, handleSubmit }) {
return <SaveButton loading={loading} onClick={handleSubmit} />
}
If necessary, the default text can be overridden using the text prop.
Injecting Actions Into the Header Bar
For full page settings layouts, we include a Save button in the header bar. The HeaderBarActions component acts as a portal to transport a Save button into the correct location.
import { HeaderBarActions, SaveButton } from '@shared/settings-kit'
export function FormFooter({ loading, handleSubmit }) {
return (
<>
<SaveButton loading={loading} onClick={handleSubmit} />
<HeaderBarActions>
<SaveButton loading={loading} onClick={handleSubmit} />
</HeaderBarActions>
</>
)
}
In the above example, both SaveButtons are driven by the same props and accept the same onClick handler. The only difference is the second will be displayed in the header bar.
Full Page Settings Layouts
When rendering additional settings pages, a full page layout (one that does not include the automatically-provided section title) may be preferred. In these cases, the FullPageLayout component can be used to position the content correctly.
import { FullPageLayout } from '@shared/settings-kit'
export function BigSettingsPage() {
return <FullPageLayout>{/* Additional page content */}</FullPageLayout>
}