Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Colors codemod types #396

Merged
merged 13 commits into from
Jan 15, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Colors } from '@freenow/wave';

export interface LabelVariant {
color?: Colors
backgroundColor?: Colors
borderColor?: Colors
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ReadBareCssColorVariable } from '@freenow/wave';

export interface LabelVariant {
color?: ReadBareCssColorVariable
martimalek marked this conversation as resolved.
Show resolved Hide resolved
backgroundColor?: ReadBareCssColorVariable
borderColor?: ReadBareCssColorVariable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Button } from '@freenow/wave';
import React from 'react';

export const ButtonTest = (): JSX.Element => (
<Button mr="1" mt="3" disabled>
Clone
</Button>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Button } from '@freenow/wave';
import React from 'react';

export const ButtonTest = (): JSX.Element => (
<Button mr="1" mt="3" disabled>
Clone
</Button>
);
4 changes: 3 additions & 1 deletion src/codemods/__tests__/colors-to-css-vars-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ const tests = [
'color-in-JSX-single-import',
'template-multi-quasis-even',
'template-multi-quasis-odd',
'template-single-quasis'
'template-single-quasis',
'color-as-type',
'no-colors-usage'
];

describe('colors-to-css-vars', () => {
Expand Down
37 changes: 30 additions & 7 deletions src/codemods/colors-to-css-vars.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { API, FileInfo, Identifier, JSCodeshift, TemplateLiteral } from 'jscodeshift';
import { API, FileInfo, Identifier, ImportDeclaration, JSCodeshift, TemplateLiteral } from 'jscodeshift';
import { Options } from 'recast';

const ColorsToCssVariablesMap = {
Expand Down Expand Up @@ -35,6 +35,8 @@ const ColorsToCssVariablesMap = {
NEGATIVE_ORANGE_50: 'var(--wave-b-color-orange-50)'
};

const CSS_COLORS_TYPE_NAME = 'ReadBareCssColorVariable';

const replaceColorsForCssVarsInTemplateLiterals = (
j: JSCodeshift,
localColorNames: string[],
Expand Down Expand Up @@ -117,6 +119,9 @@ export default (file: FileInfo, api: API, options: Options) => {
// Find Colors named imports in @freenow/wave imports
const colorsImports = waveNamedImports.filter(path => path.node.imported.name === 'Colors');

// Early return in case colors is not used
if (colorsImports.size() === 0) return ast.toSource(printOptions);
martimalek marked this conversation as resolved.
Show resolved Hide resolved

// Get the local Colors import names
colorsImports.forEach(spec => {
if (spec.node.local?.name) localColorNames.push(spec.node.local.name);
Expand All @@ -129,7 +134,7 @@ export default (file: FileInfo, api: API, options: Options) => {
replaceColorsForCssVarsInTemplateLiterals(j, localColorNames, templateLiteral);
});

// Find all remaining Colors usage
// Find all remaining Colors member usage (e.g. Colors.x)
ast.find(j.MemberExpression, {
object: {
name: (colorName: string) => localColorNames.includes(colorName)
Expand All @@ -146,13 +151,31 @@ export default (file: FileInfo, api: API, options: Options) => {
ex.replace(cssVarStringNode);
});

// If it is the only named import from wave, remove the whole Wave import
if (waveImports.size() === 1 && waveNamedImports.size() === 1 && colorsImports.size() === 1) {
// Find usages of Colors as a type
const usagesAsTypes = ast.find(j.TSTypeReference, {
typeName: {
name: (colorName: string) => localColorNames.includes(colorName)
}
});

usagesAsTypes.forEach(type => {
// Replace the usage of Colors as a type for the corresponding type name
const cssColorTypeReference = j.tsTypeReference(j.identifier(CSS_COLORS_TYPE_NAME));
type.replace(cssColorTypeReference);
});

// Remove the Colors import
colorsImports.remove();

// If Colors is the only named import from wave, remove the whole Wave import
if (usagesAsTypes.size() === 0 && waveImports.size() === 1 && waveNamedImports.size() === 1) {
waveImports.remove();
}

// If there are other named imports from wave, remove only the Colors named import
} else if (waveNamedImports.size() > 1) {
colorsImports.remove();
// If Colors is used as a type add the import for the new css colors type
if (usagesAsTypes.size() > 0) {
const importDeclaration: ImportDeclaration = waveImports.get(0).node;
importDeclaration.specifiers.push(j.importSpecifier(j.identifier(CSS_COLORS_TYPE_NAME)));
}

return ast.toSource(printOptions);
Expand Down
4 changes: 2 additions & 2 deletions src/components/InlineSpinner/InlineSpinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import * as React from 'react';
import styled, { keyframes } from 'styled-components';
import { compose, margin, MarginProps, ResponsiveValue, variant } from 'styled-system';
import { getSemanticValue } from '../../utils/cssVariables';
import { ReadCssColorVariable } from '../../essentials/Colors/types';
import { ReadSemanticCssColorVariable } from '../../essentials/Colors/types';

interface InlineSpinnerProps extends MarginProps {
/**
* Override the color of the spinner
*/
// the below is the hack to keep autocomplete showing semantic variables but allowing any string as well
// eslint-disable-next-line @typescript-eslint/ban-types
color?: ReadCssColorVariable | (string & {});
color?: ReadSemanticCssColorVariable | (string & {});
/**
* Set the size of the component
*/
Expand Down
12 changes: 6 additions & 6 deletions src/essentials/Colors/Colors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SemanticColorsSchema } from './types';
import { BareColorsSchema, SemanticColorsSchema } from './types';

// Bare Colors Tier (--wave-b-color-...)
export const Colors = {
Expand Down Expand Up @@ -51,7 +51,7 @@ export const Colors = {
50: 'hsl(21, 100%, 97%)'
},
transparent: 'transparent'
} as const;
} satisfies BareColorsSchema;

// AUTHENTIC = primary now
// ACTION = secondary now
Expand Down Expand Up @@ -108,7 +108,7 @@ export const SemanticColors = {
element: {
disabled: {
faded: Colors.blue.primary[50],
default: Colors.blue.primary[200],
default: Colors.blue.primary[200]
},
primary: {
default: Colors.blue.primary[900],
Expand All @@ -131,7 +131,7 @@ export const SemanticColors = {
default: Colors.green[50]
},
warning: {
default: Colors.yellow[50],
default: Colors.yellow[50]
},
danger: {
faded: Colors.orange[50],
Expand Down Expand Up @@ -179,7 +179,7 @@ export const SemanticColors = {
},
accent: {
faded: Colors.blue.secondary[350],
default: Colors.blue.secondary[900],
default: Colors.blue.secondary[900]
},
focus: Colors.blue.secondary[900],
disabled: Colors.blue.primary[200],
Expand Down Expand Up @@ -285,7 +285,7 @@ export const SemanticColorsDarkSchema = {
emphasized: Colors.white
},
success: {
default: Colors.green[50],
default: Colors.green[50]
},
warning: {
default: Colors.yellow[50]
Expand Down
79 changes: 74 additions & 5 deletions src/essentials/Colors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,58 @@ export type HSL = `hsl(${number}, ${number}%, ${number}%)` | `hsla(${number}, ${

export type Color = HSL | 'transparent';

export type BareColorsSchema = {
martimalek marked this conversation as resolved.
Show resolved Hide resolved
white: HSL;
black: HSL;
blue: {
primary: {
'1100': HSL;
'900': HSL;
'750': HSL;
'550': HSL;
'350': HSL;
'200': HSL;
'50': HSL;
};
secondary: {
'1000': HSL;
'900': HSL;
'350': HSL;
'150': HSL;
'100': HSL;
'50': HSL;
};
};
red: {
'1000': HSL;
'900': HSL;
};
magenta: {
'1000': HSL;
'900': HSL;
'350': HSL;
'50': HSL;
};
green: {
'1000': HSL;
'900': HSL;
'350': HSL;
'50': HSL;
};
yellow: {
'900': HSL;
'350': HSL;
'50': HSL;
};
orange: {
'1000': HSL;
'900': HSL;
'350': HSL;
'50': HSL;
};
transparent: 'transparent';
};

export type SemanticColorsSchema = {
transparent: 'transparent';
white: Color;
Expand Down Expand Up @@ -163,20 +215,37 @@ export type SemanticColorsSchema = {
};
};

// Bare Colors
type BareColorToken = Join<Leaves<BareColorsSchema>, '-'>;
type BareColorTokenCssVariable = `--wave-b-color-${BareColorToken}`;

type BareHslComponentsToken = `${BareColorToken}-hsl`;
type BareHslComponentsTokenCssVariable = `--wave-b-color-${BareHslComponentsToken}`;

export type BareColorCssVariable = BareColorTokenCssVariable | BareHslComponentsTokenCssVariable;

export type ReadBareCssColorVariable =
| `var(${BareColorTokenCssVariable})`
| `var(${BareColorTokenCssVariable}, ${string})`;
export type ReadBareCssVariable = `var(${BareColorCssVariable})` | `var(${BareColorCssVariable}, ${string})`;

// Semantic Colors
type SemanticColorToken = Join<Leaves<SemanticColorsSchema>, '-'>;
type SemanticColorTokenCssVariable = `--wave-s-color-${SemanticColorToken}`;

type SemanticHslComponentsToken = `${SemanticColorToken}-hsl`;
type HslComponentsTokenCssVariable = `--wave-s-color-${SemanticHslComponentsToken}`;
type SemanticHslComponentsTokenCssVariable = `--wave-s-color-${SemanticHslComponentsToken}`;

export type SemanticToken = SemanticColorToken | SemanticHslComponentsToken;
export type SemanticColorCssVariable = SemanticColorTokenCssVariable | HslComponentsTokenCssVariable;
export type SemanticColorCssVariable = SemanticColorTokenCssVariable | SemanticHslComponentsTokenCssVariable;

export type ReadCssColorVariable =
export type ReadSemanticCssColorVariable =
| `var(${SemanticColorTokenCssVariable})`
| `var(${SemanticColorTokenCssVariable}, ${string})`;
export type ReadCssVariable = `var(${SemanticColorCssVariable})` | `var(${SemanticColorCssVariable}, ${string})`;
export type ReadSemanticCssVariable =
| `var(${SemanticColorCssVariable})`
| `var(${SemanticColorCssVariable}, ${string})`;

export type ComponentSemanticTokens = {
[key: string]: ReadCssVariable | ComponentSemanticTokens;
[key: string]: ReadSemanticCssVariable | ComponentSemanticTokens;
};
6 changes: 6 additions & 0 deletions src/essentials/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export { Elevation } from './Elevation/Elevation';
export { Spaces } from './Spaces/Spaces';
export { Breakpoints, MediaQueries } from './Breakpoints/Breakpoints';
export type {
ReadBareCssColorVariable,
ReadBareCssVariable,
ReadSemanticCssColorVariable,
ReadSemanticCssVariable
} from './Colors/types';
4 changes: 2 additions & 2 deletions src/icons/IconProps.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ComponentPropsWithoutRef } from 'react';
import { ReadCssColorVariable } from '../essentials/Colors/types';
import { ReadSemanticCssColorVariable } from '../essentials/Colors/types';

export interface IconProps extends ComponentPropsWithoutRef<'svg'> {
/**
* Overrides the default color of the icon.
*/
color?: ReadCssColorVariable | 'inherit' | (string & {});
color?: ReadSemanticCssColorVariable | 'inherit' | (string & {});
/**
* Adjusts the size of the icon with defaults or the size in pixels.
*/
Expand Down
12 changes: 8 additions & 4 deletions src/utils/cssVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// 3. Apply the design system prefix to the variable names.
// 4. Concatenate entries to a valid CSS variables declaration.

import { ReadCssVariable, SemanticToken } from '../essentials/Colors/types';
import { ReadSemanticCssVariable, SemanticToken } from '../essentials/Colors/types';

export const DS_PREFIX = 'wave';

Expand Down Expand Up @@ -78,8 +78,11 @@ const generateHslComponentsCssVariableEntries = (
})
.filter(entry => entry !== undefined);

export const applyPrefix = <T extends string>(variableName: T, tier: 'b' | 's' | 'l', namespace: 'color' = 'color'): string =>
`--${DS_PREFIX}-${tier}-${namespace}-${variableName}`;
export const applyPrefix = <T extends string>(
variableName: T,
tier: 'b' | 's' | 'l',
namespace: 'color' = 'color'
): string => `--${DS_PREFIX}-${tier}-${namespace}-${variableName}`;

export const generateCssVariables = (tokens: TokenObject, tier: 'b' | 's'): ReadonlyArray<string> => {
const entries = generateCssVariableEntries(tokens);
Expand All @@ -96,4 +99,5 @@ export const generateBareTierCssVariables = (tokens: TokenObject): ReadonlyArray
export const generateSemanticTierCssVariables = (tokens: TokenObject): ReadonlyArray<string> =>
generateCssVariables(tokens, 's');

export const getSemanticValue = (token: SemanticToken): ReadCssVariable => `var(--${DS_PREFIX}-s-color-${token})`;
export const getSemanticValue = (token: SemanticToken): ReadSemanticCssVariable =>
`var(--${DS_PREFIX}-s-color-${token})`;