diff --git a/.changeset/hungry-clouds-return.md b/.changeset/hungry-clouds-return.md new file mode 100644 index 00000000000..e7adc59e287 --- /dev/null +++ b/.changeset/hungry-clouds-return.md @@ -0,0 +1,20 @@ +--- +'@clerk/clerk-js': minor +"@clerk/types": minor +--- + +Introducing a development mode warning when in development mode in order to mitigate going to production with development keys. + +In case need to deactivate this UI change temporarily to simulate how components will look in production, you can do so by adding the `unsafe_disableDevelopmentModeWarnings` layout appearance prop to `` + +Example: + +```tsx + +``` diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index 64b5e92de19..102610d8898 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -41,6 +41,7 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource afterLeaveOrganizationUrl!: string; afterCreateOrganizationUrl!: string; googleOneTapClientId?: string; + showDevModeWarning!: boolean; public constructor(data: DisplayConfigJSON) { super(); @@ -80,6 +81,7 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource this.afterLeaveOrganizationUrl = data.after_leave_organization_url; this.afterCreateOrganizationUrl = data.after_create_organization_url; this.googleOneTapClientId = data.google_one_tap_client_id; + this.showDevModeWarning = data.show_devmode_warning; return this; } } diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx index 4e6b4e6dc39..7aedd1560da 100644 --- a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx +++ b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx @@ -3,6 +3,7 @@ import { useClerk } from '@clerk/shared/react'; import { useCreateOrganizationContext } from '../../contexts'; import { localizationKeys } from '../../customizables'; import { Card, useCardState, withCardStateProvider } from '../../elements'; +import { useDevMode } from '../../hooks/useDevMode'; import { CreateOrganizationForm } from './CreateOrganizationForm'; export const CreateOrganizationPage = withCardStateProvider(() => { @@ -10,10 +11,15 @@ export const CreateOrganizationPage = withCardStateProvider(() => { const { mode, navigateAfterCreateOrganization, skipInvitationScreen } = useCreateOrganizationContext(); const card = useCardState(); + const { showDevModeNotice } = useDevMode(); return ( ({ width: t.sizes.$108 })}> - ({ padding: `${t.space.$4} ${t.space.$5} ${t.space.$6}` })}> + ({ + padding: `${t.space.$4} ${t.space.$5} ${showDevModeNotice ? t.space.$12 : t.space.$6}`, + })} + > {card.error} & { withFooterPages?: boolean }>((props, ref) => { - const { sx, withFooterPages = false, ...rest } = props; - const { branded } = useEnvironment().displayConfig; + React.forwardRef< + HTMLDivElement, + PropsOfComponent & { + withFooterPages?: boolean; + devModeNoticeSx?: ThemableCssProp; + outerSx?: ThemableCssProp; + withDevOverlay?: boolean; + } + >((props, ref) => { + const { sx, outerSx, withFooterPages = false, withDevOverlay = false, devModeNoticeSx, ...rest } = props; + const { displayConfig } = useEnvironment(); + const { showDevModeNotice } = useDevMode(); - if (!(branded || withFooterPages)) { + if (!(displayConfig.branded || withFooterPages) && !showDevModeNotice) { return null; } return ( - ({ - ':has(div:only-child)': { - justifyContent: 'center', - }, - justifyContent: 'space-between', + { width: '100%', - padding: `0 ${t.space.$8}`, - }), - sx, + position: 'relative', + isolation: 'isolate', + }, + outerSx, ]} - {...rest} - ref={ref} > - {branded && ( - ({ color: t.colors.$colorTextSecondary })} - > - <> - Secured by - - - - )} + {withDevOverlay && } + ({ + gap: displayConfig.branded || withFooterPages ? t.space.$2 : 0, + marginLeft: 'auto', + marginRight: 'auto', + width: '100%', + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + position: 'relative', + })} + > + {(displayConfig.branded || withFooterPages) && ( + + {displayConfig.branded && ( + ({ color: t.colors.$colorTextSecondary })} + > + <> + Secured by + + + + )} + + {withFooterPages && } + + )} - {withFooterPages && } - + + + ); }), ); diff --git a/packages/clerk-js/src/ui/elements/Card/CardContent.tsx b/packages/clerk-js/src/ui/elements/Card/CardContent.tsx index bb68b89eeb9..f18454620ae 100644 --- a/packages/clerk-js/src/ui/elements/Card/CardContent.tsx +++ b/packages/clerk-js/src/ui/elements/Card/CardContent.tsx @@ -10,7 +10,7 @@ import { useLocalizations, } from '../../customizables'; import { Close } from '../../icons'; -import { type PropsOfComponent } from '../../styledSystem'; +import type { PropsOfComponent } from '../../styledSystem'; import { useCardState, useFlowMetadata } from '../contexts'; import { IconButton } from '../IconButton'; import { useUnsafeModalContext } from '../Modal'; diff --git a/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx b/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx index 438eb5b5ede..23ceb1cf69a 100644 --- a/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx +++ b/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useEnvironment } from '../../contexts'; import { descriptors, Flex, Link, localizationKeys, useAppearance } from '../../customizables'; +import { useDevMode } from '../../hooks/useDevMode'; import type { InternalTheme, PropsOfComponent } from '../../styledSystem'; import { common, mqu } from '../../styledSystem'; import { colors } from '../../utils'; @@ -12,12 +13,14 @@ type CardFooterProps = PropsOfComponent & { }; export const CardFooter = React.forwardRef((props, ref) => { const { children, isProfileFooter = false, sx, ...rest } = props; - const { branded } = useEnvironment().displayConfig; + const { displayConfig } = useEnvironment(); + const { branded } = displayConfig; + const { showDevModeNotice } = useDevMode(); const { helpPageUrl, privacyPageUrl, termsPageUrl } = useAppearance().parsedLayout; const sponsorOrLinksExist = !!(branded || helpPageUrl || privacyPageUrl || termsPageUrl); const showSponsorAndLinks = isProfileFooter ? branded : sponsorOrLinksExist; - if (!children && !showSponsorAndLinks) { + if (!children && !(showSponsorAndLinks || showDevModeNotice)) { return null; } @@ -64,7 +67,13 @@ export const CardFooter = React.forwardRef((pro > {children} - {showSponsorAndLinks && } + ({ + padding: t.space.$none, + })} + withDevOverlay + /> ); }); diff --git a/packages/clerk-js/src/ui/elements/DevModeNotice.tsx b/packages/clerk-js/src/ui/elements/DevModeNotice.tsx new file mode 100644 index 00000000000..9e95e1771ad --- /dev/null +++ b/packages/clerk-js/src/ui/elements/DevModeNotice.tsx @@ -0,0 +1,55 @@ +import type { ThemableCssProp } from 'ui/styledSystem'; + +import { Box, Text } from '../customizables'; +import { useDevMode } from '../hooks/useDevMode'; + +type DevModeOverlayProps = { + gradient?: number; +}; + +export const DevModeOverlay = (props: DevModeOverlayProps) => { + const { gradient = 60 } = props; + const { showDevModeNotice } = useDevMode(); + + if (!showDevModeNotice) { + return null; + } + + return ( + ({ + userSelect: 'none', + pointerEvents: 'none', + inset: 0, + position: 'absolute', + background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, + maskImage: `linear-gradient(transparent ${gradient}%, black)`, + })} + /> + ); +}; + +type DevModeNoticeProps = { sx?: ThemableCssProp }; +export const DevModeNotice = (props: DevModeNoticeProps) => { + const { sx } = props; + const { showDevModeNotice } = useDevMode(); + + if (!showDevModeNotice) { + return null; + } + + return ( + ({ + color: t.colors.$warning500, + fontWeight: t.fontWeights.$semibold, + padding: t.space.$1x5, + }), + sx, + ]} + > + Development mode + + ); +}; diff --git a/packages/clerk-js/src/ui/elements/Navbar.tsx b/packages/clerk-js/src/ui/elements/Navbar.tsx index 6e694305bb4..94383e5f341 100644 --- a/packages/clerk-js/src/ui/elements/Navbar.tsx +++ b/packages/clerk-js/src/ui/elements/Navbar.tsx @@ -12,6 +12,7 @@ import { animations, common, mqu } from '../styledSystem'; import { colors } from '../utils'; import { Card } from './Card'; import { withFloatingTree } from './contexts'; +import { DevModeOverlay } from './DevModeNotice'; import { Popover } from './Popover'; type NavbarContextValue = { isOpen: boolean; open: () => void; close: () => void }; @@ -140,6 +141,7 @@ const NavbarContainer = ( }, flex: `0 0 ${t.space.$57}`, width: t.sizes.$57, + position: 'relative', maxWidth: t.space.$57, background: common.mergedColorsBackground( colors.setAlpha(t.colors.$colorBackground, 1), @@ -151,6 +153,8 @@ const NavbarContainer = ( justifyContent: 'space-between', })} > + + ({ gap: t.space.$6, flex: `0 0 ${t.space.$60}` })}> ({ @@ -172,10 +176,9 @@ const NavbarContainer = ( ({ + sx={{ width: 'fit-content', - paddingLeft: theme.space.$3, - })} + }} /> ); diff --git a/packages/clerk-js/src/ui/elements/PopoverCard.tsx b/packages/clerk-js/src/ui/elements/PopoverCard.tsx index 1ccdf83a74c..d12a6f5ade9 100644 --- a/packages/clerk-js/src/ui/elements/PopoverCard.tsx +++ b/packages/clerk-js/src/ui/elements/PopoverCard.tsx @@ -1,17 +1,20 @@ import React from 'react'; import { useEnvironment } from '../contexts'; -import { Col, Flex, Flow, useAppearance } from '../customizables'; +import { Col, descriptors, Flex, Flow, useAppearance } from '../customizables'; +import type { ElementDescriptor } from '../customizables/elementDescriptors'; import type { PropsOfComponent } from '../styledSystem'; import { animations, common } from '../styledSystem'; import { colors } from '../utils'; import { Card } from '.'; const PopoverCardRoot = React.forwardRef>((props, ref) => { + const { elementDescriptor, ...rest } = props; return ( ({ width: t.sizes.$94, @@ -70,8 +73,6 @@ const PopoverCardFooter = (props: PropsOfComponent) => { ), marginTop: `-${t.space.$2}`, paddingTop: t.space.$2, - borderBottomLeftRadius: 'inherit', - borderBottomRightRadius: 'inherit', '&:empty': { padding: 0, marginTop: 0, @@ -89,12 +90,16 @@ const PopoverCardFooter = (props: PropsOfComponent) => { > {children} - {shouldShowTagOrLinks && ( - ({ padding: `${t.space.$4} ${t.space.$8}` })} - /> - )} + ({ + padding: `${t.space.$4} ${t.space.$none}`, + })} + withFooterPages={!!shouldShowTagOrLinks} + devModeNoticeSx={t => ({ + padding: t.space.$none, + })} + withDevOverlay + /> ); }; diff --git a/packages/clerk-js/src/ui/elements/index.ts b/packages/clerk-js/src/ui/elements/index.ts index 349d8d6845a..6d86e286eea 100644 --- a/packages/clerk-js/src/ui/elements/index.ts +++ b/packages/clerk-js/src/ui/elements/index.ts @@ -55,3 +55,4 @@ export * from './Card'; export * from './ProfileCard'; export * from './Gauge'; export * from './Animated'; +export * from './DevModeNotice'; diff --git a/packages/clerk-js/src/ui/hooks/useDevMode.tsx b/packages/clerk-js/src/ui/hooks/useDevMode.tsx new file mode 100644 index 00000000000..19fb9cddf80 --- /dev/null +++ b/packages/clerk-js/src/ui/hooks/useDevMode.tsx @@ -0,0 +1,19 @@ +import { useMemo } from 'react'; + +import { useEnvironment } from '../contexts'; +import { useAppearance } from '../customizables'; + +export function useDevMode() { + const { displayConfig, isDevelopmentOrStaging } = useEnvironment(); + const isDevelopment = isDevelopmentOrStaging(); + const { unsafe_disableDevelopmentModeWarnings = false } = useAppearance().parsedLayout; + const developmentUiDisabled = isDevelopment && unsafe_disableDevelopmentModeWarnings; + const showDevModeNotice = useMemo( + () => !developmentUiDisabled && displayConfig.showDevModeWarning, + [developmentUiDisabled, displayConfig], + ); + + return { + showDevModeNotice, + }; +} diff --git a/packages/clerk-js/src/ui/polishedAppearance.ts b/packages/clerk-js/src/ui/polishedAppearance.ts index 98f027c8826..fc56872c229 100644 --- a/packages/clerk-js/src/ui/polishedAppearance.ts +++ b/packages/clerk-js/src/ui/polishedAppearance.ts @@ -164,6 +164,10 @@ export const polishedAppearance: Appearance = { borderWidth: 0, boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`, }, + popoverBox: { + borderWidth: 0, + boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`, + }, card: { ...cardContentStyles(theme), }, diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index d09c12a1e4d..03b14920735 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -143,6 +143,7 @@ export type ElementsConfig = { cardBox: WithOptions; card: WithOptions; actionCard: WithOptions; + popoverBox: WithOptions; logoBox: WithOptions; logoImage: WithOptions; @@ -611,6 +612,13 @@ export type Layout = { * @default true */ animations?: boolean; + + /** + * This option disables development mode warning. + * We don't recommend disabling this unless you want to see a preview of how the components will look in production. + * @default false + */ + unsafe_disableDevelopmentModeWarnings?: boolean; }; export type SignInTheme = Theme; diff --git a/packages/types/src/displayConfig.ts b/packages/types/src/displayConfig.ts index ca75c6cfc99..b3de9bca056 100644 --- a/packages/types/src/displayConfig.ts +++ b/packages/types/src/displayConfig.ts @@ -35,6 +35,7 @@ export interface DisplayConfigJSON { after_leave_organization_url: string; after_create_organization_url: string; google_one_tap_client_id?: string; + show_devmode_warning: boolean; } export interface DisplayConfigResource extends ClerkResource { @@ -68,4 +69,5 @@ export interface DisplayConfigResource extends ClerkResource { afterLeaveOrganizationUrl: string; afterCreateOrganizationUrl: string; googleOneTapClientId?: string; + showDevModeWarning: boolean; } diff --git a/playground/nextjs/pages/_app.tsx b/playground/nextjs/pages/_app.tsx index 6bb74d3c773..bb19af04c9b 100644 --- a/playground/nextjs/pages/_app.tsx +++ b/playground/nextjs/pages/_app.tsx @@ -22,6 +22,7 @@ function MyApp({ Component, pageProps }: AppProps) { const [styleReset, setStyleReset] = useState(false); const [animations, setAnimations] = useState(true); const [primaryColor, setPrimaryColor] = useState(undefined); + const [disableDevMode, setDisableDevMode] = useState(false); const onToggleDark = () => { if (window.document.body.classList.contains('dark-mode')) { @@ -37,6 +38,10 @@ function MyApp({ Component, pageProps }: AppProps) { setAnimations(s => !s); }; + const onToggleDevMode = () => { + setDisableDevMode(s => !s); + } + const onToggleSmooth = () => { if (window.document.body.classList.contains('font-smoothing')) { setSelectedSmoothing(false); @@ -62,6 +67,7 @@ function MyApp({ Component, pageProps }: AppProps) { }, layout: { animations, + unsafe_disableDevelopmentModeWarnings: disableDevMode, }, }} {...pageProps} @@ -72,6 +78,8 @@ function MyApp({ Component, pageProps }: AppProps) { onToggleSmooth={onToggleSmooth} onResetStyles={() => setStyleReset(s => !s)} onToggleAnimations={onToggleAnimations} + devMode={disableDevMode} + onToggleDevMode={onToggleDevMode} animations={animations} styleReset={styleReset} smooth={selectedSmoothing} @@ -88,9 +96,11 @@ type AppBarProps = { onToggleSmooth: React.MouseEventHandler; onResetStyles: React.MouseEventHandler; onToggleAnimations: React.MouseEventHandler; + onToggleDevMode: React.MouseEventHandler; smooth: boolean; styleReset: boolean; animations: boolean; + devMode: boolean; onPrimaryColorChange: (primaryColor: string | undefined) => void; }; @@ -128,11 +138,12 @@ const AppBar = (props: AppBarProps) => { - - + +
- - + + +