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

feat(clerk-js,themes): Introduce internal polished theme and a clerk theme that enables opt out #2753

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/quick-walls-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ describe('AppearanceProvider element flows', () => {
);

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

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

const { result } = renderHook(() => useAppearance(), { wrapper });
expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(themeAColor);
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(themeBColor);
//polished theme is index 0
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(themeAColor);
expect(result.current.parsedElements[2]['alert'].backgroundColor).toBe(themeBColor);
});

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

const { result } = renderHook(() => useAppearance(), { wrapper });
expect(result.current.parsedElements[0]['alert'].backgroundColor).toBe(knownColors[themeAColor]);
//polished theme is index 0
expect(result.current.parsedElements[1]['alert'].backgroundColor).toBe(knownColors[themeAColor]);
});
});

Expand Down
13 changes: 13 additions & 0 deletions packages/clerk-js/src/ui/customizables/parseAppearance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Appearance, DeepPartial, Elements, Layout, Theme } from '@clerk/types';

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

const parsedInternalTheme = parseVariables(appearanceList);
const parsedLayout = parseLayout(appearanceList);

if (
!appearanceList.find(a => {
//@ts-expect-error not public api
return !!a.simpleStyles;
})
) {
appearanceList.unshift(polishedAppearance);
}

const parsedElements = parseElements(
appearanceList.map(appearance => {
if (!appearance.elements || typeof appearance.elements !== 'function') {
Expand All @@ -77,9 +88,11 @@ const expand = (theme: Theme | undefined, cascade: any[]) => {
if (!theme) {
return;
}

(Array.isArray(theme.baseTheme) ? theme.baseTheme : [theme.baseTheme]).forEach(baseTheme =>
expand(baseTheme as Theme, cascade),
);

cascade.push(theme);
};

Expand Down
5 changes: 4 additions & 1 deletion packages/clerk-js/src/ui/customizables/parseVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ export const createColorScales = (theme: Theme) => {
const colorTextTertiary = toHSLA(variables.colorTextTertiary) || colors.makeTransparent(colorTextSecondary, 0.4);
const colorTextLabel = toHSLA(variables.colorTextLabel) || colors.makeTransparent(variables.colorText, 0.05);

const primaryScale = colorOptionToHslaLightnessScale(variables.colorPrimary, 'primary');

return removeUndefinedProps({
...colorOptionToHslaLightnessScale(variables.colorPrimary, 'primary'),
...primaryScale,
...colorOptionToHslaAlphaScale(variables.colorPrimary, 'primaryAlpha'),
...colorOptionToHslaLightnessScale(variables.colorDanger, 'danger'),
...colorOptionToHslaAlphaScale(variables.colorDanger, 'dangerAlpha'),
Expand All @@ -28,6 +30,7 @@ export const createColorScales = (theme: Theme) => {
...colorOptionToHslaLightnessScale(variables.colorWarning, 'warning'),
...colorOptionToHslaAlphaScale(variables.colorWarning, 'warningAlpha'),
...colorOptionToHslaAlphaScale(variables.colorNeutral, 'neutralAlpha'),
primaryHover: colors.adjustForLightness(primaryScale?.primary500),
colorTextOnPrimaryBackground: toHSLA(variables.colorTextOnPrimaryBackground),
colorText: toHSLA(variables.colorText),
colorTextSecondary,
Expand Down
1 change: 1 addition & 0 deletions packages/clerk-js/src/ui/foundations/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const colors = Object.freeze({
primary700: '#25232A',
primary800: '#201D23',
primary900: '#1B171C',
primaryHover: '#3B3C45', //primary 500 adjusted for lightness
...colorOptionToHslaAlphaScale('#2F3037', 'primaryAlpha')!,
danger50: '#FEF2F2',
danger100: '#FEE5E5',
Expand Down
39 changes: 39 additions & 0 deletions packages/clerk-js/src/ui/polishedAppearance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Appearance } from '@clerk/types';

import type { InternalTheme } from './foundations';

export const polishedAppearance = {
elements: ({ theme }: { theme: InternalTheme }) => {
return {
button: {
border: 0,
'&[data-variant="solid"]': {
'&:hover': null,
'&:focus': null,
'&:after': {
position: 'absolute',
content: '""',
borderRadius: 'inherit',
zIndex: -1,
inset: 0,
opacity: 1,
transitionProperty: theme.transitionProperty.$common,
transitionDuration: theme.transitionDuration.$controls,
background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha150} 0%, ${theme.colors.$transparent} 100%)`,
},
'&:hover::after': {
opacity: 0,
},
'&:active::after': {
opacity: 1,
},
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)`,
},
'&[data-variant="outline"]': {
boxShadow:
'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)',
},
},
};
},
} satisfies Appearance;
38 changes: 15 additions & 23 deletions packages/clerk-js/src/ui/primitives/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { common, createCssVariables, createVariants } from '../styledSystem';
import { applyDataStateProps } from './applyDataStateProps';
import { Flex } from './Flex';

const vars = createCssVariables('accent', 'accentDark', 'accentContrast', 'alpha', 'border');
const vars = createCssVariables('accent', 'accentHover', 'accentContrast', 'alpha', 'border');

const { applyVariants, filterProps } = createVariants(
(theme, props: OwnProps & { colorScheme?: 'primary' | 'neutral' | 'danger' }) => {
Expand Down Expand Up @@ -49,21 +49,21 @@ const { applyVariants, filterProps } = createVariants(
colorScheme: {
primary: {
[vars.accent]: theme.colors.$primary500,
[vars.accentDark]: theme.colors.$primary600,
[vars.accentHover]: theme.colors.$primaryHover,
[vars.border]: theme.colors.$primary500,
[vars.accentContrast]: theme.colors.$colorTextOnPrimaryBackground,
[vars.alpha]: theme.colors.$neutralAlpha50,
},
neutral: {
[vars.accent]: theme.colors.$neutralAlpha600,
[vars.accentDark]: theme.colors.$neutralAlpha700,
[vars.accentHover]: theme.colors.$neutralAlpha700,
[vars.border]: theme.colors.$neutralAlpha200,
[vars.accentContrast]: theme.colors.$white,
[vars.alpha]: theme.colors.$neutralAlpha50,
},
danger: {
[vars.accent]: theme.colors.$danger500,
[vars.accentDark]: theme.colors.$danger600,
[vars.accentHover]: theme.colors.$danger600,
[vars.accentContrast]: theme.colors.$white,
[vars.border]: theme.colors.$danger500,
[vars.alpha]: theme.colors.$dangerAlpha50,
Expand All @@ -76,23 +76,14 @@ const { applyVariants, filterProps } = createVariants(
boxShadow: theme.shadows.$buttonShadow,
border: theme.borders.$normal,
borderColor: vars.accent,
':after': {
position: 'absolute',
content: '""',
borderRadius: 'inherit',
zIndex: -1,
inset: 0,
opacity: 1,
transitionProperty: theme.transitionProperty.$common,
transitionDuration: theme.transitionDuration.$controls,
background: `linear-gradient(180deg, ${theme.colors.$whiteAlpha150} 0%, ${theme.colors.$transparent} 100%)`,
},
':hover::after': {
opacity: 0,
},
':active::after': {
opacity: 1,
'&:hover': {
backgroundColor: vars.accentHover,
},
'&:focus': props.hoverAsFocus
? {
backgroundColor: vars.accentHover,
}
: undefined,
},
outline: {
border: theme.borders.$normal,
Expand All @@ -104,8 +95,8 @@ const { applyVariants, filterProps } = createVariants(
},
ghost: {
color: vars.accent,
'&:hover': { backgroundColor: vars.alpha, color: vars.accentDark },
'&:focus': props.hoverAsFocus ? { backgroundColor: vars.alpha, color: vars.accentDark } : undefined,
'&:hover': { backgroundColor: vars.alpha, color: vars.accentHover },
'&:focus': props.hoverAsFocus ? { backgroundColor: vars.alpha, color: vars.accentHover } : undefined,
},
link: {
minHeight: 'fit-content',
Expand Down Expand Up @@ -259,7 +250,8 @@ const SimpleButton = React.forwardRef<HTMLButtonElement, ButtonProps>((props, re
onClick={onClick}
css={applyVariants(parsedProps) as any}
disabled={isDisabled}
data-variant={props.variant || 'primary'}
data-variant={props.variant || 'solid'}
data-color={props.colorScheme || 'primary'}
ref={ref}
>
{children}
Expand Down
9 changes: 5 additions & 4 deletions packages/clerk-js/src/ui/primitives/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export type InputProps = PrimitiveProps<'input'> & StyleVariants<typeof applyVar
export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const fieldControl = useFormField() || {};
// @ts-expect-error Typescript is complaining that `errorMessageId` does not exist. We are clearly passing them from above.
const { errorMessageId, ignorePasswordManager, ...fieldControlProps } = sanitizeInputProps(fieldControl, [
'errorMessageId',
'ignorePasswordManager',
]);
const { errorMessageId, ignorePasswordManager, feedbackType, ...fieldControlProps } = sanitizeInputProps(
fieldControl,
['errorMessageId', 'ignorePasswordManager', 'feedbackType'],
);

const propsWithoutVariants = filterProps({
...props,
Expand Down Expand Up @@ -90,6 +90,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref)
aria-describedby={errorMessageId}
aria-required={_required}
aria-disabled={_disabled}
data-feedback={feedbackType}
css={applyVariants(propsWithoutVariants)}
/>
);
Expand Down
1 change: 1 addition & 0 deletions packages/themes/src/themes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './dark';
export * from './shadesOfPurple';
export * from './neobrutalism';
export * from './reset';
6 changes: 6 additions & 0 deletions packages/themes/src/themes/reset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { experimental_createTheme } from '../createTheme';

export const reset = experimental_createTheme({
//@ts-expect-error not public api
simpleStyles: true,
});
8 changes: 6 additions & 2 deletions playground/nextjs/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SignOutButton,
UserButton,
} from '@clerk/nextjs';
import { dark, neobrutalism, shadesOfPurple } from '@clerk/themes';
import { dark, neobrutalism, reset, shadesOfPurple } from '@clerk/themes';
import Link from 'next/link';
import React, { FunctionComponent, useEffect, useState } from 'react';

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

const onToggleDark = () => {
Expand Down Expand Up @@ -50,7 +51,7 @@ function MyApp({ Component, pageProps }: AppProps) {
return (
<ClerkProvider
appearance={{
baseTheme: themes[selectedTheme],
baseTheme: styleReset ? [reset, themes[selectedTheme]] : themes[selectedTheme],
variables: {
colorPrimary:primaryColor,
},
Expand All @@ -66,6 +67,7 @@ function MyApp({ Component, pageProps }: AppProps) {
onChangeTheme={e => setSelectedTheme(e.target.value as any)}
onToggleDark={onToggleDark}
onToggleSmooth={onToggleSmooth}
onResetStyles={() => setStyleReset((s) => !s)}
smooth={selectedSmoothing}
onPrimaryColorChange={setPrimaryColor}
/>
Expand All @@ -78,6 +80,7 @@ type AppBarProps = {
onChangeTheme: React.ChangeEventHandler<HTMLSelectElement>;
onToggleDark: React.MouseEventHandler<HTMLButtonElement>;
onToggleSmooth: React.MouseEventHandler<HTMLButtonElement>;
onResetStyles: React.MouseEventHandler<HTMLButtonElement>;
smooth: boolean;
onPrimaryColorChange: (primaryColor: string | undefined) => void;
};
Expand Down Expand Up @@ -118,6 +121,7 @@ const AppBar = (props: AppBarProps) => {
</select>
<button onClick={props.onToggleDark}>toggle dark mode</button>
<button onClick={props.onToggleSmooth}>font-smoothing: {props.smooth ? 'On' : 'Off'}</button>
<button onClick={props.onResetStyles} style={{position:'absolute', left: '10px', bottom:'10px'}}>simple styles</button>
<input type='color' onChange={(e) => props.onPrimaryColorChange(e.target.value)}/>
<UserButton />

Expand Down