diff --git a/.changeset/mosaic-rename-merge-style-props.md b/.changeset/mosaic-rename-merge-style-props.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/mosaic-rename-merge-style-props.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/ui/src/mosaic/__tests__/props.test.ts b/packages/ui/src/mosaic/__tests__/props.test.ts index 54761210c46..2e3e9d66933 100644 --- a/packages/ui/src/mosaic/__tests__/props.test.ts +++ b/packages/ui/src/mosaic/__tests__/props.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { mergeProps, themeProps } from '../props'; +import { mergeStyleProps, themeProps } from '../props'; // The public styling contract lives here, not in any one component: the stable // `.cl-` class, `data-` variant reflection, and the class/style merge @@ -37,38 +37,38 @@ describe('themeProps', () => { }); }); -describe('mergeProps', () => { +describe('mergeStyleProps', () => { it('concatenates className across bags, base first', () => { - const merged = mergeProps({ className: 'cl-button' }, { className: 'x1 x2' }); + const merged = mergeStyleProps({ className: 'cl-button' }, { className: 'x1 x2' }); expect(merged.className).toBe('cl-button x1 x2'); }); it('accepts a trailing string as an appended className', () => { - const merged = mergeProps({ className: 'cl-button' }, { className: 'x1' }, 'consumer'); + const merged = mergeStyleProps({ className: 'cl-button' }, { className: 'x1' }, 'consumer'); expect(merged.className).toBe('cl-button x1 consumer'); }); it('accepts a trailing object as a style and lets it win', () => { - const merged = mergeProps({ style: { marginTop: '2px', color: 'red' } }, undefined, { marginTop: '8px' }); + const merged = mergeStyleProps({ style: { marginTop: '2px', color: 'red' } }, undefined, { marginTop: '8px' }); expect(merged.style).toEqual({ marginTop: '8px', color: 'red' }); }); it('disambiguates the trailing className and style pair by position', () => { - const merged = mergeProps({ className: 'cl-button' }, undefined, 'consumer', { marginTop: '8px' }); + const merged = mergeStyleProps({ className: 'cl-button' }, undefined, 'consumer', { marginTop: '8px' }); expect(merged.className).toBe('cl-button consumer'); expect(merged.style).toEqual({ marginTop: '8px' }); }); it('treats a string second argument as a className', () => { - expect(mergeProps({ className: 'cl-button' }, 'consumer').className).toBe('cl-button consumer'); + expect(mergeStyleProps({ className: 'cl-button' }, 'consumer').className).toBe('cl-button consumer'); }); it('drops className entirely when nothing contributes one', () => { - expect(mergeProps({ 'data-intent': 'primary' }, {})).not.toHaveProperty('className'); + expect(mergeStyleProps({ 'data-intent': 'primary' }, {})).not.toHaveProperty('className'); }); it('preserves non-class/style props from both bags', () => { - const merged = mergeProps({ 'data-intent': 'primary' }, { role: 'button' }); + const merged = mergeStyleProps({ 'data-intent': 'primary' }, { role: 'button' }); expect(merged).toMatchObject({ 'data-intent': 'primary', role: 'button' }); }); }); diff --git a/packages/ui/src/mosaic/components/button/button.tsx b/packages/ui/src/mosaic/components/button/button.tsx index b991702c5a9..62d0dffbe46 100644 --- a/packages/ui/src/mosaic/components/button/button.tsx +++ b/packages/ui/src/mosaic/components/button/button.tsx @@ -1,7 +1,7 @@ import * as stylex from '@stylexjs/stylex'; import React from 'react'; -import { mergeProps, themeProps } from '../../props'; +import { mergeStyleProps, themeProps } from '../../props'; import { styles } from './button.styles'; export interface ButtonProps extends React.ComponentPropsWithRef<'button'> { @@ -33,7 +33,7 @@ export const Button = React.forwardRef(function ref={ref} type='button' disabled={disabled} - {...mergeProps( + {...mergeStyleProps( themeProps('button', { intent, variant, size, shape, fullWidth, disabled }), stylex.props( styles.base, diff --git a/packages/ui/src/mosaic/props.ts b/packages/ui/src/mosaic/props.ts index 9722e4cbd4c..16000abdb05 100644 --- a/packages/ui/src/mosaic/props.ts +++ b/packages/ui/src/mosaic/props.ts @@ -72,14 +72,17 @@ function mergeTwoProps(base: PropsObject, overrides: PropsObject): PropsObject { * `className`/`style` into one spreadable object. Positional, mirroring astryx's * helper so the consumer's `className` and `style` can be passed raw: * - * mergeProps(themeProps('button', { variant }), stylex.props(...), className, style) + * mergeStyleProps(themeProps('button', { variant }), stylex.props(...), className, style) * * The trailing pair disambiguates by type — a string is a `className`, an object * is a `style` — which is why `style` is accepted directly rather than wrapped. * Order is deliberate: stable class + data-attrs, then StyleX atoms, then the * consumer's `className`/`style` last so they win. + * + * Distinct from `@clerk/headless`'s `mergeProps`: this only fuses styling output + * (className/style) and does not chain event handlers. */ -export function mergeProps( +export function mergeStyleProps( first: PropsObject, second?: PropsObject | string, classNameOrStyle?: string | React.CSSProperties, diff --git a/packages/ui/src/mosaic/styles/index.ts b/packages/ui/src/mosaic/styles/index.ts index 787900e54bf..f1716fae927 100644 --- a/packages/ui/src/mosaic/styles/index.ts +++ b/packages/ui/src/mosaic/styles/index.ts @@ -18,4 +18,4 @@ export type ColorVarName = keyof typeof colorVars; export type RadiusVarName = keyof typeof radiusVars; export type SpacingVarName = keyof typeof spacingVars; export type TypeScaleVarName = keyof typeof typeScaleVars; -export { mergeProps, themeProps } from '../props'; +export { mergeStyleProps, themeProps } from '../props';