Skip to content

Commit

Permalink
chore(theme): refactor theme tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
DRiFTy17 committed Oct 23, 2023
1 parent 1adc332 commit 057b7cb
Show file tree
Hide file tree
Showing 39 changed files with 900 additions and 190 deletions.
3 changes: 3 additions & 0 deletions src/dev/pages/theme/theme.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="swatches" id="container"></div>

<script type="module" src="theme.ts"></script>
8 changes: 8 additions & 0 deletions src/dev/pages/theme/theme.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<%-
include('./src/partials/page.ejs', {
page: {
title: 'Theme',
includePath: './pages/theme/theme.ejs'
}
})
%>
25 changes: 25 additions & 0 deletions src/dev/pages/theme/theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.swatches {
display: flex;
flex-direction: column;
gap: 24px;
}

.swatch-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

.swatch {
border: 1px solid var(--forge-theme-outline);
box-sizing: border-box;
height: 100px;
width: 200px;
font-size: 14px;
padding: 8px;
border-radius: 4px;
}

h2 {
margin: 0;
}
167 changes: 167 additions & 0 deletions src/dev/pages/theme/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import '$src/shared';
import './theme.scss';

interface ISwatchGroup {
header?: string;
swatches: ISwatch[];
}

interface ISwatch {
text?: string;
background: string;
foreground?: string;
}

const SWATCH_GROUPS: ISwatchGroup[] = [
{
header: 'Surface',
swatches: [
{ text: 'Surface', background: 'surface' },
{ text: 'Surface dim (background)', background: 'surface-dim' },
{ text: 'Surface bright', background: 'surface-bright' }
]
},
{
swatches: [
{ text: 'Surface container', background: 'surface-container', foreground: 'on-surface-container' },
{ text: 'Surface container (low)', background: 'surface-container-low', foreground: 'on-surface' },
{ text: 'Surface container (medium)', background: 'surface-container-medium', foreground: 'on-surface' },
{ text: 'Surface container (high)', background: 'surface-container-high', foreground: 'on-surface-container-high' }
]
},
{
header: 'Key colors',
swatches: [
{ text: 'Primary', background: 'primary', foreground: 'on-primary' },
{ text: 'Primary container', background: 'primary-container', foreground: 'on-primary-container' }
]
},
{
swatches: [
{ text: 'Secondary', background: 'secondary', foreground: 'on-secondary' },
{ text: 'Secondary container', background: 'secondary-container', foreground: 'on-secondary-container' }
]
},
{
swatches: [
{ text: 'Tertiary', background: 'tertiary', foreground: 'on-tertiary' },
{ text: 'Tertiary container', background: 'tertiary-container', foreground: 'on-tertiary-container' }
]
},
{
header: 'Status',
swatches: [
{ text: 'Success', background: 'success', foreground: 'on-success' },
{ text: 'Success container', background: 'success-container', foreground: 'on-success-container' }
]
},
{
swatches: [
{ text: 'Error ', background: 'error', foreground: 'on-error' },
{ text: 'Error container', background: 'error-container', foreground: 'on-error-container' }
]
},
{
swatches: [
{ text: 'Warning', background: 'warning', foreground: 'on-warning' },
{ text: 'Warning container', background: 'warning-container', foreground: 'on-warning-container' }
]
},
{
swatches: [
{ text: 'Info', background: 'info', foreground: 'on-info' },
{ text: 'Info container', background: 'info-container', foreground: 'on-info-container' }
]
},
{
header: 'Text',
swatches: [
{ text: 'High', background: 'text-high', foreground: 'text-high-inverse' },
{ text: 'Medium', background: 'text-medium', foreground: 'text-high-inverse' },
{ text: 'Low', background: 'text-low', foreground: 'text-high' },
{ text: 'Lowest', background: 'text-lowest', foreground: 'text-high' }
]
},
{
header: 'Utilities',
swatches: [
{ text: 'Outline', background: 'outline', foreground: 'text-high' }
]
}
];

const container = document.getElementById('container');

