Skip to content

Commit aecb873

Browse files
committed
refactor(ui): share one base props type across Mosaic components
Heading, Text and Badge each derived their props differently: two base types (`React.ComponentPropsWithRef` vs headless `ComponentProps`), two things omitted (`color` vs `render`), the render union restated inline in one and imported in the other, and two ways of narrowing `color` — `Omit` then redeclare in the interfaces, silent intersection in the type alias. Collapse all of it into `MosaicComponentProps<Tag>`, mirroring Base UI's `BaseUIComponentProps`. `color` is omitted once, so the next component to expose it as a variant inherits the narrowing instead of rediscovering it. Type-only: all three components resolve to the same props as before.
1 parent 71384a1 commit aecb873

5 files changed

Lines changed: 25 additions & 11 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/ui/src/mosaic/components/badge/badge.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { type ComponentProps, type RenderProp, useRender } from '@clerk/headless/utils';
1+
import { useRender } from '@clerk/headless/utils';
22
import * as stylex from '@stylexjs/stylex';
33
import React from 'react';
44

5+
import type { MosaicComponentProps } from '../../props';
56
import { mergeStyleProps, themeProps } from '../../props';
67
import { colors, styles } from './badge.styles';
78

8-
export type BadgeProps = Omit<ComponentProps<'span'>, 'render'> & {
9+
export type BadgeProps = MosaicComponentProps<'span'> & {
910
color?: 'primary' | 'neutral' | 'warning' | 'negative' | 'positive';
10-
render?: RenderProp<React.ComponentPropsWithRef<'span'>> | React.ReactElement;
1111
};
1212

1313
/**

packages/ui/src/mosaic/components/heading/heading.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
import type { RenderPropOrElement } from '@clerk/headless/utils';
21
import { useRender } from '@clerk/headless/utils';
32
import * as stylex from '@stylexjs/stylex';
43
import React from 'react';
54

5+
import type { MosaicComponentProps } from '../../props';
66
import { mergeStyleProps, themeProps } from '../../props';
77
import { useContextProps } from '../../utils/context';
88
import type { TypographyColor, TypographySize } from '../typography.styles';
99
import { colors, sizes } from '../typography.styles';
1010
import { styles } from './heading.styles';
1111

12-
// `color` replaces the legacy HTML `color` attribute, whose `string` type would widen the variant.
13-
export interface HeadingProps extends Omit<React.ComponentPropsWithRef<'h2'>, 'color'> {
12+
export interface HeadingProps extends MosaicComponentProps<'h2'> {
1413
size?: TypographySize;
1514
color?: TypographyColor;
16-
render?: RenderPropOrElement<'h2'>;
1715
}
1816

1917
export const HeadingContext = React.createContext<Partial<HeadingProps> | null>(null);

packages/ui/src/mosaic/components/text/text.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import type { RenderPropOrElement } from '@clerk/headless/utils';
21
import { useRender } from '@clerk/headless/utils';
32
import * as stylex from '@stylexjs/stylex';
43
import React from 'react';
54

5+
import type { MosaicComponentProps } from '../../props';
66
import { mergeStyleProps, themeProps } from '../../props';
77
import { useContextProps } from '../../utils/context';
88
import type { TypographyColor, TypographySize } from '../typography.styles';
99
import { colors, sizes } from '../typography.styles';
1010

11-
// `color` replaces the legacy HTML `color` attribute, whose `string` type would widen the variant.
12-
export interface TextProps extends Omit<React.ComponentPropsWithRef<'p'>, 'color'> {
11+
export interface TextProps extends MosaicComponentProps<'p'> {
1312
size?: TypographySize;
1413
color?: TypographyColor;
15-
render?: RenderPropOrElement<'p'>;
1614
}
1715

1816
export const TextContext = React.createContext<Partial<TextProps> | null>(null);

packages/ui/src/mosaic/props.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
import type { RenderPropOrElement } from '@clerk/headless/utils';
12
import type React from 'react';
23

4+
/**
5+
* The base props every Mosaic component accepts: the native props for its default
6+
* tag, plus the `render` escape hatch that swaps the rendered element.
7+
*
8+
* `color` is omitted because it is a non-standard HTML attribute typed `string`,
9+
* which would widen any component that exposes `color` as a variant. Omitting it
10+
* here rather than per component means a new component inherits the narrowing.
11+
*/
12+
export type MosaicComponentProps<Tag extends keyof React.JSX.IntrinsicElements> = Omit<
13+
React.ComponentPropsWithRef<Tag>,
14+
'color'
15+
> & {
16+
render?: RenderPropOrElement<Tag>;
17+
};
18+
319
// The public styling contract, emitted onto a component's root element:
420
// 1. `--cl-*` vars — from `tokens.stylex.ts` (`:root { --cl-color-primary: … }`)
521
// 2. `.cl-<slot>` class — from `themeProps` (`.cl-button { … }`)

0 commit comments

Comments
 (0)