Skip to content

Commit

Permalink
Merge pull request #527 from tokens-studio/css-clamp
Browse files Browse the repository at this point in the history
add css clamp node
  • Loading branch information
mck authored Nov 20, 2024
2 parents df632ca + a9bac04 commit f473f46
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/light-files-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tokens-studio/graph-engine": minor
---

Add a new node for css clamp function that outputs your accessible css clamp function for fluid dimensions.
105 changes: 105 additions & 0 deletions packages/graph-engine/src/nodes/css/accessibleClamp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { NumberSchema, StringSchema } from '../../schemas/index.js';

export default class NodeDefinition extends Node {
static title = 'CSS Accessible Clamp';
static type = 'studio.tokens.css.accessibleClamp';
static description =
'Generates a CSS clamp function for fluid typography that uses rem in addition to ensure accessible values.';

declare inputs: ToInput<{
minSize: number;
maxSize: number;
minViewport: number;
maxViewport: number;
baseFontSize: number;
precision: number;
}>;
declare outputs: ToOutput<{
value: string;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('minSize', {
type: {
...NumberSchema,
default: 16,
description: 'Minimum font size in pixels'
}
});
this.addInput('maxSize', {
type: {
...NumberSchema,
default: 24,
description: 'Maximum font size in pixels'
}
});
this.addInput('minViewport', {
type: {
...NumberSchema,
default: 320,
description: 'Minimum viewport width in pixels'
}
});
this.addInput('maxViewport', {
type: {
...NumberSchema,
default: 1920,
description: 'Maximum viewport width in pixels'
}
});
this.addInput('baseFontSize', {
type: {
...NumberSchema,
default: 16,
description:
'Base font size in pixels (usually browser default of 16px)'
}
});
this.addInput('precision', {
type: {
...NumberSchema,
default: 3,
minimum: 0,
description: 'Number of decimal places in the output'
}
});
this.addOutput('value', {
type: StringSchema
});
}

execute(): void | Promise<void> {
const {
minSize,
maxSize,
minViewport,
maxViewport,
baseFontSize,
precision
} = this.getAllInputs();

// Convert sizes to rem
const minSizeRem = minSize / baseFontSize;
const maxSizeRem = maxSize / baseFontSize;

// Calculate the preferred value parameters
const slope = (maxSize - minSize) / (maxViewport - minViewport);
const viewportValue = slope * 100; // Convert to vw units
const intersect = minSize - slope * minViewport;
const relativeValue = intersect / baseFontSize; // Convert to rem

// Format the values with specified precision
const formattedMin = Number(minSizeRem.toFixed(precision));
const formattedMax = Number(maxSizeRem.toFixed(precision));
const formattedVw = Number(viewportValue.toFixed(precision));
const formattedRel = Number(relativeValue.toFixed(precision));

// Construct the clamp function
const clampValue = `clamp(${formattedMin}rem, calc(${formattedVw}vw + ${formattedRel}rem), ${formattedMax}rem)`;

this.outputs.value.set(clampValue);
}
}
3 changes: 2 additions & 1 deletion packages/graph-engine/src/nodes/css/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import accessibleClamp from './accessibleClamp.js';
import box from './box.js';
import cssFunction from './cssFunction.js';
import map from './map.js';

export const nodes = [box, cssFunction, map];
export const nodes = [box, accessibleClamp, cssFunction, map];
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Graph } from '../../../../src/graph/graph.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/css/accessibleClamp.js';

describe('css/clamp', () => {
test('generates clamp function with default values', () => {
const graph = new Graph();
const node = new Node({ graph });

node.execute();

expect(node.outputs.value.value).toBe(
'clamp(1rem, calc(0.5vw + 0.9rem), 1.5rem)'
);
});

test('generates clamp function with custom values', () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.minSize.setValue(16);
node.inputs.maxSize.setValue(32);
node.inputs.minViewport.setValue(320);
node.inputs.maxViewport.setValue(1200);
node.inputs.baseFontSize.setValue(16);
node.inputs.precision.setValue(3);

node.execute();

expect(node.outputs.value.value).toBe(
'clamp(1rem, calc(1.818vw + 0.636rem), 2rem)'
);
});

test('handles different precision values', () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.minSize.setValue(16);
node.inputs.maxSize.setValue(32);
node.inputs.minViewport.setValue(320);
node.inputs.maxViewport.setValue(1200);
node.inputs.baseFontSize.setValue(16);
node.inputs.precision.setValue(1);

node.execute();

expect(node.outputs.value.value).toBe(
'clamp(1rem, calc(1.8vw + 0.6rem), 2rem)'
);
});

test('handles zero precision', () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.minSize.setValue(16);
node.inputs.maxSize.setValue(32);
node.inputs.minViewport.setValue(320);
node.inputs.maxViewport.setValue(1200);
node.inputs.baseFontSize.setValue(16);
node.inputs.precision.setValue(0);

node.execute();

expect(node.outputs.value.value).toBe(
'clamp(1rem, calc(2vw + 1rem), 2rem)'
);
});

test('handles high precision values', () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.minSize.setValue(16);
node.inputs.maxSize.setValue(32);
node.inputs.minViewport.setValue(320);
node.inputs.maxViewport.setValue(1200);
node.inputs.baseFontSize.setValue(16);
node.inputs.precision.setValue(5);

node.execute();

expect(node.outputs.value.value).toBe(
'clamp(1rem, calc(1.81818vw + 0.63636rem), 2rem)'
);
});
});

0 comments on commit f473f46

Please sign in to comment.