Skip to content

Commit ef07a60

Browse files
feat(ui): add SettingsGroup block
1 parent c234942 commit ef07a60

9 files changed

Lines changed: 691 additions & 0 deletions

File tree

.changeset/settings-group-block.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/swingset/src/components/DocsViewer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const docModules: Record<string, Record<string, React.ComponentType>> = {
2929
},
3030
blocks: {
3131
destructive: dynamic(() => import('../stories/destructive.mdx')),
32+
'settings-group': dynamic(() => import('../stories/settings-group.mdx')),
3233
},
3334
components: {
3435
avatar: dynamic(() => import('../stories/avatar.mdx')),

packages/swingset/src/lib/registry.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ import {
106106
ThemedScrollbar as ScrollAreaThemedScrollbar,
107107
} from '../stories/scroll-area.stories';
108108
import { meta as selectMeta } from '../stories/select.stories';
109+
import {
110+
ConnectedAccounts as SettingsGroupConnectedAccounts,
111+
Default as SettingsGroupDefault,
112+
Destructive as SettingsGroupDestructive,
113+
meta as settingsGroupMeta,
114+
} from '../stories/settings-group.stories';
109115
import { Default as TabsComponentDefault, meta as tabsComponentMeta } from '../stories/tabs.component.stories';
110116
import { meta as tabsMeta } from '../stories/tabs.stories';
111117
import {
@@ -126,6 +132,12 @@ import { toSlug } from './slug';
126132
import type { StoryModule } from './types';
127133

128134
const destructiveModule: StoryModule = { meta: destructiveMeta, Default: DestructiveDefault };
135+
const settingsGroupModule: StoryModule = {
136+
meta: settingsGroupMeta,
137+
Default: SettingsGroupDefault,
138+
ConnectedAccounts: SettingsGroupConnectedAccounts,
139+
Destructive: SettingsGroupDestructive,
140+
};
129141
const organizationProfileLeaveSectionModule: StoryModule = {
130142
meta: organizationProfileLeaveSectionMeta,
131143
Default: OrganizationProfileLeaveSectionDefault,
@@ -269,6 +281,7 @@ export const registry: StoryModule[] = [
269281
organizationProfileDeleteSectionModule,
270282
// Blocks
271283
destructiveModule,
284+
settingsGroupModule,
272285
// Components
273286
avatarModule,
274287
badgeModule,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as SettingsGroupStories from './settings-group.stories';
2+
3+
# SettingsGroup
4+
5+
A settings block that carries the canonical section, surface, separator, and label/control layout. Use it for product settings rather than assembling those decisions from generic cards and item groups.
6+
7+
## Usage
8+
9+
```tsx
10+
import { SettingsGroup } from '@clerk/ui/mosaic/block/settings-group';
11+
12+
<SettingsGroup.Root>
13+
<SettingsGroup.Title>Account</SettingsGroup.Title>
14+
<SettingsGroup.List>
15+
<SettingsGroup.Row>
16+
<SettingsGroup.Media>{icon}</SettingsGroup.Media>
17+
<SettingsGroup.Label description='Shown throughout the application.'>
18+
Name
19+
</SettingsGroup.Label>
20+
<SettingsGroup.Control>{control}</SettingsGroup.Control>
21+
</SettingsGroup.Row>
22+
</SettingsGroup.List>
23+
</SettingsGroup.Root>
24+
```
25+
26+
`SettingsGroup.List` owns the outlined surface and separation between direct `SettingsGroup.Row` children. `SettingsGroup.Media` reserves a consistent slot for icons, avatars, and provider marks. Each row places its label opposite its control. Use `SettingsGroup.Control` for inputs, switches, menus, and buttons—not only actions.
27+
28+
When a row contains one form control, add `field` to associate its label automatically:
29+
30+
```tsx
31+
<SettingsGroup.Row field>
32+
<SettingsGroup.Label>Name</SettingsGroup.Label>
33+
<SettingsGroup.Control>
34+
<Input />
35+
</SettingsGroup.Control>
36+
</SettingsGroup.Row>
37+
```
38+
39+
## Examples
40+
41+
<Story
42+
name='Default'
43+
storyModule={SettingsGroupStories}
44+
composition={[
45+
{ name: 'Avatar', href: '/components/avatar', layer: 'Components' },
46+
{ name: 'Input', href: '/components/input', layer: 'Components' },
47+
]}
48+
/>
49+
50+
### Connected accounts
51+
52+
<Story
53+
name='ConnectedAccounts'
54+
storyModule={SettingsGroupStories}
55+
composition={[
56+
{ name: 'Button', href: '/components/button', layer: 'Components' },
57+
{ name: 'Icon', href: '/components/icon', layer: 'Components' },
58+
]}
59+
/>
60+
61+
### Destructive setting
62+
63+
<Story
64+
name='Destructive'
65+
storyModule={SettingsGroupStories}
66+
/>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/** @jsxImportSource @emotion/react */
2+
import { SettingsGroup } from '@clerk/ui/mosaic/block/settings-group';
3+
import { Avatar } from '@clerk/ui/mosaic/components/avatar';
4+
import { Button } from '@clerk/ui/mosaic/components/button';
5+
import { Icon } from '@clerk/ui/mosaic/components/icon';
6+
import { Input } from '@clerk/ui/mosaic/components/input';
7+
8+
import type { StoryMeta } from '@/lib/types';
9+
10+
export { default as __source } from './settings-group.stories?raw';
11+
12+
const providerIconUrl = (provider: string) => `https://img.clerk.com/static/${provider}.svg`;
13+
14+
function ProviderIcon({ provider }: { provider: string }) {
15+
return (
16+
<img
17+
alt=''
18+
src={providerIconUrl(provider)}
19+
style={{ display: 'block', height: 24, width: 24 }}
20+
/>
21+
);
22+
}
23+
24+
export const meta: StoryMeta = {
25+
group: 'Blocks',
26+
title: 'SettingsGroup',
27+
source: 'packages/ui/src/mosaic/block/settings-group.tsx',
28+
styleEngine: 'stylex',
29+
};
30+
31+
export function Default() {
32+
return (
33+
<SettingsGroup.Root style={{ maxWidth: 560 }}>
34+
<SettingsGroup.Title>Account</SettingsGroup.Title>
35+
<SettingsGroup.List>
36+
<SettingsGroup.Row>
37+
<SettingsGroup.Label>Profile picture</SettingsGroup.Label>
38+
<SettingsGroup.Control>
39+
<Avatar.Root size='md'>
40+
<Avatar.Image
41+
alt='Preston Booth'
42+
src='https://avatars.githubusercontent.com/u/51144033?v=4'
43+
/>
44+
<Avatar.Fallback>PB</Avatar.Fallback>
45+
</Avatar.Root>
46+
</SettingsGroup.Control>
47+
</SettingsGroup.Row>
48+
<SettingsGroup.Row field>
49+
<SettingsGroup.Label>Name</SettingsGroup.Label>
50+
<SettingsGroup.Control>
51+
<Input
52+
defaultValue='Preston Booth'
53+
style={{ width: 176 }}
54+
/>
55+
</SettingsGroup.Control>
56+
</SettingsGroup.Row>
57+
<SettingsGroup.Row field>
58+
<SettingsGroup.Label>Username</SettingsGroup.Label>
59+
<SettingsGroup.Control>
60+
<Input
61+
defaultValue='prestonxyz'
62+
style={{ width: 176 }}
63+
/>
64+
</SettingsGroup.Control>
65+
</SettingsGroup.Row>
66+
</SettingsGroup.List>
67+
</SettingsGroup.Root>
68+
);
69+
}
70+
71+
export function ConnectedAccounts() {
72+
return (
73+
<SettingsGroup.Root style={{ maxWidth: 560 }}>
74+
<SettingsGroup.Title>Connected accounts</SettingsGroup.Title>
75+
<SettingsGroup.List>
76+
<SettingsGroup.Row>
77+
<SettingsGroup.Media>
78+
<ProviderIcon provider='google' />
79+
</SettingsGroup.Media>
80+
<SettingsGroup.Label description='test@google.com'>Google</SettingsGroup.Label>
81+
<SettingsGroup.Control>
82+
<Button
83+
aria-label='Manage Google'
84+
color='neutral'
85+
shape='square'
86+
size='sm'
87+
variant='ghost'
88+
>
89+
<Icon name='ellipsis' />
90+
</Button>
91+
</SettingsGroup.Control>
92+
</SettingsGroup.Row>
93+
<SettingsGroup.Row>
94+
<SettingsGroup.Media>
95+
<ProviderIcon provider='apple' />
96+
</SettingsGroup.Media>
97+
<SettingsGroup.Label>Apple</SettingsGroup.Label>
98+
<SettingsGroup.Control>
99+
<Button
100+
color='neutral'
101+
size='sm'
102+
variant='outline'
103+
>
104+
Connect
105+
</Button>
106+
</SettingsGroup.Control>
107+
</SettingsGroup.Row>
108+
</SettingsGroup.List>
109+
</SettingsGroup.Root>
110+
);
111+
}
112+
113+
export function Destructive() {
114+
return (
115+
<SettingsGroup.Root style={{ maxWidth: 560 }}>
116+
<SettingsGroup.Title>Danger zone</SettingsGroup.Title>
117+
<SettingsGroup.List>
118+
<SettingsGroup.Row>
119+
<SettingsGroup.Label description='This action is permanent and irreversible.'>
120+
Delete account
121+
</SettingsGroup.Label>
122+
<SettingsGroup.Control>
123+
<Button
124+
color='negative'
125+
size='sm'
126+
variant='outline'
127+
>
128+
Delete account
129+
</Button>
130+
</SettingsGroup.Control>
131+
</SettingsGroup.Row>
132+
</SettingsGroup.List>
133+
</SettingsGroup.Root>
134+
);
135+
}

0 commit comments

Comments
 (0)