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

Enable conditional styling for the css prop #242

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
jantimon marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading