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

createTheme, createGlobalTheme: Add support for assigning themes to a layer #1512

Merged
merged 4 commits into from
Dec 20, 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
51 changes: 51 additions & 0 deletions .changeset/nasty-wasps-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
'@vanilla-extract/css': minor
---

`createTheme`, `createGlobalTheme`: Add support for assigning themes to a layer

Themes can now be assigned to a layer by name using the `@layer` key at the top-level of the theme definition.

**EXAMPLE USAGE**:

```ts
// themes.css.ts
import { createTheme, createGlobalTheme, layer } from '@vanilla-extract/css';

export const themeLayer = layer();

export const [themeA, vars] = createTheme({
'@layer': themeLayer,
color: {
brand: 'blue'
},
font: {
body: 'arial'
}
});

export const vars = createGlobalTheme(':root', {
'@layer': themeLayer,
space: {
small: '10px',
large: '20px',
}
});
```

This will generate the following CSS:

```css
@layer themes_themeLayer__1k6oxph0;
@layer themes_themeLayer__1k6oxph0 {
.themes_themeA__1k6oxph1 {
--color-brand__1k6oxph2: blue;
--font-body__1k6oxph3: arial;
}

:root {
--space-small__z05zdf1: 10px;
--space-large__z05zdf2: 20px;
}
}
```
31 changes: 31 additions & 0 deletions fixtures/themed/src/themes.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createTheme,
assignVars,
style,
layer,
} from '@vanilla-extract/css';

export const theme = style({});
Expand Down Expand Up @@ -31,6 +32,36 @@ export const altTheme = createTheme(vars, {
},
});

const themeLayer = layer();

// Not tested visually, exported for CSS output testing
export const [altTheme2Class, altTheme2Contract] = createTheme({
'@layer': themeLayer,
colors: {
backgroundColor: 'green',
text: 'white',
},
space: {
1: '8px',
2: '12px',
3: '16px',
},
});

// Not tested visually, exported for CSS output testing
export const altTheme3 = createGlobalTheme(':root', altTheme2Contract, {
'@layer': 'globalThemeLayer',
colors: {
backgroundColor: 'green',
text: 'white',
},
space: {
1: '8px',
2: '12px',
3: '16px',
},
});

export const responsiveTheme = style({
vars: assignVars(vars, {
colors: {
Expand Down
67 changes: 52 additions & 15 deletions packages/css/src/theme.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,77 @@
import type { Contract, MapLeafNodes } from '@vanilla-extract/private';
import type { ThemeVars, Tokens } from './types';
import type { GlobalStyleRule, Resolve, ThemeVars, Tokens } from './types';
import { appendCss, registerClassName } from './adapter';
import { getFileScope } from './fileScope';
import { generateIdentifier } from './identifier';
import { createThemeContract, assignVars } from './vars';

type WithOptionalLayer<T extends Tokens> = T & {
'@layer'?: string;
};

type WithoutLayer<T> = Omit<T, '@layer'>;

export function createGlobalTheme<ThemeTokens extends Tokens>(
selector: string,
tokens: ThemeTokens,
): ThemeVars<ThemeTokens>;
tokens: WithOptionalLayer<ThemeTokens>,
): Resolve<WithoutLayer<ThemeVars<ThemeTokens>>>;
export function createGlobalTheme<ThemeContract extends Contract>(
selector: string,
themeContract: ThemeContract,
tokens: MapLeafNodes<ThemeContract, string>,
tokens: WithOptionalLayer<MapLeafNodes<ThemeContract, string>>,
): void;
export function createGlobalTheme(
selector: string,
arg2: any,
arg3?: any,
): any {
const shouldCreateVars = Boolean(!arg3);
const themeContractProvided = Boolean(arg3);

const tokenArg = (
themeContractProvided ? arg3 : arg2
) as WithOptionalLayer<Tokens>;

const themeVars = shouldCreateVars
? createThemeContract(arg2)
: (arg2 as ThemeVars<any>);
const { layerName, tokens } = extractLayerFromTokens(tokenArg);

const tokens = shouldCreateVars ? arg2 : arg3;
const themeContract = themeContractProvided
? (arg2 as ThemeVars<any>)
: createThemeContract(tokens);

let rule: GlobalStyleRule = {
vars: assignVars(themeContract, tokens),
};

if (layerName) {
rule = {
'@layer': {
[layerName]: rule,
},
};
}

appendCss(
{
type: 'global',
selector: selector,
rule: { vars: assignVars(themeVars, tokens) },
rule,
},
getFileScope(),
);

if (shouldCreateVars) {
return themeVars;
if (!themeContractProvided) {
return themeContract;
}
}

export function createTheme<ThemeContract extends Contract>(
themeContract: ThemeContract,
tokens: MapLeafNodes<ThemeContract, string>,
tokens: WithOptionalLayer<MapLeafNodes<ThemeContract, string>>,
debugId?: string,
): string;
export function createTheme<ThemeTokens extends Tokens>(
tokens: ThemeTokens,
tokens: WithOptionalLayer<ThemeTokens>,
debugId?: string,
): [className: string, vars: ThemeVars<ThemeTokens>];
): [className: string, vars: Resolve<WithoutLayer<ThemeVars<ThemeTokens>>>];
export function createTheme(arg1: any, arg2?: any, arg3?: string): any {
const themeClassName = generateIdentifier(
typeof arg2 === 'object' ? arg3 : arg2,
Expand All @@ -64,3 +86,18 @@ export function createTheme(arg1: any, arg2?: any, arg3?: string): any {

return vars ? [themeClassName, vars] : themeClassName;
}

function extractLayerFromTokens(
tokens: WithOptionalLayer<MapLeafNodes<any, string>>,
): {
layerName?: string;
tokens: MapLeafNodes<any, string>;
} {
if ('@layer' in tokens) {
const { '@layer': layerName, ...rest } = tokens;

return { layerName, tokens: rest };
}

return { tokens };
}
4 changes: 4 additions & 0 deletions packages/css/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import type { AtRule, Properties } from 'csstype';

import type { SimplePseudos } from './simplePseudos';

export type Resolve<T> = {
[Key in keyof T]: T[Key];
} & {};

// csstype is yet to ship container property types as they are not in
// the output MDN spec files yet. Remove this once that's done.
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries
Expand Down
Loading
Loading