From 39ec28e983dd33f46d559b99b7583bd430ec98eb Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Sep 2026 12:47:40 -0400 Subject: [PATCH] chore(swingset): replace className/style rows with xstyle in the props table Mosaic components omit className and style, so the swingset props table was advertising props they don't take. Show the xstyle prop instead, and give Otp an xstyle prop so it matches every other Mosaic component. --- .changeset/swingset-proptable-style-rows.md | 2 ++ packages/swingset/src/components/PropTable.tsx | 13 +++---------- packages/swingset/src/stories/banner.mdx | 1 - packages/swingset/src/stories/otp.component.mdx | 1 - .../ui/src/mosaic/components/otp/otp.test.tsx | 17 +++++++++++++++++ packages/ui/src/mosaic/components/otp/otp.tsx | 7 +++++-- 6 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 .changeset/swingset-proptable-style-rows.md diff --git a/.changeset/swingset-proptable-style-rows.md b/.changeset/swingset-proptable-style-rows.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/swingset-proptable-style-rows.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/swingset/src/components/PropTable.tsx b/packages/swingset/src/components/PropTable.tsx index 22d2fd228b2..80ed0655551 100644 --- a/packages/swingset/src/components/PropTable.tsx +++ b/packages/swingset/src/components/PropTable.tsx @@ -15,16 +15,9 @@ interface ExtraProp { interface PropTableProps { meta: StoryMeta; extra?: ExtraProp[]; - /** Set false for a component that styles itself and does not want `className`/`style` advertised. */ - styleProps?: boolean; } -const STYLEX_ROWS: ExtraProp[] = [ - { name: 'className', type: 'string' }, - { name: 'style', type: 'CSSProperties' }, -]; - -export function PropTable({ meta, extra = [], styleProps = true }: PropTableProps) { +export function PropTable({ meta, extra = [] }: PropTableProps) { const playground = usePlayground(); const variants = meta.styles?._variants ?? {}; const defaults = meta.styles?._defaultVariants ?? {}; @@ -40,7 +33,7 @@ export function PropTable({ meta, extra = [], styleProps = true }: PropTableProp return { name, type, default: defDisplay }; }), ...extra, - ...(styleProps ? STYLEX_ROWS : []), + { name: 'xstyle', type: 'XStyle' }, ]; return ( @@ -56,7 +49,7 @@ export function PropTable({ meta, extra = [], styleProps = true }: PropTableProp {rows.map(row => { // The default is a static cell; the Value column is the live control. Variant - // props get a knob there; non-variant rows (the engine rows, extra) have no control. + // props get a knob there; the extra and xstyle rows have no control. const knob = playground?.knobs[row.name]; return ( diff --git a/packages/swingset/src/stories/banner.mdx b/packages/swingset/src/stories/banner.mdx index cbccb761c8c..ff38bc97db9 100644 --- a/packages/swingset/src/stories/banner.mdx +++ b/packages/swingset/src/stories/banner.mdx @@ -13,7 +13,6 @@ import * as BannerStories from './banner.stories'; ## Usage diff --git a/packages/swingset/src/stories/otp.component.mdx b/packages/swingset/src/stories/otp.component.mdx index b4777ef644c..a27f1291fe1 100644 --- a/packages/swingset/src/stories/otp.component.mdx +++ b/packages/swingset/src/stories/otp.component.mdx @@ -13,7 +13,6 @@ import * as OtpStories from './otp.component.stories'; screen.getAllByRole('textbox'); +const atoms = stylex.create({ + spaced: { marginTop: '8px' }, +}); + describe('Mosaic Otp', () => { it('renders one slot per character and applies the default status', () => { render( @@ -214,4 +219,16 @@ describe('Mosaic Otp', () => { expect(ref.current).toBe(slots()[0]); }); + + it('merges xstyle atoms after the root atoms', () => { + render( + , + ); + + const group = screen.getByRole('group', { name: 'Verification code' }); + expect(group).toHaveClass('cl-otp', stylex.props(atoms.spaced).className ?? ''); + }); }); diff --git a/packages/ui/src/mosaic/components/otp/otp.tsx b/packages/ui/src/mosaic/components/otp/otp.tsx index a5fdc5b1a1d..e6e46d021ae 100644 --- a/packages/ui/src/mosaic/components/otp/otp.tsx +++ b/packages/ui/src/mosaic/components/otp/otp.tsx @@ -3,6 +3,7 @@ import { Otp as Primitive } from '@clerk/headless/otp'; import * as stylex from '@stylexjs/stylex'; import React from 'react'; +import type { MosaicStyleProps } from '../../props'; import { mergeStyleProps, themeProps } from '../../props'; import { inputStyles } from '../../utils/input.styles'; import { reset } from '../../utils/reset.styles'; @@ -12,7 +13,8 @@ import { styles } from './otp.styles'; /** How the entered code currently reads back to the user. */ export type OtpStatus = 'neutral' | 'success' | 'error'; -export interface OtpProps extends Omit { +export interface OtpProps + extends Omit, MosaicStyleProps { /** The number of boxes in the code. @default 6 */ length?: number; /** Colours every slot for the verification outcome. Defaults to the enclosing `Field`'s validity. */ @@ -58,6 +60,7 @@ export const Otp = React.forwardRef(function MosaicO 'aria-invalid': ariaInvalidProp, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, + xstyle, ...rest }, ref, @@ -82,7 +85,7 @@ export const Otp = React.forwardRef(function MosaicO disabled={disabled} required={required} id={fieldProps?.id ?? id} - {...mergeStyleProps(themeProps('otp', { status, disabled }), stylex.props(reset.base, styles.root))} + {...mergeStyleProps(themeProps('otp', { status, disabled }), stylex.props(reset.base, styles.root, xstyle))} aria-labelledby={fieldProps?.['aria-labelledby'] ?? ariaLabelledBy} aria-describedby={fieldProps?.['aria-describedby'] ?? ariaDescribedBy} >