Skip to content

Commit 360e50b

Browse files
alexcarpentertmilewski
authored andcommitted
wip
1 parent 89e7d60 commit 360e50b

File tree

4 files changed

+203
-71
lines changed

4 files changed

+203
-71
lines changed

packages/clerk-js/src/ui/customizables/parseVariables.ts

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,33 @@ import type { Theme } from '@clerk/types';
22

33
import { spaceScaleKeys } from '../foundations/sizes';
44
import type { fontSizes, fontWeights } from '../foundations/typography';
5+
import { fromEntries, removeUndefinedProps } from '../utils';
56
import {
6-
colorOptionToHslaAlphaScale,
7-
colorOptionToHslaLightnessScale,
8-
colors,
9-
fromEntries,
10-
removeUndefinedProps,
11-
} from '../utils';
7+
createAlphaScaleWithTransparentize,
8+
createColorMixLightnessScale,
9+
lighten,
10+
transparentize,
11+
} from '../utils/colorMix';
1212

1313
export const createColorScales = (theme: Theme) => {
1414
const variables = theme.variables || {};
1515

16-
const primaryScale = colorOptionToHslaLightnessScale(variables.colorPrimary, 'primary');
17-
const primaryAlphaScale = colorOptionToHslaAlphaScale(primaryScale?.primary500, 'primaryAlpha');
18-
const dangerScale = colorOptionToHslaLightnessScale(variables.colorDanger, 'danger');
19-
const dangerAlphaScale = colorOptionToHslaAlphaScale(dangerScale?.danger500, 'dangerAlpha');
20-
const successScale = colorOptionToHslaLightnessScale(variables.colorSuccess, 'success');
21-
const successAlphaScale = colorOptionToHslaAlphaScale(successScale?.success500, 'successAlpha');
22-
const warningScale = colorOptionToHslaLightnessScale(variables.colorWarning, 'warning');
23-
const warningAlphaScale = colorOptionToHslaAlphaScale(warningScale?.warning500, 'warningAlpha');
16+
const primaryScale = createColorMixLightnessScale(variables.colorPrimary, 'primary');
17+
const primaryBase = primaryScale?.primary500;
18+
const primaryAlphaScale = primaryBase ? createAlphaScaleWithTransparentize(primaryBase, 'primaryAlpha') : undefined;
19+
const dangerScale = createColorMixLightnessScale(variables.colorDanger, 'danger');
20+
const dangerBase = dangerScale?.danger500;
21+
const dangerAlphaScale = dangerBase ? createAlphaScaleWithTransparentize(dangerBase, 'dangerAlpha') : undefined;
22+
const successScale = createColorMixLightnessScale(variables.colorSuccess, 'success');
23+
const successBase = successScale?.success500;
24+
const successAlphaScale = successBase ? createAlphaScaleWithTransparentize(successBase, 'successAlpha') : undefined;
25+
const warningScale = createColorMixLightnessScale(variables.colorWarning, 'warning');
26+
const warningBase = warningScale?.warning500;
27+
const warningAlphaScale = warningBase ? createAlphaScaleWithTransparentize(warningBase, 'warningAlpha') : undefined;
28+
const neutralAlphaScales =
29+
typeof variables.colorNeutral === 'string' && variables.colorNeutral
30+
? createAlphaScaleWithTransparentize(variables.colorNeutral, 'neutralAlpha')
31+
: {};
2432

2533
return removeUndefinedProps({
2634
...primaryScale,
@@ -31,22 +39,20 @@ export const createColorScales = (theme: Theme) => {
3139
...successAlphaScale,
3240
...warningScale,
3341
...warningAlphaScale,
34-
...colorOptionToHslaAlphaScale(variables.colorNeutral, 'neutralAlpha'),
35-
primaryHover: colors.adjustForLightness(primaryScale?.primary500),
36-
colorTextOnPrimaryBackground: toHSLA(variables.colorTextOnPrimaryBackground),
37-
colorText: toHSLA(variables.colorText),
38-
colorTextSecondary: toHSLA(variables.colorTextSecondary) || colors.makeTransparent(variables.colorText, 0.35),
39-
colorInputText: toHSLA(variables.colorInputText),
40-
colorBackground: toHSLA(variables.colorBackground),
41-
colorInputBackground: toHSLA(variables.colorInputBackground),
42-
colorShimmer: toHSLA(variables.colorShimmer),
42+
...neutralAlphaScales,
43+
// TODO(Colors): We are not adjusting the lightness based on the colorPrimary lightness
44+
primaryHover: primaryBase ? lighten(primaryBase, '90%') : undefined,
45+
colorTextOnPrimaryBackground: variables.colorTextOnPrimaryBackground,
46+
colorText: variables.colorText,
47+
colorTextSecondary:
48+
variables.colorTextSecondary || (variables.colorText ? transparentize(variables.colorText, '35%') : undefined),
49+
colorInputText: variables.colorInputText,
50+
colorBackground: variables.colorBackground,
51+
colorInputBackground: variables.colorInputBackground,
52+
colorShimmer: variables.colorShimmer,
4353
});
4454
};
4555

46-
export const toHSLA = (str: string | undefined) => {
47-
return str ? colors.toHslaString(str) : undefined;
48-
};
49-
5056
export const createRadiiUnits = (theme: Theme) => {
5157
const { borderRadius } = theme.variables || {};
5258
if (borderRadius === undefined) {
Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
1-
import { colorOptionToHslaAlphaScale } from '../utils/colorOptionToHslaScale';
1+
import { createAlphaScaleWithTransparentize, transparentize } from '../utils/colorMix';
22

33
export const whiteAlpha = Object.freeze({
4-
whiteAlpha25: 'hsla(0, 0%, 100%, 0.02)',
5-
whiteAlpha50: 'hsla(0, 0%, 100%, 0.03)',
6-
whiteAlpha100: 'hsla(0, 0%, 100%, 0.07)',
7-
whiteAlpha150: 'hsla(0, 0%, 100%, 0.11)',
8-
whiteAlpha200: 'hsla(0, 0%, 100%, 0.15)',
9-
whiteAlpha300: 'hsla(0, 0%, 100%, 0.28)',
10-
whiteAlpha400: 'hsla(0, 0%, 100%, 0.41)',
11-
whiteAlpha500: 'hsla(0, 0%, 100%, 0.53)',
12-
whiteAlpha600: 'hsla(0, 0%, 100%, 0.62)',
13-
whiteAlpha700: 'hsla(0, 0%, 100%, 0.73)',
14-
whiteAlpha750: 'hsla(0, 0%, 100%, 0.78)',
15-
whiteAlpha800: 'hsla(0, 0%, 100%, 0.81)',
16-
whiteAlpha850: 'hsla(0, 0%, 100%, 0.84)',
17-
whiteAlpha900: 'hsla(0, 0%, 100%, 0.87)',
18-
whiteAlpha950: 'hsla(0, 0%, 100%, 0.92)',
4+
whiteAlpha25: transparentize('white', '2%'),
5+
whiteAlpha50: transparentize('white', '3%'),
6+
whiteAlpha100: transparentize('white', '7%'),
7+
whiteAlpha150: transparentize('white', '11%'),
8+
whiteAlpha200: transparentize('white', '15%'),
9+
whiteAlpha300: transparentize('white', '28%'),
10+
whiteAlpha400: transparentize('white', '41%'),
11+
whiteAlpha500: transparentize('white', '53%'),
12+
whiteAlpha600: transparentize('white', '62%'),
13+
whiteAlpha700: transparentize('white', '73%'),
14+
whiteAlpha750: transparentize('white', '78%'),
15+
whiteAlpha800: transparentize('white', '81%'),
16+
whiteAlpha850: transparentize('white', '84%'),
17+
whiteAlpha900: transparentize('white', '87%'),
18+
whiteAlpha950: transparentize('white', '92%'),
1919
} as const);
2020

2121
export const neutralAlpha = Object.freeze({
22-
neutralAlpha25: 'hsla(0, 0%, 0%, 0.02)',
23-
neutralAlpha50: 'hsla(0, 0%, 0%, 0.03)',
24-
neutralAlpha100: 'hsla(0, 0%, 0%, 0.07)',
25-
neutralAlpha150: 'hsla(0, 0%, 0%, 0.11)',
26-
neutralAlpha200: 'hsla(0, 0%, 0%, 0.15)',
27-
neutralAlpha300: 'hsla(0, 0%, 0%, 0.28)',
28-
neutralAlpha400: 'hsla(0, 0%, 0%, 0.41)',
29-
neutralAlpha500: 'hsla(0, 0%, 0%, 0.53)',
30-
neutralAlpha600: 'hsla(0, 0%, 0%, 0.62)',
31-
neutralAlpha700: 'hsla(0, 0%, 0%, 0.73)',
32-
neutralAlpha750: 'hsla(0, 0%, 0%, 0.78)',
33-
neutralAlpha800: 'hsla(0, 0%, 0%, 0.81)',
34-
neutralAlpha850: 'hsla(0, 0%, 0%, 0.84)',
35-
neutralAlpha900: 'hsla(0, 0%, 0%, 0.87)',
36-
neutralAlpha950: 'hsla(0, 0%, 0%, 0.92)',
22+
neutralAlpha25: transparentize('black', '2%'),
23+
neutralAlpha50: transparentize('black', '3%'),
24+
neutralAlpha100: transparentize('black', '7%'),
25+
neutralAlpha150: transparentize('black', '11%'),
26+
neutralAlpha200: transparentize('black', '15%'),
27+
neutralAlpha300: transparentize('black', '28%'),
28+
neutralAlpha400: transparentize('black', '41%'),
29+
neutralAlpha500: transparentize('black', '53%'),
30+
neutralAlpha600: transparentize('black', '62%'),
31+
neutralAlpha700: transparentize('black', '73%'),
32+
neutralAlpha750: transparentize('black', '78%'),
33+
neutralAlpha800: transparentize('black', '81%'),
34+
neutralAlpha850: transparentize('black', '84%'),
35+
neutralAlpha900: transparentize('black', '87%'),
36+
neutralAlpha950: transparentize('black', '92%'),
3737
} as const);
3838

3939
export const colors = Object.freeze({
@@ -49,7 +49,7 @@ export const colors = Object.freeze({
4949
colorTextSecondary: '#747686',
5050
colorInputText: '#131316',
5151
colorTextOnPrimaryBackground: 'white',
52-
colorShimmer: 'rgba(255, 255, 255, 0.36)',
52+
colorShimmer: transparentize('white', '36%'),
5353
transparent: 'transparent',
5454
white: 'white',
5555
black: 'black',
@@ -64,8 +64,7 @@ export const colors = Object.freeze({
6464
primary800: '#201D23',
6565
primary900: '#1B171C',
6666
primaryHover: '#3B3C45', //primary 500 adjusted for lightness
67-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68-
...colorOptionToHslaAlphaScale('#2F3037', 'primaryAlpha')!,
67+
...createAlphaScaleWithTransparentize('#2F3037', 'primaryAlpha'),
6968
danger50: '#FEF2F2',
7069
danger100: '#FEE5E5',
7170
danger200: '#FECACA',
@@ -77,8 +76,7 @@ export const colors = Object.freeze({
7776
danger800: '#991B1B',
7877
danger900: '#7F1D1D',
7978
danger950: '#450A0A',
80-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
81-
...colorOptionToHslaAlphaScale('#EF4444', 'dangerAlpha')!,
79+
...createAlphaScaleWithTransparentize('#EF4444', 'dangerAlpha'),
8280
warning50: '#FFF6ED',
8381
warning100: '#FFEBD5',
8482
warning200: '#FED1AA',
@@ -90,8 +88,7 @@ export const colors = Object.freeze({
9088
warning800: '#9A2F12',
9189
warning900: '#7C2912',
9290
warning950: '#431207',
93-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
94-
...colorOptionToHslaAlphaScale('#F36B16', 'warningAlpha')!,
91+
...createAlphaScaleWithTransparentize('#F36B16', 'warningAlpha'),
9592
success50: '#F0FDF2',
9693
success100: '#DCFCE2',
9794
success200: '#BBF7C6',
@@ -103,6 +100,5 @@ export const colors = Object.freeze({
103100
success800: '#166527',
104101
success900: '#145323',
105102
success950: '#052E0F',
106-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
107-
...colorOptionToHslaAlphaScale('#22C543', 'successAlpha')!,
103+
...createAlphaScaleWithTransparentize('#22C543', 'successAlpha'),
108104
} as const);

packages/clerk-js/src/ui/utils/colorMix.ts

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
/**
2+
* A percentage string
3+
* @example '10%'
4+
*/
5+
type Percentage = `${number}%`;
6+
17
/**
28
* Mix a color with a color tint
39
* @param color - The color to mix
410
* @param percentage - The percentage to mix the color with
511
* @param colorTint - The color to mix the color with
612
* @returns The mixed color
713
*/
8-
export function colorMix(color: string, percentage: number, colorTint: string) {
14+
export function colorMix(color: string, percentage: Percentage, colorTint: string) {
915
return `color-mix(in oklch, ${color} ${percentage}, ${colorTint})`;
1016
}
1117

@@ -15,7 +21,7 @@ export function colorMix(color: string, percentage: number, colorTint: string) {
1521
* @param percentage - The percentage to transparentize the color by
1622
* @returns The transparentized color
1723
*/
18-
export function transparentize(color: string, percentage: number) {
24+
export function transparentize(color: string, percentage: Percentage) {
1925
return colorMix(color, percentage, 'transparent');
2026
}
2127

@@ -25,7 +31,7 @@ export function transparentize(color: string, percentage: number) {
2531
* @param percentage - The percentage to lighten the color by
2632
* @returns The lightened color
2733
*/
28-
export function lighten(color: string, percentage: number) {
34+
export function lighten(color: string, percentage: Percentage) {
2935
return colorMix(color, percentage, 'white');
3036
}
3137

@@ -35,6 +41,128 @@ export function lighten(color: string, percentage: number) {
3541
* @param percentage - The percentage to darken the color by
3642
* @returns The darkened color
3743
*/
38-
export function darken(color: string, percentage: number) {
44+
export function darken(color: string, percentage: Percentage) {
3945
return colorMix(color, percentage, 'black');
4046
}
47+
48+
/**
49+
* A map of alpha shades to percentages
50+
*/
51+
const ALPHA_SHADES_MAP: Record<string, string> = {
52+
'25': '2%',
53+
'50': '3%',
54+
'100': '7%',
55+
'150': '11%',
56+
'200': '15%',
57+
'300': '28%',
58+
'400': '41%',
59+
'500': '53%',
60+
'600': '62%',
61+
'700': '73%',
62+
'750': '78%',
63+
'800': '81%',
64+
'850': '84%',
65+
'900': '87%',
66+
'950': '92%',
67+
};
68+
69+
/**
70+
* Create an alpha scale with transparentize
71+
* @param baseColor - The base color
72+
* @param prefix - The prefix for the scale
73+
* @returns The alpha scale
74+
*/
75+
export function createAlphaScaleWithTransparentize<P extends string>(
76+
baseColor: string,
77+
prefix: P,
78+
): Record<`${P}${keyof typeof ALPHA_SHADES_MAP}`, string> {
79+
const scale = {} as Record<`${P}${keyof typeof ALPHA_SHADES_MAP}`, string>;
80+
for (const shadeKey in ALPHA_SHADES_MAP) {
81+
if (Object.prototype.hasOwnProperty.call(ALPHA_SHADES_MAP, shadeKey)) {
82+
const percentage = ALPHA_SHADES_MAP[shadeKey];
83+
// @ts-expect-error - TODO: align percentage type
84+
scale[`${prefix}${shadeKey}`] = transparentize(baseColor, percentage);
85+
}
86+
}
87+
return scale;
88+
}
89+
90+
const LIGHTNESS_SHADES_DEF: Record<string, { type: 'lighten' | 'darken' | 'base'; amount: Percentage }> = {
91+
'25': { type: 'lighten', amount: '92%' },
92+
'50': { type: 'lighten', amount: '85%' },
93+
'100': { type: 'lighten', amount: '70%' },
94+
'150': { type: 'lighten', amount: '55%' },
95+
'200': { type: 'lighten', amount: '40%' },
96+
'300': { type: 'lighten', amount: '25%' },
97+
'400': { type: 'lighten', amount: '10%' },
98+
'500': { type: 'base', amount: '0%' },
99+
'600': { type: 'darken', amount: '10%' },
100+
'700': { type: 'darken', amount: '25%' },
101+
'750': { type: 'darken', amount: '40%' },
102+
'800': { type: 'darken', amount: '55%' },
103+
'850': { type: 'darken', amount: '70%' },
104+
'900': { type: 'darken', amount: '85%' },
105+
'950': { type: 'darken', amount: '92%' },
106+
};
107+
108+
const ALL_LIGHTNESS_SHADE_KEYS = Object.keys(LIGHTNESS_SHADES_DEF);
109+
110+
/**
111+
* Creates a full lightness color scale (shades 25-950) using color-mix lighten/darken.
112+
* @param colorOption The base color string (used as 500 shade) or a partial scale object.
113+
* @param prefix The prefix for the scale keys (e.g., 'primary').
114+
* @returns A prefixed color scale object, or undefined if colorOption is undefined.
115+
*/
116+
export function createColorMixLightnessScale<P extends string>(
117+
colorOption: string | Partial<Record<keyof typeof LIGHTNESS_SHADES_DEF, string>> | undefined,
118+
prefix: P,
119+
): Record<`${P}${keyof typeof LIGHTNESS_SHADES_DEF}`, string> | undefined {
120+
if (!colorOption) {
121+
return undefined;
122+
}
123+
124+
let baseFor500: string;
125+
const userProvidedShades: Partial<Record<keyof typeof LIGHTNESS_SHADES_DEF, string>> = {};
126+
127+
if (typeof colorOption === 'string') {
128+
baseFor500 = colorOption;
129+
} else {
130+
if (!colorOption['500']) {
131+
throw new Error(
132+
`Color scale generation for prefix '${prefix}' failed: The '500' shade is required in the colorOption object.`,
133+
);
134+
}
135+
baseFor500 = colorOption['500'];
136+
for (const key of ALL_LIGHTNESS_SHADE_KEYS) {
137+
if (colorOption[key]) {
138+
userProvidedShades[key] = colorOption[key];
139+
}
140+
}
141+
}
142+
143+
const generatedScale: Partial<Record<keyof typeof LIGHTNESS_SHADES_DEF, string>> = {};
144+
for (const shadeKey of ALL_LIGHTNESS_SHADE_KEYS) {
145+
const definition = LIGHTNESS_SHADES_DEF[shadeKey];
146+
switch (definition.type) {
147+
case 'base':
148+
generatedScale[shadeKey] = baseFor500;
149+
break;
150+
case 'lighten':
151+
generatedScale[shadeKey] = lighten(baseFor500, definition.amount);
152+
break;
153+
case 'darken':
154+
generatedScale[shadeKey] = darken(baseFor500, definition.amount);
155+
break;
156+
}
157+
}
158+
159+
const mergedScale = { ...generatedScale, ...userProvidedShades };
160+
161+
const resultScale = {} as Record<`${P}${keyof typeof LIGHTNESS_SHADES_DEF}`, string>;
162+
for (const key of ALL_LIGHTNESS_SHADE_KEYS) {
163+
if (mergedScale[key]) {
164+
resultScale[`${prefix}${key}`] = mergedScale[key];
165+
}
166+
}
167+
return resultScale;
168+
}

packages/clerk-js/src/ui/utils/colors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ const hslaColorToHslaString = ({ h, s, l, a }: HslaColor): HslaColorString => {
255255
};
256256

257257
const parse = (str: string): ParsedResult => {
258+
// TODO(Colors): This is a temporary fix to allow for custom colors to be passed in as variables
259+
return str;
258260
const prefix = str.substr(0, 3).toLowerCase();
259261
let res;
260262
if (prefix === 'hsl') {

0 commit comments

Comments
 (0)