Skip to content

Commit

Permalink
Unify type of static css prop and dynamic css mixin (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mad-Kat authored Jan 9, 2025
1 parent 171898f commit 145c621
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 24 deletions.
8 changes: 8 additions & 0 deletions .changeset/smart-cars-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"next-yak": patch
---

Refactor CSS prop types to improve type inference and consistency

- Added new type `CSSProp` to be used by custom components to receive the `css` prop in a somewhat typesafe way
- Updated `css` function to return `ComponentStyle` only
6 changes: 2 additions & 4 deletions packages/next-yak/runtime/__tests__/cssPropTest.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @jsxImportSource next-yak */
// this is only a type check file and should not be executed

import { css, styled } from "next-yak";
import { css, CSSProp, styled } from "next-yak";
import { CSSProperties } from "react";

declare module "next-yak" {
Expand All @@ -28,9 +28,7 @@ const NestedComponentWithCssProp = () => (
</div>
);

const ComponentThatTakesCssProp = (p: {
css: { className: string; style?: CSSProperties };
}) => <div {...p}>anything</div>;
const ComponentThatTakesCssProp = (p: CSSProp) => <div {...p}>anything</div>;

const ComponentWithCssPropAsProp = () => {
return <ComponentThatTakesCssProp css={css``} />;
Expand Down
16 changes: 16 additions & 0 deletions packages/next-yak/runtime/__tests__/typeTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,19 @@ const SelectorMixinsShouldNotAlterType = () => {

<X />;
};

const InferenceShouldWorkWithComplexTypes = () => {
const Title = styled<React.ComponentPropsWithRef<"strong" | "h1">>(
{} as any,
)<{
$primary: boolean;
}>`
${({ $primary }) => {
if ($primary) {
return css`
color: red;
`;
}
}}
`;
};
33 changes: 22 additions & 11 deletions packages/next-yak/runtime/cssLiteral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,32 @@ import type { YakTheme } from "./index.d.ts";

export const yakComponentSymbol = Symbol("yak");

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

export type StaticCSSProp = {
className: string;
style?: CSSProperties;
/**
* Convenience type to specify the CSS prop type.
* This type is used to allow the css prop on components.
*
* @example
* ```tsx
* const ComponentThatTakesCssProp = (p: CSSProp) =>
* <div {...p}>anything</div>;
* ```
*/
export type CSSProp = {
/** This prop is transformed during compilation and doesn't exist at runtime. */
css?: ComponentStyles<Record<keyof any, never>>;
/** The css prop will return the name of the class and automatically merged with an existing class */
className?: string;
/** The css prop will return the style object and automatically merged with an existing style */
style?: {
[key: string]: string;
};
};

export type CSSInterpolation<TProps> =
Expand All @@ -22,7 +38,6 @@ export type CSSInterpolation<TProps> =
| null
| false
| ComponentStyles<TProps>
| StaticCSSProp
| {
// type only identifier to allow targeting components
// e.g. styled.svg`${Button}:hover & { fill: red; }`
Expand Down Expand Up @@ -61,10 +76,8 @@ export type PropsToClassNameFn = (props: unknown) =>
export function css<TProps>(
styles: TemplateStringsArray,
...values: CSSInterpolation<NoInfer<TProps> & { theme: YakTheme }>[]
): TProps extends object ? ComponentStyles<TProps> : StaticCSSProp;
export function css<TProps>(
...args: Array<any>
): TProps extends object ? ComponentStyles<TProps> : StaticCSSProp {
): ComponentStyles<TProps>;
export function css<TProps>(...args: Array<any>): ComponentStyles<TProps> {
const classNames: string[] = [];
const dynamicCssFunctions: PropsToClassNameFn[] = [];
const style: Record<string, string> = {};
Expand Down Expand Up @@ -109,11 +122,9 @@ export function css<TProps>(
// Non Dynamic CSS
if (dynamicCssFunctions.length === 0) {
const className = classNames.join(" ");
// @ts-expect-error - Conditional return types are tricky in the implementation and generate false positives
return () => ({ className, style });
}

// @ts-expect-error - Conditional return types are tricky in the implementation and generate false positives
return (props: unknown) => {
const allClassNames: string[] = [...classNames];
const allStyles: Record<string, string> = { ...style };
Expand Down
6 changes: 3 additions & 3 deletions packages/next-yak/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
export { useTheme, YakThemeProvider } from "next-yak/context";
export type { YakTheme } from "./context/index.d.ts";

export { css } from "./mocks/cssLiteral.js";
export { styled } from "./mocks/styled.js";
export { keyframes } from "./mocks/keyframes.js";
export { atoms } from "./atoms.js";
export { css, type CSSProp } from "./mocks/cssLiteral.js";
export { keyframes } from "./mocks/keyframes.js";
export { styled } from "./mocks/styled.js";
6 changes: 3 additions & 3 deletions packages/next-yak/runtime/jsx-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ReactJSXRuntime from "react/jsx-runtime";
import type { StaticCSSProp } from "./mocks/cssLiteral.js";
import type { ComponentStyles } from "./mocks/cssLiteral.js";

const Fragment = ReactJSXRuntime.Fragment;
const jsx = ReactJSXRuntime.jsx;
Expand All @@ -18,9 +18,9 @@ export declare namespace YakJSX {
React.JSX.IntrinsicClassAttributes<T>;
export type IntrinsicElements = {
[K in keyof React.JSX.IntrinsicElements]: React.JSX.IntrinsicElements[K] & {
css?: StaticCSSProp;
css?: ComponentStyles<Record<keyof any, never>>;
};
};
}

export { type YakJSX as JSX, Fragment, jsx, jsxs };
export { Fragment, jsx, jsxs, type YakJSX as JSX };
6 changes: 5 additions & 1 deletion packages/next-yak/runtime/mocks/cssLiteral.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { css as cssInternal, PropsToClassNameFn } from "../cssLiteral.js";

export type { StaticCSSProp, CSSInterpolation } from "../cssLiteral.js";
export type {
ComponentStyles,
CSSInterpolation,
CSSProp,
} from "../cssLiteral.js";

/**
* Allows to use CSS styles in a styled or css block
Expand Down
4 changes: 2 additions & 2 deletions packages/next-yak/runtime/styled.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
CSSInterpolation,
StaticCSSProp,
ComponentStyles,
css,
yakComponentSymbol,
} from "./cssLiteral.js";
Expand Down Expand Up @@ -83,7 +83,7 @@ type YakComponent<
TAttrsOut extends AttrsMerged<T, TAttrsIn> = AttrsMerged<T, TAttrsIn>,
> = React.FunctionComponent<
T & {
css?: StaticCSSProp;
css?: ComponentStyles<Record<keyof any, never>>;
}
> & {
[yakComponentSymbol]: [
Expand Down

0 comments on commit 145c621

Please sign in to comment.