Skip to content

Commit

Permalink
[pigment][react] Add Box component
Browse files Browse the repository at this point in the history
This is nothing but just a wrapper component with special handling for
transformed sx prop.
  • Loading branch information
brijeshb42 committed Mar 13, 2024
1 parent eda9f79 commit 43dba01
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 30 deletions.
6 changes: 6 additions & 0 deletions packages/pigment-css-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@
},
"./exports/createUseThemeProps": {
"default": "./exports/createUseThemeProps.js"
},
"./Box": {
"types": "./build/Box.d.ts",
"import": "./build/Box.mjs",
"require": "./build/Box.js",
"default": "./build/Box.js"
}
},
"nx": {
Expand Down
27 changes: 27 additions & 0 deletions packages/pigment-css-react/src/Box.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { BaseDefaultProps, Substitute, NoInfer } from './base';
import type { SxProp } from './sx';

export type PolymorphicComponentProps<
SxProp,
BaseProps extends object,
AsTarget extends React.ElementType | undefined,
AsTargetProps extends object = AsTarget extends React.ElementType
? React.ComponentPropsWithRef<AsTarget>
: BaseDefaultProps,
> = NoInfer<Omit<Substitute<BaseProps, AsTargetProps>, 'as' | 'component'>> & {
as?: AsTarget;
component?: AsTarget;
sx?: SxProp;
children?: React.ReactNode;
};

export interface PolymorphicComponent<SxProp, BaseProps extends BaseDefaultProps>
extends React.ForwardRefExoticComponent<BaseProps> {
<AsTarget extends React.ElementType | undefined = undefined>(
props: PolymorphicComponentProps<SxProp, BaseProps, AsTarget>,
): JSX.Element;
}

declare const Box: PolymorphicComponent<SxProp, {}>;

export { Box };
51 changes: 51 additions & 0 deletions packages/pigment-css-react/src/Box.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as React from 'react';

// eslint-disable-next-line react/prop-types
export const Box = React.forwardRef(
(
{
as = 'div',
// Added to support compatibility with @mui/system
component,
/**
* The type of the transformed sx prop is either a
* "string" if the css passed was fully static or an
* object with the following shape:
* {
* className: string,
* vars: Record<string, [string | number, boolean]>
* }
*/
sx,
className,
style,
...rest
},
ref,
) => {
const Component = component ?? as;
// eslint-disable-next-line react/prop-types
const sxClass = typeof sx === 'string' ? sx : sx?.className;
const classes = [className, sxClass].filter(Boolean).join(' ');
// eslint-disable-next-line react/prop-types
const sxVars = sx && typeof sx !== 'string' ? sx?.vars : {};
const varStyles = {};

if (sxVars) {
Object.entries(sxVars).forEach(([cssVariable, [value, isUnitLess]]) => {
if (typeof value === 'string' || isUnitLess) {
varStyles[`--${cssVariable}`] = value;
} else {
varStyles[`--${cssVariable}`] = `${value}px`;
}
});
}

const styles = {
...style,
...varStyles,
};

return <Component ref={ref} className={classes} style={styles} {...rest} />;
},
);
29 changes: 29 additions & 0 deletions packages/pigment-css-react/src/base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,32 @@ export type CSSObjectNoCallback =
| CSSPropertiesMultiValue
| CSSPseudosNoCallback
| CSSOthersObjectNoCallback;

export type BaseDefaultProps = object;

export type NoInfer<T> = [T][T extends any ? 0 : never];
type FastOmit<T extends object, U extends string | number | symbol> = {
[K in keyof T as K extends U ? never : K]: T[K];
};

export type Substitute<A extends object, B extends object> = FastOmit<A, keyof B> & B;

export type PolymorphicComponentProps<
SxProp,
BaseProps extends object,
AsTarget extends React.ElementType | undefined,
AsTargetProps extends object = AsTarget extends React.ElementType
? React.ComponentPropsWithRef<AsTarget>
: BaseDefaultProps,
> = NoInfer<Omit<Substitute<BaseProps, AsTargetProps>, 'as'>> & {
as?: AsTarget;
sx?: SxProp;
children?: React.ReactNode;
};

export interface PolymorphicComponent<SxProp, BaseProps extends BaseDefaultProps>
extends React.ForwardRefExoticComponent<BaseProps> {
<AsTarget extends React.ElementType | undefined = undefined>(
props: PolymorphicComponentProps<SxProp, BaseProps, AsTarget>,
): JSX.Element;
}
2 changes: 1 addition & 1 deletion packages/pigment-css-react/src/processors/sx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class SxProcessor extends BaseProcessor {

doRuntimeReplacement() {
const t = this.astService;
// do not replace if sx prop is not on zero-runtime styled component
// do not replace if sx prop is not a Pigment styled component
if (!this.elementClassName) {
return;
}
Expand Down
30 changes: 2 additions & 28 deletions packages/pigment-css-react/src/styled.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import type * as React from 'react';
import type { CSSObject } from './base';
import type { BaseDefaultProps, CSSObject, PolymorphicComponent, Substitute } from './base';
import type { ThemeArgs } from './theme';
import type { SxProp } from './sx';
import { Primitve } from './keyframes';

type Falsy = false | 0 | '' | null | undefined;

type BaseDefaultProps = object;

export type NoInfer<T> = [T][T extends any ? 0 : never];
type FastOmit<T extends object, U extends string | number | symbol> = {
[K in keyof T as K extends U ? never : K]: T[K];
};
export type Substitute<A extends object, B extends object> = FastOmit<A, keyof B> & B;

export interface StyledVariants<Props extends BaseDefaultProps> {
props: Partial<Props> | ((props: Props) => boolean);
style: CSSObject<Props>;
Expand All @@ -31,26 +23,8 @@ export type StyledArgument<Props extends BaseDefaultProps> =
| StyledCssArgument<Props>
| StyledCallback<Props>;

export type PolymorphicComponentProps<
BaseProps extends object,
AsTarget extends React.ElementType | undefined,
AsTargetProps extends object = AsTarget extends React.ElementType
? React.ComponentPropsWithRef<AsTarget>
: BaseDefaultProps,
> = NoInfer<Omit<Substitute<BaseProps, AsTargetProps>, 'as'>> & {
as?: AsTarget;
sx?: SxProp;
};

export interface PolymorphicComponent<BaseProps extends BaseDefaultProps>
extends React.ForwardRefExoticComponent<BaseProps> {
<AsTarget extends React.ElementType | undefined = undefined>(
props: PolymorphicComponentProps<BaseProps, AsTarget>,
): JSX.Element;
}

export interface StyledComponent<Props extends BaseDefaultProps = BaseDefaultProps>
extends PolymorphicComponent<Props> {
extends PolymorphicComponent<SxProp, Props> {
defaultProps?: Partial<Props> | undefined;
toString: () => string;
}
Expand Down
22 changes: 22 additions & 0 deletions packages/pigment-css-react/tests/Box.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { Box } from '@pigment-css/react/Box';

export function App() {
return (
<Box as="div" sx={{ display: 'flex' }}>
<Box as="p" sx={() => ({ color: 'primary' })}>
Hello{' '}
<Box as="a" href="https://mui.com" download>
Link
</Box>
<Box component="dialog" open>
Dialog
</Box>
{/* @ts-expect-error */}
<Box component="dialog" as="button" open>
Dialog 2
</Box>
</Box>
</Box>
);
}
2 changes: 1 addition & 1 deletion packages/pigment-css-react/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const baseConfig: Options = {
export default defineConfig([
{
...baseConfig,
entry: ['./src/index.ts', './src/theme.ts'],
entry: ['./src/index.ts', './src/theme.ts', './src/Box.jsx'],
},
{
...baseConfig,
Expand Down

0 comments on commit 43dba01

Please sign in to comment.