Skip to content
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
6 changes: 6 additions & 0 deletions .changeset/rename-appearance-layout-to-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/ui': major
---

Renamed `appearance.layout` to `appearance.options` across all appearance configurations. This is a breaking change - update all instances of `appearance.layout` to `appearance.options` in your codebase.

6 changes: 3 additions & 3 deletions packages/react/src/contexts/__tests__/ClerkProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ describe('ClerkProvider', () => {
expectTypeOf({ ...defaultProps, appearance: {} }).toMatchTypeOf<ClerkProviderProps>();
});

it('includes variables, elements, layout, theme', () => {
it('includes variables, elements, options baseTheme', () => {
expectTypeOf({
...defaultProps,
appearance: { elements: {}, variables: {}, layout: {}, theme: dark },
appearance: { elements: {}, variables: {}, options: {}, thene: dark },
}).toMatchTypeOf<ClerkProviderProps>();
});

Expand All @@ -68,7 +68,7 @@ describe('ClerkProvider', () => {

expectTypeOf({
...defaultProps,
appearance: { layout: { nonExistentKey: '' } },
appearance: { options: { nonExistentKey: '' } },
}).not.toMatchTypeOf<ClerkProviderProps>();

// expectTypeOf({
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/Checkout/CheckoutComplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const CheckoutComplete = () => {
const [mousePosition, setMousePosition] = useState({ x: 256, y: 256 });

const prefersReducedMotion = usePrefersReducedMotion();
const { animations: layoutAnimations } = useAppearance().parsedLayout;
const { animations: layoutAnimations } = useAppearance().parsedOptions;
const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;

const checkoutSuccessRootRef = useRef<HTMLSpanElement>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function PricingTableMatrix({
highlightedPlan,
}: PricingTableMatrixProps) {
const prefersReducedMotion = usePrefersReducedMotion();
const { animations: layoutAnimations } = useAppearance().parsedLayout;
const { animations: layoutAnimations } = useAppearance().parsedOptions;
const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;
const pricingTableMatrixId = React.useId();
const segmentedControlId = `${pricingTableMatrixId}-segmented-control`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ describe('SignInStart', () => {
<AppearanceProvider
appearanceKey={'signIn'}
appearance={{
layout: {
options: {
socialButtonsVariant: 'blockButton',
},
}}
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/SignUp/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export const SignUpForm = (props: SignUpFormProps) => {
onlyLegalAcceptedMissing = false,
handleEmailPhoneToggle,
} = props;
const { showOptionalFields } = useAppearance().parsedLayout;
const { showOptionalFields } = useAppearance().parsedOptions;

const shouldShow = (name: keyof typeof fields) => {
// In case both email & phone are optional, then don't take into account the
// Layout showOptionalFields prop and the required field.
// Options showOptionalFields prop and the required field.
if ((name === 'emailAddress' || name === 'phoneNumber') && canToggleEmailPhone) {
return !!fields[name];
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/SignUp/SignUpStart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function SignUpStartInternal(): JSX.Element {
const clerk = useClerk();
const status = useLoadingStatus();
const signUp = useCoreSignUp();
const { showOptionalFields } = useAppearance().parsedLayout;
const { showOptionalFields } = useAppearance().parsedOptions;
const { userSettings, authConfig } = useEnvironment();
const { navigate } = useRouter();
const { attributes } = userSettings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ describe('SignUpStart', () => {
<AppearanceProvider
appearanceKey={'signUp'}
appearance={{
layout: {
options: {
socialButtonsVariant: 'blockButton',
},
}}
Expand Down
72 changes: 36 additions & 36 deletions packages/ui/src/customizables/__tests__/parseAppearance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,13 @@ describe('AppearanceProvider element flows', () => {
});
});

describe('AppearanceProvider layout flows', () => {
it('sets the parsedLayout correctly from the globalAppearance prop', () => {
describe('AppearanceProvider options flows', () => {
it('sets the parsedOptions correctly from the globalAppearance prop', () => {
const wrapper = ({ children }) => (
<AppearanceProvider
appearanceKey='signIn'
globalAppearance={{
layout: {
options: {
helpPageUrl: 'https://example.com/help',
logoImageUrl: 'https://placehold.co/64x64.png',
logoLinkUrl: 'https://example.com/',
Expand All @@ -246,23 +246,23 @@ describe('AppearanceProvider layout flows', () => {
);

const { result } = renderHook(() => useAppearance(), { wrapper });
expect(result.current.parsedLayout.helpPageUrl).toBe('https://example.com/help');
expect(result.current.parsedLayout.logoImageUrl).toBe('https://placehold.co/64x64.png');
expect(result.current.parsedLayout.logoLinkUrl).toBe('https://example.com/');
expect(result.current.parsedLayout.privacyPageUrl).toBe('https://example.com/privacy');
expect(result.current.parsedLayout.termsPageUrl).toBe('https://example.com/terms');
expect(result.current.parsedLayout.logoPlacement).toBe('inside');
expect(result.current.parsedLayout.showOptionalFields).toBe(false);
expect(result.current.parsedLayout.socialButtonsPlacement).toBe('bottom');
expect(result.current.parsedLayout.socialButtonsVariant).toBe('iconButton');
expect(result.current.parsedOptions.helpPageUrl).toBe('https://example.com/help');
expect(result.current.parsedOptions.logoImageUrl).toBe('https://placehold.co/64x64.png');
expect(result.current.parsedOptions.logoLinkUrl).toBe('https://example.com/');
expect(result.current.parsedOptions.privacyPageUrl).toBe('https://example.com/privacy');
expect(result.current.parsedOptions.termsPageUrl).toBe('https://example.com/terms');
expect(result.current.parsedOptions.logoPlacement).toBe('inside');
expect(result.current.parsedOptions.showOptionalFields).toBe(false);
expect(result.current.parsedOptions.socialButtonsPlacement).toBe('bottom');
expect(result.current.parsedOptions.socialButtonsVariant).toBe('iconButton');
});

it('sets the parsedLayout correctly from the appearance prop', () => {
it('sets the parsedOptions correctly from the appearance prop', () => {
const wrapper = ({ children }) => (
<AppearanceProvider
appearanceKey='signIn'
appearance={{
layout: {
options: {
helpPageUrl: 'https://example.com/help',
logoImageUrl: 'https://placehold.co/64x64.png',
logoLinkUrl: 'https://example.com/',
Expand All @@ -280,23 +280,23 @@ describe('AppearanceProvider layout flows', () => {
);

const { result } = renderHook(() => useAppearance(), { wrapper });
expect(result.current.parsedLayout.helpPageUrl).toBe('https://example.com/help');
expect(result.current.parsedLayout.logoImageUrl).toBe('https://placehold.co/64x64.png');
expect(result.current.parsedLayout.logoLinkUrl).toBe('https://example.com/');
expect(result.current.parsedLayout.privacyPageUrl).toBe('https://example.com/privacy');
expect(result.current.parsedLayout.termsPageUrl).toBe('https://example.com/terms');
expect(result.current.parsedLayout.logoPlacement).toBe('outside');
expect(result.current.parsedLayout.showOptionalFields).toBe(true);
expect(result.current.parsedLayout.socialButtonsPlacement).toBe('top');
expect(result.current.parsedLayout.socialButtonsVariant).toBe('blockButton');
expect(result.current.parsedOptions.helpPageUrl).toBe('https://example.com/help');
expect(result.current.parsedOptions.logoImageUrl).toBe('https://placehold.co/64x64.png');
expect(result.current.parsedOptions.logoLinkUrl).toBe('https://example.com/');
expect(result.current.parsedOptions.privacyPageUrl).toBe('https://example.com/privacy');
expect(result.current.parsedOptions.termsPageUrl).toBe('https://example.com/terms');
expect(result.current.parsedOptions.logoPlacement).toBe('outside');
expect(result.current.parsedOptions.showOptionalFields).toBe(true);
expect(result.current.parsedOptions.socialButtonsPlacement).toBe('top');
expect(result.current.parsedOptions.socialButtonsVariant).toBe('blockButton');
});

it('sets the parsedLayout correctly from the globalAppearance and appearance prop', () => {
it('sets the parsedOptions correctly from the globalAppearance and appearance prop', () => {
const wrapper = ({ children }) => (
<AppearanceProvider
appearanceKey='signIn'
globalAppearance={{
layout: {
options: {
helpPageUrl: 'https://example.com/help',
logoImageUrl: 'https://placehold.co/64x64.png',
logoLinkUrl: 'https://example.com/',
Expand All @@ -309,7 +309,7 @@ describe('AppearanceProvider layout flows', () => {
},
}}
appearance={{
layout: {
options: {
helpPageUrl: 'https://second.example.com/help',
logoImageUrl: 'https://placehold.co/32x32@2.png',
logoLinkUrl: 'https://second.example.com/',
Expand All @@ -327,15 +327,15 @@ describe('AppearanceProvider layout flows', () => {
);

const { result } = renderHook(() => useAppearance(), { wrapper });
expect(result.current.parsedLayout.helpPageUrl).toBe('https://second.example.com/help');
expect(result.current.parsedLayout.logoImageUrl).toBe('https://placehold.co/32x32@2.png');
expect(result.current.parsedLayout.logoLinkUrl).toBe('https://second.example.com/');
expect(result.current.parsedLayout.privacyPageUrl).toBe('https://second.example.com/privacy');
expect(result.current.parsedLayout.termsPageUrl).toBe('https://second.example.com/terms');
expect(result.current.parsedLayout.logoPlacement).toBe('outside');
expect(result.current.parsedLayout.showOptionalFields).toBe(true);
expect(result.current.parsedLayout.socialButtonsPlacement).toBe('top');
expect(result.current.parsedLayout.socialButtonsVariant).toBe('blockButton');
expect(result.current.parsedOptions.helpPageUrl).toBe('https://second.example.com/help');
expect(result.current.parsedOptions.logoImageUrl).toBe('https://placehold.co/32x32@2.png');
expect(result.current.parsedOptions.logoLinkUrl).toBe('https://second.example.com/');
expect(result.current.parsedOptions.privacyPageUrl).toBe('https://second.example.com/privacy');
expect(result.current.parsedOptions.termsPageUrl).toBe('https://second.example.com/terms');
expect(result.current.parsedOptions.logoPlacement).toBe('outside');
expect(result.current.parsedOptions.showOptionalFields).toBe(true);
expect(result.current.parsedOptions.socialButtonsPlacement).toBe('top');
expect(result.current.parsedOptions.socialButtonsVariant).toBe('blockButton');
});

it('removes the base theme when simpleStyles is passed to globalAppearance', () => {
Expand Down Expand Up @@ -426,7 +426,7 @@ describe('AppearanceProvider captcha', () => {
expect(result.current.parsedCaptcha.language).toBe('el-GR');
});

it('sets the parsedLayout correctly from the globalAppearance and appearance prop', () => {
it('sets the parsedOptions correctly from the globalAppearance and appearance prop', () => {
const wrapper = ({ children }) => (
<AppearanceProvider
appearanceKey='signIn'
Expand Down
16 changes: 8 additions & 8 deletions packages/ui/src/customizables/parseAppearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fastDeepMergeAndReplace } from '@clerk/shared/utils';

import { baseTheme, getBaseTheme } from '../baseTheme';
import { createInternalTheme, defaultInternalTheme } from '../foundations';
import type { Appearance, CaptchaAppearanceOptions, Elements, Layout, Theme } from '../internal/appearance';
import type { Appearance, CaptchaAppearanceOptions, Elements, Options, Theme } from '../internal/appearance';
import type { InternalTheme } from '../styledSystem';
import {
createColorScales,
Expand All @@ -17,7 +17,7 @@ import {

export type ParsedElements = Elements[];
export type ParsedInternalTheme = InternalTheme;
export type ParsedLayout = Required<Layout>;
export type ParsedOptions = Required<Options>;
export type ParsedCaptcha = Required<CaptchaAppearanceOptions>;

type PublicAppearanceTopLevelKey = keyof Omit<
Expand All @@ -34,11 +34,11 @@ export type AppearanceCascade = {
export type ParsedAppearance = {
parsedElements: ParsedElements;
parsedInternalTheme: ParsedInternalTheme;
parsedLayout: ParsedLayout;
parsedOptions: ParsedOptions;
parsedCaptcha: ParsedCaptcha;
};

const defaultLayout: ParsedLayout = {
const defaultOptions: ParsedOptions = {
logoPlacement: 'inside',
socialButtonsPlacement: 'top',
socialButtonsVariant: 'auto',
Expand Down Expand Up @@ -75,7 +75,7 @@ export const parseAppearance = (cascade: AppearanceCascade): ParsedAppearance =>
);

const parsedInternalTheme = parseVariables(appearanceList);
const parsedLayout = parseLayout(appearanceList);
const parsedOptions = parseOptions(appearanceList);
const parsedCaptcha = parseCaptcha(appearanceList);

if (
Expand All @@ -97,7 +97,7 @@ export const parseAppearance = (cascade: AppearanceCascade): ParsedAppearance =>
return res;
}),
);
return { parsedElements, parsedInternalTheme, parsedLayout, parsedCaptcha };
return { parsedElements, parsedInternalTheme, parsedOptions, parsedCaptcha };
};

const expand = (theme: Theme | undefined, cascade: any[]) => {
Expand All @@ -124,8 +124,8 @@ const parseElements = (appearances: Appearance[]) => {
return appearances.map(appearance => ({ ...appearance?.elements }));
};

const parseLayout = (appearanceList: Appearance[]) => {
return { ...defaultLayout, ...appearanceList.reduce((acc, appearance) => ({ ...acc, ...appearance.layout }), {}) };
const parseOptions = (appearanceList: Appearance[]) => {
return { ...defaultOptions, ...appearanceList.reduce((acc, appearance) => ({ ...acc, ...appearance.options }), {}) };
};

const parseCaptcha = (appearanceList: Appearance[]) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/elements/Animated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type AnimatedProps = PropsWithChildren<{ asChild?: boolean }>;

export const Animated = (props: AnimatedProps) => {
const { children, asChild } = props;
const { animations } = useAppearance().parsedLayout;
const { animations } = useAppearance().parsedOptions;
const [parent] = useAutoAnimate();

if (asChild) {
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/elements/ApplicationLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ export const ApplicationLogo: React.FC<ApplicationLogoProps> = (props: Applicati
const imageRef = React.useRef<HTMLImageElement>(null);
const [loaded, setLoaded] = React.useState(false);
const { logoImageUrl, applicationName, homeUrl } = useEnvironment().displayConfig;
const { parsedLayout } = useAppearance();
const imageSrc = src || parsedLayout.logoImageUrl || logoImageUrl;
const { parsedOptions } = useAppearance();
const imageSrc = src || parsedOptions.logoImageUrl || logoImageUrl;
const imageAlt = alt || applicationName;
const logoUrl = href || parsedLayout.logoLinkUrl || homeUrl;
const logoUrl = href || parsedOptions.logoLinkUrl || homeUrl;

if (!imageSrc) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/elements/Card/CardFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>((pro
const { displayConfig } = useEnvironment();
const { branded } = displayConfig;
const { showDevModeNotice } = useDevMode();
const { helpPageUrl, privacyPageUrl, termsPageUrl } = useAppearance().parsedLayout;
const { helpPageUrl, privacyPageUrl, termsPageUrl } = useAppearance().parsedOptions;
const sponsorOrLinksExist = !!(branded || helpPageUrl || privacyPageUrl || termsPageUrl);
const showSponsorAndLinks = isProfileFooter ? branded : sponsorOrLinksExist;

Expand Down Expand Up @@ -87,7 +87,7 @@ const CardFooterLink = (props: PropsOfComponent<typeof Link>): JSX.Element => {
};

export const CardFooterLinks = React.memo((): JSX.Element | null => {
const { helpPageUrl, privacyPageUrl, termsPageUrl } = useAppearance().parsedLayout;
const { helpPageUrl, privacyPageUrl, termsPageUrl } = useAppearance().parsedOptions;

if (!helpPageUrl && !privacyPageUrl && !termsPageUrl) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/elements/Card/CardRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const CardRoot = React.forwardRef<HTMLDivElement, CardRootProps>((props,

return (
<>
{appearance.parsedLayout.logoPlacement === 'outside' && (
{appearance.parsedOptions.logoPlacement === 'outside' && (
<ApplicationLogo
sx={t => ({
position: 'relative',
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/elements/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ interface ContentProps {

const Content = React.forwardRef<HTMLDivElement, ContentProps>(({ children }, ref) => {
const prefersReducedMotion = usePrefersReducedMotion();
const { animations: layoutAnimations } = useAppearance().parsedLayout;
const { animations: layoutAnimations } = useAppearance().parsedOptions;
const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;
const { strategy, refs, context, getFloatingProps, direction } = useDrawerContext();
const mergedRefs = useMergeRefs([ref, refs.setFloating]);
Expand Down Expand Up @@ -454,7 +454,7 @@ interface ConfirmationProps {
const Confirmation = React.forwardRef<HTMLDivElement, ConfirmationProps>(
({ open, onOpenChange, children, actionsSlot, roleProps }, ref) => {
const prefersReducedMotion = usePrefersReducedMotion();
const { animations: layoutAnimations } = useAppearance().parsedLayout;
const { animations: layoutAnimations } = useAppearance().parsedOptions;
const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;

const { refs, context } = useFloating({
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/elements/FormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type { FeedbackType, useFormControlFeedback } from '../utils/useFormContr

function useFormTextAnimation() {
const prefersReducedMotion = usePrefersReducedMotion();
const { animations: appearanceAnimations } = useAppearance().parsedLayout;
const { animations: appearanceAnimations } = useAppearance().parsedOptions;

const getFormTextAnimation = useCallback(
(enterAnimation: boolean, options?: { inDelay?: boolean }): ThemableCssProp => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/elements/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Root = React.memo(
const { sx, children, contentSx, gap = 6, showLogo = false, showDivider = false, ...rest } = props;
const appearance = useAppearance();

const logoIsVisible = appearance.parsedLayout.logoPlacement === 'inside' && showLogo;
const logoIsVisible = appearance.parsedOptions.logoPlacement === 'inside' && showLogo;
const verticalDividerIsVisible = showDivider && logoIsVisible;

return (
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/elements/LegalConsentCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ export const LegalCheckbox = (
},
) => {
const { displayConfig } = useEnvironment();
const { parsedLayout } = useAppearance();
const { parsedOptions } = useAppearance();

const termsLink = parsedLayout.termsPageUrl || displayConfig.termsUrl;
const privacyPolicy = parsedLayout.privacyPageUrl || displayConfig.privacyPolicyUrl;
const termsLink = parsedOptions.termsPageUrl || displayConfig.termsUrl;
const privacyPolicy = parsedOptions.privacyPageUrl || displayConfig.privacyPolicyUrl;

return (
<Field.Root {...props}>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/elements/PopoverCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const PopoverCardContent = (props: PropsOfComponent<typeof Flex>) => {
const PopoverCardFooter = (props: PropsOfComponent<typeof Flex>) => {
const { sx, children, ...rest } = props;
const { branded } = useEnvironment().displayConfig;
const { privacyPageUrl, termsPageUrl, helpPageUrl } = useAppearance().parsedLayout;
const { privacyPageUrl, termsPageUrl, helpPageUrl } = useAppearance().parsedOptions;
const shouldShowTagOrLinks = branded || privacyPageUrl || termsPageUrl || helpPageUrl;

return (
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/elements/ReversibleContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const SocialButtonsReversibleContainerWithDivider = (props: React.PropsWi

return (
<ReversibleContainer
reverse={appearance.parsedLayout.socialButtonsPlacement === 'bottom'}
reverse={appearance.parsedOptions.socialButtonsPlacement === 'bottom'}
{...props}
>
{childrenWithDivider}
Expand Down
Loading