function buildSwatches(): void {
SWATCH_GROUPS.forEach(group => {
const swatchContainer = document.createElement('div');
swatchContainer.classList.add('swatch-container');

if (group.header) {
const header = document.createElement('h2');
header.classList.add('forge-typography--heading-03');
header.textContent = group.header;
container.appendChild(header);
}

group.swatches.forEach(config => {
swatchContainer.appendChild(createSwatch(config));
});

container.appendChild(swatchContainer);
});
}

function createSwatch(config: ISwatch): HTMLElement {
const swatch = document.createElement('div');
swatch.classList.add('swatch');
if (config.text) {
swatch.textContent = config.text;
}
swatch.style.setProperty('background-color', `var(--forge-theme-${config.background})`);

if (config.foreground) {
swatch.style.setProperty('color', `var(--forge-theme-${config.foreground})`);
}

return swatch;
}

buildSwatches();

function hexToRGB(hex: string): [number, number, number] {
let hexValue = hex.replace('#', '');
if (hexValue.length === 3) {
hexValue = hexValue.split('').map(char => char + char).join('');
}
const r = parseInt(hexValue.substring(0, 2), 16);
const g = parseInt(hexValue.substring(2, 4), 16);
const b = parseInt(hexValue.substring(4, 6), 16);
return [r, g, b];
}

function _linearChannelValue($channelValue: number): number {
const normalizedChannelValue = $channelValue / 255;
if (normalizedChannelValue < 0.03928) {
return normalizedChannelValue / 12.92;
}
return Math.pow(normalizedChannelValue + 0.055 / 1.055, 2.4);
}

// Calculate the luminance for a color.
// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
function _luminance($color): number {
const [r, g, b] = hexToRGB($color);
const red = _linearChannelValue(r);
const green = _linearChannelValue(g);
const blue = _linearChannelValue(b);
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
}

// Calculate the contrast ratio between two colors.
// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
function contrastRatio($back: string, $front: string): number {
const backLum = _luminance($back) + 0.05;
const foreLum = _luminance($front) + 0.05;
return Math.max(backLum, foreLum) / Math.min(backLum, foreLum);
}
1 change: 1 addition & 0 deletions src/dev/src/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
{ "label": "Table", "path": "/pages/table/table.html" },
{ "label": "Tabs", "path": "/pages/tabs/tabs.html", "tags": ["view", "bar"] },
{ "label": "Text field", "path": "/pages/text-field/text-field.html", "tags": ["form"] },
{ "label": "Theme", "path": "/pages/theme/theme.html", "tags": ["color"] },
{ "label": "Time picker", "path": "/pages/time-picker/time-picker.html" },
{ "label": "Toast", "path": "/pages/toast/toast.html" },
{ "label": "Toolbar", "path": "/pages/toolbar/toolbar.html" },
Expand Down
4 changes: 2 additions & 2 deletions src/dev/src/styles/_header.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#page-app-bar {
--forge-app-bar-theme-background: var(--mdc-theme-secondary);
--forge-app-bar-theme-on-background: var(--mdc-theme-text-primary-on-light);
--forge-app-bar-theme-background: var(--forge-theme-secondary);
--forge-app-bar-theme-on-background: var(--forge-theme-on-secondary);
}
12 changes: 11 additions & 1 deletion src/dev/src/styles/_options.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;

forge-drawer:has(.options-container) {
--forge-drawer-width: 300px;
}

forge-text-field,
forge-select {
&:not(:first-child) {
margin-block-start: 8px;
}

&:not(:last-child) {
margin-block-end: 8px;
}
}

