Skip to content

Commit aad8052

Browse files
authored
feat(clerk-js,themes): Introduce internal polished theme and a clerk theme that enables opt out (clerk#2753)
1 parent 0ee1777 commit aad8052

File tree

11 files changed

+99
-34
lines changed

11 files changed

+99
-34
lines changed

.changeset/quick-walls-share.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ describe('AppearanceProvider element flows', () => {
176176
);
177177

178178
const { result } = renderHook(() => useAppearance(), { wrapper });
179-
expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(themeAColor);
179+
//polished theme is index 0
180+
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(themeAColor);
180181
});
181182

182183
it('sets the parsedElements correctly from the globalAppearance and appearance prop', () => {
@@ -199,8 +200,9 @@ describe('AppearanceProvider element flows', () => {
199200
);
200201

201202
const { result } = renderHook(() => useAppearance(), { wrapper });
202-
expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(themeAColor);
203-
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(themeBColor);
203+
//polished theme is index 0
204+
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(themeAColor);
205+
expect(result.current.parsedElements[2]['alert'].backgroundColor).toBe(themeBColor);
204206
});
205207

206208
it('sets the parsedElements correctly when a function is passed for the elements', () => {
@@ -221,7 +223,8 @@ describe('AppearanceProvider element flows', () => {
221223
);
222224

223225
const { result } = renderHook(() => useAppearance(), { wrapper });
224-
expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(knownColors[themeAColor]);
226+
//polished theme is index 0
227+
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(knownColors[themeAColor]);
225228
});
226229
});
227230

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

+13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Appearance, DeepPartial, Elements, Layout, Theme } from '@clerk/types';
22

