Skip to content

Commit 961ae31

Browse files
feat(ui): Add mosaic organization profile components (#9045)
1 parent 26aa837 commit 961ae31

55 files changed

Lines changed: 888 additions & 397 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/open-heads-brush.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/Composition.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export interface CompositionPiece {
1212
}
1313

1414
// Mosaic layers, high → low. Drives the order the composition groups render in.
15-
// Plural to match the sidebar group names.
16-
const LAYER_ORDER = ['AIO', 'Panels', 'Sections', 'Blocks', 'Components', 'Primitives'];
15+
// Matches the sidebar group names.
16+
const LAYER_ORDER = ['Organization', 'Blocks', 'Components', 'Primitives'];
1717

1818
function layerRank(layer: string): number {
1919
const i = LAYER_ORDER.indexOf(layer);

packages/swingset/src/components/DocsViewer.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ import { ViewSource } from './ViewSource';
1010
// MDX docs keyed by `group` slug → `component` slug. Group-aware so identically-named
1111
// entries (the headless `Dialog` primitive vs. the styled `Dialog` component) stay distinct.
1212
const docModules: Record<string, Record<string, React.ComponentType>> = {
13-
aio: {
13+
organization: {
1414
'organization-profile': dynamic(() => import('../stories/organization-profile.mdx')),
15-
},
16-
panels: {
17-
'organization-profile-general': dynamic(() => import('../stories/organization-profile-general.mdx')),
18-
},
19-
sections: {
20-
'leave-organization': dynamic(() => import('../stories/leave-organization.mdx')),
21-
'delete-organization': dynamic(() => import('../stories/delete-organization.mdx')),
15+
'organization-profile-general-panel': dynamic(() => import('../stories/organization-profile-general-panel.mdx')),
16+
'organization-profile-leave-section': dynamic(() => import('../stories/organization-profile-leave-section.mdx')),
17+
'organization-profile-delete-section': dynamic(() => import('../stories/organization-profile-delete-section.mdx')),
2218
},
2319
blocks: {
2420
destructive: dynamic(() => import('../stories/destructive.mdx')),

packages/swingset/src/components/app-sidebar.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,19 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
7171
<SidebarMenu>
7272
{components.map(({ mod, componentSlug }) => {
7373
const href = `/${groupSlug}/${componentSlug}`;
74+
// Hooks (e.g. `useDataTable`) are called, not rendered — show `useX()` rather
75+
// than JSX `<useX />`. Everything else is a component.
76+
const isHook = /^use[A-Z]/.test(mod.meta.title);
77+
const usage = isHook ? `${mod.meta.title}()` : `<${mod.meta.title} />`;
7478
return (
7579
<SidebarMenuItem key={mod.meta.title}>
7680
<SidebarMenuButton
77-
className='h-auto justify-between py-1 text-xs leading-relaxed'
81+
className='h-auto items-start py-1 text-xs leading-relaxed'
7882
isActive={pathname === href}
7983
render={<Link href={href} />}
8084
>
81-
<span className='truncate'>{mod.meta.label ?? mod.meta.title}</span>
82-
<span className='text-sidebar-foreground/50 shrink-0 font-mono text-[10px] leading-none'>
83-
{`<${mod.meta.title} />`}
85+
<span className='whitespace-normal! break-all font-mono text-[10px] leading-relaxed'>
86+
{usage}
8487
</span>
8588
</SidebarMenuButton>
8689
</SidebarMenuItem>

packages/swingset/src/lib/registry.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import {
88
meta as cardComponentMeta,
99
} from '../stories/card.component.stories';
1010
import { meta as collapsibleMeta } from '../stories/collapsible.stories';
11-
import {
12-
Default as DeleteOrganizationDefault,
13-
meta as deleteOrganizationMeta,
14-
} from '../stories/delete-organization.stories';
1511
import { Default as DestructiveDefault, meta as destructiveMeta } from '../stories/destructive.stories';
1612
import { Default as DialogDefault, meta as dialogComponentMeta } from '../stories/dialog.component.stories';
1713
import { meta as dialogMeta } from '../stories/dialog.stories';
@@ -37,19 +33,23 @@ import {
3733
meta as inputMeta,
3834
Sizes as InputSizes,
3935
} from '../stories/input.stories';
40-
import {
41-
Default as LeaveOrganizationDefault,
42-
meta as leaveOrganizationMeta,
43-
} from '../stories/leave-organization.stories';
4436
import { meta as menuMeta } from '../stories/menu.stories';
4537
import {
4638
Default as OrganizationProfileDefault,
4739
meta as organizationProfileMeta,
4840
} from '../stories/organization-profile.stories';
4941
import {
50-
Default as OrganizationProfileGeneralDefault,
51-
meta as organizationProfileGeneralMeta,
52-
} from '../stories/organization-profile-general.stories';
42+
Default as OrganizationProfileDeleteSectionDefault,
43+
meta as organizationProfileDeleteSectionMeta,
44+
} from '../stories/organization-profile-delete-section.stories';
45+
import {
46+
Default as OrganizationProfileGeneralPanelDefault,
47+
meta as organizationProfileGeneralPanelMeta,
48+
} from '../stories/organization-profile-general-panel.stories';
49+
import {
50+
Default as OrganizationProfileLeaveSectionDefault,
51+
meta as organizationProfileLeaveSectionMeta,
52+
} from '../stories/organization-profile-leave-section.stories';
5353
import { meta as otpMeta } from '../stories/otp.stories';
5454
import { meta as popoverMeta } from '../stories/popover.stories';
5555
import { meta as selectMeta } from '../stories/select.stories';
@@ -67,12 +67,18 @@ import { toSlug } from './slug';
6767
import type { StoryModule } from './types';
6868

6969
const destructiveModule: StoryModule = { meta: destructiveMeta, Default: DestructiveDefault };
70-
const leaveOrganizationModule: StoryModule = { meta: leaveOrganizationMeta, Default: LeaveOrganizationDefault };
71-
const deleteOrganizationModule: StoryModule = { meta: deleteOrganizationMeta, Default: DeleteOrganizationDefault };
70+
const organizationProfileLeaveSectionModule: StoryModule = {
71+
meta: organizationProfileLeaveSectionMeta,
72+
Default: OrganizationProfileLeaveSectionDefault,
73+
};
74+
const organizationProfileDeleteSectionModule: StoryModule = {
75+
meta: organizationProfileDeleteSectionMeta,
76+
Default: OrganizationProfileDeleteSectionDefault,
77+
};
7278
const organizationProfileModule: StoryModule = { meta: organizationProfileMeta, Default: OrganizationProfileDefault };
73-
const organizationProfileGeneralModule: StoryModule = {
74-
meta: organizationProfileGeneralMeta,
75-
Default: OrganizationProfileGeneralDefault,
79+
const organizationProfileGeneralPanelModule: StoryModule = {
80+
meta: organizationProfileGeneralPanelMeta,
81+
Default: OrganizationProfileGeneralPanelDefault,
7682
};
7783

7884
const cardComponentModule: StoryModule = { meta: cardComponentMeta, Default: CardDefault, Centered: CardCentered };
@@ -121,13 +127,11 @@ const tooltipModule: StoryModule = { meta: tooltipMeta };
121127
const useDataTableModule: StoryModule = { meta: useDataTableMeta };
122128

123129
export const registry: StoryModule[] = [
124-
// AIO
130+
// Organization
125131
organizationProfileModule,
126-
// Panels
127-
organizationProfileGeneralModule,
128-
// Sections
129-
leaveOrganizationModule,
130-
deleteOrganizationModule,
132+
organizationProfileGeneralPanelModule,
133+
organizationProfileLeaveSectionModule,
134+
organizationProfileDeleteSectionModule,
131135
// Blocks
132136
destructiveModule,
133137
// Components

packages/swingset/src/stories/delete-organization.stories.tsx

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/swingset/src/stories/icon.stories.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/** @jsxImportSource @emotion/react */
2-
import type { MosaicIconRenderer } from '@clerk/ui/mosaic/appearance';
32
import type { IconProps } from '@clerk/ui/mosaic/components/icon';
43
import { Icon, iconRecipe } from '@clerk/ui/mosaic/components/icon';
54
import { iconRegistry } from '@clerk/ui/mosaic/icons/registry';
@@ -75,26 +74,30 @@ export function Names() {
7574
);
7675
}
7776

78-
// Mosaic's styling (sizing/color) applies to the override just like the built-in glyph, so the
79-
// replacement only needs its viewBox + paths — spread `props` to receive the sizing className and
80-
// `data-cl-slot`. Defined at module scope (not inline in the story) to keep a stable component type.
81-
const CircleGlyph: MosaicIconRenderer = props => (
82-
<svg
83-
{...props}
84-
viewBox='0 0 20 20'
85-
fill='currentColor'
86-
>
87-
<circle
88-
cx={10}
89-
cy={10}
90-
r={6}
91-
/>
92-
</svg>
93-
);
94-
9577
export function Override() {
9678
return (
97-
<MosaicProvider appearance={{ icons: { 'chevron-right': CircleGlyph } }}>
79+
<MosaicProvider
80+
appearance={{
81+
// Overrides are elements now, not render functions: Mosaic injects its sizing className and
82+
// `data-cl-slot` into the element via cloneElement, so the replacement only needs its viewBox
83+
// + paths. Passing an element (vs a function) also lets overrides be supplied from a Server
84+
// Component, since elements serialize across the RSC boundary.
85+
icons: {
86+
'chevron-right': (
87+
<svg
88+
viewBox='0 0 20 20'
89+
fill='currentColor'
90+
>
91+
<circle
92+
cx={10}
93+
cy={10}
94+
r={6}
95+
/>
96+
</svg>
97+
),
98+
},
99+
}}
100+
>
98101
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
99102
<Icon name='chevron-right' />
100103
<Icon name='chevron-left' />

packages/swingset/src/stories/leave-organization.stories.tsx

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/swingset/src/stories/delete-organization.mdx renamed to packages/swingset/src/stories/organization-profile-delete-section.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import * as DeleteOrganizationStories from './delete-organization.stories';
1+
import * as OrganizationProfileDeleteSectionStories from './organization-profile-delete-section.stories';
22

3-
# Delete Organization
3+
# Organization Profile Delete Section
44

55
A section that owns the open/deleting state and wires the `Destructive` block to the delete-organization flow.
66

77
<Story
88
name='Default'
9-
storyModule={DeleteOrganizationStories}
9+
storyModule={OrganizationProfileDeleteSectionStories}
1010
composition={[
1111
{ name: 'Destructive', href: '/blocks/destructive', layer: 'Blocks' },
1212
{ name: 'Button', href: '/components/button', layer: 'Components' },
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** @jsxImportSource @emotion/react */
2+
import { useMachine } from '@clerk/ui/mosaic/machine/useMachine';
3+
import { organizationProfileDeleteSectionMachine } from '@clerk/ui/mosaic/organization/organization-profile-delete-section.machine';
4+
import { OrganizationProfileDeleteSectionView } from '@clerk/ui/mosaic/organization/organization-profile-delete-section.view';
5+
6+
import type { StoryMeta } from '@/lib/types';
7+
8+
export const meta: StoryMeta = {
9+
group: 'Organization',
10+
title: 'OrganizationProfileDeleteSection',
11+
source: 'packages/ui/src/mosaic/organization/organization-profile-delete-section.tsx',
12+
};
13+
14+
export function Default() {
15+
const [snapshot, send, actor] = useMachine(organizationProfileDeleteSectionMachine, {
16+
context: {
17+
organizationName: 'Acme Inc',
18+
destroyOrganization: () => new Promise<void>(resolve => setTimeout(resolve, 800)),
19+
},
20+
});
21+
22+
return (
23+
<OrganizationProfileDeleteSectionView
24+
snapshot={snapshot}
25+
send={send}
26+
canSubmit={actor.can({ type: 'CONFIRM' })}
27+
/>
28+
);
29+
}

0 commit comments

Comments
 (0)