Skip to content

Commit

Permalink
Enable conditional styling for the css prop (#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mad-Kat authored Dec 19, 2024
1 parent 5ce7f16 commit 2f0ba89
Show file tree
Hide file tree
Showing 9 changed files with 654 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/red-rats-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"next-yak": patch
"yak-swc": patch
---

Enable conditional styling for the css prop
12 changes: 12 additions & 0 deletions packages/example/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ export default function Home() {
>
CSS Prop works if this is green
</p>
<p
css={css`
color: red;
${() =>
true &&
css`
color: green;
`}
`}
>
Conditional CSS Prop works if this is green
</p>
<Inputs />
</main>
</YakThemeProvider>
Expand Down
58 changes: 58 additions & 0 deletions packages/next-yak/runtime/__tests__/cssPropTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,61 @@ const StyledComponentWithCSSProp = () => {
</Text>
</div>;
};

const ComponentWithConditionalCSSButWithoutOwnProps = () => {
const x = Math.random() > 0.5;
return (
<div
css={css`
${() =>
x &&
css`
padding: 20px;
`}
`}
/>
);
};

const ComponentWithConditionalCSSVarsButWithoutOwnProps = () => {
const x = Math.random() > 0.5;
return (
<div
css={css`
padding: ${() => x && "20px"};
`}
/>
);
};

const ComponentWithDynamicCSSShouldGenerateTypeError = () => {
return (
<div
// @ts-expect-error - properties not supported
css={css<{ $primary: boolean }>`
padding: ${({ $primary }) => $primary && "20px"};
`}
/>
);
};

const dynamicMixin = css<{ $primary: boolean }>`
${({ $primary }) =>
$primary &&
css`
font-size: 1.7rem;
`}
`;

const ComponentWithCSSThatUsesDynamicMixinShouldGenerateTypeError = () => {
return (
<div
css={css`
${
// @ts-expect-error - properties not supported
dynamicMixin
}
`}
/>
);
};
9 changes: 5 additions & 4 deletions packages/next-yak/runtime/cssLiteral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,13 @@ export type PropsToClassNameFn = (props: unknown) =>
* Therefore this is only an internal function only and it must be cast to any
* before exported to the user.
*/
export function css(styles: TemplateStringsArray, ...values: []): StaticCSSProp;
export function css<TProps = {}>(
export function css<TProps>(
styles: TemplateStringsArray,
...values: CSSInterpolation<NoInfer<TProps> & { theme: YakTheme }>[]
): ComponentStyles<TProps>;
): TProps extends object ? ComponentStyles<TProps> : StaticCSSProp;
export function css<TProps>(
...args: Array<any>
): StaticCSSProp | ComponentStyles<TProps> {
): TProps extends object ? ComponentStyles<TProps> : StaticCSSProp {
const classNames: string[] = [];
const dynamicCssFunctions: PropsToClassNameFn[] = [];
const style: Record<string, string> = {};
Expand Down Expand Up @@ -110,9 +109,11 @@ 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
2 changes: 1 addition & 1 deletion packages/next-yak/runtime/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const yakStyled = <
CSSInterpolation<T & NoInfer<TCSSProps> & { theme: YakTheme }>
>
) => {
const getRuntimeStyles = css(styles, ...(values as any));
const getRuntimeStyles = css<object>(styles, ...(values as any));
const yak = (props: Substitute<TCSSProps & T, TAttrsIn>) => {
// if the css component does not require arguments
// it can be called without arguments and we skip calling useTheme()
Expand Down
15 changes: 7 additions & 8 deletions packages/yak-swc/yak_swc/src/yak_transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,16 @@ impl YakTransform for TransformCssMixin {
) -> YakTransformResult {
let has_dynamic_content = !runtime_expressions.is_empty() || !runtime_css_variables.is_empty();

if (self.is_exported || self.is_within_jsx_attribute) && has_dynamic_content {
if self.is_exported && has_dynamic_content && !self.is_within_jsx_attribute {
// For now dynamic mixins are not supported cross file
// as the scope handling is quite complicated
let error_msg = if self.is_exported {
"Dynamic mixins must not be exported. Please ensure that this mixin requires no props."
} else {
"Dynamic mixins must not be used within JSX attributes. Please ensure that this mixin requires no props."
};

HANDLER.with(|handler| {
handler.struct_span_err(expression.span, error_msg).emit();
handler
.struct_span_err(
expression.span,
"Dynamic mixins must not be exported. Please ensure that this mixin requires no props.",
)
.emit();
});
}

Expand Down
207 changes: 207 additions & 0 deletions packages/yak-swc/yak_swc/tests/fixture/css-prop-conditional/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { css } from "next-yak";

const Elem = () => {
const show = Math.random() > 0.5;
return (
<div
css={css`
${() =>
show &&
css`
color: red;
`}
`}
/>
);
};

const Elem2 = () => {
const show = Math.random() > 0.5;
return (
<div
css={css`
${() =>
show &&
css`
color: red;
`}
`}
className="test-class"
/>
);
};

const Elem3 = () => {
const show = Math.random() > 0.5;
return (
<div
style={{ padding: "5px" }}
css={css`
${() =>
show &&
css`
padding: 10px;
`}
`}
/>
);
};

const Elem4 = (props: any) => {
const show = Math.random() > 0.5;
return (
<div
css={css`
${() =>
show &&
css`
color: green;
`}
`}
{...props}
/>
);
};

const Elem5 = (props: any) => {
return (
<div
css={css`
${() =>
props.show &&
css`
color: purple;
`}
`}
{...props.a}
{...props.b}
/>
);
};

const Elem6 = (props: any) => {
return (
<div
css={css`
${() =>
props.show &&
css`
font-size: 16px;
`}
`}
className="main"
style={{ fontWeight: "bold" }}
/>
);
};

const Elem7 = (props: any) => {
return (
<div
css={css`
${() => props.show && css``}
`}
className="empty-css"
/>
);
};

const Elem8 = () => {
const show = Math.random() > 0.5;
return (
<div
css={css`
color: ${() => show && "red"};
`}
/>
);
};

const Elem9 = () => {
const show = Math.random() > 0.5;
return (
<div
css={css`
color: ${() => show && "red"};
`}
className="test-class"
/>
);
};

const Elem10 = () => {
const show = Math.random() > 0.5;
return (
<div
style={{ padding: "5px" }}
css={css`
padding: ${() => show && "10px"};
`}
/>
);
};

const Elem11 = (props: any) => {
const show = Math.random() > 0.5;
return (
<div
css={css`
color: ${() => show && "green"};
`}
{...props}
/>
);
};

const Elem12 = (props: any) => {
return (
<div
css={css`
color: ${() => props.show && "purple"};
`}
{...props.a}
{...props.b}
/>
);
};

const Elem13 = (props: any) => {
return (
<div
css={css`
font-size: ${() => props.show && "16px"};
`}
className="main"
style={{ fontWeight: "bold" }}
/>
);
};

const Elem14 = (props: any) => {
return (
<div
css={css`
display: ${() => props.show && "block"};
`}
className="empty-css"
/>
);
};

const Elem15 = (props: any) => {
return (
<div
css={css`
${() =>
props.a &&
css`
${() =>
props.b &&
css`
color: ${() => props.c && "orange"};
`}
`}
`}
/>
);
};
Loading

0 comments on commit 2f0ba89

Please sign in to comment.