33
import { createInternalTheme, defaultInternalTheme } from '../foundations';
4+
import { polishedAppearance } from '../polishedAppearance';
45
import type { InternalTheme } from '../styledSystem';
56
import { fastDeepMergeAndReplace } from '../utils';
67
import {
@@ -60,6 +61,16 @@ export const parseAppearance = (cascade: AppearanceCascade): ParsedAppearance =>
6061

6162
const parsedInternalTheme = parseVariables(appearanceList);
6263
const parsedLayout = parseLayout(appearanceList);
64+
65+
if (
66+
!appearanceList.find(a => {
67+
//@ts-expect-error not public api
68+
return !!a.simpleStyles;
69+
})
70+
) {
71+
appearanceList.unshift(polishedAppearance);
72+
}
73+
6374
const parsedElements = parseElements(
6475
appearanceList.map(appearance => {
6576
if (!appearance.elements || typeof appearance.elements !== 'function') {
@@ -77,9 +88,11 @@ const expand = (theme: Theme | undefined, cascade: any[]) => {
7788
if (!theme) {
7889
return;
7990
}
91+
8092
(Array.isArray(theme.baseTheme) ? theme.baseTheme : [theme.baseTheme]).forEach(baseTheme =>
8193
expand(baseTheme as Theme, cascade),
8294
);
95+
8396
cascade.push(theme);
8497
};
8598

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ export const createColorScales = (theme: Theme) => {
1818
const colorTextTertiary = toHSLA(variables.colorTextTertiary) || colors.makeTransparent(colorTextSecondary, 0.4);
1919
const colorTextLabel = toHSLA(variables.colorTextLabel) || colors.makeTransparent(variables.colorText, 0.05);
2020

21+
const primaryScale = colorOptionToHslaLightnessScale(variables.colorPrimary, 'primary');
22+
2123
return removeUndefinedProps({
22-
...colorOptionToHslaLightnessScale(variables.colorPrimary, 'primary'),
24+
...primaryScale,
2325
...colorOptionToHslaAlphaScale(variables.colorPrimary, 'primaryAlpha'),
2426
...colorOptionToHslaLightnessScale(variables.colorDanger, 'danger'),
2527
...colorOptionToHslaAlphaScale(variables.colorDanger, 'dangerAlpha'),
@@ -28,6 +30,7 @@ export const createColorScales = (theme: Theme) => {
2830
...colorOptionToHslaLightnessScale(variables.colorWarning, 'warning'),
2931
...colorOptionToHslaAlphaScale(variables.colorWarning, 'warningAlpha'),
3032
...colorOptionToHslaAlphaScale(variables.colorNeutral, 'neutralAlpha'),
33+
primaryHover: colors.adjustForLightness(primaryScale?.primary500),
3134
colorTextOnPrimaryBackground: toHSLA(variables.colorTextOnPrimaryBackground),
3235
colorText: toHSLA(variables.colorText),
3336
colorTextSecondary,

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

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const colors = Object.freeze({
6767
primary700: '#25232A',
6868
primary800: '#201D23',
6969
primary900: '#1B171C',
70+
primaryHover: '#3B3C45', //primary 500 adjusted for lightness
7071
...colorOptionToHslaAlphaScale('#2F3037', 'primaryAlpha')!,
7172
danger50: '#FEF2F2',
7273
danger100: '#FEE5E5',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Appearance } from '@clerk/types';
2+
3+
import type { InternalTheme } from './foundations';
4+
5+
export const polishedAppearance = {
6+
elements: ({ theme }: { theme: InternalTheme }) => {
7+
return {
8+
button: {
9+
border: 0,
10+
'&[data-variant="solid"]': {
11+
'&:hover': null,
12+
'&:focus': null,
13+
'&:after': {
14+
position: 'absolute',
15+
content: '""',
16+
borderRadius: 'inherit',
17+
zIndex: -1,
18+
inset: 0,
19+
opacity: 1,
20+
transitionProperty: theme.transitionProperty.$common,
21+
transitionDuration: theme.transitionDuration.$controls,
22+
background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha150} 0%, ${theme.colors.$transparent} 100%)`,
23+
},
24+
'&:hover::after': {
25+
opacity: 0,
26+
},
27+
'&:active::after': {
28+
opacity: 1,
29+
},
30+
boxShadow: `0px 0px 0px 1px ${theme.colors.$primary500}, 0px 1px 1px 0px rgba(255, 255, 255, 0.07) inset, 0px 2px 3px 0px rgba(34, 42, 53, 0.20), 0px 1px 1px 0px rgba(0, 0, 0, 0.24)`,
31+
},
32+
'&[data-variant="outline"]': {
33+
boxShadow:
34+
'0px 2px 3px -1px rgba(0, 0, 0, 0.08), 0px 1px 0px 0px rgba(0, 0, 0, 0.02), 0px 0px 0px 1px rgba(0, 0, 0, 0.08)',
35+
},
36+
},
37+
};
38+
},
39+
} satisfies Appearance;

packages/clerk-js/src/ui/primitives/Button.tsx

+15-23
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { common, createCssVariables, createVariants } from '../styledSystem';
88
import { applyDataStateProps } from './applyDataStateProps';
99
import { Flex } from './Flex';
1010

11-
const vars = createCssVariables('accent', 'accentDark', 'accentContrast', 'alpha', 'border');
11+
const vars = createCssVariables('accent', 'accentHover', 'accentContrast', 'alpha', 'border');
1212

1313
const { applyVariants, filterProps } = createVariants(
1414
(theme, props: OwnProps & { colorScheme?: 'primary' | 'neutral' | 'danger' }) => {
@@ -49,21 +49,21 @@ const { applyVariants, filterProps } = createVariants(
4949
colorScheme: {
5050
primary: {
5151
[vars.accent]: theme.colors.$primary500,
52-
[vars.accentDark]: theme.colors.$primary600,
52+
[vars.accentHover]: theme.colors.$primaryHover,
5353
[vars.border]: theme.colors.$primary500,
5454
[vars.accentContrast]: theme.colors.$colorTextOnPrimaryBackground,
5555
[vars.alpha]: theme.colors.$neutralAlpha50,
5656
},
5757
neutral: {
5858
[vars.accent]: theme.colors.$neutralAlpha600,
59-
[vars.accentDark]: theme.colors.$neutralAlpha700,
59+
[vars.accentHover]: theme.colors.$neutralAlpha700,
6060
[vars.border]: theme.colors.$neutralAlpha200,
6161
[vars.accentContrast]: theme.colors.$white,
6262
[vars.alpha]: theme.colors.$neutralAlpha50,
6363
},
6464
danger: {
6565
[vars.accent]: theme.colors.$danger500,
66-
[vars.accentDark]: theme.colors.$danger600,
66+
[vars.accentHover]: theme.colors.$danger600,
6767
[vars.accentContrast]: theme.colors.$white,
6868
[vars.border]: theme.colors.$danger500,
6969
[vars.alpha]: theme.colors.$dangerAlpha50,
@@ -76,23 +76,14 @@ const { applyVariants, filterProps } = createVariants(
7676
boxShadow: theme.shadows.$buttonShadow,
7777
border: theme.borders.$normal,
7878
borderColor: vars.accent,
79-
':after': {
80-
position: 'absolute',
81-
content: '""',
82-
borderRadius: 'inherit',
83-
zIndex: -1,
84-
inset: 0,
85-
opacity: 1,
86-
transitionProperty: theme.transitionProperty.$common,
87-
transitionDuration: theme.transitionDuration.$controls,
88-
background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha150} 0%, ${theme.colors.$transparent} 100%)`,
89-
},
90-
':hover::after': {
91-
opacity: 0,
92-
},
93-
':active::after': {
94-
opacity: 1,
79+
'&:hover': {
80+
backgroundColor: vars.accentHover,
9581
},
82+
'&:focus': props.hoverAsFocus
83+
? {
84+
backgroundColor: vars.accentHover,
85+
}
86+
: undefined,
9687
},
9788
outline: {
9889
border: theme.borders.$normal,
@@ -104,8 +95,8 @@ const { applyVariants, filterProps } = createVariants(
10495
},
10596
ghost: {
10697
color: vars.accent,
107-
'&:hover': { backgroundColor: vars.alpha, color: vars.accentDark },
108-
'&:focus': props.hoverAsFocus ? { backgroundColor: vars.alpha, color: vars.accentDark } : undefined,
98+
'&:hover': { backgroundColor: vars.alpha, color: vars.accentHover },
99+
'&:focus': props.hoverAsFocus ? { backgroundColor: vars.alpha, color: vars.accentHover } : undefined,
109100
},
110101
link: {
111102
minHeight: 'fit-content',
@@ -259,7 +250,8 @@ const SimpleButton = React.forwardRef<HTMLButtonElement, ButtonProps>((props, re
259250
onClick={onClick}
260251
css={applyVariants(parsedProps) as any}
261252
disabled={isDisabled}
262-
data-variant={props.variant || 'primary'}
253+
data-variant={props.variant || 'solid'}
254+
data-color={props.colorScheme || 'primary'}
263255
ref={ref}
264256
>
265257
{children}

packages/clerk-js/src/ui/primitives/Input.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ export type InputProps = PrimitiveProps<'input'> & StyleVariants<typeof applyVar
4444
export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
4545
const fieldControl = useFormField() || {};
4646
// @ts-expect-error Typescript is complaining that `errorMessageId` does not exist. We are clearly passing them from above.
47-
const { errorMessageId, ignorePasswordManager, ...fieldControlProps } = sanitizeInputProps(fieldControl, [
48-
'errorMessageId',
49-
'ignorePasswordManager',
50-
]);
47+
const { errorMessageId, ignorePasswordManager, feedbackType, ...fieldControlProps } = sanitizeInputProps(
48+
fieldControl,
49+
['errorMessageId', 'ignorePasswordManager', 'feedbackType'],
50+
);
5151

5252
const propsWithoutVariants = filterProps({
5353
...props,
@@ -90,6 +90,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref)
9090
aria-describedby={errorMessageId}
9191
aria-required={_required}
9292
aria-disabled={_disabled}
93+
data-feedback={feedbackType}
9394
css={applyVariants(propsWithoutVariants)}
9495
/>
9596
);

packages/themes/src/themes/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './dark';
22
export * from './shadesOfPurple';
33
export * from './neobrutalism';
4+
export * from './reset';

packages/themes/src/themes/reset.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { experimental_createTheme } from '../createTheme';
2+
3+
export const reset = experimental_createTheme({
4+
//@ts-expect-error not public api
5+
simpleStyles: true,
6+
});

playground/nextjs/pages/_app.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
SignOutButton,
1111
UserButton,
1212
} from '@clerk/nextjs';
13-
import { dark, neobrutalism, shadesOfPurple } from '@clerk/themes';
13+
import { dark, neobrutalism, reset, shadesOfPurple } from '@clerk/themes';
1414
import Link from 'next/link';
1515
import React, { FunctionComponent, useEffect, useState } from 'react';
1616

@@ -19,6 +19,7 @@ const themes = { default: undefined, dark, neobrutalism, shadesOfPurple };
1919
function MyApp({ Component, pageProps }: AppProps) {
2020
const [selectedTheme, setSelectedTheme] = useState<keyof typeof themes>('default');
2121
const [selectedSmoothing, setSelectedSmoothing] = useState<boolean>(true);
22+
const [styleReset, setStyleReset] = useState<boolean>(false);
2223
const [primaryColor, setPrimaryColor] = useState<string | undefined>(undefined);
2324

2425
const onToggleDark = () => {
@@ -50,7 +51,7 @@ function MyApp({ Component, pageProps }: AppProps) {
5051
return (
5152
<ClerkProvider
5253
appearance={{
53-
baseTheme: themes[selectedTheme],
54+
baseTheme: styleReset ? [reset, themes[selectedTheme]] : themes[selectedTheme],
5455
variables: {
5556
colorPrimary:primaryColor,
5657
},
@@ -66,6 +67,7 @@ function MyApp({ Component, pageProps }: AppProps) {
6667
onChangeTheme={e => setSelectedTheme(e.target.value as any)}
6768
onToggleDark={onToggleDark}
6869
onToggleSmooth={onToggleSmooth}
70+
onResetStyles={() => setStyleReset((s) => !s)}
6971
smooth={selectedSmoothing}
7072
onPrimaryColorChange={setPrimaryColor}
7173
/>
@@ -78,6 +80,7 @@ type AppBarProps = {
7880
onChangeTheme: React.ChangeEventHandler<HTMLSelectElement>;
7981
onToggleDark: React.MouseEventHandler<HTMLButtonElement>;
8082
onToggleSmooth: React.MouseEventHandler<HTMLButtonElement>;
83+
onResetStyles: React.MouseEventHandler<HTMLButtonElement>;
8184
smooth: boolean;
8285
onPrimaryColorChange: (primaryColor: string | undefined) => void;
8386
};
@@ -118,6 +121,7 @@ const AppBar = (props: AppBarProps) => {
118121
</select>
119122
<button onClick={props.onToggleDark}>toggle dark mode</button>
120123
<button onClick={props.onToggleSmooth}>font-smoothing: {props.smooth ? 'On' : 'Off'}</button>
124+
<button onClick={props.onResetStyles} style={{position:'absolute', left: '10px', bottom:'10px'}}>simple styles</button>
121125
<input type='color' onChange={(e) => props.onPrimaryColorChange(e.target.value)}/>
122126
<UserButton />
123127

0 commit comments

Comments
 (0)