Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/wild-members-gather.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 2 additions & 0 deletions packages/swingset/src/components/DocsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ const docModules: Record<string, Record<string, React.ComponentType>> = {
},
panels: {
'organization-profile-general': dynamic(() => import('../stories/organization-profile-general.mdx')),
'organization-profile-members': dynamic(() => import('../stories/organization-profile-members.mdx')),
},
sections: {
'leave-organization': dynamic(() => import('../stories/leave-organization.mdx')),
'delete-organization': dynamic(() => import('../stories/delete-organization.mdx')),
'members-list': dynamic(() => import('../stories/members-list.mdx')),
},
blocks: {
destructive: dynamic(() => import('../stories/destructive.mdx')),
Expand Down
12 changes: 12 additions & 0 deletions packages/swingset/src/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
Default as LeaveOrganizationDefault,
meta as leaveOrganizationMeta,
} from '../stories/leave-organization.stories';
import { Default as MembersListDefault, meta as membersListMeta } from '../stories/members-list.stories';
import { meta as menuMeta } from '../stories/menu.stories';
import {
Default as OrganizationProfileDefault,
Expand All @@ -50,6 +51,10 @@ import {
Default as OrganizationProfileGeneralDefault,
meta as organizationProfileGeneralMeta,
} from '../stories/organization-profile-general.stories';
import {
Default as OrganizationProfileMembersDefault,
meta as organizationProfileMembersMeta,
} from '../stories/organization-profile-members.stories';
import { meta as otpMeta } from '../stories/otp.stories';
import { meta as popoverMeta } from '../stories/popover.stories';
import { meta as selectMeta } from '../stories/select.stories';
Expand All @@ -74,6 +79,11 @@ const organizationProfileGeneralModule: StoryModule = {
meta: organizationProfileGeneralMeta,
Default: OrganizationProfileGeneralDefault,
};
const organizationProfileMembersModule: StoryModule = {
meta: organizationProfileMembersMeta,
Default: OrganizationProfileMembersDefault,
};
const membersListModule: StoryModule = { meta: membersListMeta, Default: MembersListDefault };

const cardComponentModule: StoryModule = { meta: cardComponentMeta, Default: CardDefault, Centered: CardCentered };

Expand Down Expand Up @@ -125,9 +135,11 @@ export const registry: StoryModule[] = [
organizationProfileModule,
// Panels
organizationProfileGeneralModule,
organizationProfileMembersModule,
// Sections
leaveOrganizationModule,
deleteOrganizationModule,
membersListModule,
// Blocks
destructiveModule,
// Components
Expand Down
15 changes: 15 additions & 0 deletions packages/swingset/src/stories/members-list.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as MembersListStories from './members-list.stories';

# Members List

Lists an organization's active members with search, pagination, and per-row removal. The section owns the search (uncommitted value vs. committed query) and removal flow in its machine, derives plain rows from the Clerk memberships resource in its controller, and renders them through the Mosaic table, input, and button components — so the view stays free of Clerk resources.

<Story
name='Default'
storyModule={MembersListStories}
composition={[
{ name: 'Input', href: '/components/input', layer: 'Components' },
{ name: 'Button', href: '/components/button', layer: 'Components' },
{ name: 'Text', href: '/components/text', layer: 'Components' },
]}
/>
74 changes: 74 additions & 0 deletions packages/swingset/src/stories/members-list.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/** @jsxImportSource @emotion/react */
import { useMachine } from '@clerk/ui/mosaic/machine/useMachine';
import type { MemberRow } from '@clerk/ui/mosaic/sections/members-list.controller';
import { membersListMachine } from '@clerk/ui/mosaic/sections/members-list.machine';
import { MembersListView } from '@clerk/ui/mosaic/sections/members-list.view';
import { useState } from 'react';

import type { StoryMeta } from '@/lib/types';

export const meta: StoryMeta = {
group: 'Sections',
title: 'MembersList',
label: 'Members List',
source: 'packages/ui/src/mosaic/sections/members-list.tsx',
};

const DEMO_MEMBERS: Omit<MemberRow, 'onRemove'>[] = [
{
id: 'mem_1',
name: 'Ada Lovelace',
identifier: 'ada@example.com',
roleLabel: 'Admin',
joinedAt: '1/12/2024',
isCurrentUser: true,
isBanned: false,
},
{
id: 'mem_2',
name: 'Alan Turing',
identifier: 'alan@example.com',
roleLabel: 'Member',
joinedAt: '3/4/2024',
isCurrentUser: false,
isBanned: false,
},
{
id: 'mem_3',
name: 'Grace Hopper',
identifier: 'grace@example.com',
roleLabel: 'Member',
joinedAt: '6/9/2024',
isCurrentUser: false,
isBanned: true,
},
];

export function Default() {
const [snapshot, send] = useMachine(membersListMachine);
const [page, setPage] = useState(1);

const rows: MemberRow[] = DEMO_MEMBERS.map(member => ({
...member,
onRemove: () =>
send({
type: 'REMOVE_MEMBER',
membershipId: member.id,
// Simulate the network round-trip so the row shows its "Removing…" state.
run: () => new Promise<void>(resolve => setTimeout(resolve, 700)),
}),
}));

return (
<MembersListView
snapshot={snapshot}
send={send}
rows={rows}
canManage
page={page}
pageCount={3}
isLoading={false}
onPageChange={setPage}
/>
);
}
11 changes: 11 additions & 0 deletions packages/swingset/src/stories/organization-profile-members.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as OrganizationProfileMembersStories from './organization-profile-members.stories';

# Organization Profile Members

The Members tab panel of the Organization Profile — composes the active-members list shown under "Members".

<Story
name='Default'
storyModule={OrganizationProfileMembersStories}
composition={[{ name: 'MembersList', href: '/sections/members-list', layer: 'Sections' }]}
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** @jsxImportSource @emotion/react */
import { OrganizationProfileMembersView } from '@clerk/ui/mosaic/panels/organization-profile-members-view';

import type { StoryMeta } from '@/lib/types';

import { Default as MembersListDemo } from './members-list.stories';

export const meta: StoryMeta = {
group: 'Panels',
title: 'OrganizationProfileMembers',
label: 'Org Profile Members',
source: 'packages/ui/src/mosaic/panels/organization-profile-members.tsx',
};

export function Default() {
return <OrganizationProfileMembersView membersList={<MembersListDemo />} />;
}
1 change: 1 addition & 0 deletions packages/swingset/src/stories/organization-profile.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The full Organization Profile AIO — lays out the organization panels under a t
storyModule={OrganizationProfileStories}
composition={[
{ name: 'OrganizationProfileGeneral', href: '/panels/organization-profile-general', layer: 'Panels' },
{ name: 'OrganizationProfileMembers', href: '/panels/organization-profile-members', layer: 'Panels' },
{ name: 'Tabs', href: '/components/tabs', layer: 'Components' },
]}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OrganizationProfileView } from '@clerk/ui/mosaic/aio/organization-profi
import type { StoryMeta } from '@/lib/types';

import { Default as OrganizationProfileGeneralDemo } from './organization-profile-general.stories';
import { Default as OrganizationProfileMembersDemo } from './organization-profile-members.stories';

export const meta: StoryMeta = {
group: 'AIO',
Expand All @@ -13,5 +14,10 @@ export const meta: StoryMeta = {
};

export function Default() {
return <OrganizationProfileView general={<OrganizationProfileGeneralDemo />} />;
return (
<OrganizationProfileView
general={<OrganizationProfileGeneralDemo />}
members={<OrganizationProfileMembersDemo />}
/>
);
}
17 changes: 4 additions & 13 deletions packages/ui/src/mosaic/aio/organization-profile-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { Tabs } from '../components/tabs';
interface OrganizationProfileViewProps {
/** The General tab's panel content. */
general: ReactNode;
/** The Members tab's panel content. */
members: ReactNode;
}

export function OrganizationProfileView({ general }: OrganizationProfileViewProps) {
export function OrganizationProfileView({ general, members }: OrganizationProfileViewProps) {
return (
<Box
sx={{
Expand All @@ -31,18 +33,7 @@ export function OrganizationProfileView({ general }: OrganizationProfileViewProp
<Tabs.Tab value='members'>Members</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value='general'>{general}</Tabs.Panel>
<Tabs.Panel value='members'>
<Box
render={p => <h1 {...p} />}
sx={t => ({
...t.text('base'),
fontWeight: t.font.medium,
textAlign: 'center',
})}
>
Members content
</Box>
</Tabs.Panel>
<Tabs.Panel value='members'>{members}</Tabs.Panel>
</Tabs.Root>
</Box>
);
Expand Down
8 changes: 7 additions & 1 deletion packages/ui/src/mosaic/aio/organization-profile.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { OrganizationProfileGeneral } from '../panels/organization-profile-general';
import { OrganizationProfileMembers } from '../panels/organization-profile-members';
import { OrganizationProfileView } from './organization-profile-view';

export function OrganizationProfile() {
return <OrganizationProfileView general={<OrganizationProfileGeneral />} />;
return (
<OrganizationProfileView
general={<OrganizationProfileGeneral />}
members={<OrganizationProfileMembers />}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ReactNode } from 'react';

import { Box } from '../components/box';

interface OrganizationProfileMembersViewProps {
/** The active-members list section. */
membersList: ReactNode;
}

export function OrganizationProfileMembersView({ membersList }: OrganizationProfileMembersViewProps) {
return (
<Box
sx={{
width: '100%',
containerType: 'inline-size',
}}
>
{membersList}
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { MembersList } from '../sections/members-list';
import { OrganizationProfileMembersView } from './organization-profile-members-view';

export function OrganizationProfileMembers() {
return <OrganizationProfileMembersView membersList={<MembersList />} />;
}
Loading
Loading