forge-switch {
&::part(label) {
Expand Down
49 changes: 47 additions & 2 deletions src/lib/core/styles/_utils.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use 'sass:map';
@use 'sass:math';
@use './core/config';

///
Expand All @@ -19,8 +20,13 @@
/// To provide a module reference variable with a different fallback value, set the
/// $type parameter to `value` and `$value` will be used verbatim.
///
/// Example:
/// ```scss
/// $my-token: module-ref(comp, color, primary-color); // => var(--forge-comp-color, var(--_primary-color));
/// ```
///
@function module-ref($module, $token, $value, $type: ref, $prefix: config.$prefix) {
$fallback: if($type == ref, var(--_#{$value}), $value);
$fallback: if($type == ref, module-var($value), $value);
@return _create-var($module, $token, $fallback, $prefix);
}

Expand All @@ -35,10 +41,28 @@
/// Creates a CSS custom property declaration for a module token using the
/// provided value as the variable fallback.
///
/// Example:
/// ```scss
/// $my-token: module-val(comp, color, red); // => var(--forge-comp-color, red);
/// $my-other-token: module-val(comp, color, theme.variable(primary)); // => var(--forge-comp-color, var(--forge-theme-primary));
/// ```
///
@function module-val($module, $token, $value, $prefix: config.$prefix) {
@return _create-var($module, $token, $value, $prefix);
}

///
/// Creates a CSS custom property variable reference for a token in the provided module.
///
/// Example:
/// ```scss
/// $my-var: variable-ref(theme, color); // => var(--forge-theme-color);
/// ```
///
@function variable-ref($module, $token, $value: null, $prefix: config.$prefix) {
@return _create-var($module, $token, $value, $prefix);
}

///
/// Emits CSS custom property declarations for tokens on a per-module basis,
/// using the provided token map.
Expand All @@ -63,7 +87,28 @@
///
/// Creates a CSS custom property declaration for a module token, using the provided fallback value.
///
@function _create-var($module, $token, $value, $prefix: config.$prefix) {
@function _create-var($module, $token, $value: null, $prefix: config.$prefix) {
@if $value == null {
@return var(--#{$prefix}-#{$module}-#{$token});
}
@return var(--#{$prefix}-#{$module}-#{$token}, $value);
}

///
/// Rounds a number to the specified number of decimal places.
///
@function round($value, $fractionDigits: 0) {
$power: math.pow(10, $fractionDigits);
@return math.div(math.round($power * $value), $power);
}

///
/// Flattens multiple maps into a single map.
///
@function flatten($maps...) {
$merged: ();
@each $map in $maps {
$merged: map.merge($merged, $map);
}
@return $merged;
}
2 changes: 1 addition & 1 deletion src/lib/core/styles/spacing/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/// Example:
/// ```scss
/// .my-class {
/// margin: spacing.variable('200'); // => margin: var(--forge-spacing-standard, 16px);
/// margin: spacing.variable('100'); // => margin: var(--forge-spacing-standard, 16px);
/// }
/// ```
///
Expand Down
58 changes: 58 additions & 0 deletions src/lib/core/styles/theme/_color-utils.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
@use 'sass:meta';
@use 'sass:math';
@use 'sass:color';

@function _linear-channel-value($channel-value) {
$normalized-channel-value: math.div($channel-value, 255);
@if $normalized-channel-value < 0.03928 {
@return math.div($normalized-channel-value, 12.92);
}

@return math.pow(math.div($normalized-channel-value + 0.055, 1.055), 2.4);
}

// Calculate the luminance for a color.
// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
@function luminance($color) {
$red: _linear-channel-value(color.red($color));
$green: _linear-channel-value(color.green($color));
$blue: _linear-channel-value(color.blue($color));

@return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue;
}

// Calculate the contrast ratio between two colors.
// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
@function contrast-ratio($back, $front) {
$backLum: luminance($back) + 0.05;
$foreLum: luminance($front) + 0.05;
@return math.div(math.max($backLum, $foreLum), math.min($backLum, $foreLum));
}

// Determine whether the color is 'light' or 'dark'.
@function tone($color) {
@if $color == 'dark' or $color == 'light' {
@return $color;
}

@if meta.type-of($color) != 'color' {
@warn '#{$color} is not a color. Falling back to "dark" tone.';
@return 'dark';
}

$minimumContrast: 3.1;
$lightContrast: contrast-ratio($color, white);
$darkContrast: contrast-ratio($color, rgba(black, 0.87));

@if ($lightContrast < $minimumContrast) and ($darkContrast > $lightContrast) {
@return 'light';
} @else {
@return 'dark';
}
}

// Determine whether to use dark or light text on top of given color to meet accessibility standards for contrast.
// Returns 'dark' if the given color is light and 'light' if the given color is dark.
@function contrast-tone($color) {
@return if(tone($color) == 'dark', 'light', 'dark');
}
Loading

0 comments on commit 057b7cb

Please sign in to comment.