Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

css literal mock #119

Merged
merged 10 commits into from
Jul 23, 2024
107 changes: 106 additions & 1 deletion packages/next-yak/runtime/mocks/cssLiteral.ts
Original file line number Diff line number Diff line change
@@ -1 +1,106 @@
export * from "../cssLiteral.js";
import { CSSProperties } from "react";
import type { YakTheme } from "../index.d.ts";

type ComponentStyles<TProps> = (props: TProps) => {
className: string;
style?: {
[key: string]: string;
};
};

export type StaticCSSProp = {
className: string;
style?: CSSProperties;
};

export type CSSInterpolation<TProps> =
| string
| number
| undefined
| null
| false
| ComponentStyles<TProps>
| StaticCSSProp
| {
// type only identifier to allow targeting components
// e.g. styled.svg`${Button}:hover & { fill: red; }`
__yak: true;
}
| ((props: TProps) => CSSInterpolation<TProps>);

type CSSStyles<TProps = {}> = {
style: { [key: string]: string | ((props: TProps) => string) };
};

jantimon marked this conversation as resolved.
Show resolved Hide resolved
type CSSFunction = <TProps = {}>(
styles: TemplateStringsArray,
...values: CSSInterpolation<TProps & { theme: YakTheme }>[]
) => ComponentStyles<TProps>;

jantimon marked this conversation as resolved.
Show resolved Hide resolved
type PropsToClassNameFn = (props: unknown) =>
| {
className?: string;
style?: Record<string, string>;
}
| PropsToClassNameFn;

/**
* Allows to use CSS styles in a styled or css block
*
* e.g.
*
* ```tsx
* const Component = styled.div`
* color: black;
* ${({$active}) => $active && css`color: red;`}
* `;
* ```
*/
export function css(styles: TemplateStringsArray, ...values: []): StaticCSSProp;
export function css<TProps = {}>(
styles: TemplateStringsArray,
...values: CSSInterpolation<TProps & { theme: YakTheme }>[]
): ComponentStyles<TProps>;
export function css<TProps>(
styles: TemplateStringsArray,
...args: CSSInterpolation<TProps & { theme: YakTheme }>[]
): StaticCSSProp | ComponentStyles<TProps> {
const dynamicCssFunctions: PropsToClassNameFn[] = [];
for (const arg of args as Array<string | CSSFunction | CSSStyles<any>>) {
// Dynamic CSS e.g.
// css`${props => props.active && css`color: red;`}`
// compiled -> css((props: { active: boolean }) => props.active && css("yak31e4"))
if (typeof arg === "function") {
dynamicCssFunctions.push(arg as unknown as PropsToClassNameFn);
}
}
if (dynamicCssFunctions.length === 0) {
return {
className: "",
style: undefined,
};
}
jantimon marked this conversation as resolved.
Show resolved Hide resolved
return (<T>(props: T) => {
for (let i = 0; i < dynamicCssFunctions.length; i++) {
// run the dynamic expressions and ignore the return value
// the execution is important to ensure that the user code is executed
// the same way as in the real runtime
executeDynamicExpressionRecursively(props, dynamicCssFunctions[i]);
}
return {
className: "",
style: undefined,
};
}) as ComponentStyles<TProps>;
}

function executeDynamicExpressionRecursively(
props: unknown,
expression: PropsToClassNameFn,
) {
let result = expression(props);
while (typeof result === "function") {
result = result(props);
}
return result;
}
Loading