From 6559499ad9e86e6099cbaf0d8cecccbd122b3754 Mon Sep 17 00:00:00 2001 From: Preston Booth Date: Mon, 2 Nov 2026 20:07:52 -0700 Subject: [PATCH 1/2] refactor(mosaic): share media sizes and radii between IconFrame, Avatar and Item Co-Authored-By: Claude Sonnet 5.5 --- .changeset/quiet-icons-align.md | 5 +++ .../src/components/avatar/avatar.styles.ts | 36 +++++-------------- .../mosaic/src/components/avatar/avatar.tsx | 2 ++ .../icon/__tests__/icon-frame.test.tsx | 2 +- .../src/components/icon/icon-frame.styles.ts | 11 ++---- .../mosaic/src/components/icon/icon-frame.tsx | 9 ++--- .../mosaic/src/components/item/item.styles.ts | 4 +-- .../user-profile-provider-media.styles.ts | 10 +++--- .../mosaic/src/styles/media-size.styles.ts | 26 ++++++++++++++ packages/swingset/src/stories/icon-frame.mdx | 4 +-- .../src/stories/icon-frame.stories.tsx | 14 ++++---- 11 files changed, 65 insertions(+), 58 deletions(-) create mode 100644 .changeset/quiet-icons-align.md create mode 100644 packages/mosaic/src/styles/media-size.styles.ts diff --git a/.changeset/quiet-icons-align.md b/.changeset/quiet-icons-align.md new file mode 100644 index 00000000000..6a196738134 --- /dev/null +++ b/.changeset/quiet-icons-align.md @@ -0,0 +1,5 @@ +--- +'@clerk/mosaic': patch +--- + +`IconFrame`, `Avatar` and `Item.Media` now share one size scale and set of corner radii. `IconFrame` sizes are now `xs`, `sm`, `md` and `lg` (24, 32, 38 and 48px) and the default is `md`, replacing `sm`, `md`, `lg` and `xl`. Icon frames and avatars in rows are 38px instead of 40px, square `Avatar` at `md` has a 6px radius instead of 8px, and provider icons in the connected accounts, enterprise accounts and Web3 wallets sections are 16px instead of 20px. diff --git a/packages/mosaic/src/components/avatar/avatar.styles.ts b/packages/mosaic/src/components/avatar/avatar.styles.ts index 2da226047e0..452780dbe3a 100644 --- a/packages/mosaic/src/components/avatar/avatar.styles.ts +++ b/packages/mosaic/src/components/avatar/avatar.styles.ts @@ -88,38 +88,18 @@ export const styles = stylex.create({ // shape — square takes the radius its size sets below; circle rounds fully export const shapes = stylex.create({ circle: { borderRadius: radiusVars['--cl-radius-full'] }, - square: { borderRadius: 'var(--_cl-avatar-radius)' }, + square: { borderRadius: 'var(--_cl-media-radius)' }, }); -// size — square box; font-size is ~0.4x the box, the ceiling at which two initials still clear -// the circle's clip. +// size — the box and radius come from `mediaSizes`; font-size is ~0.4x the box, the ceiling at which +// two initials still clear the circle's clip. export const sizes = stylex.create({ - xs: { - '--_cl-avatar-radius': radiusVars['--cl-radius-sm'], - fontSize: '0.625rem', - height: space['6'], - width: space['6'], - }, - sm: { - '--_cl-avatar-radius': radiusVars['--cl-radius-md'], - fontSize: '0.8125rem', - height: space['8'], - width: space['8'], - }, - md: { - '--_cl-avatar-radius': radiusVars['--cl-radius-lg'], - fontSize: '0.9375rem', - height: space['9.5'], - width: space['9.5'], - }, - lg: { - '--_cl-avatar-radius': radiusVars['--cl-radius-lg'], - fontSize: '1.125rem', - height: space['12'], - width: space['12'], - }, + xs: { fontSize: '0.625rem' }, + sm: { fontSize: '0.8125rem' }, + md: { fontSize: '0.9375rem' }, + lg: { fontSize: '1.125rem' }, fit: { - '--_cl-avatar-radius': radiusVars['--cl-radius-md'], + '--_cl-media-radius': radiusVars['--cl-radius-md'], fontSize: '0.75rem', height: '100%', width: '100%', diff --git a/packages/mosaic/src/components/avatar/avatar.tsx b/packages/mosaic/src/components/avatar/avatar.tsx index ae2f70278e8..5ea81429a84 100644 --- a/packages/mosaic/src/components/avatar/avatar.tsx +++ b/packages/mosaic/src/components/avatar/avatar.tsx @@ -6,6 +6,7 @@ import { useRender } from '../../primitives/utils'; import type { MosaicComponentProps, MosaicElementProps } from '../../props'; import { mergeStyleProps, themeProps } from '../../props'; import { focusOutline } from '../../styles/focus-outline.styles'; +import { mediaSizes } from '../../styles/media-size.styles'; import { reset } from '../../styles/reset.styles'; import { skeletonStyles } from '../../styles/skeleton.styles'; import { shapes, sizes, styles } from './avatar.styles'; @@ -55,6 +56,7 @@ const AvatarRoot = React.forwardRef(function Mosai reset.base, styles.base, shapes[shape], + size !== 'fit' && mediaSizes[size], sizes[size], interactive && styles.interactive, interactive && focusOutline.visible, diff --git a/packages/mosaic/src/components/icon/__tests__/icon-frame.test.tsx b/packages/mosaic/src/components/icon/__tests__/icon-frame.test.tsx index 13865b6dcb0..d9b52eb01ed 100644 --- a/packages/mosaic/src/components/icon/__tests__/icon-frame.test.tsx +++ b/packages/mosaic/src/components/icon/__tests__/icon-frame.test.tsx @@ -19,7 +19,7 @@ describe('Mosaic IconFrame', () => { expect(frame).toHaveClass('cl-icon-frame'); expect(frame).toHaveAttribute('data-bordered', ''); expect(frame).not.toHaveAttribute('data-filled'); - expect(frame).toHaveAttribute('data-size', 'xl'); + expect(frame).toHaveAttribute('data-size', 'md'); }); it('reflects its treatment and size', () => { diff --git a/packages/mosaic/src/components/icon/icon-frame.styles.ts b/packages/mosaic/src/components/icon/icon-frame.styles.ts index aba75af6bb7..e4a29088d48 100644 --- a/packages/mosaic/src/components/icon/icon-frame.styles.ts +++ b/packages/mosaic/src/components/icon/icon-frame.styles.ts @@ -1,11 +1,11 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, radiusVars, space } from '../../tokens.stylex'; +import { colorVars } from '../../tokens.stylex'; export const styles = stylex.create({ base: { borderColor: 'transparent', - borderRadius: radiusVars['--cl-radius-md'], + borderRadius: 'var(--_cl-media-radius)', borderStyle: 'solid', borderWidth: '1px', overflow: 'hidden', @@ -23,10 +23,3 @@ export const styles = stylex.create({ backgroundColor: colorVars['--cl-color-neutral-alpha-100'], }, }); - -export const sizes = stylex.create({ - sm: { height: space['7'] }, - md: { height: space['8'] }, - lg: { height: space['9'] }, - xl: { height: space['10'] }, -}); diff --git a/packages/mosaic/src/components/icon/icon-frame.tsx b/packages/mosaic/src/components/icon/icon-frame.tsx index c4824662e06..578ee0e8782 100644 --- a/packages/mosaic/src/components/icon/icon-frame.tsx +++ b/packages/mosaic/src/components/icon/icon-frame.tsx @@ -4,17 +4,18 @@ import React from 'react'; import { useRender } from '../../primitives/utils'; import type { MosaicComponentProps } from '../../props'; import { mergeStyleProps, themeProps } from '../../props'; +import { mediaSizes } from '../../styles/media-size.styles'; import { reset } from '../../styles/reset.styles'; -import { sizes, styles } from './icon-frame.styles'; +import { styles } from './icon-frame.styles'; export interface IconFrameProps extends MosaicComponentProps<'span'> { bordered?: boolean; filled?: boolean; - size?: 'sm' | 'md' | 'lg' | 'xl'; + size?: 'xs' | 'sm' | 'md' | 'lg'; } export const IconFrame = React.forwardRef(function MosaicIconFrame( - { bordered = true, filled = false, size = 'xl', render, xstyle, ...rest }, + { bordered = true, filled = false, size = 'md', render, xstyle, ...rest }, ref, ) { return useRender({ @@ -27,7 +28,7 @@ export const IconFrame = React.forwardRef(funct stylex.props( reset.base, styles.base, - sizes[size], + mediaSizes[size], bordered && styles.bordered, filled && styles.filled, xstyle, diff --git a/packages/mosaic/src/components/item/item.styles.ts b/packages/mosaic/src/components/item/item.styles.ts index 337e00123f0..1905a48a930 100644 --- a/packages/mosaic/src/components/item/item.styles.ts +++ b/packages/mosaic/src/components/item/item.styles.ts @@ -98,8 +98,8 @@ export const media = stylex.create({ }, xs: { width: space['5'] }, - md: { width: space['10'] }, - lg: { width: space['10'] }, + md: { width: space['9.5'] }, + lg: { width: space['9.5'] }, }); export const content = stylex.create({ diff --git a/packages/mosaic/src/features/user-profile/user-profile-provider-media.styles.ts b/packages/mosaic/src/features/user-profile/user-profile-provider-media.styles.ts index f3739c8f284..787fd58db24 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-provider-media.styles.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-provider-media.styles.ts @@ -3,15 +3,15 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, radiusVars, space } from '../../tokens.stylex'; export const styles = stylex.create({ - icon: { display: 'block', objectFit: 'contain', height: space['5'], width: space['5'] }, + icon: { display: 'block', objectFit: 'contain', height: space['4'], width: space['4'] }, maskedIcon: { backgroundColor: 'currentColor', display: 'block', maskPosition: 'center', maskRepeat: 'no-repeat', maskSize: 'contain', - height: space['5'], - width: space['5'], + height: space['4'], + width: space['4'], }, appleOffset: { transform: 'translateY(-1px)', @@ -25,7 +25,7 @@ export const styles = stylex.create({ display: 'inline-flex', fontSize: '0.625rem', justifyContent: 'center', - height: space['5'], - width: space['5'], + height: space['4'], + width: space['4'], }, }); diff --git a/packages/mosaic/src/styles/media-size.styles.ts b/packages/mosaic/src/styles/media-size.styles.ts new file mode 100644 index 00000000000..082e6a0c4d5 --- /dev/null +++ b/packages/mosaic/src/styles/media-size.styles.ts @@ -0,0 +1,26 @@ +import * as stylex from '@stylexjs/stylex'; + +import { radiusVars, space } from '../tokens.stylex'; + +export const mediaSizes = stylex.create({ + xs: { + '--_cl-media-radius': radiusVars['--cl-radius-sm'], + height: space['6'], + width: space['6'], + }, + sm: { + '--_cl-media-radius': radiusVars['--cl-radius-md'], + height: space['8'], + width: space['8'], + }, + md: { + '--_cl-media-radius': radiusVars['--cl-radius-md'], + height: space['9.5'], + width: space['9.5'], + }, + lg: { + '--_cl-media-radius': radiusVars['--cl-radius-lg'], + height: space['12'], + width: space['12'], + }, +}); diff --git a/packages/swingset/src/stories/icon-frame.mdx b/packages/swingset/src/stories/icon-frame.mdx index 63869ac256e..bc672992e82 100644 --- a/packages/swingset/src/stories/icon-frame.mdx +++ b/packages/swingset/src/stories/icon-frame.mdx @@ -2,7 +2,7 @@ import * as IconFrameStories from './icon-frame.stories'; # IconFrame -IconFrame centers an icon or other visual in a square Mosaic surface. `bordered` and `filled` are independent treatments, while `size` controls the frame dimensions. The default is a bordered 40px frame; the filled treatment uses `--cl-color-surface-subtle`. +IconFrame centers an icon or other visual in a square Mosaic surface. `bordered` and `filled` are independent treatments, while `size` controls the frame dimensions. The default is a bordered 38px frame; the filled treatment uses `--cl-color-surface-subtle`. ## Playground @@ -45,7 +45,7 @@ import { Checkmark } from '@clerk/mosaic/icons/glyphs/checkmark'; composition={[{ name: 'Icon', href: '/components/icon', layer: 'Components' }]} /> -`sm`, `md`, `lg`, and `xl` map to 28px, 32px, 36px, and 40px respectively. +`xs`, `sm`, `md`, and `lg` map to 24px, 32px, 38px, and 48px respectively, the same steps and corner radii as `Avatar`. ### Treatments diff --git a/packages/swingset/src/stories/icon-frame.stories.tsx b/packages/swingset/src/stories/icon-frame.stories.tsx index c84e954ed64..aafad069f36 100644 --- a/packages/swingset/src/stories/icon-frame.stories.tsx +++ b/packages/swingset/src/stories/icon-frame.stories.tsx @@ -36,12 +36,12 @@ export const meta: StoryMeta = { _variants: { bordered: { true: {}, false: {} }, filled: { true: {}, false: {} }, - size: { sm: {}, md: {}, lg: {}, xl: {} }, + size: { xs: {}, sm: {}, md: {}, lg: {} }, }, _defaultVariants: { bordered: true, filled: false, - size: 'xl', + size: 'md', }, }, }; @@ -61,25 +61,25 @@ export function Default(props: Record) { export function Sizes() { return (
- + - + - + - + Date: Tue, 3 Nov 2026 10:20:16 -0700 Subject: [PATCH 2/2] refactor(mosaic): use a 40px md media size and revert row media widths Co-Authored-By: Claude Sonnet 5.5 --- .changeset/quiet-icons-align.md | 2 +- packages/mosaic/src/components/avatar/avatar.styles.ts | 2 +- packages/mosaic/src/components/item/item.styles.ts | 4 ++-- packages/mosaic/src/styles/media-size.styles.ts | 4 ++-- packages/swingset/src/stories/icon-frame.mdx | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.changeset/quiet-icons-align.md b/.changeset/quiet-icons-align.md index 6a196738134..a6bd33ae5ae 100644 --- a/.changeset/quiet-icons-align.md +++ b/.changeset/quiet-icons-align.md @@ -2,4 +2,4 @@ '@clerk/mosaic': patch --- -`IconFrame`, `Avatar` and `Item.Media` now share one size scale and set of corner radii. `IconFrame` sizes are now `xs`, `sm`, `md` and `lg` (24, 32, 38 and 48px) and the default is `md`, replacing `sm`, `md`, `lg` and `xl`. Icon frames and avatars in rows are 38px instead of 40px, square `Avatar` at `md` has a 6px radius instead of 8px, and provider icons in the connected accounts, enterprise accounts and Web3 wallets sections are 16px instead of 20px. +`IconFrame` and `Avatar` now share one size scale and set of corner radii: `xs`, `sm`, `md` and `lg` are 24, 32, 40 and 48px. `IconFrame` sizes are now `xs`, `sm`, `md` and `lg` and the default is `md` (40px), replacing `sm`, `md`, `lg` and `xl`. `Avatar` `md` is 40px instead of 38px, square `Avatar` at `md` has a 6px radius instead of 8px, and provider icons in the connected accounts, enterprise accounts and Web3 wallets sections are 16px instead of 20px. diff --git a/packages/mosaic/src/components/avatar/avatar.styles.ts b/packages/mosaic/src/components/avatar/avatar.styles.ts index 452780dbe3a..c22763f4b50 100644 --- a/packages/mosaic/src/components/avatar/avatar.styles.ts +++ b/packages/mosaic/src/components/avatar/avatar.styles.ts @@ -85,7 +85,7 @@ export const styles = stylex.create({ }, }); -// shape — square takes the radius its size sets below; circle rounds fully +// shape — square takes the radius its size sets (see `mediaSizes`); circle rounds fully export const shapes = stylex.create({ circle: { borderRadius: radiusVars['--cl-radius-full'] }, square: { borderRadius: 'var(--_cl-media-radius)' }, diff --git a/packages/mosaic/src/components/item/item.styles.ts b/packages/mosaic/src/components/item/item.styles.ts index 1905a48a930..337e00123f0 100644 --- a/packages/mosaic/src/components/item/item.styles.ts +++ b/packages/mosaic/src/components/item/item.styles.ts @@ -98,8 +98,8 @@ export const media = stylex.create({ }, xs: { width: space['5'] }, - md: { width: space['9.5'] }, - lg: { width: space['9.5'] }, + md: { width: space['10'] }, + lg: { width: space['10'] }, }); export const content = stylex.create({ diff --git a/packages/mosaic/src/styles/media-size.styles.ts b/packages/mosaic/src/styles/media-size.styles.ts index 082e6a0c4d5..898b2cff62a 100644 --- a/packages/mosaic/src/styles/media-size.styles.ts +++ b/packages/mosaic/src/styles/media-size.styles.ts @@ -15,8 +15,8 @@ export const mediaSizes = stylex.create({ }, md: { '--_cl-media-radius': radiusVars['--cl-radius-md'], - height: space['9.5'], - width: space['9.5'], + height: space['10'], + width: space['10'], }, lg: { '--_cl-media-radius': radiusVars['--cl-radius-lg'], diff --git a/packages/swingset/src/stories/icon-frame.mdx b/packages/swingset/src/stories/icon-frame.mdx index bc672992e82..ec2bb829221 100644 --- a/packages/swingset/src/stories/icon-frame.mdx +++ b/packages/swingset/src/stories/icon-frame.mdx @@ -2,7 +2,7 @@ import * as IconFrameStories from './icon-frame.stories'; # IconFrame -IconFrame centers an icon or other visual in a square Mosaic surface. `bordered` and `filled` are independent treatments, while `size` controls the frame dimensions. The default is a bordered 38px frame; the filled treatment uses `--cl-color-surface-subtle`. +IconFrame centers an icon or other visual in a square Mosaic surface. `bordered` and `filled` are independent treatments, while `size` controls the frame dimensions. The default is a bordered 40px frame; the filled treatment uses `--cl-color-surface-subtle`. ## Playground @@ -45,7 +45,7 @@ import { Checkmark } from '@clerk/mosaic/icons/glyphs/checkmark'; composition={[{ name: 'Icon', href: '/components/icon', layer: 'Components' }]} /> -`xs`, `sm`, `md`, and `lg` map to 24px, 32px, 38px, and 48px respectively, the same steps and corner radii as `Avatar`. +`xs`, `sm`, `md`, and `lg` map to 24px, 32px, 40px, and 48px respectively, the same steps and corner radii as `Avatar`. ### Treatments