Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { style, variants } from './variants';
import * as React from 'react';

import { Composite, type RenderProp } from './composite';
import { type StyleRule, type VariantProps, style, variants } from './variants';

export const buttonStyles = variants({
base: style(theme => ({
appearance: 'none',
boxSizing: 'border-box',
position: 'relative',
display: 'inline-flex',
alignItems: 'center',
Expand All @@ -16,8 +18,6 @@ export const buttonStyles = variants({
fontFamily: theme.fontFamilies.sans,
border: 'none',
outline: 'none',
margin: 0,
paddingBlock: 0,
paddingInline: theme.spacing[3],
cursor: 'pointer',
textDecoration: 'none',
Expand Down Expand Up @@ -55,7 +55,7 @@ export const buttonStyles = variants({
},
})),
variants: {
variant: {
color: {
primary: style(theme => ({
background: theme.colors.purple[700],
color: theme.colors.white,
Expand All @@ -73,7 +73,29 @@ export const buttonStyles = variants({
},
},
defaultVariants: {
variant: 'primary',
color: 'primary',
fullWidth: false,
},
});

type ButtonVariantProps = VariantProps<typeof buttonStyles>;

type ButtonProps = ButtonVariantProps & {
render?: RenderProp;
sx?: StyleRule;
};

export const Button = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & ButtonProps>(
function Button(props, forwardedRef) {
const { render = <button type='button' />, color, fullWidth, sx, ...domProps } = props;

return (
<Composite
render={render}
sx={[buttonStyles({ color, fullWidth }), sx]}
ref={forwardedRef}
{...domProps}
/>
);
},
);
49 changes: 49 additions & 0 deletions packages/ui/src/ceramic/composite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// eslint-disable-next-line no-restricted-imports
import { type Interpolation, type Theme, jsx } from '@emotion/react';
import * as React from 'react';

import { type StyleRule, style, variants } from './variants';

const compositeStyles = variants({
base: style(() => ({
boxSizing: 'border-box',
margin: 0,
padding: 0,
})),
});

export type RenderProp = React.JSX.Element | ((props: React.HTMLAttributes<HTMLElement>) => React.JSX.Element);

type SxArray = (StyleRule | undefined | null | false)[];

function renderJsx(render: RenderProp | undefined, computedProps: React.HTMLAttributes<HTMLElement>) {
if (typeof render === 'function') {
return render(computedProps);
}
if (render) {
return jsx(render.type, { ...render.props, ...computedProps });
}
return jsx('div', computedProps);
}

interface CompositeProps {
render?: RenderProp;
sx?: StyleRule | SxArray;
}

export const Composite = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & CompositeProps>(
function Composite(props, forwardedRef) {
const { render, sx, ...domProps } = props;
const renderElementProps = render && typeof render !== 'function' ? render.props : {};
const sxEntries = Array.isArray(sx) ? sx : [sx];

const computedProps = {
...domProps,
...renderElementProps,
ref: forwardedRef,
css: [compositeStyles({}), ...sxEntries] as Interpolation<Theme>[],
};

return renderJsx(render, computedProps);
},
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { style, variants } from './variants';
import * as React from 'react';

import { Composite, type RenderProp } from './composite';
import { type StyleRule, type VariantProps, style, variants } from './variants';

export const textStyles = variants({
base: style(theme => ({
Expand Down Expand Up @@ -46,3 +49,25 @@ export const textStyles = variants({
font: 'sans',
},
});

type TextVariantProps = VariantProps<typeof textStyles>;

type TextProps = TextVariantProps & {
render?: RenderProp;
sx?: StyleRule;
};

export const Text = React.forwardRef<HTMLElement, Omit<React.HTMLProps<HTMLElement>, 'color'> & TextProps>(
function Text(props, forwardedRef) {
const { render = <p />, variant, color, font, sx, ...domProps } = props;

return (
<Composite
render={render}
sx={[textStyles({ variant, color, font }), sx]}
ref={forwardedRef}
{...domProps}
/>
);
},
);
15 changes: 6 additions & 9 deletions packages/ui/src/ceramic/variants.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { fastDeepMergeAndReplace } from '@clerk/shared/utils';
// eslint-disable-next-line no-restricted-imports
import type { Interpolation, Theme } from '@emotion/react';

import { type CeramicTheme } from './theme';

type CSSObject = Record<string, any>;
export type CSSObject = Record<string, any>;
// StyleFunction uses CeramicTheme to provide proper typing for theme parameter
type StyleFunction = (theme: CeramicTheme) => CSSObject;
type StyleRule = CSSObject | StyleFunction;
export type StyleFunction = (theme: CeramicTheme) => CSSObject;
export type StyleRule = CSSObject | StyleFunction;

// Convert string literal "true" | "false" to boolean (CVA's StringToBoolean)
type StringToBoolean<T> = T extends 'true' | 'false' ? boolean : T;
Expand Down Expand Up @@ -143,9 +141,8 @@ function conditionMatches(
export function variants<T>(config: VariantsConfig<T>) {
const { base, variants: variantDefinitions = {} as T, defaultVariants = {}, compoundVariants = [] } = config;

return (props: ConfigVariants<T> = {}): Interpolation<Theme> => {
return ((theme: Theme) => {
const ceramicTheme = theme as unknown as CeramicTheme;
return (props: ConfigVariants<T> = {}): StyleFunction => {
return (ceramicTheme: CeramicTheme) => {
const computedStyles: CSSObject = {};
const variantDefs = variantDefinitions as Record<string, Record<string, StyleRule>>;

Expand Down Expand Up @@ -178,6 +175,6 @@ export function variants<T>(config: VariantsConfig<T>) {
}

return computedStyles;
}) as unknown as Interpolation<Theme>;
};
};
}
Loading