Skip to content

Commit

Permalink
Added a custom preset + transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
GhitaNektt committed Oct 31, 2024
1 parent 58e3b7b commit 65fd00b
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/tokens/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Preset } from 'unocss'
import type { PresetYouCanOptions } from './types'

declare function presetYouCan(options?: PresetYouCanOptions): Preset

export default presetYouCan
100 changes: 100 additions & 0 deletions packages/tokens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import animations from './src/animation.tokens.json';
import colors from './src/colors.tokens.json';
import radius from './src/radius.tokens.json';
import spacing from './src/spacing.tokens.json';
import typography from './src/typography.tokens.json';

import { createColorScale, flattenObject } from './utils';

import {
transformAnimations,
transformColors,
transformRadius,
// transformShadows,
transformSpacing,
transformTypography,
} from './transformers';

const presetYouCan = () => {
const flattenedColors = flattenObject(colors);
const semanticColors = createColorScale(colors);
const flattenedAnimations = flattenObject(animations);
const flattenedRadius = flattenObject(radius);
// const flattenedShadows = flattenObject(shadows);
const flattenedSpacing = flattenObject(spacing);
const flattenedTypography = flattenObject(typography);

const colorRules = [
[/^text-(.+)-(\d+)$/, ([, color, shade]: string[]) => {
if (semanticColors[color]?.[shade]) {
return {
color: semanticColors[color][shade],
};
}
}],
[/^bg-(.+)-(\d+)$/, ([, color, shade]: string[]) => {
if (semanticColors[color]?.[shade]) {
return {
'background-color': semanticColors[color][shade],
};
}
}],
[/^border-(.+)-(\d+)$/, ([, color, shade]: string[]) => {
if (semanticColors[color]?.[shade]) {
return {
'border-color': semanticColors[color][shade],
};
}
}],
];

return {
name: '@youcan/tokens',
theme: {
colors: transformColors(flattenedColors),
transitionTimingFunction: transformAnimations(flattenedAnimations),
borderRadius: transformRadius(flattenedRadius),
// boxShadow: transformShadows(flattenedShadows),
spacing: transformSpacing(flattenedSpacing),
...transformTypography(flattenedTypography),
},
rules: colorRules,
variants: [
// Hover variant
(matcher: string) => {
if (!matcher.startsWith('hover:')) {
return matcher;
}

return {
matcher: matcher.slice(6),
selector: (s: string) => `${s}:hover`,
};
},
// Focus variant
(matcher: string) => {
if (!matcher.startsWith('focus:')) {
return matcher;
}

return {
matcher: matcher.slice(6),
selector: (s: string) => `${s}:focus`,
};
},
// Active variant
(matcher: string) => {
if (!matcher.startsWith('active:')) {
return matcher;
}

return {
matcher: matcher.slice(7),
selector: (s: string) => `${s}:active`,
};
},
],
};
};

export default presetYouCan;
23 changes: 23 additions & 0 deletions packages/tokens/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@youcan/tokens",
"type": "module",
"version": "1.0.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
},
"peerDependencies": {
"unocss": "^0.58.0"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.0.0",
"unocss": "^0.58.0"
}
}
88 changes: 88 additions & 0 deletions packages/tokens/transformers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { AnimationToken, RadiusToken, SpacingToken, TypographyToken } from './types';
import { createColorScale } from './utils';

export const transformAnimations = (tokens: Record<string, AnimationToken>) => {
const transitions: Record<string, string> = {};

Object.entries(tokens).forEach(([key, token]) => {
transitions[key] = `${token.$value} ${token.$extension?.easing || 'ease-out'}`;
});

return transitions;
};

export const transformColors = (tokens: Record<string, any>) => {
const semanticColors = createColorScale(tokens);
const flatColors: Record<string, string> = {};

Object.entries(semanticColors).forEach(([category, shades]) => {
Object.entries(shades).forEach(([shade, value]) => {
flatColors[`${category}-${shade}`] = value;
});
});

return flatColors;
};

export const transformRadius = (tokens: Record<string, RadiusToken>) => {
const radius: Record<string, string> = {};

Object.entries(tokens).forEach(([key, token]) => {
radius[key] = `${token.$value}px`;
});

return radius;
};

