diff --git a/frontend/src/designTokens/designTokens.json b/frontend/src/designTokens/designTokens.json new file mode 100644 index 00000000..4b92939f --- /dev/null +++ b/frontend/src/designTokens/designTokens.json @@ -0,0 +1,343 @@ +{ + "colors": { + "light": { + "primary": "#9941FF", + "text-primary": "#9941FF", + "on-primary": "#FFFFFF", + "primary-hover": "#871EFF", + "secondary": "#000000", + "text-secondary": "#000000", + "on-secondary": "#FFFFFF", + "tertiary": "#757575", + "text-tertiary": "#757575", + "on-tertiary": "#FFFFFF", + "background": "#F5F0FD", + "on-background": "#757575", + "surface": "#FFFFFF", + "on-surface": "#929292", + "error": "#E61D1D", + "on-error": "#FFFFFF", + "error-hover": "#CD0000", + "warning": "#BE5702", + "on-warning": "#FFFFFF", + "warning-hover": "#A64C03", + "success": "#1C8731", + "on-success": "#FFFFFF", + "success-hover": "#1F7629", + "border": "#E6E6E6", + "disabled": "#E6E6E6", + "text-disabled": "#B3B3B3" + }, + "dark": { + "primary": "#9941FF", + "text-primary": "#9941FF", + "on-primary": "#FFFFFF", + "primary-hover": "#871EFF", + "secondary": "#FFFFFF", + "text-secondary": "#FFFFFF", + "on-secondary": "#000000", + "tertiary": "#929292", + "text-tertiary": "#929292", + "on-tertiary": "#000000", + "background": "#000000", + "on-background": "#FFFFFF", + "surface": "#666666", + "on-surface": "#FFFFFF", + "error": "#E61D1D", + "on-error": "#FFFFFF", + "error-hover": "#CD0000", + "warning": "#BE5702", + "on-warning": "#FFFFFF", + "warning-hover": "#A64C03", + "success": "#1C8731", + "on-success": "#FFFFFF", + "success-hover": "#1F7629", + "border": "#757575", + "disabled": "#666666", + "text-disabled": "#929292" + } + }, + "borderRadius": { + "none": "0px", + "extra-small": "2px", + "small": "4px", + "medium": "8px", + "large": "12px", + "extra-large": "24px" + }, + "fontFamily": { + "plain": "Inter, sans-serif, system-ui, ui-sans-serif, Marker Felt, Trebuchet MS" + }, + "fontSize": { + "desktop": { + "headline-desktop": [ + "4.125rem", + { + "lineHeight": "5.5rem", + "fontWeight": "500" + } + ], + "headline": [ + "4.125rem", + { + "lineHeight": "5.5rem", + "fontWeight": "500" + } + ], + "subtitle-desktop": [ + "3.25rem", + { + "lineHeight": "4.25rem", + "fontWeight": "500" + } + ], + "subtitle": [ + "3.25rem", + { + "lineHeight": "4.25rem", + "fontWeight": "500" + } + ], + "heading-1-desktop": [ + "2.5rem", + { + "lineHeight": "3.25rem", + "fontWeight": "500" + } + ], + "heading-1": [ + "2.5rem", + { + "lineHeight": "3.25rem", + "fontWeight": "500" + } + ], + "heading-2-desktop": [ + "2rem", + { + "lineHeight": "2.75rem", + "fontWeight": "500" + } + ], + "heading-2": [ + "2rem", + { + "lineHeight": "2.75rem", + "fontWeight": "500" + } + ], + "heading-3-desktop": [ + "1.625rem", + { + "lineHeight": "2.25rem", + "fontWeight": "500" + } + ], + "heading-3": [ + "1.625rem", + { + "lineHeight": "2.25rem", + "fontWeight": "500" + } + ], + "heading-4-desktop": [ + "1.25rem", + { + "lineHeight": "1.75rem", + "fontWeight": "500" + } + ], + "heading-4": [ + "1.25rem", + { + "lineHeight": "1.75rem", + "fontWeight": "500" + } + ], + "body-extended-desktop": [ + "1rem", + { + "lineHeight": "1.5rem", + "fontWeight": "400" + } + ], + "body-extended": [ + "1rem", + { + "lineHeight": "1.5rem", + "fontWeight": "400" + } + ], + "body-extended-semibold-desktop": [ + "1rem", + { + "lineHeight": "1.5rem", + "fontWeight": "600" + } + ], + "body-extended-semibold": [ + "1rem", + { + "lineHeight": "1.5rem", + "fontWeight": "600" + } + ], + "body-base-desktop": [ + "0.875rem", + { + "lineHeight": "1.25rem", + "fontWeight": "400" + } + ], + "body-base": [ + "0.875rem", + { + "lineHeight": "1.25rem", + "fontWeight": "400" + } + ], + "body-base-semibold-desktop": [ + "0.875rem", + { + "lineHeight": "1.25rem", + "fontWeight": "600" + } + ], + "body-base-semibold": [ + "0.875rem", + { + "lineHeight": "1.25rem", + "fontWeight": "600" + } + ], + "caption-desktop": [ + "0.75rem", + { + "lineHeight": "1rem", + "fontWeight": "400" + } + ], + "caption": [ + "0.75rem", + { + "lineHeight": "1rem", + "fontWeight": "400" + } + ], + "caption-semibold-desktop": [ + "0.75rem", + { + "lineHeight": "1rem", + "fontWeight": "600" + } + ], + "caption-semibold": [ + "0.75rem", + { + "lineHeight": "1rem", + "fontWeight": "600" + } + ] + }, + "mobile": { + "headline-mobile": [ + "2rem", + { + "lineHeight": "2.5rem", + "fontWeight": "500" + } + ], + "subtitle-mobile": [ + "1.125rem", + { + "lineHeight": "1.5rem", + "fontWeight": "500" + } + ], + "heading-1-mobile": [ + "1.75rem", + { + "lineHeight": "2.25rem", + "fontWeight": "500" + } + ], + "heading-2-mobile": [ + "1.5rem", + { + "lineHeight": "2rem", + "fontWeight": "500" + } + ], + "heading-3-mobile": [ + "1.25rem", + { + "lineHeight": "1.75rem", + "fontWeight": "500" + } + ], + "heading-4-mobile": [ + "1.125rem", + { + "lineHeight": "1.5rem", + "fontWeight": "500" + } + ], + "body-extended-mobile": [ + "1rem", + { + "lineHeight": "1.5rem", + "fontWeight": "400" + } + ], + "body-extended-semibold-mobile": [ + "1rem", + { + "lineHeight": "1.5rem", + "fontWeight": "600" + } + ], + "body-base-mobile": [ + "0.875rem", + { + "lineHeight": "1.25rem", + "fontWeight": "400" + } + ], + "body-base-semibold-mobile": [ + "0.875rem", + { + "lineHeight": "1.25rem", + "fontWeight": "600" + } + ], + "caption-mobile": [ + "0.75rem", + { + "lineHeight": "1rem", + "fontWeight": "400" + } + ], + "caption-semibold-mobile": [ + "0.75rem", + { + "lineHeight": "1rem", + "fontWeight": "600" + } + ] + } + }, + "fontWeight": { + "headline": 500, + "subtitle": 500, + "heading-1": 500, + "heading-2": 500, + "heading-3": 500, + "heading-4": 500, + "body-extended": 400, + "body-extended-semibold": 600, + "body-base": 400, + "body-base-semibold": 600, + "caption": 400, + "caption-semibold": 600 + } +} \ No newline at end of file diff --git a/frontend/src/designTokens/designTokens.ts b/frontend/src/designTokens/designTokens.ts new file mode 100644 index 00000000..22c7fa2b --- /dev/null +++ b/frontend/src/designTokens/designTokens.ts @@ -0,0 +1,11 @@ +import typography from './tokens/typography'; +import { colors } from './tokens/color'; +import shape from './tokens/shape'; + +const designTokens = { + typography, + color: colors, + shape, +}; + +export default designTokens; diff --git a/frontend/src/designTokens/helpers/tokensToJson.ts b/frontend/src/designTokens/helpers/tokensToJson.ts new file mode 100644 index 00000000..c781e8e0 --- /dev/null +++ b/frontend/src/designTokens/helpers/tokensToJson.ts @@ -0,0 +1,157 @@ +/* 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, Device } from '../tokens/typography/typescale'; + +const outputFile = path.resolve('frontend/src/designTokens/designTokens.json'); + +type FontSize = [string, { lineHeight: string; fontWeight: string }]; + +type UnwrappedTokens = { + lightColor: Record; + darkColor: Record; + shape: Record; + elevation: Record; + state: Record; + motion: { + duration: Record; + easing: Record; + }; + typography: { + typeface: Record; + typeScale: Record< + Device, + Record< + TypeScale, + { + fontSize: string; + lineHeight: string; + fontWeight: number; + } + > + >; + weight: Record; + }; +}; + +/** + * 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, + lightColor: designTokens.color.light, + darkColor: designTokens.color.dark, + }) as UnwrappedTokens; + + const desktopFontSize = parseResponsiveFontSize(tokens.typography.typeScale.desktop, 'desktop'); + const mobileFontSize = parseResponsiveFontSize(tokens.typography.typeScale.mobile, 'mobile'); + + const tailwindExtend = { + colors: { + light: tokens.lightColor, + dark: tokens.darkColor, + }, + borderRadius: tokens.shape, + boxShadow: tokens.elevation, + opacity: tokens.state, + transitionDuration: tokens.motion?.duration, + transitionTimingFunction: tokens.motion?.easing, + fontFamily: tokens.typography?.typeface, + fontSize: { + desktop: desktopFontSize, + mobile: mobileFontSize, + }, + 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) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => [key, unwrapValue(value)]) + ); +} + +/** + * Type guard to check if a value is a Record. + * @param {unknown} value - The value to check. + * @returns {boolean} True if the value is a Record, false otherwise. + */ +function isRecord(value: unknown): value is Record { + 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 responsive font size objects into Tailwind CSS fontSize format. + * Converts each font size entry into a tuple with fontSize and associated properties. + * @param {Record} fontSizes - The font size objects to transform. + * @param {Device} device - The device type (desktop or mobile). + * @returns {Record} The transformed font sizes in Tailwind format. + */ +function parseResponsiveFontSize( + fontSizes: Record< + TypeScale, + { + fontSize: string; + lineHeight: string; + fontWeight: number; + } + >, + device: Device +): Record { + return Object.entries(fontSizes).reduce( + (acc, [key, val]) => { + // After unwrapValue, the structure is already flattened + const { fontSize } = val; + const { lineHeight } = val; + const fontWeight = val.fontWeight.toString(); + + const deviceKey = `${key}-${device}`; + acc[deviceKey] = [fontSize, { lineHeight, fontWeight }]; + + if (device === 'desktop') { + acc[key] = [fontSize, { lineHeight, fontWeight }]; + } + + return acc; + }, + {} as Record + ); +} + +designTokensToJson(); diff --git a/frontend/src/designTokens/index.ts b/frontend/src/designTokens/index.ts new file mode 100644 index 00000000..4fc6d19e --- /dev/null +++ b/frontend/src/designTokens/index.ts @@ -0,0 +1,3 @@ +import designTokens from './designTokens.ts'; + +export default designTokens; diff --git a/frontend/src/designTokens/tokens/color.ts b/frontend/src/designTokens/tokens/color.ts new file mode 100644 index 00000000..b52ee038 --- /dev/null +++ b/frontend/src/designTokens/tokens/color.ts @@ -0,0 +1,337 @@ +/** + * Color Tokens + * + * These tokens define the core color system used throughout the interface. + * Each token represents a semantic purpose — rather than a specific hex value — + * allowing color changes to propagate consistently across the design system. + */ + +const colorVariables = { + cta: { + 50: '#F5F0FD', + 500: '#9941FF', + 600: '#871EFF', + }, + information: { + 50: '#E8F4FB', + 400: '#2997F0', + 500: '#0276D5', + }, + canvas: '#FFFFFF', + 'canvas-text': '#000000', + accent: { + 300: '#B3B3B3', + 400: '#929292', + 500: '#757575', + 600: '#666666', + }, + neutral: { + 100: '#E6E6E6', + 300: '#B3B3B3', + 400: '#929292', + 500: '#757575', + }, + alert: { + 500: '#E61D1D', + 600: '#CD0000', + }, + warning: { + 500: '#BE5702', + 600: '#A64C03', + }, + success: { + 500: '#1C8731', + 600: '#1F7629', + }, +}; + +const lightColors = { + primary: { + value: colorVariables.cta[500], + type: 'color', + description: 'Main brand color used for primary actions and highlights.', + }, + 'text-primary': { + value: colorVariables.cta[500], + type: 'color', + description: 'Primary text color used for main content and headings.', + }, + 'on-primary': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color used on primary surfaces.', + }, + 'primary-hover': { + value: colorVariables.cta[600], + type: 'color', + description: 'Main brand color for hovering.', + }, + + secondary: { + value: colorVariables['canvas-text'], + type: 'color', + description: 'Secondary brand color and accent.', + }, + 'text-secondary': { + value: colorVariables['canvas-text'], + type: 'color', + description: 'Secondary text color used for subheadings and less prominent content.', + }, + 'on-secondary': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color used on secondary surfaces.', + }, + + tertiary: { + value: colorVariables.accent[500], + type: 'color', + description: 'Tertiary brand color and accent.', + }, + 'text-tertiary': { + value: colorVariables.accent[500], + type: 'color', + description: 'Tertiary text color used for less prominent content.', + }, + 'on-tertiary': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color used on tertiary surfaces.', + }, + + background: { + value: colorVariables.cta[50], + type: 'color', + description: 'Default background color for the interface.', + }, + 'on-background': { + value: colorVariables.accent[500], + type: 'color', + description: 'Text or icon color used on background surfaces.', + }, + + surface: { + value: colorVariables.canvas, + type: 'color', + description: 'Default surface color for cards and containers.', + }, + 'on-surface': { + value: colorVariables.accent[400], + type: 'color', + description: 'Text or icon color used on surface elements.', + }, + + error: { + value: colorVariables.alert[500], + type: 'color', + description: 'Color representing error states and critical messages.', + }, + 'on-error': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color for text/icons on error surfaces.', + }, + 'error-hover': { + value: colorVariables.alert[600], + type: 'color', + description: 'Error color for hover states.', + }, + + warning: { + value: colorVariables.warning[500], + type: 'color', + description: 'Color representing warning states and cautionary messages.', + }, + 'on-warning': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color for text/icons on warning surfaces.', + }, + 'warning-hover': { + value: colorVariables.warning[600], + type: 'color', + description: 'Warning color for hover states.', + }, + + success: { + value: colorVariables.success[500], + type: 'color', + description: 'Color representing success states and positive messages.', + }, + 'on-success': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color for text/icons on success surfaces.', + }, + 'success-hover': { + value: colorVariables.success[600], + type: 'color', + description: 'Success color for hover states.', + }, + + border: { + value: colorVariables.neutral[100], + type: 'color', + description: 'Color used for borders and dividers between elements.', + }, + disabled: { + value: colorVariables.neutral[100], + type: 'color', + description: 'Color used for disabled backgrounds.', + }, + 'text-disabled': { + value: colorVariables.neutral[300], + type: 'color', + description: 'Text color used for disabled elements.', + }, +}; + +const darkColors = { + primary: { + value: colorVariables.cta[500], + type: 'color', + description: 'Main brand color used for primary actions and highlights.', + }, + 'text-primary': { + value: colorVariables.cta[500], + type: 'color', + description: 'Primary text color used for main content and headings.', + }, + 'on-primary': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color used on primary surfaces.', + }, + 'primary-hover': { + value: colorVariables.cta[600], + type: 'color', + description: 'Main brand color for hovering.', + }, + + secondary: { + value: colorVariables.canvas, + type: 'color', + description: 'Secondary brand color and accent.', + }, + 'text-secondary': { + value: colorVariables.canvas, + type: 'color', + description: 'Secondary text color used for subheadings and less prominent content.', + }, + 'on-secondary': { + value: colorVariables['canvas-text'], + type: 'color', + description: 'Foreground color used on secondary surfaces.', + }, + + tertiary: { + value: colorVariables.accent[400], + type: 'color', + description: 'Tertiary brand color and accent.', + }, + 'text-tertiary': { + value: colorVariables.accent[400], + type: 'color', + description: 'Tertiary text color used for less prominent content.', + }, + 'on-tertiary': { + value: colorVariables['canvas-text'], + type: 'color', + description: 'Foreground color used on tertiary surfaces.', + }, + + background: { + value: colorVariables['canvas-text'], + type: 'color', + description: 'Default background color for the interface.', + }, + 'on-background': { + value: colorVariables.canvas, + type: 'color', + description: 'Text or icon color used on background surfaces.', + }, + + surface: { + value: colorVariables.accent[600], + type: 'color', + description: 'Default surface color for cards and containers.', + }, + 'on-surface': { + value: colorVariables.canvas, + type: 'color', + description: 'Text or icon color used on surface elements.', + }, + + error: { + value: colorVariables.alert[500], + type: 'color', + description: 'Color representing error states and critical messages.', + }, + 'on-error': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color for text/icons on error surfaces.', + }, + 'error-hover': { + value: colorVariables.alert[600], + type: 'color', + description: 'Error color for hover states.', + }, + + warning: { + value: colorVariables.warning[500], + type: 'color', + description: 'Color representing warning states and cautionary messages.', + }, + 'on-warning': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color for text/icons on warning surfaces.', + }, + 'warning-hover': { + value: colorVariables.warning[600], + type: 'color', + description: 'Warning color for hover states.', + }, + + success: { + value: colorVariables.success[500], + type: 'color', + description: 'Color representing success states and positive messages.', + }, + 'on-success': { + value: colorVariables.canvas, + type: 'color', + description: 'Foreground color for text/icons on success surfaces.', + }, + 'success-hover': { + value: colorVariables.success[600], + type: 'color', + description: 'Success color for hover states.', + }, + + border: { + value: colorVariables.accent[500], + type: 'color', + description: 'Color used for borders and dividers between elements.', + }, + disabled: { + value: colorVariables.accent[600], + type: 'color', + description: 'Color used for disabled backgrounds.', + }, + 'text-disabled': { + value: colorVariables.accent[400], + type: 'color', + description: 'Text color used for disabled elements.', + }, +}; + +const colors = { + light: lightColors, + dark: darkColors, +}; + +// Maintain backward compatibility +export default lightColors; +export { colors, lightColors, darkColors }; diff --git a/frontend/src/designTokens/tokens/shape.ts b/frontend/src/designTokens/tokens/shape.ts new file mode 100644 index 00000000..4826ed46 --- /dev/null +++ b/frontend/src/designTokens/tokens/shape.ts @@ -0,0 +1,41 @@ +/** + * Shape Tokens + * + * These tokens define the corner radius values used throughout the design system. + * They provide a consistent approach to rounding corners on various UI components, + * enhancing the overall visual coherence and user experience. + */ +const shape = { + none: { + value: '0px', + type: 'radius', + description: 'No rounding; sharp corners.', + }, + 'extra-small': { + value: '2px', + type: 'radius', + description: 'Minimal rounding for very subtle corner softening.', + }, + small: { + value: '4px', + type: 'radius', + description: 'Small corner radius for buttons and small components.', + }, + medium: { + value: '8px', + type: 'radius', + description: 'Default rounding for most UI elements.', + }, + large: { + value: '12px', + type: 'radius', + description: 'Larger rounding for cards and containers.', + }, + 'extra-large': { + value: '24px', + type: 'radius', + description: 'Significant rounding for prominent elements or modals.', + }, +}; + +export default shape; diff --git a/frontend/src/designTokens/tokens/typography/index.ts b/frontend/src/designTokens/tokens/typography/index.ts new file mode 100644 index 00000000..a6437c7d --- /dev/null +++ b/frontend/src/designTokens/tokens/typography/index.ts @@ -0,0 +1,3 @@ +import typography from './typography'; + +export default typography; diff --git a/frontend/src/designTokens/tokens/typography/typeface.ts b/frontend/src/designTokens/tokens/typography/typeface.ts new file mode 100644 index 00000000..c1c02434 --- /dev/null +++ b/frontend/src/designTokens/tokens/typography/typeface.ts @@ -0,0 +1,23 @@ +/** + * Typeface Design Tokens + * + * This module defines the primary typeface used across the user interface. + * It specifies the font family along with a description of its intended use. + */ +const typeface = { + plain: { + value: 'Inter, sans-serif, system-ui, ui-sans-serif, Marker Felt, Trebuchet MS', + type: 'fontFamily', + description: 'Primary typeface for all text elements in the user interface.', + }, +}; + +export type Typeface = keyof typeof typeface; + +export type TypefaceProps = { + value: string; + type: 'fontFamily'; + description: string; +}; + +export default typeface as Record; diff --git a/frontend/src/designTokens/tokens/typography/typescale.ts b/frontend/src/designTokens/tokens/typography/typescale.ts new file mode 100644 index 00000000..2666f9b4 --- /dev/null +++ b/frontend/src/designTokens/tokens/typography/typescale.ts @@ -0,0 +1,153 @@ +/** + * Typography Type Scale Design Tokens + * + * This module defines a comprehensive type scale for typography, + * specifying font sizes, line heights, and font weights for various + * text elements such as headlines, subtitles, headings, body text, and captions. + * Each token includes responsive variants for desktop and mobile devices. + */ + +// Helper function to convert px to rem (16px = 1rem) +const pxToRem = (px: number) => `${px / 16}rem`; + +const typeScale = { + desktop: { + headline: { + fontSize: { value: pxToRem(66), description: 'Equivalent to 66px' }, + lineHeight: { value: pxToRem(88), description: 'Equivalent to 88px' }, + fontWeight: { value: 500, description: 'Medium weight for headline desktop' }, + }, + subtitle: { + fontSize: { value: pxToRem(52), description: 'Equivalent to 52px' }, + lineHeight: { value: pxToRem(68), description: 'Equivalent to 68px' }, + fontWeight: { value: 500, description: 'Medium weight for subtitle desktop' }, + }, + 'heading-1': { + fontSize: { value: pxToRem(40), description: 'Equivalent to 40px' }, + lineHeight: { value: pxToRem(52), description: 'Equivalent to 52px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 1 desktop' }, + }, + 'heading-2': { + fontSize: { value: pxToRem(32), description: 'Equivalent to 32px' }, + lineHeight: { value: pxToRem(44), description: 'Equivalent to 44px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 2 desktop' }, + }, + 'heading-3': { + fontSize: { value: pxToRem(26), description: 'Equivalent to 26px' }, + lineHeight: { value: pxToRem(36), description: 'Equivalent to 36px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 3 desktop' }, + }, + 'heading-4': { + fontSize: { value: pxToRem(20), description: 'Equivalent to 20px' }, + lineHeight: { value: pxToRem(28), description: 'Equivalent to 28px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 4 desktop' }, + }, + 'body-extended': { + fontSize: { value: pxToRem(16), description: 'Equivalent to 16px' }, + lineHeight: { value: pxToRem(24), description: 'Equivalent to 24px' }, + fontWeight: { value: 400, description: 'Regular weight for extended body desktop' }, + }, + 'body-extended-semibold': { + fontSize: { value: pxToRem(16), description: 'Equivalent to 16px' }, + lineHeight: { value: pxToRem(24), description: 'Equivalent to 24px' }, + fontWeight: { value: 600, description: 'Semibold weight for extended body desktop' }, + }, + 'body-base': { + fontSize: { value: pxToRem(14), description: 'Equivalent to 14px' }, + lineHeight: { value: pxToRem(20), description: 'Equivalent to 20px' }, + fontWeight: { value: 400, description: 'Regular weight for base body desktop' }, + }, + 'body-base-semibold': { + fontSize: { value: pxToRem(14), description: 'Equivalent to 14px' }, + lineHeight: { value: pxToRem(20), description: 'Equivalent to 20px' }, + fontWeight: { value: 600, description: 'Semibold weight for base body desktop' }, + }, + caption: { + fontSize: { value: pxToRem(12), description: 'Equivalent to 12px' }, + lineHeight: { value: pxToRem(16), description: 'Equivalent to 16px' }, + fontWeight: { value: 400, description: 'Regular weight for caption desktop' }, + }, + 'caption-semibold': { + fontSize: { value: pxToRem(12), description: 'Equivalent to 12px' }, + lineHeight: { value: pxToRem(16), description: 'Equivalent to 16px' }, + fontWeight: { value: 600, description: 'Semibold weight for caption desktop' }, + }, + }, + + mobile: { + headline: { + fontSize: { value: pxToRem(32), description: 'Equivalent to 32px' }, + lineHeight: { value: pxToRem(40), description: 'Equivalent to 40px' }, + fontWeight: { value: 500, description: 'Medium weight for headline mobile' }, + }, + subtitle: { + fontSize: { value: pxToRem(18), description: 'Equivalent to 18px' }, + lineHeight: { value: pxToRem(24), description: 'Equivalent to 24px' }, + fontWeight: { value: 500, description: 'Medium weight for subtitle mobile' }, + }, + 'heading-1': { + fontSize: { value: pxToRem(28), description: 'Equivalent to 28px' }, + lineHeight: { value: pxToRem(36), description: 'Equivalent to 36px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 1 mobile' }, + }, + 'heading-2': { + fontSize: { value: pxToRem(24), description: 'Equivalent to 24px' }, + lineHeight: { value: pxToRem(32), description: 'Equivalent to 32px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 2 mobile' }, + }, + 'heading-3': { + fontSize: { value: pxToRem(20), description: 'Equivalent to 20px' }, + lineHeight: { value: pxToRem(28), description: 'Equivalent to 28px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 3 mobile' }, + }, + 'heading-4': { + fontSize: { value: pxToRem(18), description: 'Equivalent to 18px' }, + lineHeight: { value: pxToRem(24), description: 'Equivalent to 24px' }, + fontWeight: { value: 500, description: 'Medium weight for heading 4 mobile' }, + }, + 'body-extended': { + fontSize: { value: pxToRem(16), description: 'Equivalent to 16px' }, + lineHeight: { value: pxToRem(24), description: 'Equivalent to 24px' }, + fontWeight: { value: 400, description: 'Regular weight for extended body mobile' }, + }, + 'body-extended-semibold': { + fontSize: { value: pxToRem(16), description: 'Equivalent to 16px' }, + lineHeight: { value: pxToRem(24), description: 'Equivalent to 24px' }, + fontWeight: { value: 600, description: 'Semibold weight for extended body mobile' }, + }, + 'body-base': { + fontSize: { value: pxToRem(14), description: 'Equivalent to 14px' }, + lineHeight: { value: pxToRem(20), description: 'Equivalent to 20px' }, + fontWeight: { value: 400, description: 'Regular weight for base body mobile' }, + }, + 'body-base-semibold': { + fontSize: { value: pxToRem(14), description: 'Equivalent to 14px' }, + lineHeight: { value: pxToRem(20), description: 'Equivalent to 20px' }, + fontWeight: { value: 600, description: 'Semibold weight for base body mobile' }, + }, + caption: { + fontSize: { value: pxToRem(12), description: 'Equivalent to 12px' }, + lineHeight: { value: pxToRem(16), description: 'Equivalent to 16px' }, + fontWeight: { value: 400, description: 'Regular weight for caption mobile' }, + }, + 'caption-semibold': { + fontSize: { value: pxToRem(12), description: 'Equivalent to 12px' }, + lineHeight: { value: pxToRem(16), description: 'Equivalent to 16px' }, + fontWeight: { value: 600, description: 'Semibold weight for caption mobile' }, + }, + }, +}; + +// Updated types to match the new structure +export type Device = keyof typeof typeScale; +export type TypeScale = keyof typeof typeScale.desktop; // Both desktop and mobile have same keys + +export type TypeScaleProps = { + fontSize: { value: string; description: string }; + lineHeight: { value: string; description: string }; + fontWeight: { value: number; description: string }; +}; + +export type TypeScaleStructure = Record>; + +export default typeScale as TypeScaleStructure; diff --git a/frontend/src/designTokens/tokens/typography/typography.ts b/frontend/src/designTokens/tokens/typography/typography.ts new file mode 100644 index 00000000..ddf5dcca --- /dev/null +++ b/frontend/src/designTokens/tokens/typography/typography.ts @@ -0,0 +1,18 @@ +/** + * Typography Tokens + * + * This module aggregates typography-related design tokens, including + * typefaces, type scales, and font weights. These tokens ensure + * consistent typographic styles across the user interface. + */ +import typeface from './typeface'; +import typeScale from './typescale'; +import weight from './weight'; + +const typography = { + typeface, + typeScale, + weight, +}; + +export default typography; diff --git a/frontend/src/designTokens/tokens/typography/weight.ts b/frontend/src/designTokens/tokens/typography/weight.ts new file mode 100644 index 00000000..bc8d81da --- /dev/null +++ b/frontend/src/designTokens/tokens/typography/weight.ts @@ -0,0 +1,91 @@ +/** + * Typography Weight Design Tokens + * + * These tokens define the standard font weights used throughout + * the user interface. They provide a consistent typographic + * hierarchy by specifying regular, medium, and bold weights + * for various text elements. + */ +const weight = { + headline: { + value: 500, + type: 'fontWeight', + description: 'Font weight for headline text', + }, + + subtitle: { + value: 500, + type: 'fontWeight', + description: 'Font weight for subtitle text', + }, + + 'heading-1': { + value: 500, + type: 'fontWeight', + description: 'Font weight for heading level 1', + }, + + 'heading-2': { + value: 500, + type: 'fontWeight', + description: 'Font weight for heading level 2', + }, + + 'heading-3': { + value: 500, + type: 'fontWeight', + description: 'Font weight for heading level 3', + }, + + 'heading-4': { + value: 500, + type: 'fontWeight', + description: 'Font weight for heading level 4', + }, + + 'body-extended': { + value: 400, + type: 'fontWeight', + description: 'Font weight for extended body text', + }, + + 'body-extended-semibold': { + value: 600, + type: 'fontWeight', + description: 'Semibold font weight for extended body text', + }, + + 'body-base': { + value: 400, + type: 'fontWeight', + description: 'Font weight for base body text', + }, + + 'body-base-semibold': { + value: 600, + type: 'fontWeight', + description: 'Semibold font weight for base body text', + }, + + caption: { + value: 400, + type: 'fontWeight', + description: 'Font weight for caption text', + }, + + 'caption-semibold': { + value: 600, + type: 'fontWeight', + description: 'Semibold font weight for caption text', + }, +}; + +export type Weight = keyof typeof weight; + +export type WeightProps = { + value: string | number; + type: 'fontWeight'; + description: string; +}; + +export default weight as Record; diff --git a/sonar-project.properties b/sonar-project.properties index f5ea12bb..a1b0bb04 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -11,5 +11,5 @@ sonar.projectName=vonage-video-react-app # Encoding of the source code. Default is default system encoding sonar.sourceEncoding=UTF-8 # sonar.sources=src -sonar.exclusions=**/*.spec.tsx,**/*.spec.ts,**/*.test.ts,tests/**,node_modules/**,integration-tests/**,scripts/** +sonar.exclusions=**/*.spec.tsx,**/*.spec.ts,**/*.test.ts,tests/**,node_modules/**,integration-tests/**,scripts/**,frontend/src/designTokens/** sonar.javascript.lcov.reportPaths=**/coverage/lcov.info