diff --git a/.changeset/quick-walls-share.md b/.changeset/quick-walls-share.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/quick-walls-share.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx b/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx index 953948b4d71..80faf9a013d 100644 --- a/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx +++ b/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx @@ -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', () => { @@ -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', () => { @@ -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]); }); }); diff --git a/packages/clerk-js/src/ui/customizables/parseAppearance.ts b/packages/clerk-js/src/ui/customizables/parseAppearance.ts index da6cf2b6041..7fc6133ef7b 100644 --- a/packages/clerk-js/src/ui/customizables/parseAppearance.ts +++ b/packages/clerk-js/src/ui/customizables/parseAppearance.ts @@ -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 { @@ -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') { @@ -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); }; diff --git a/packages/clerk-js/src/ui/customizables/parseVariables.ts b/packages/clerk-js/src/ui/customizables/parseVariables.ts index a137a314063..3be9b321b51 100644 --- a/packages/clerk-js/src/ui/customizables/parseVariables.ts +++ b/packages/clerk-js/src/ui/customizables/parseVariables.ts @@ -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'), @@ -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, diff --git a/packages/clerk-js/src/ui/foundations/colors.ts b/packages/clerk-js/src/ui/foundations/colors.ts index 0124edae064..f5c67f34485 100644 --- a/packages/clerk-js/src/ui/foundations/colors.ts +++ b/packages/clerk-js/src/ui/foundations/colors.ts @@ -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', diff --git a/packages/clerk-js/src/ui/polishedAppearance.ts b/packages/clerk-js/src/ui/polishedAppearance.ts new file mode 100644 index 00000000000..2d4d9757a2a --- /dev/null +++ b/packages/clerk-js/src/ui/polishedAppearance.ts @@ -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; diff --git a/packages/clerk-js/src/ui/primitives/Button.tsx b/packages/clerk-js/src/ui/primitives/Button.tsx index 46c37daba43..f985e851c73 100644 --- a/packages/clerk-js/src/ui/primitives/Button.tsx +++ b/packages/clerk-js/src/ui/primitives/Button.tsx @@ -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' }) => { @@ -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, @@ -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, @@ -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', @@ -259,7 +250,8 @@ const SimpleButton = React.forwardRef((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} diff --git a/packages/clerk-js/src/ui/primitives/Input.tsx b/packages/clerk-js/src/ui/primitives/Input.tsx index 698f6a84366..60c1150f36d 100644 --- a/packages/clerk-js/src/ui/primitives/Input.tsx +++ b/packages/clerk-js/src/ui/primitives/Input.tsx @@ -44,10 +44,10 @@ export type InputProps = PrimitiveProps<'input'> & StyleVariants((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, @@ -90,6 +90,7 @@ export const Input = React.forwardRef((props, ref) aria-describedby={errorMessageId} aria-required={_required} aria-disabled={_disabled} + data-feedback={feedbackType} css={applyVariants(propsWithoutVariants)} /> ); diff --git a/packages/themes/src/themes/index.ts b/packages/themes/src/themes/index.ts index e8c549e1631..3b022130d3d 100644 --- a/packages/themes/src/themes/index.ts +++ b/packages/themes/src/themes/index.ts @@ -1,3 +1,4 @@ export * from './dark'; export * from './shadesOfPurple'; export * from './neobrutalism'; +export * from './reset'; diff --git a/packages/themes/src/themes/reset.ts b/packages/themes/src/themes/reset.ts new file mode 100644 index 00000000000..bcb5da5643c --- /dev/null +++ b/packages/themes/src/themes/reset.ts @@ -0,0 +1,6 @@ +import { experimental_createTheme } from '../createTheme'; + +export const reset = experimental_createTheme({ + //@ts-expect-error not public api + simpleStyles: true, +}); diff --git a/playground/nextjs/pages/_app.tsx b/playground/nextjs/pages/_app.tsx index fe6fd4fc7d8..706b7e496ed 100644 --- a/playground/nextjs/pages/_app.tsx +++ b/playground/nextjs/pages/_app.tsx @@ -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'; @@ -19,6 +19,7 @@ const themes = { default: undefined, dark, neobrutalism, shadesOfPurple }; function MyApp({ Component, pageProps }: AppProps) { const [selectedTheme, setSelectedTheme] = useState('default'); const [selectedSmoothing, setSelectedSmoothing] = useState(true); + const [styleReset, setStyleReset] = useState(false); const [primaryColor, setPrimaryColor] = useState(undefined); const onToggleDark = () => { @@ -50,7 +51,7 @@ function MyApp({ Component, pageProps }: AppProps) { return ( setSelectedTheme(e.target.value as any)} onToggleDark={onToggleDark} onToggleSmooth={onToggleSmooth} + onResetStyles={() => setStyleReset((s) => !s)} smooth={selectedSmoothing} onPrimaryColorChange={setPrimaryColor} /> @@ -78,6 +80,7 @@ type AppBarProps = { onChangeTheme: React.ChangeEventHandler; onToggleDark: React.MouseEventHandler; onToggleSmooth: React.MouseEventHandler; + onResetStyles: React.MouseEventHandler; smooth: boolean; onPrimaryColorChange: (primaryColor: string | undefined) => void; }; @@ -118,6 +121,7 @@ const AppBar = (props: AppBarProps) => { + props.onPrimaryColorChange(e.target.value)}/>