diff --git a/.changeset/user-profile-section-headers.md b/.changeset/user-profile-section-headers.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/user-profile-section-headers.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/mosaic/src/components/profile/profile.styles.ts b/packages/mosaic/src/components/profile/profile.styles.ts index c525ced3a8d..000b1168e6f 100644 --- a/packages/mosaic/src/components/profile/profile.styles.ts +++ b/packages/mosaic/src/components/profile/profile.styles.ts @@ -373,8 +373,11 @@ export const panelStyles = stylex.create({ flexDirection: 'column', }, sections: { - gap: space['10'], + gap: space['8'], display: 'flex', flexDirection: 'column', }, + groups: { + rowGap: space['8'], + }, }); diff --git a/packages/mosaic/src/components/section/section.markers.stylex.ts b/packages/mosaic/src/components/section/section.markers.stylex.ts index c2598c508aa..cd5ab064f7a 100644 --- a/packages/mosaic/src/components/section/section.markers.stylex.ts +++ b/packages/mosaic/src/components/section/section.markers.stylex.ts @@ -1,3 +1,4 @@ import * as stylex from '@stylexjs/stylex'; export const sectionNestedItemMarker = stylex.defineMarker(); +export const sectionHeaderMarker = stylex.defineMarker(); diff --git a/packages/mosaic/src/components/section/section.styles.ts b/packages/mosaic/src/components/section/section.styles.ts index 9cc55d4fa0b..01a348d99c4 100644 --- a/packages/mosaic/src/components/section/section.styles.ts +++ b/packages/mosaic/src/components/section/section.styles.ts @@ -1,7 +1,7 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, fontWeightVars, radiusVars, sectionVars, space, typeScaleVars } from '../../tokens.stylex'; -import { sectionNestedItemMarker } from './section.markers.stylex'; +import { sectionHeaderMarker, sectionNestedItemMarker } from './section.markers.stylex'; export const styles = stylex.create({ root: { @@ -46,16 +46,20 @@ export const styles = stylex.create({ header: { marginInline: space['4'], paddingBlock: space['3'], - borderBlockEndColor: colorVars['--cl-color-border'], - borderBlockEndStyle: 'solid', - borderBlockEndWidth: '1px', + minHeight: space['13'], width: 'auto', }, items: { - paddingInline: space['4'], + marginInline: space['4'], + borderBlockStartColor: colorVars['--cl-color-border'], + borderBlockStartStyle: 'solid', + borderBlockStartWidth: { + default: '0px', + [stylex.when.siblingBefore(':where(*)', sectionHeaderMarker)]: '1px', + }, display: 'flex', flexDirection: 'column', - width: '100%', + width: 'auto', }, item: { alignItems: 'center', diff --git a/packages/mosaic/src/components/section/section.tsx b/packages/mosaic/src/components/section/section.tsx index 94291af3c4f..d333fd45a1a 100644 --- a/packages/mosaic/src/components/section/section.tsx +++ b/packages/mosaic/src/components/section/section.tsx @@ -12,7 +12,7 @@ import { reset } from '../../utils/reset.styles'; import { sizes as typographySizes, styles as typographyStyles } from '../../utils/typography.styles'; import type { HeadingProps } from '../heading'; import { Heading } from '../heading'; -import { sectionNestedItemMarker } from './section.markers.stylex'; +import { sectionHeaderMarker, sectionNestedItemMarker } from './section.markers.stylex'; import { styles } from './section.styles'; export type SectionRootProps = Omit, 'title'>; @@ -134,7 +134,7 @@ const Header = React.forwardRef(function Sec props: { ...mergeStyleProps( themeProps('section-header'), - stylex.props(reset.base, styles.item, styles.header, xstyle), + stylex.props(reset.base, styles.item, styles.header, sectionHeaderMarker, xstyle), rest, ), }, diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-billing-panel.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-billing-panel.view.test.tsx index 01baad9dd5a..77919da223e 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-billing-panel.view.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-billing-panel.view.test.tsx @@ -54,7 +54,7 @@ describe('UserProfileBillingPanelView', () => { renderView(); expect(screen.getByRole('heading', { level: 3, name: 'Billing' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { level: 4, name: 'Subscription' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Subscription' })).toBeInTheDocument(); expect(screen.getByRole('region', { name: 'Payment methods' })).toBeInTheDocument(); expect(screen.getByText('Basic Plan')).toBeInTheDocument(); expect(screen.getByText('$12.00')).toBeInTheDocument(); diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-delete-section.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-delete-section.view.test.tsx index 9f70ce5dea6..218ae200b53 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-delete-section.view.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-delete-section.view.test.tsx @@ -22,7 +22,7 @@ describe('UserProfileDeleteSectionView', () => { it('renders the danger zone with the dialog closed', () => { renderView(); - expect(screen.getByRole('heading', { name: 'Danger zone' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Danger zone' })).toBeInTheDocument(); expect( screen.getByText('Permanently delete this account and all its data. This cannot be undone.'), ).toBeInTheDocument(); diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-section.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-section.view.test.tsx index fd65d4206b1..a69c9c063b7 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-section.view.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-section.view.test.tsx @@ -29,9 +29,9 @@ function renderView(overrides: Partial = {} describe('passkeys section', () => { it('shows existing passkeys without actions when their callbacks are unavailable', () => { - renderView({ onAdd: undefined, onRename: undefined, onRemove: undefined, sectionTitle: 'Authentication' }); + renderView({ onAdd: undefined, onRename: undefined, onRemove: undefined }); - expect(screen.getByRole('heading', { name: 'Authentication' })).toBeVisible(); + expect(screen.getByRole('region', { name: 'Passkeys' })).toBeVisible(); expect(screen.getByText('Passkeys')).toBeVisible(); expect(screen.getByText('MacBook')).toBeVisible(); expect(screen.getByText('iPhone')).toBeVisible(); @@ -120,9 +120,9 @@ describe('passkeys section', () => { }); it('keeps an empty section visible when Add is unavailable', () => { - renderView({ passkeys: [], onAdd: undefined, sectionTitle: 'Authentication' }); + renderView({ passkeys: [], onAdd: undefined }); - expect(screen.getByRole('heading', { name: 'Authentication' })).toBeVisible(); + expect(screen.getByRole('region', { name: 'Passkeys' })).toBeVisible(); expect(screen.getByText('Passkeys')).toBeVisible(); expect(screen.getByText('No passkeys added')).toBeVisible(); expect(screen.queryByRole('button', { name: 'Add passkey' })).not.toBeInTheDocument(); diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-password-section.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-password-section.view.test.tsx index 21a49b497e9..eda84512687 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-password-section.view.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-password-section.view.test.tsx @@ -43,7 +43,7 @@ describe('UserProfilePasswordSectionView', () => { const user = userEvent.setup(); renderView({ hasPassword: false, onSubmitPassword }); - expect(screen.getByRole('heading', { level: 4, name: 'Authentication' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Password' })).toBeInTheDocument(); expect(screen.getByText('Password')).toBeVisible(); expect(screen.queryByText('••••••••••••••••••')).not.toBeInTheDocument(); expect(screen.getByText('No password set')).toBeVisible(); @@ -109,7 +109,7 @@ describe('UserProfilePasswordSectionView', () => { , ); - expect(screen.getByRole('region', { name: 'Authentication' })).toBeVisible(); + expect(screen.getByRole('region', { name: 'Password' })).toBeVisible(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); 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 d6037709190..6e39db3223c 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 @@ -61,7 +61,7 @@ describe('UserProfileProfilePanelView', () => { /> , ); - expect(screen.queryByRole('heading', { name: 'Connected accounts' })).not.toBeInTheDocument(); + expect(screen.queryByRole('region', { name: 'Connected accounts' })).not.toBeInTheDocument(); expect(screen.getByRole('alertdialog', { name: 'Remove connected account' })).toBeInTheDocument(); await act(async () => { @@ -94,7 +94,7 @@ describe('UserProfileProfilePanelView', () => { /> , ); - expect(screen.queryByRole('heading', { name: 'Web3 wallets' })).not.toBeInTheDocument(); + expect(screen.queryByRole('region', { name: 'Web3 wallets' })).not.toBeInTheDocument(); expect(screen.getByRole('alertdialog')).toHaveTextContent('0x1234'); await act(async () => { removal.resolve(); @@ -293,9 +293,9 @@ describe('UserProfileProfilePanelView', () => { onDeleteAccount, }); - expect(screen.getByRole('heading', { level: 4, name: 'Connected accounts' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Connected accounts' })).toBeInTheDocument(); expect(screen.getByText('Google')).toBeVisible(); - expect(screen.getByRole('heading', { level: 4, name: 'Danger zone' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Danger zone' })).toBeInTheDocument(); expect(screen.getByText('Delete account', { selector: '.cl-section-label' })).toBeInTheDocument(); expect(screen.getByText('Permanently delete this account and all its data. This cannot be undone.')).toHaveClass( 'cl-section-description', @@ -349,7 +349,7 @@ describe('UserProfileProfilePanelView', () => { onRemoveWeb3Wallet: vi.fn(), }); - expect(screen.getByRole('heading', { level: 4, name: 'Web3 wallets' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Web3 wallets' })).toBeInTheDocument(); expect(screen.getByText('MetaMask')).toBeInTheDocument(); expect(screen.getByText('0x1234...5678')).toBeInTheDocument(); expect(within(screen.getByRole('region', { name: 'Web3 wallets' })).getByText('Primary')).toBeInTheDocument(); 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 c0c7b3ab75b..79ce0161d7a 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 @@ -61,16 +61,16 @@ describe('UserProfileSecurityPanelView', () => { renderView({ onDeleteAccount: vi.fn(() => Promise.resolve()) }); expect(screen.getByRole('heading', { level: 3, name: 'Security' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { level: 4, name: 'Authentication' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { level: 4, name: 'Active devices' })).toBeInTheDocument(); - expect(screen.getByRole('heading', { level: 4, name: 'Danger zone' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Authentication' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Active devices' })).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Danger zone' })).toBeInTheDocument(); expect(screen.getByText('Password')).toBeVisible(); expect(screen.getByText('Passkeys')).toBeVisible(); expect(screen.getByText('2-step verification')).toBeVisible(); 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(screen.getByText('Clerk App on macOS')).toBeInTheDocument(); expect( screen.getByText('Permanently delete this account and all its data. This cannot be undone.'), ).toBeInTheDocument(); @@ -131,8 +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('group', { name: 'Other devices' }); - await user.click(within(otherDevices).getByRole('button', { name: 'Manage Safari on iOS' })); + await user.click(screen.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' })); await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()); @@ -179,7 +178,7 @@ describe('UserProfileSecurityPanelView', () => { expect(screen.queryByRole('menuitem', { name: 'Sign out' })).not.toBeInTheDocument(); }); - it('keeps the authentication heading on MFA when existing passkeys are hidden', () => { + it('keeps the authentication section on MFA when existing passkeys are hidden', () => { renderView({ hasPassword: false, passkeysVisible: false, @@ -191,17 +190,16 @@ describe('UserProfileSecurityPanelView', () => { expect(screen.queryByText('Passkeys')).not.toBeInTheDocument(); expect(screen.queryByText('Passkey')).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Add passkey' })).not.toBeInTheDocument(); - expect(screen.getByRole('heading', { name: 'Authentication' })).toBeVisible(); + expect(screen.getByRole('region', { name: 'Authentication' })).toBeVisible(); expect(screen.getByText('2-step verification')).toBeVisible(); }); - it('keeps one authentication heading when passkeys are empty and Add is unavailable', () => { + it('keeps one authentication section when passkeys are empty and Add is unavailable', () => { renderView({ hasPassword: false, passkeys: [], onAddPasskey: undefined }); const section = screen.getByRole('region', { name: 'Authentication' }); expect(within(section).getByText('Passkeys')).toBeVisible(); 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('group', { name: '2-step verification' })).toBeVisible(); }); @@ -226,7 +224,7 @@ describe('UserProfileSecurityPanelView', () => { /> , ); - expect(screen.getByText('Authentication')).toBeInTheDocument(); + expect(screen.getByRole('region', { name: 'Authentication' })).toBeInTheDocument(); expect(screen.getByRole('alertdialog', { name: 'Remove passkey' })).toBeVisible(); await act(async () => { @@ -234,7 +232,7 @@ describe('UserProfileSecurityPanelView', () => { await removal.promise; }); await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()); - expect(screen.getByRole('heading', { name: 'Authentication' })).toBeVisible(); + expect(screen.getByRole('region', { name: 'Authentication' })).toBeVisible(); expect(screen.getByText('Passkeys')).toBeVisible(); expect(screen.getByText('No passkeys added')).toBeVisible(); expect(screen.queryByRole('button', { name: 'Add passkey' })).not.toBeInTheDocument(); 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 92ef04d1168..abdd8dedcbe 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,3 +1,4 @@ +import { panelStyles } from '../../../components/profile'; import { Section } from '../../../components/section'; import { useMessages } from '../../../localization'; import type { FileRejection } from '../../../primitives/file-upload'; @@ -112,10 +113,17 @@ export function UserProfileAccountSectionView({ ); return ( - + - {m.sectionTitle} + + + {m.sectionTitle} + + { const signedOut = signedOutAll.current; signedOutAll.current = false; @@ -84,10 +84,27 @@ export function UserProfileActiveDevicesSectionView({ return (
- + - {m.title} + + + {m.title} + + {onSignOutAllOtherDevices && otherDevices.length > 0 ? ( + + + + ) : null} + {currentDevices.length > 0 ? ( currentDevices.map(device => ( @@ -107,50 +124,18 @@ export function UserProfileActiveDevicesSectionView({ )} + {otherDevices.map(device => ( + + deviceDetails.open(device)} + /> + + ))} - {otherDevices.length > 0 ? ( - - - - - - {fill(otherDevices.length === 1 ? m.otherDevice : m.otherDevices, { - count: String(otherDevices.length), - })} - - - {onSignOutAllOtherDevices ? ( - - - - ) : null} - - - {otherDevices.map(device => ( - deviceDetails.open(device)} - /> - ))} - - - - ) : null} {onSignOutAllOtherDevices ? ( {hasRows ? ( - + - {m.title} + + + {m.title} + + {accounts.map(account => ( + - {m.sectionTitle} + + + {m.sectionTitle} + + 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 ce2cec3856d..f729aa174ae 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 @@ -24,10 +24,14 @@ export function UserProfileEnterpriseAccountsSectionView({ return null; } return ( - + - {m.title} + + + {m.title} + + {accounts.map(account => ( ; addControl?: ReactNode; - sectionTitle?: string; /** Renders only the group, for composing inside a parent `Section.Root`. */ asGroup?: boolean; onAdd?: (type: UserProfileMfaAddableMethod) => void; @@ -39,7 +38,6 @@ export function UserProfileMfaSectionView({ addableMethods, addButtonRef, addControl, - sectionTitle, asGroup, onAdd, onRegenerateBackupCodes, @@ -102,7 +100,6 @@ export function UserProfileMfaSectionView({ emptyLabel={m.empty} hasItems={methods.length > 0} label={m.label} - sectionTitle={sectionTitle} asGroup={asGroup} > {methods.map(method => ( diff --git a/packages/mosaic/src/features/user-profile/user-profile-passkeys-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-passkeys-section.view.tsx index 2f6c5ed71f4..7e7c5a51f38 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-passkeys-section.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-passkeys-section.view.tsx @@ -20,7 +20,6 @@ export interface UserProfilePasskey { export interface UserProfilePasskeysSectionViewProps { passkeys: UserProfilePasskey[]; - sectionTitle?: string; /** Renders only the group, for composing inside a parent `Section.Root`. */ asGroup?: boolean; onAdd?: () => void; @@ -31,7 +30,6 @@ export interface UserProfilePasskeysSectionViewProps { export function UserProfilePasskeysSectionView({ passkeys, - sectionTitle, asGroup = false, onAdd, addError, @@ -54,7 +52,6 @@ export function UserProfilePasskeysSectionView({ variant={asGroup ? 'contained' : 'default'} aria-label={asGroup ? m.label : undefined} > - {sectionTitle ? {sectionTitle} : null} @@ -119,7 +116,7 @@ export function UserProfilePasskeysSectionView({ {group} diff --git a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-row.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-row.view.tsx index 5a3c8d234b3..0814c07fe0a 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-row.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-row.view.tsx @@ -19,30 +19,36 @@ export function UserProfilePasswordRowView({ requiresCurrentPassword = false, managedBy, onSubmitPassword, -}: Omit) { +}: UserProfilePasswordSectionViewProps) { const m = useMessages('userProfilePasswordSection'); return ( - - + <> + {m.label} - {hasPassword ? m.masked : m.noPasswordSet} - {managedBy ? ( - - - - ) : onSubmitPassword ? ( - - - - ) : null} - - + + + + + {hasPassword ? m.masked : m.noPasswordSet} + + {managedBy ? ( + + + + ) : onSubmitPassword ? ( + + + + ) : null} + + + ); } diff --git a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.messages.ts b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.messages.ts index 1ad7b77b90b..f6550798bbf 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.messages.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.messages.ts @@ -1,5 +1,4 @@ export const userProfilePasswordSectionMessages = { - sectionTitle: 'Authentication', label: 'Password', masked: '••••••••••••••••••', noPasswordSet: 'No password set', diff --git a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.types.ts b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.types.ts index 0a116af5895..220d1fdf280 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.types.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.types.ts @@ -12,7 +12,6 @@ export interface UserProfilePasswordManagedBy { } export interface UserProfilePasswordSectionViewProps { - sectionTitle?: string; /** Renders only the group, for composing inside a parent `Section.Root`. */ asGroup?: boolean; hasPassword?: boolean; diff --git a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.view.tsx index f0c0099cc4a..269e1cd4849 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-password-section.view.tsx @@ -11,7 +11,6 @@ export type { } from './user-profile-password-section.types'; export function UserProfilePasswordSectionView({ - sectionTitle, asGroup = false, hasPassword = false, requiresCurrentPassword = false, @@ -19,14 +18,15 @@ export function UserProfilePasswordSectionView({ onSubmitPassword, }: UserProfilePasswordSectionViewProps) { const m = useMessages('userProfilePasswordSection'); - const title = sectionTitle ?? m.sectionTitle; if (!hasPassword && !managedBy && !onSubmitPassword) { return null; } const group = ( - - {title ? {title} : null} + ); - return asGroup ? group : {group}; + return asGroup ? group : {group}; } 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 1f6f3431dfc..0bdacd11bf6 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 @@ -5,7 +5,6 @@ import { Icon } from '../../components/icon'; import { Section } from '../../components/section'; export function UserProfileSecurityList({ - sectionTitle, asGroup = false, label, addLabel, @@ -15,7 +14,6 @@ export function UserProfileSecurityList({ addControl, children, }: { - sectionTitle?: string; asGroup?: boolean; label: string; addLabel: string; @@ -30,7 +28,6 @@ export function UserProfileSecurityList({ variant={asGroup ? 'contained' : 'default'} aria-label={asGroup ? label : undefined} > - {sectionTitle ? {sectionTitle} : null} @@ -72,5 +69,5 @@ export function UserProfileSecurityList({ ); - return asGroup ? group : {group}; + return asGroup ? group : {group}; } 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 3f0ddd38bfc..84a9ae285cc 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 @@ -87,7 +87,10 @@ export function UserProfileSecurityPanelView({ Security
{hasAuthentication ? ( - + {showPassword ? ( + - Subscription + + + Subscription + + 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 90b354ed64d..19201c76642 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 @@ -46,10 +46,14 @@ export function UserProfileWeb3WalletsSectionView({ return ( <> {hasRows ? ( - + - {m.title} + + + {m.title} + + {wallets.map(wallet => ( void generateBackupCodes('regenerate') : undefined, diff --git a/packages/swingset/src/stories/section.mdx b/packages/swingset/src/stories/section.mdx index f756da6fce6..839f0fae655 100644 --- a/packages/swingset/src/stories/section.mdx +++ b/packages/swingset/src/stories/section.mdx @@ -28,7 +28,7 @@ import { Section } from '@clerk/mosaic/components/section'; `Section.Group` pairs an optional `Section.Title` with a `Section.Surface`. The surface is the outlined card. Its direct `Section.Row` children own separation and may stack one or more `Section.Item` entries. Each item lays out optional media, flexible content, and trailing actions. `Section.Media` accepts `sm`, `md`, `lg`, and `xl` sizes. -For a nested list, place a `Section.Header` and a `Section.Items` directly inside the surface. The header draws the divider above the list, and nested items are separated by dividers of their own. +For a nested list, place a `Section.Header` and a `Section.Items` directly inside the surface. The list draws the divider below the header, and nested items are separated by dividers of their own. A `Section.Header` can also head plain `Section.Row` children, which draw their own dividers. A section can hold more than one `Section.Group`. Set `variant='contained'` on a group that holds a nested list, such as a user's email addresses inside the account section. A contained group renders with `role='group'` and `data-variant='contained'`, so it can take its own accessible name and be styled apart from the section's main group. diff --git a/packages/swingset/src/stories/section.stories.tsx b/packages/swingset/src/stories/section.stories.tsx index 69609e503f4..105246ba6d3 100644 --- a/packages/swingset/src/stories/section.stories.tsx +++ b/packages/swingset/src/stories/section.stories.tsx @@ -264,6 +264,8 @@ export function FlatContainedGroups() { background: none !important; } .cl-section-items { + margin-inline: 0 !important; + padding-inline: calc(var(--cl-spacing) * 4) !important; border: 1px solid var(--cl-color-border) !important; border-radius: var(--cl-radius-xl) !important; background: var(--cl-color-background) !important; diff --git a/packages/swingset/src/stories/user-profile-passkeys-section.mdx b/packages/swingset/src/stories/user-profile-passkeys-section.mdx index b36689c2f9c..40f74188b38 100644 --- a/packages/swingset/src/stories/user-profile-passkeys-section.mdx +++ b/packages/swingset/src/stories/user-profile-passkeys-section.mdx @@ -14,14 +14,13 @@ import * as Stories from './user-profile-passkeys-section.stories'; ## Props -| Prop | Type | Default | Description | -| -------------- | ----------------------------------------------------- | ------------ | ---------------------------------------------------------------- | -| `passkeys` | `UserProfilePasskey[]` | — (required) | Names and formatted creation/last-used labels. | -| `sectionTitle` | `string` | — | Optional surrounding section heading. | -| `onAdd` | `() => void` | — | Shows Add when supplied. | -| `addError` | `string` | — | Inline creation error. | -| `onRename` | `(id: string, name: string) => void \| Promise` | — | Resolve to close; reject with an Error to display the failure. | -| `onRemove` | `(id: string) => void \| Promise` | — | Shared confirmation awaits removal and displays rejected errors. | +| Prop | Type | Default | Description | +| ---------- | ----------------------------------------------------- | ------------ | ---------------------------------------------------------------- | +| `passkeys` | `UserProfilePasskey[]` | — (required) | Names and formatted creation/last-used labels. | +| `onAdd` | `() => void` | — | Shows Add when supplied. | +| `addError` | `string` | — | Inline creation error. | +| `onRename` | `(id: string, name: string) => void \| Promise` | — | Resolve to close; reject with an Error to display the failure. | +| `onRemove` | `(id: string) => void \| Promise` | — | Shared confirmation awaits removal and displays rejected errors. | ## Usage diff --git a/packages/swingset/src/stories/user-profile-passkeys-section.stories.tsx b/packages/swingset/src/stories/user-profile-passkeys-section.stories.tsx index f57448d1b14..9ea16aa95c0 100644 --- a/packages/swingset/src/stories/user-profile-passkeys-section.stories.tsx +++ b/packages/swingset/src/stories/user-profile-passkeys-section.stories.tsx @@ -18,12 +18,7 @@ export const meta: StoryMeta = { function PasskeysExample({ empty = false, failOnce = false }: { empty?: boolean; failOnce?: boolean }): ReactElement { const fixture = usePasskeysFixture({ empty, failOnce }); - return ( - - ); + return ; } export function Default(): ReactElement { @@ -39,7 +34,6 @@ export function CreationUnavailable(): ReactElement { return ( ); diff --git a/packages/swingset/src/stories/user-profile.mdx b/packages/swingset/src/stories/user-profile.mdx index 5fd35d80134..de796755de9 100644 --- a/packages/swingset/src/stories/user-profile.mdx +++ b/packages/swingset/src/stories/user-profile.mdx @@ -28,3 +28,13 @@ and animates the popup; the profile paints it, names it, and carries the dismiss name='Overlay' storyModule={Stories} /> + +## Flat contained groups + +Every section surface restyled flat from external CSS, using the same selectors as the Section +`FlatContainedGroups` example. + + diff --git a/packages/swingset/src/stories/user-profile.stories.tsx b/packages/swingset/src/stories/user-profile.stories.tsx index 04a8034c036..35da874e1c7 100644 --- a/packages/swingset/src/stories/user-profile.stories.tsx +++ b/packages/swingset/src/stories/user-profile.stories.tsx @@ -53,3 +53,88 @@ export function Overlay() { ); } + +export function FlatContainedGroups() { + const { activePage, setActivePage, pages } = useUserProfileFixture(); + return ( + <> + + + + ); +}