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}
>