Skip to content
Open
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
179 changes: 179 additions & 0 deletions frontend/src/designTokens/designTokens.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{
"colors": {
"primary": "#3E007E",
"on-primary": "#FFFFFF",
"primary-hover": "#3E007E2F",
"secondary": "#2F293B",
"on-secondary": "#FFFFFF",
"tertiary": "#2A005E",
"on-tertiary": "#FFFFFF",
"background": "#FFFFFF",
"darkGrey": "#202124",
"background-disabled": "#f5f5f5",
"on-background": "#1E1925",
"surface": "#FCF8F8",
"on-surface": "#000000",
"error": "#600004",
"on-error": "#FFFFFF",
"warning": "#FCC723",
"on-warning": "#000000",
"success": "#2E8D32",
"on-success": "#FFFFFF",
"text-primary": "#333333",
"text-secondary": "#666666",
"border": "#DDDDDD",
"disabled": "#999999",
"accent": "#2E7D32"
},
"borderRadius": {
"none": "0px",
"extra-small": "2px",
"small": "4px",
"medium": "8px",
"large": "12px",
"extra-large": "24px"
},
"boxShadow": {
"level0": "none",
"level1": "0 1px 2px rgba(0,0,0,0.1)",
"level2": "0 2px 4px rgba(0,0,0,0.12)",
"level3": "0 4px 6px rgba(0,0,0,0.14)",
"level4": "0 8px 12px rgba(0,0,0,0.16)",
"level5": "0 12px 16px rgba(0,0,0,0.18)"
},
"opacity": {
"hover-opacity": "0.08",
"focus-opacity": "0.12",
"pressed-opacity": "0.16",
"disabled-opacity": "0.38"
},
"transitionDuration": {
"instant": "50ms",
"quick": "100ms",
"short": "150ms",
"medium": "200ms",
"long": "300ms",
"extraLong": "500ms"
},
"transitionTimingFunction": {
"standard": "cubic-bezier(0.2, 0, 0, 1)",
"accelerate": "cubic-bezier(0.3, 0, 1, 1)",
"decelerate": "cubic-bezier(0, 0, 0.3, 1)",
"linear": "linear"
},
"fontFamily": {
"plain": "'Inter', sans-serif"
},
"fontSize": {
"label-small": [
"0.6875rem",
{
"lineHeight": "1rem",
"fontWeight": 500
}
],
"label-medium": [
"0.75rem",
{
"lineHeight": "1rem",
"fontWeight": 500
}
],
"label-large": [
"0.875rem",
{
"lineHeight": "1.25rem",
"fontWeight": 500
}
],
"body-small": [
"0.75rem",
{
"lineHeight": "1rem",
"fontWeight": 400
}
],
"body-medium": [
"0.875rem",
{
"lineHeight": "1.25rem",
"fontWeight": 400
}
],
"body-large": [
"1rem",
{
"lineHeight": "1.5rem",
"fontWeight": 400
}
],
"title-small": [
"0.875rem",
{
"lineHeight": "1.25rem",
"fontWeight": 500
}
],
"title-medium": [
"1rem",
{
"lineHeight": "1.5rem",
"fontWeight": 500
}
],
"title-large": [
"1.375rem",
{
"lineHeight": "1.75rem",
"fontWeight": 400
}
],
"headline-small": [
"1.5rem",
{
"lineHeight": "2rem",
"fontWeight": 400
}
],
"headline-medium": [
"1.75rem",
{
"lineHeight": "2.25rem",
"fontWeight": 400
}
],
"headline-large": [
"2rem",
{
"lineHeight": "2.5rem",
"fontWeight": 400
}
],
"display-small": [
"2.25rem",
{
"lineHeight": "2.75rem",
"fontWeight": 400
}
],
"display-medium": [
"2.8125rem",
{
"lineHeight": "3.25rem",
"fontWeight": 400
}
],
"display-large": [
"3.5625rem",
{
"lineHeight": "4rem",
"fontWeight": 400
}
]
},
"fontWeight": {
"weight-regular": 400,
"weight-medium": 500,
"weight-bold": 700
}
}
17 changes: 17 additions & 0 deletions frontend/src/designTokens/designTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import motion from './tokens/motion';
import typography from './tokens/typography';
import color from './tokens/color';
import elevation from './tokens/elevation';
import shape from './tokens/shape';
import state from './tokens/state';

