Skip to content

Commit

Permalink
[💥]: Implement V2 (#7)
Browse files Browse the repository at this point in the history
* Remove redundant files

* Add utilities

* Add VariableGenerator component

* Implement new create

* Expose api

* Update tsconfig

* Update version

* Update next config

* Update dev environment
  • Loading branch information
mimshins authored Apr 7, 2024
1 parent f23b97c commit 53632fe
Show file tree
Hide file tree
Showing 20 changed files with 349 additions and 338 deletions.
16 changes: 16 additions & 0 deletions app/Tokens.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import * as React from "react";
import { VariantSelector } from "./theming";

type Props = {
children?: React.ReactNode;
};

const Tokens = (props: Props) => {
const { children } = props;

return <VariantSelector variant="light">{children}</VariantSelector>;
};

export default Tokens;
13 changes: 3 additions & 10 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
"use client";

import * as React from "react";
import { getVariablesAsStyles, theme, ThemeProvider } from "./theming";

const ssrCssVariables = getVariablesAsStyles(theme);
import Tokens from "./Tokens";

const RootLayout = (props: { children: React.ReactNode }) => (
<html
lang="en"
style={ssrCssVariables}
>
<html lang="en">
<body>
<ThemeProvider theme={theme}>{props.children}</ThemeProvider>
<Tokens>{props.children}</Tokens>
</body>
</html>
);
Expand Down
13 changes: 7 additions & 6 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"use client";

import { ThemeProvider } from "./theming";
import { VariantSelector } from "./theming";

const Page = () => {
return (
<ThemeProvider
theme={{ colors: { neutral: { text: { tertiary: "#000" } } } }}
>
<h1>Dev Page</h1>
</ThemeProvider>
<>
<span>Light</span>
<VariantSelector variant="dark">
<span>Dark</span>
</VariantSelector>
</>
);
};

Expand Down
97 changes: 56 additions & 41 deletions app/theming.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,64 @@
import { createTheming, defaultCssVariableGenerator } from "../lib";
import { create } from "../lib";

export const theme = {
colors: {
primary: {
base: "1",
hover: "2",
active: "3",
disabled: "4",
},
secondary: {
base: "5",
hover: "6",
active: "7",
disabled: "8",
const variants = {
dark: {
colors: {
primary: {
base: "d1",
hover: "d2",
active: "d3",
disabled: "d4",
},
secondary: {
base: "d5",
hover: "d6",
active: "d7",
disabled: "d8",
},
neutral: {
text: {
base: "d9",
secondary: "d10",
tertiary: "d11",
},
background: {
base: "d12",
container: "d13",
elevated: "d14",
},
},
},
neutral: {
text: {
base: "9",
secondary: "10",
tertiary: "11",
boolean: true,
},
light: {
colors: {
primary: {
base: "l1",
hover: "l2",
active: "l3",
disabled: "l4",
},
background: {
base: "12",
container: "13",
elevated: "14",
secondary: {
base: "l5",
hover: "l6",
active: "l7",
disabled: "l8",
},
neutral: {
text: {
base: "l9",
secondary: "l10",
tertiary: "l11",
},
background: {
base: "l12",
container: "l13",
elevated: "l14",
},
},
},
boolean: false,
},
dark: 1,
};

const tokenFamilyNameMap: Record<keyof typeof theme, string> = {
colors: "color",
dark: "dark",
};

export const { ThemeProvider, useTheme, getVariablesAsStyles } = createTheming(
theme,
{
initializeVariablesOnHTMLRoot: true,
cssVariableGenerator: (tokenFamilyKey, tokenPath, tokenValue) =>
defaultCssVariableGenerator(
tokenFamilyNameMap[tokenFamilyKey].toLowerCase(),
tokenPath,
tokenValue,
),
},
);
export const { VariantSelector, useTokens } = create(variants);
58 changes: 58 additions & 0 deletions lib/VariableGenerator/VariableGenerator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from "react";
import { generateCssVariables, getCSSVariablesAsInlineStyle } from "./utils";

export type Props = {
children?: React.ReactNode;
tokens: Record<string, unknown>;
variant: string;
disableCSSVariableGeneration: boolean;
cssVariableGenerator: (context: {
tokenFamilyKey: string;
tokenKey: string;
tokenPath: string;
tokenValue: unknown;
}) => { variableName: string; variableValue: string } | null;
};

export type CSSVariableGenerator = NonNullable<Props["cssVariableGenerator"]>;
export type GeneratedCSSVariable = ReturnType<CSSVariableGenerator>;
export type GeneratedCSSVariables = Array<GeneratedCSSVariable>;

const VariableGenerator = (props: Props) => {
const {
children,
tokens,
variant,
disableCSSVariableGeneration,
cssVariableGenerator,
} = props;

const cssVariables = React.useMemo<GeneratedCSSVariables>(
() =>
disableCSSVariableGeneration
? []
: generateCssVariables(tokens, cssVariableGenerator),
// eslint-disable-next-line react-hooks/exhaustive-deps
[variant, disableCSSVariableGeneration],
);

const cssVariableProps = React.useMemo<React.CSSProperties>(
() =>
disableCSSVariableGeneration
? {}
: getCSSVariablesAsInlineStyle(cssVariables),
[cssVariables, disableCSSVariableGeneration],
);

return (
<div
data-name="VariantGenerator"
data-variant={variant}
style={cssVariableProps}
>
{children}
</div>
);
};

export default VariableGenerator;
11 changes: 11 additions & 0 deletions lib/VariableGenerator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export {
default,
type CSSVariableGenerator,
type GeneratedCSSVariable,
type Props,
} from "./VariableGenerator";
export {
defaultCSSVariableGenerator,
generateCssVariables,
getCSSVariablesAsInlineStyle,
} from "./utils";
84 changes: 84 additions & 0 deletions lib/VariableGenerator/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { isPlainObject } from "../utils";
import type {
CSSVariableGenerator,
GeneratedCSSVariable,
GeneratedCSSVariables,
Props,
} from "./VariableGenerator";

export const defaultCSSVariableGenerator: CSSVariableGenerator = context => {
const { tokenFamilyKey, tokenPath, tokenValue } = context;

if (!["string", "number"].includes(typeof tokenValue)) return null;

const pathString = tokenPath.replaceAll(".", "-").toLowerCase();
const name = `${tokenFamilyKey.toLowerCase()}${pathString.length > 0 ? "-" : ""}${pathString}`;

const value =
typeof tokenValue === "number" ? `${tokenValue}px` : String(tokenValue);

return {
variableName: name,
variableValue: value,
};
};

export const generateCssVariables = (
tokens: Props["tokens"],
generate: CSSVariableGenerator,
): GeneratedCSSVariables => {
const variables: GeneratedCSSVariables[] = [];

const recurse = (
parent: Record<string, unknown>,
path: string[],
): GeneratedCSSVariables => {
const cssVariables = Object.entries(parent).reduce(
(result, currentEntry) => {
const [tokenKey, tokenValue] = currentEntry;
const newPath = [...path, tokenKey];

if (!isPlainObject(tokenValue)) {
const generatedCSSVariable: GeneratedCSSVariable = generate({
tokenFamilyKey: newPath[0] ?? tokenKey,
tokenPath: newPath.slice(1).join("."),
tokenKey,
tokenValue,
});

result.push(generatedCSSVariable);

return result;
}

return recurse(tokenValue, newPath);
},
[] as GeneratedCSSVariables,
);

variables.push(cssVariables);

return cssVariables;
};

recurse(tokens, []);

return variables.flat();
};

export const getCSSVariablesAsInlineStyle = (
variables: GeneratedCSSVariables,
) => {
const inlineStyle = variables.reduce(
(result, variable) => {
if (variable) {
result[`--${variable.variableName}`] = variable.variableValue;
}

return result;
},
{} as Record<string, string>,
);

return inlineStyle;
};
Loading

0 comments on commit 53632fe

Please sign in to comment.