-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Brijesh Bittu
committed
Feb 3, 2025
1 parent
631b7c0
commit 6e259a9
Showing
10 changed files
with
259 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { Primitive } from '@pigment-css/core'; | ||
import { ClassInfo, css } from '@pigment-css/core/runtime'; | ||
import * as React from 'react'; | ||
import isPropValid from '@emotion/is-prop-valid'; | ||
|
||
type StyledInfo = ClassInfo & { | ||
displayName?: string; | ||
vars?: Record<string, [(...args: unknown[]) => Primitive, boolean]>; | ||
}; | ||
|
||
function defaultShouldForwardProp(propName: string): boolean { | ||
// if first character is $ | ||
if (propName.charCodeAt(0) === 36) { | ||
return false; | ||
} | ||
if (propName === 'as') { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
function shouldForwardProp(propName: string) { | ||
if (defaultShouldForwardProp(propName)) { | ||
return isPropValid(propName); | ||
} | ||
return false; | ||
} | ||
|
||
function getStyle(props: ClassInfo['defaultVariants'], vars: StyledInfo['vars']) { | ||
const newStyle: Record<string, Primitive> = {}; | ||
if (!props || !vars) { | ||
return newStyle; | ||
} | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const key in vars) { | ||
if (!vars.hasOwnProperty(key)) { | ||
continue; | ||
} | ||
const [variableFunction, isUnitLess] = vars[key]; | ||
const value = variableFunction(props); | ||
if (typeof value === 'undefined') { | ||
continue; | ||
} | ||
if (typeof value === 'string' || isUnitLess) { | ||
newStyle[key] = value; | ||
} else { | ||
newStyle[key] = `${value}px`; | ||
} | ||
} | ||
return newStyle; | ||
} | ||
|
||
export function styled<T extends React.ElementType>(tag: T) { | ||
if (process.env.NODE_ENV === 'development') { | ||
if (tag === undefined) { | ||
throw new Error( | ||
'You are trying to create a styled element with an undefined component.\nYou may have forgotten to import it.', | ||
); | ||
} | ||
} | ||
const shouldForwardPropLocal = | ||
typeof tag === 'string' ? shouldForwardProp : defaultShouldForwardProp; | ||
|
||
function scopedStyled({ | ||
classes, | ||
variants = [], | ||
defaultVariants = {}, | ||
vars, | ||
displayName = '', | ||
}: StyledInfo) { | ||
const cssFn = css({ | ||
classes, | ||
variants, | ||
defaultVariants, | ||
}); | ||
const baseClasses = cssFn(); | ||
|
||
const StyledComponent = React.forwardRef< | ||
React.ComponentRef<T>, | ||
React.ComponentPropsWithoutRef<T> & { | ||
as?: React.ElementType; | ||
className?: string; | ||
style?: React.CSSProperties; | ||
} | ||
>(function render(props, ref) { | ||
const newProps: Record<string, unknown> = {}; | ||
|
||
// eslint-disable-next-line no-restricted-syntax | ||
for (const key in props) { | ||
// if first char is $ | ||
if (shouldForwardPropLocal(key)) { | ||
newProps[key] = props[key]; | ||
} | ||
} | ||
newProps.className = variants.length === 0 ? baseClasses : baseClasses; | ||
newProps.style = { | ||
...props.style, | ||
...getStyle(props, vars), | ||
}; | ||
|
||
const Component = props.as ?? tag; | ||
return <Component ref={ref} {...newProps} />; | ||
}); | ||
|
||
if (displayName) { | ||
StyledComponent.displayName = displayName; | ||
} else { | ||
StyledComponent.displayName = 'Styled(Pigment)'; | ||
} | ||
|
||
// @ts-expect-error No TS check required | ||
// eslint-disable-next-line no-underscore-dangle | ||
StyledComponent.__styled_by_pigment_css = true; | ||
|
||
return StyledComponent; | ||
} | ||
|
||
return scopedStyled; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,75 @@ | ||
export declare function styled(): void; | ||
import type * as React from 'react'; | ||
import { Variants, BaseInterface, CssArg, VariantNames, Primitive } from '@pigment-css/core'; | ||
|
||
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; | ||
|
||
interface RequiredProps { | ||
className?: string; | ||
style?: React.CSSProperties; | ||
} | ||
|
||
export type PolymorphicComponentProps< | ||
Props extends {}, | ||
AsTarget extends React.ElementType | undefined, | ||
AsTargetProps extends object = AsTarget extends React.ElementType | ||
? React.ComponentPropsWithRef<AsTarget> | ||
: {}, | ||
> = NoInfer<Omit<Substitute<Props, AsTargetProps>, 'as'>> & { | ||
as?: AsTarget; | ||
children?: React.ReactNode; | ||
}; | ||
|
||
export interface PolymorphicComponent<Props extends {}> | ||
extends React.ForwardRefExoticComponent<Props> { | ||
<AsTarget extends React.ElementType | undefined = undefined>( | ||
props: PolymorphicComponentProps<Props, AsTarget>, | ||
): React.JSX.Element; | ||
} | ||
|
||
type StyledArgument<V extends Variants> = CssArg<V>; | ||
|
||
interface StyledComponent<Props extends {}> extends PolymorphicComponent<Props> { | ||
defaultProps?: Partial<Props> | undefined; | ||
toString: () => string; | ||
} | ||
|
||
interface StyledOptions extends BaseInterface {} | ||
|
||
export interface CreateStyledComponent< | ||
Component extends React.ElementType, | ||
OuterProps extends object, | ||
> { | ||
<Props extends {} = {}>( | ||
arg: TemplateStringsArray, | ||
...templateArgs: (Primitive | ((props: Props) => Primitive))[] | ||
): StyledComponent<Substitute<OuterProps, Props>> & (Component extends string ? {} : Component); | ||
|
||
<V extends {}>( | ||
...styles: Array<StyledArgument<V>> | ||
): StyledComponent<Substitute<OuterProps, V extends Variants ? VariantNames<V> : V>> & | ||
(Component extends string ? {} : Component); | ||
} | ||
|
||
export interface CreateStyled { | ||
< | ||
TagOrComponent extends React.ElementType, | ||
FinalProps extends {} = React.ComponentPropsWithRef<TagOrComponent>, | ||
>( | ||
tag: TagOrComponent, | ||
options?: StyledOptions, | ||
): CreateStyledComponent<TagOrComponent, FinalProps>; | ||
} | ||
|
||
export type CreateStyledIndex = { | ||
[Key in keyof React.JSX.IntrinsicElements]: CreateStyledComponent< | ||
Key, | ||
React.JSX.IntrinsicElements[Key] | ||
>; | ||
}; | ||
|
||
export declare const styled: CreateStyled & CreateStyledIndex; |
3 changes: 3 additions & 0 deletions
3
packages/pigment-css-react-new/tests/styled/fixtures/dummy-component.fixture.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function TestComponent() { | ||
return <h1>Hello</h1>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/pigment-css-react-new/tests/styled/fixtures/styled.output.css
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 52 additions & 20 deletions
72
packages/pigment-css-react-new/tests/styled/styled.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,52 @@ | ||
// import { styled } from '../../src/styled'; | ||
|
||
// const Button = styled('button')({ | ||
// color: 'red', | ||
// variants: { | ||
// hue: { | ||
// primary: { | ||
// color: 'red', | ||
// backgroundColor: 'blue', | ||
// }, | ||
// }, | ||
// }, | ||
// }); | ||
|
||
// // @ts-expect-error `download` does not exist on button | ||
// <Button download>Hello</Button>; | ||
|
||
// <Button as="a" download> | ||
// Hello | ||
// </Button>; | ||
import { t } from '@pigment-css/theme'; | ||
import { styled } from '../../src/styled'; | ||
|
||
declare module '@pigment-css/theme' { | ||
interface Theme { | ||
palette: { | ||
main: string; | ||
}; | ||
} | ||
} | ||
|
||
const Button = styled('button')({ | ||
color: 'red', | ||
variants: { | ||
btnSize: { | ||
small: { | ||
padding: 0, | ||
}, | ||
medium: { | ||
padding: '1rem', | ||
}, | ||
large: { | ||
padding: '2rem', | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const Div1 = styled.div<{ $size?: 'small' | 'medium' | 'large' }>` | ||
color: red; | ||
padding: ${({ $size }) => ($size === 'small' ? 2 : 4)}; | ||
`; | ||
|
||
<Div1 onClick={() => undefined}> | ||
<Button type="button" btnSize="medium" />; | ||
</Div1>; | ||
|
||
const Button2 = styled('button')<{ $isRed: boolean }>({ | ||
color: 'red', | ||
backgroundColor: 'red', | ||
}); | ||
|
||
<Button2 $isRed className="" />; | ||
|
||
function TestComponent({}: { className?: string; style?: React.CSSProperties; hello: string }) { | ||
return <div>Hello</div>; | ||
} | ||
|
||
styled(TestComponent)` | ||
color: red; | ||
background-color: ${t('$palette.main')}; | ||
`; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.