(function Section
render,
ref,
props: {
- ...mergeStyleProps(themeProps('section-item', { nested }), stylex.props(reset.base, styles.item, xstyle), rest),
+ ...mergeStyleProps(
+ themeProps('section-item', { nested }),
+ stylex.props(reset.base, styles.item, nested && styles.nestedItem, nested && sectionNestedItemMarker, xstyle),
+ rest,
+ ),
},
});
});
@@ -291,7 +335,9 @@ export const Section = {
Root,
Title,
Group,
+ Surface,
Row,
+ Header,
Items,
Item,
Media,
diff --git a/packages/mosaic/src/features/organization-profile/organization-profile-danger-section/organization-profile-danger-section.view.tsx b/packages/mosaic/src/features/organization-profile/organization-profile-danger-section/organization-profile-danger-section.view.tsx
index 6d70e5a9bef..fbb0d49f964 100644
--- a/packages/mosaic/src/features/organization-profile/organization-profile-danger-section/organization-profile-danger-section.view.tsx
+++ b/packages/mosaic/src/features/organization-profile/organization-profile-danger-section/organization-profile-danger-section.view.tsx
@@ -26,30 +26,32 @@ export function OrganizationProfileDangerSectionView({
return (
- {m.sectionTitle}
- {onLeave ? (
-
- ) : null}
- {onDelete ? (
-
- ) : null}
+ {m.sectionTitle}
+
+ {onLeave ? (
+
+ ) : null}
+ {onDelete ? (
+
+ ) : null}
+
);
diff --git a/packages/mosaic/src/features/organization-profile/organization-profile-workspace-section/organization-profile-workspace-section.view.tsx b/packages/mosaic/src/features/organization-profile/organization-profile-workspace-section/organization-profile-workspace-section.view.tsx
index 11d06db54d8..b7cfe351c7b 100644
--- a/packages/mosaic/src/features/organization-profile/organization-profile-workspace-section/organization-profile-workspace-section.view.tsx
+++ b/packages/mosaic/src/features/organization-profile/organization-profile-workspace-section/organization-profile-workspace-section.view.tsx
@@ -32,24 +32,26 @@ export function OrganizationProfileWorkspaceSectionView({
return (
- {m.sectionTitle}
-
-
-
+ {m.sectionTitle}
+
+
+
+
+
);
diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-profile-panel.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-profile-panel.view.test.tsx
index a12b39214b2..7a1a0190f6e 100644
--- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-profile-panel.view.test.tsx
+++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-profile-panel.view.test.tsx
@@ -213,7 +213,7 @@ describe('UserProfileProfilePanelView', () => {
expect(screen.queryByRole('alert')).toBeNull();
});
- it('breaks out both contact types when multiple accounts are allowed', () => {
+ it('nests both contact types as groups inside Account when multiple accounts are allowed', () => {
renderView({
emails: [{ id: 'email_1', value: 'item1@clerk.dev', isDefault: true }],
onAddEmail: vi.fn(),
@@ -222,11 +222,13 @@ describe('UserProfileProfilePanelView', () => {
});
const accountSection = screen.getByRole('region', { name: 'Account' });
- const emailSection = screen.getByRole('region', { name: 'Email' });
- const phoneSection = screen.getByRole('region', { name: 'Phone' });
+ const emailSection = within(accountSection).getByRole('group', { name: 'Email' });
+ const phoneSection = within(accountSection).getByRole('group', { name: 'Phone' });
- expect(accountSection).not.toContainElement(emailSection);
- expect(accountSection).not.toContainElement(phoneSection);
+ expect(screen.queryByRole('region', { name: 'Email' })).not.toBeInTheDocument();
+ expect(screen.queryByRole('region', { name: 'Phone' })).not.toBeInTheDocument();
+ expect(emailSection).toHaveAttribute('data-variant', 'contained');
+ expect(emailSection.querySelector('.cl-section-header')).toHaveTextContent('Email');
expect(emailSection).toHaveTextContent('item1@clerk.dev');
expect(phoneSection).toHaveTextContent('+1 (801) 888-8181');
expect(within(emailSection).getByRole('button', { name: 'Add email' })).toHaveTextContent('Add');
@@ -247,8 +249,8 @@ describe('UserProfileProfilePanelView', () => {
expect(accountSection).toHaveTextContent('+1 (801) 888-8181');
expect(within(accountSection).getByRole('button', { name: 'Update email' })).toBeInTheDocument();
expect(within(accountSection).getByRole('button', { name: 'Update phone number' })).toBeInTheDocument();
- expect(screen.queryByRole('region', { name: 'Email' })).not.toBeInTheDocument();
- expect(screen.queryByRole('region', { name: 'Phone' })).not.toBeInTheDocument();
+ expect(screen.queryByRole('group', { name: 'Email' })).not.toBeInTheDocument();
+ expect(screen.queryByRole('group', { name: 'Phone' })).not.toBeInTheDocument();
});
it('forwards inline contact update and add actions', async () => {
@@ -273,7 +275,7 @@ describe('UserProfileProfilePanelView', () => {
it('renders an actionable empty state when no phone number exists', () => {
renderView({ phones: [], onSendPhoneCode: () => Promise.resolve(), onVerifyPhoneCode: () => Promise.resolve() });
- const phoneSection = screen.getByRole('region', { name: 'Phone' });
+ const phoneSection = screen.getByRole('group', { name: 'Phone' });
const emptyState = within(phoneSection).getByText('No phone numbers added');
expect(emptyState.closest('.cl-section-items')).not.toBeNull();
diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-security-panel.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-security-panel.view.test.tsx
index f1bab06b2aa..eca770a25c6 100644
--- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-security-panel.view.test.tsx
+++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-security-panel.view.test.tsx
@@ -67,8 +67,8 @@ describe('UserProfileSecurityPanelView', () => {
expect(screen.getByText('Password')).toBeVisible();
expect(screen.getByText('Passkeys')).toBeVisible();
expect(screen.getByText('2-step verification')).toBeVisible();
- expect(screen.getByRole('region', { name: 'Passkeys' })).toBeInTheDocument();
- expect(screen.getByRole('region', { name: '2-step verification' })).toBeInTheDocument();
+ expect(screen.getByRole('group', { name: 'Passkeys' })).toBeInTheDocument();
+ expect(screen.getByRole('group', { name: '2-step verification' })).toBeInTheDocument();
expect(screen.getByText('This device')).toBeInTheDocument();
expect(screen.getByText('2 other devices')).toBeInTheDocument();
expect(
@@ -131,7 +131,7 @@ describe('UserProfileSecurityPanelView', () => {
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Remove', exact: true }));
await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument());
- const otherDevices = screen.getByRole('region', { name: 'Other devices' });
+ const otherDevices = screen.getByRole('group', { name: 'Other devices' });
await user.click(within(otherDevices).getByRole('button', { name: 'Manage Safari on iOS' }));
await user.click(screen.getByRole('menuitem', { name: 'Sign out' }));
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Sign out' }));
@@ -203,7 +203,7 @@ describe('UserProfileSecurityPanelView', () => {
expect(within(section).getByText('No passkeys added')).toBeVisible();
expect(screen.getByRole('heading', { name: 'Authentication' })).toBeVisible();
expect(screen.queryByRole('button', { name: 'Add passkey' })).not.toBeInTheDocument();
- expect(screen.getByRole('region', { name: '2-step verification' })).toBeVisible();
+ expect(screen.getByRole('group', { name: '2-step verification' })).toBeVisible();
});
it('keeps the empty section and final passkey confirmation mounted without Add', async () => {
diff --git a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.styles.ts b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.styles.ts
deleted file mode 100644
index 73d555e346a..00000000000
--- a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.styles.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import * as stylex from '@stylexjs/stylex';
-
-import { space } from '../../../tokens.stylex';
-
-export const styles = stylex.create({
- sections: {
- gap: space['8'],
- display: 'flex',
- flexDirection: 'column',
- width: '100%',
- },
-});
diff --git a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.view.tsx
index 890216a2425..0ec26bf8a0f 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-account-section.view.tsx
@@ -1,9 +1,6 @@
-import * as stylex from '@stylexjs/stylex';
-
import { Section } from '../../../components/section';
import { useMessages } from '../../../localization';
import type { FileRejection } from '../../../primitives/file-upload';
-import { styles } from './user-profile-account-section.styles';
import type {
UserProfileEmail,
UserProfileNameAttribute,
@@ -115,10 +112,10 @@ export function UserProfileAccountSectionView({
);
return (
-
-
+
+
{m.sectionTitle}
-
+
{!allowMultipleAccounts ? emailRow : null}
{!allowMultipleAccounts ? phoneRow : null}
-
-
- {allowMultipleAccounts ? (
-
- {emailRow}
-
- ) : null}
- {allowMultipleAccounts ? (
-
- {phoneRow}
-
- ) : null}
-
+
+
+ {allowMultipleAccounts ? emailRow : null}
+ {allowMultipleAccounts ? phoneRow : null}
+
);
}
diff --git a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-contact-list-row.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-contact-list-row.view.tsx
index d21c5abd97b..73a0eb05615 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-contact-list-row.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-contact-list-row.view.tsx
@@ -20,6 +20,7 @@ export interface UserProfileContactListRowViewProps {
onVerify?: (id: string) => void;
onSetPrimary?: (id: string) => void;
onRemove?: (id: string) => void;
+ children?: ReactNode;
}
export function UserProfileContactListRowView({
@@ -33,92 +34,96 @@ export function UserProfileContactListRowView({
addAction,
rowRef,
triggerRef,
+ children,
}: UserProfileContactListRowViewProps) {
const m = useMessages('userProfileAccountSection');
const emptyDescription = m[kind].empty;
return (
-
-
-
- {label}
-
- {addAction ? (
- {addAction}
- ) : onAdd ? (
-
-
-
- ) : null}
-
-
- {items.length === 0 ? (
-
-
- {emptyDescription}
-
-
- ) : (
- items.map(item => {
- const actions: ActionMenuAction[] = [];
+ variant='outline'
+ onClick={onAdd}
+ >
+
+ {m.add}
+
+
+ ) : null}
+
+
+ {items.length === 0 ? (
+
+
+ {emptyDescription}
+
+
+ ) : (
+ items.map(item => {
+ const actions: ActionMenuAction[] = [];
- if (item.isVerified === false && onVerify) {
- actions.push({
- label: item.isDefault ? m.completeVerification : m[kind].verify,
- onClick: () => onVerify(item.id),
- });
- } else if (!item.isDefault && item.isVerified === true && onSetPrimary) {
- actions.push({ label: m.setPrimary, onClick: () => onSetPrimary(item.id) });
- }
+ if (item.isVerified === false && onVerify) {
+ actions.push({
+ label: item.isDefault ? m.completeVerification : m[kind].verify,
+ onClick: () => onVerify(item.id),
+ });
+ } else if (!item.isDefault && item.isVerified === true && onSetPrimary) {
+ actions.push({ label: m.setPrimary, onClick: () => onSetPrimary(item.id) });
+ }
- if (onRemove && item.canRemove !== false) {
- actions.push({
- label: m[kind].remove,
- color: 'negative',
- onClick: () => onRemove(item.id),
- });
- }
+ if (onRemove && item.canRemove !== false) {
+ actions.push({
+ label: m[kind].remove,
+ color: 'negative',
+ onClick: () => onRemove(item.id),
+ });
+ }
- return (
-
-
-
- {item.value}
- {item.isDefault ? {m.primary} : null}
-
-
- {actions.length > 0 ? (
-
-
-
- ) : null}
-
- );
- })
- )}
-
-
+ return (
+
+
+
+ {item.value}
+ {item.isDefault ? {m.primary} : null}
+
+
+ {actions.length > 0 ? (
+
+
+
+ ) : null}
+
+ );
+ })
+ )}
+
+ {children}
+
+
);
}
diff --git a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-email-row.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-email-row.view.tsx
index 471492e5a03..11a4674b6b8 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-email-row.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-account-section/user-profile-email-row.view.tsx
@@ -121,15 +121,16 @@ export function UserProfileEmailRowView({
onRemove={onRemoveEmail ? removeEmail : undefined}
onSetPrimary={onSetPrimaryEmail && !isSettingPrimary ? id => void setPrimaryEmail(id) : undefined}
onVerify={onVerifyEmail}
- />
- {primaryError ? (
-
- {primaryError}
-
- ) : null}
+ >
+ {primaryError ? (
+
+ {primaryError}
+
+ ) : null}
+
{onRemoveEmail ? (
void setPrimaryPhone(id) : undefined}
onVerify={onVerifyPhone}
- />
- {primaryError ? (
-
- {primaryError}
-
- ) : null}
+ >
+ {primaryError ? (
+
+ {primaryError}
+
+ ) : null}
+
{onRemovePhone ? (
- {m.title}
- {currentDevices.length > 0 ? (
- currentDevices.map(device => (
-
- deviceDetails.open(device)}
- />
+ {m.title}
+
+ {currentDevices.length > 0 ? (
+ currentDevices.map(device => (
+
+ deviceDetails.open(device)}
+ />
+
+ ))
+ ) : (
+
+
+
+ {m.emptyCurrent}
+
+
- ))
- ) : (
-
-
-
- {m.emptyCurrent}
-
-
-
- )}
+ )}
+
-
- {otherDevices.length > 0 ? (
-
-
-
-
+ {otherDevices.length > 0 ? (
+
+
+
{fill(otherDevices.length === 1 ? m.otherDevice : m.otherDevices, {
@@ -133,7 +136,7 @@ export function UserProfileActiveDevicesSectionView({
) : null}
-
+
{otherDevices.map(device => (
))}
-
+
-
- ) : null}
+ ) : null}
+
{onSignOutAllOtherDevices ? (
- {m.title}
- {accounts.map(account => (
- removeAccount.open(account) : undefined}
- />
- ))}
- {onConnect
- ? availableProviders.map(provider => (
-
- ))
- : null}
+ {m.title}
+
+ {accounts.map(account => (
+ removeAccount.open(account) : undefined}
+ />
+ ))}
+ {onConnect
+ ? availableProviders.map(provider => (
+
+ ))
+ : null}
+
) : null}
diff --git a/packages/mosaic/src/features/user-profile/user-profile-delete-section/user-profile-delete-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-delete-section/user-profile-delete-section.view.tsx
index d9c00997b64..8587672cc68 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-delete-section/user-profile-delete-section.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-delete-section/user-profile-delete-section.view.tsx
@@ -20,40 +20,42 @@ export function UserProfileDeleteSectionView({ onDelete }: UserProfileDeleteSect
return (
- {m.sectionTitle}
-
-
-
- {m.sectionLabel}
- {m.sectionDescription}
-
-
-
- {m.actionLabel}
-
- }
- title={m.dialogTitle}
- description={m.dialogDescription}
- fieldLabel={fill(m.fieldLabel, { phrase: m.fieldPlaceholder })}
- confirmationValue={m.fieldPlaceholder}
- actionLabel={m.actionLabel}
- cancelLabel={m.cancelLabel}
- onDelete={onConfirm}
- isDeleting={isDeleting}
- errorMessage={errorMessage}
- />
-
-
-
+ {m.sectionTitle}
+
+
+
+
+ {m.sectionLabel}
+ {m.sectionDescription}
+
+
+
+ {m.actionLabel}
+
+ }
+ title={m.dialogTitle}
+ description={m.dialogDescription}
+ fieldLabel={fill(m.fieldLabel, { phrase: m.fieldPlaceholder })}
+ confirmationValue={m.fieldPlaceholder}
+ actionLabel={m.actionLabel}
+ cancelLabel={m.cancelLabel}
+ onDelete={onConfirm}
+ isDeleting={isDeleting}
+ errorMessage={errorMessage}
+ />
+
+
+
+
);
diff --git a/packages/mosaic/src/features/user-profile/user-profile-enterprise-accounts-section/user-profile-enterprise-accounts-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-enterprise-accounts-section/user-profile-enterprise-accounts-section.view.tsx
index c6321e37602..ce2cec3856d 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-enterprise-accounts-section/user-profile-enterprise-accounts-section.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-enterprise-accounts-section/user-profile-enterprise-accounts-section.view.tsx
@@ -25,25 +25,27 @@ export function UserProfileEnterpriseAccountsSectionView({
}
return (
- {m.title}
- {accounts.map(account => (
-
- ))}
- {onConnect
- ? connections.map(connection => (
-
- ))
- : null}
+ {m.title}
+
+ {accounts.map(account => (
+
+ ))}
+ {onConnect
+ ? connections.map(connection => (
+
+ ))
+ : null}
+
);
diff --git a/packages/mosaic/src/features/user-profile/user-profile-mfa-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-mfa-section.view.tsx
index 1a5270fe6ce..e09d512a622 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-mfa-section.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-mfa-section.view.tsx
@@ -27,6 +27,8 @@ export interface UserProfileMfaSectionViewProps {
addButtonRef?: Ref;
addControl?: ReactNode;
sectionTitle?: string;
+ /** Renders only the group, for composing inside a parent `Section.Root`. */
+ asGroup?: boolean;
onAdd?: (type: UserProfileMfaAddableMethod) => void;
onRegenerateBackupCodes?: () => void;
onRemove?: (id: string) => void | Promise;
@@ -39,13 +41,14 @@ export function UserProfileMfaSectionView({
addButtonRef,
addControl,
sectionTitle,
+ asGroup,
onAdd,
onRegenerateBackupCodes,
onRemove,
onSetDefault,
}: UserProfileMfaSectionViewProps) {
const m = useMessages('userProfileMfa');
- const section = useRef(null);
+ const section = useRef(null);
const removalFocus = useListRemovalFocus({
ids: methods.map(method => method.id),
onRemove,
@@ -108,6 +111,7 @@ export function UserProfileMfaSectionView({
hasItems={methods.length > 0}
label={m.label}
sectionTitle={sectionTitle}
+ asGroup={asGroup}
>
{methods.map(method => (
void;
addError?: string;
onRename?: (id: string, name: string) => void | Promise;
@@ -30,13 +32,14 @@ export interface UserProfilePasskeysSectionViewProps {
export function UserProfilePasskeysSectionView({
passkeys,
sectionTitle,
+ asGroup = false,
onAdd,
addError,
onRename,
onRemove,
}: UserProfilePasskeysSectionViewProps) {
const addButton = useRef(null);
- const section = useRef(null);
+ const section = useRef(null);
const removalFocus = useListRemovalFocus({
ids: passkeys.map(passkey => passkey.id),
onRemove,
@@ -44,70 +47,83 @@ export function UserProfilePasskeysSectionView({
});
const removePasskey = useMemo(() => Confirmation.createHandle(), []);
- return (
- <>
-
- {sectionTitle ? {sectionTitle} : null}
-
-
+ const group = (
+
+ {sectionTitle ? {sectionTitle} : null}
+
+
+
+ {m.label}
+
+ {onAdd ? (
+
+
+
+ {m.add}
+
+
+ ) : null}
+
+ {addError}
+
+
+ {passkeys.length > 0 ? (
+
+ {passkeys.map(passkey => (
+ removePasskey.open(passkey) : undefined}
+ />
+ ))}
+
+ ) : (
+
- {m.label}
+ {m.empty}
- {onAdd ? (
-
-
-
- {m.add}
-
-
- ) : null}
-
- {addError}
-
- {passkeys.length > 0 ? (
-
- {passkeys.map(passkey => (
- removePasskey.open(passkey) : undefined}
- />
- ))}
-
- ) : (
-
-
-
- {m.empty}
-
-
-
- )}
-
-
-
+
+ )}
+
+
+ );
+
+ return (
+ <>
+ {asGroup ? (
+ group
+ ) : (
+
+ {group}
+
+ )}
{onRemove ? (
+ const group = (
+
{title ? {title} : null}
-
+
-
-
+
+
);
+
+ return asGroup ? group : {group};
}
diff --git a/packages/mosaic/src/features/user-profile/user-profile-payment-methods-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-payment-methods-section.view.tsx
index 1a58aa7fd1f..ae10af701ec 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-payment-methods-section.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-payment-methods-section.view.tsx
@@ -30,8 +30,8 @@ export function UserProfilePaymentMethodsSectionView({
return (
-
-
+
+
Payment methods
@@ -53,7 +53,7 @@ export function UserProfilePaymentMethodsSectionView({
) : null}
-
+
{paymentMethods.length > 0 ? (
paymentMethods.map(paymentMethod => (
@@ -72,7 +72,7 @@ export function UserProfilePaymentMethodsSectionView({
)}
-
+
);
diff --git a/packages/mosaic/src/features/user-profile/user-profile-security-list.tsx b/packages/mosaic/src/features/user-profile/user-profile-security-list.tsx
index e8a2ee18d70..ab576a72b63 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-security-list.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-security-list.tsx
@@ -7,6 +7,7 @@ import { Section } from '../../components/section';
export function UserProfileSecurityList({
sectionTitle,
sectionRef,
+ asGroup = false,
label,
addLabel,
emptyLabel,
@@ -16,7 +17,8 @@ export function UserProfileSecurityList({
children,
}: {
sectionTitle?: string;
- sectionRef?: Ref;
+ sectionRef?: Ref;
+ asGroup?: boolean;
label: string;
addLabel: string;
emptyLabel: string;
@@ -25,53 +27,64 @@ export function UserProfileSecurityList({
addControl?: ReactNode;
children: ReactNode;
}) {
- return (
+ const group = (
+
+ {sectionTitle ? {sectionTitle} : null}
+
+
+
+ {label}
+
+ {addControl ? (
+ {addControl}
+ ) : onAdd ? (
+
+
+
+ Add
+
+
+ ) : null}
+
+
+ {hasItems ? (
+ children
+ ) : (
+
+
+ {emptyLabel}
+
+
+ )}
+
+
+
+ );
+
+ return asGroup ? (
+ group
+ ) : (
- {sectionTitle ? {sectionTitle} : null}
-
-
-
-
- {label}
-
- {addControl ? (
- {addControl}
- ) : onAdd ? (
-
-
-
- Add
-
-
- ) : null}
-
-
- {hasItems ? (
- children
- ) : (
-
-
- {emptyLabel}
-
-
- )}
-
-
-
+ {group}
);
}
diff --git a/packages/mosaic/src/features/user-profile/user-profile-security-panel.styles.ts b/packages/mosaic/src/features/user-profile/user-profile-security-panel.styles.ts
index 67454eca28a..ab7f24daae3 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-security-panel.styles.ts
+++ b/packages/mosaic/src/features/user-profile/user-profile-security-panel.styles.ts
@@ -27,7 +27,4 @@ export const styles = stylex.create({
display: 'flex',
flexDirection: 'column',
},
- emptySectionCards: {
- display: 'none',
- },
});
diff --git a/packages/mosaic/src/features/user-profile/user-profile-security-panel.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-security-panel.view.tsx
index 0062d803e29..d1445ccf154 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-security-panel.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-security-panel.view.tsx
@@ -1,7 +1,7 @@
-import * as stylex from '@stylexjs/stylex';
import type { ReactElement, ReactNode } from 'react';
import { Panel } from '../../components/panel';
+import { Section } from '../../components/section';
import { themeProps } from '../../props';
import type {
UserProfileActiveDevicesSectionViewProps,
@@ -19,7 +19,6 @@ import type {
UserProfilePasswordSectionViewProps,
} from './user-profile-password-section/user-profile-password-section.view';
import { UserProfilePasswordSectionView } from './user-profile-password-section/user-profile-password-section.view';
-import { styles } from './user-profile-security-panel.styles';
export type {
UserProfileDevice,
@@ -86,38 +85,43 @@ export function UserProfileSecurityPanelView({
}>
Security
-
- {showPassword ? (
-
- ) : null}
- {showPasskeys ? (
-
- ) : null}
- {mfaMethods !== undefined ? (
-
- ) : null}
-
+ {hasAuthentication ? (
+
+ {showPassword ? (
+
+ ) : null}
+ {showPasskeys ? (
+
+ ) : null}
+ {mfaMethods !== undefined ? (
+
+ ) : null}
+
+ ) : null}
{devices ? (
- Subscription
-
-
-
- {subscription.planName}
- {subscription.priceLabel}
-
- {onChangePlan ? (
+ Subscription
+
+
+
+
+ {subscription.planName}
+ {subscription.priceLabel}
+
+ {onChangePlan ? (
+
+
+ Change plan
+
+
+ ) : null}
+
+
+
+
+
+ Total due
+ {subscription.renewsAtLabel}
+
-
- Change plan
-
+ {subscription.totalDueLabel}
- ) : null}
-
-
-
-
-
- Total due
- {subscription.renewsAtLabel}
-
-
- {subscription.totalDueLabel}
-
-
-
+
+
+
);
diff --git a/packages/mosaic/src/features/user-profile/user-profile-web3-wallets-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-web3-wallets-section.view.tsx
index f4cd5ad851b..9259b1b0397 100644
--- a/packages/mosaic/src/features/user-profile/user-profile-web3-wallets-section.view.tsx
+++ b/packages/mosaic/src/features/user-profile/user-profile-web3-wallets-section.view.tsx
@@ -63,26 +63,28 @@ export function UserProfileWeb3WalletsSectionView({
ref={section}
tabIndex={-1}
>
- {m.title}
- {wallets.map(wallet => (
- removeWallet.open(wallet) : undefined}
- />
- ))}
- {onConnect
- ? availableProviders.map(provider => (
-
- ))
- : null}
+ {m.title}
+
+ {wallets.map(wallet => (
+ removeWallet.open(wallet) : undefined}
+ />
+ ))}
+ {onConnect
+ ? availableProviders.map(provider => (
+
+ ))
+ : null}
+
) : null}
diff --git a/packages/mosaic/src/tokens.stylex.ts b/packages/mosaic/src/tokens.stylex.ts
index 256a7957d35..8011f3c821f 100644
--- a/packages/mosaic/src/tokens.stylex.ts
+++ b/packages/mosaic/src/tokens.stylex.ts
@@ -296,6 +296,10 @@ export const space = stylex.defineVars({
'40': step(40),
});
+export const sectionVars = stylex.defineVars({
+ '--cl-section-row-min-height': `calc(${space['18.5']} + 1px)`,
+});
+
// =============================================================================
// Typography Tokens — type scale
// =============================================================================
diff --git a/packages/swingset/src/lib/exampleSnippet.ts b/packages/swingset/src/lib/exampleSnippet.ts
index 446e57c27af..8a9fd7f8440 100644
--- a/packages/swingset/src/lib/exampleSnippet.ts
+++ b/packages/swingset/src/lib/exampleSnippet.ts
@@ -41,9 +41,20 @@ function unwrapReturn(fnSource: string): string {
return expr;
}
- // `return ;` — a single expression statement; take up to its terminating semicolon.
- const semicolon = expr.indexOf(';');
- return semicolon === -1 ? expr : expr.slice(0, semicolon);
+ // `return ;` — the return is the function's last statement, so its terminating semicolon
+ // is the last one; an earlier one can sit inside the JSX (an inline `
+
+
+ Account
+
+
+
+
+ Name
+ Preston Booth
+
+
+
+ Edit name
+
+
+
+
+
+
+
+ Username
+ Prestonb.xyz
+
+
+
+ Edit username
+
+
+
+
+
+
+ {[
+ { label: 'Email', values: ['item1@clerk.dev', 'item2@clerk.dev'] },
+ { label: 'Phone', values: ['+1 801-888-8181'] },
+ ].map(({ label, values }) => (
+
+
+
+
+ {label}
+
+
+
+
+ Add
+
+
+
+
+ {values.map((value, index) => (
+
+
+
+ {value}
+ {index === 0 ? Primary : null}
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ ))}
+
+ >
+ );
+}
+
export function ConnectedAccounts() {
return (
- Connected accounts
-
-
-
-
- Google
- test@google.com
-
-
-
-
-
-
-
-
-
-
-
-
- Apple
-
-
-
- Connect
- Connected accounts
+
+
+
+
+
+ Google
+ test@google.com
+
+
+
-
-
-
-
+ variant='ghost'
+ >
+
+
+
+
+
+
+
+
+
+ Apple
+
+
+
+ Connect
+
+
+
+
+
+
);
@@ -353,24 +431,26 @@ export function ConnectedAccounts() {
export function IconFrameMedia() {
return (
- Team
-
-
-
-
-
-
-
-
- Engineering
- 12 members
-
-
-
+ Team
+
+
+
+
+
+
+
+
+
+ Engineering
+ 12 members
+
+
+
+
);
@@ -379,27 +459,29 @@ export function IconFrameMedia() {
export function Destructive() {
return (
- Danger zone
-
-
-
- Delete account
-
- Permanently delete this profile and all its data. This cannot be undone.
-
-
-
-
- Delete account
-
-
-
-
+ Danger zone
+
+
+
+
+ Delete account
+
+ Permanently delete this profile and all its data. This cannot be undone.
+
+
+
+
+ Delete account
+
+
+
+
+
);