// export const transformShadows = (tokens: Record<string, ShadowToken>) => {
// const shadows: Record<string, string> = {};

// Object.entries(tokens).forEach(([key, token]) => {
// const shadowValues = Array.isArray(token.$value) ? token.$value : [token.$value];
// shadows[key] = shadowValues
// .map(shadow =>
// `${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.spread}px ${shadow.color}`,
// )
// .join(', ');
// });

// return shadows;
// };

export const transformSpacing = (tokens: Record<string, SpacingToken>) => {
const spacing: Record<string, string> = {};

Object.entries(tokens).forEach(([key, token]) => {
spacing[key] = `${token.$value}px`;
});

return spacing;
};

export const transformTypography = (tokens: Record<string, TypographyToken>) => {
const typography: Record<string, any> = {
fontFamily: {},
lineHeight: {},
fontWeight: {},
fontSize: {},
};

Object.entries(tokens).forEach(([key, token]) => {
switch (token.$type) {
case 'fontFamilies':
typography.fontFamily[key] = token.$value;
break;
case 'lineHeights':
typography.lineHeight[key] = `${token.$value}px`;
break;
case 'fontWeights':
typography.fontWeight[key] = token.$value;
break;
case 'fontSize':
typography.fontSize[key] = `${token.$value}px`;
break;
}
});

return typography;
};
21 changes: 21 additions & 0 deletions packages/tokens/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"outDir": "./dist",
"declaration": true
},
"include": ["./"]
}
9 changes: 9 additions & 0 deletions packages/tokens/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'tsup';

export default defineConfig({
entry: ['./index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
external: ['unocss'],
});
69 changes: 69 additions & 0 deletions packages/tokens/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export interface DesignToken {
$value: any
$type: string
$extension?: {
easing?: string
}
}

export interface TokenObject {
[key: string]: TokenObject | DesignToken
}

export interface AnimationToken extends DesignToken {
$value: string
$type: 'duration'
$extension: {
easing: string
}
}

export interface ColorToken extends DesignToken {
$value: string
$type: 'color'
}

export interface RadiusToken extends DesignToken {
$value: number
$type: 'number'
}

export interface ShadowValue {
color: string
$type: 'dropShadow'
x: number
y: number
blur: number
spread: number
}

export interface ShadowToken extends DesignToken {
$value: ShadowValue | ShadowValue[]
$type: 'boxShadow'
}

export interface SpacingToken extends DesignToken {
$value: number
$type: 'number'
}

export interface TypographyToken extends DesignToken {
$value: string | number
$type: 'fontFamilies' | 'lineHeights' | 'fontWeights' | 'fontSize'
}

// interface SemanticColorToken extends DesignToken {
// $value: string
// $type: 'color'
// }

// interface SemanticColors {
// [key: string]: {
// [shade: string]: SemanticColorToken
// }
// }

export interface PresetYouCanOptions {
prefix?: string
colorPrefix?: string
}
44 changes: 44 additions & 0 deletions packages/tokens/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { TokenObject } from './types';

export const flattenObject = (obj: TokenObject, prefix = ''): Record<string, any> => {
return Object.entries(obj).reduce((acc, [key, value]) => {
const newPrefix = prefix ? `${prefix}-${key}` : key;

if (value && typeof value === 'object' && !('$value' in value)) {
return { ...acc, ...flattenObject(value, newPrefix) };
}

if ('$value' in value) {
return { ...acc, [newPrefix]: value.$value };
}

return acc;
}, {});
};

export const resolveTokenReference = (value: string, tokens: Record<string, any>): string => {
if (typeof value !== 'string' || !value.startsWith('{') || !value.endsWith('}')) {
return value;
}

const tokenPath = value.slice(1, -1);

return tokens[tokenPath] || value;
};

export const createColorScale = (colors: Record<string, any>) => {
const semanticColors: Record<string, Record<string, string>> = {};

Object.entries(colors).forEach(([key, value]) => {
if (typeof value === 'object' && !('$type' in value)) {
semanticColors[key] = {};
Object.entries(value).forEach(([shade, colorValue]) => {
if (typeof colorValue === 'object' && '$value' in colorValue!) {
semanticColors[key][shade] = colorValue.$value as string;
}
});
}
});

return semanticColors;
};

0 comments on commit 65fd00b

Please sign in to comment.