const designTokens = {
motion,
typography,
color,
elevation,
shape,
state,
};

export default designTokens;
124 changes: 124 additions & 0 deletions frontend/src/designTokens/helpers/tokensToJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import * as fs from 'node:fs';
import * as path from 'node:path';
import designTokens from '../designTokens.ts';
import type { Typeface } from '../tokens/typography/typeface';
import type { TypeScale } from '../tokens/typography/typescale';

const outputFile = path.resolve('frontend/src/designTokens/designTokens.json');

type FontSize = [string, { lineHeight: string; fontWeight: string }];

type UnwrappedTokens = {
color: Record<string, string>;
shape: Record<string, string>;
elevation: Record<string, string>;
state: Record<string, string>;
motion: {
duration: Record<string, string>;
easing: Record<string, string>;
};
typography: {
typeface: Record<
Typeface,
{
value: string;
}
>;
typeScale: Record<
TypeScale,
{
fontSize: string;
lineHeight: string;
fontWeight: string;
}
>;
weight: Record<string, number>;
};
};

/**
* Converts the design tokens to tailwind format and writes them to a JSON file.
*/
function designTokensToJson() {
// Ensure parent directory exists
fs.mkdirSync(path.dirname(outputFile), { recursive: true });

const tokens = unwrapValue(designTokens) as UnwrappedTokens;

const fontSize = parseFontSize(tokens.typography.typeScale);

const tailwindExtend = {
colors: tokens.color,
borderRadius: tokens.shape,
boxShadow: tokens.elevation,
opacity: tokens.state,
transitionDuration: tokens.motion?.duration,
transitionTimingFunction: tokens.motion?.easing,
fontFamily: tokens.typography?.typeface,
fontSize,
fontWeight: tokens.typography?.weight,
};

// Write or overwrite the file
fs.writeFileSync(outputFile, JSON.stringify(tailwindExtend, null, 2), {
flag: 'w',
});

console.log(`\x1b[32m✔ Design tokens JSON written to ${outputFile}\x1b[0m`);
}

/**
* Recursively unwraps the `value` properties from the design tokens.
* @param {unknown} obj - The object to unwrap.
* @returns {unknown} The unwrapped object.
*/
function unwrapValue(obj: unknown): unknown {
if (!isRecord(obj)) {
return obj;
}
if (!isUndefined(obj.value)) {
return obj.value;
}

return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, unwrapValue(value)]));
}

/**
* Type guard to check if a value is a Record<string, unknown>.
* @param {unknown} value - The value to check.
* @returns {boolean} True if the value is a Record<string, unknown>, false otherwise.
*/
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}

/**
* Type guard to check if a value is undefined.
* @param {unknown} value - The value to check.
* @returns {boolean} True if the value is undefined, false otherwise.
*/
function isUndefined(value: unknown): value is undefined {
return typeof value === 'undefined';
}

/**
* Transforms font size objects into Tailwind CSS fontSize format.
* Converts each font size entry into a tuple with fontSize and associated properties.
* @param {Record<TypeScale, { fontSize: string; lineHeight: string; fontWeight: string }>} fontSizes - The font size objects to transform.
* @returns {Record<string, FontSize>} The transformed font sizes in Tailwind format.
*/
function parseFontSize(
fontSizes: Record<TypeScale, { fontSize: string; lineHeight: string; fontWeight: string }>
): Record<string, FontSize> {
return Object.entries(fontSizes).reduce(
(acc, [key, val]) => {
const { fontSize, lineHeight, fontWeight } = val;
acc[key] = [fontSize, { lineHeight, fontWeight }];
return acc;
},
{} as Record<string, FontSize>
);
}

designTokensToJson();
3 changes: 3 additions & 0 deletions frontend/src/designTokens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import designTokens from './designTokens.ts';

export default designTokens;
Loading
Loading