Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/clear-adults-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

Updates flush card development mode indicator styling.
5 changes: 5 additions & 0 deletions .changeset/quiet-stripes-fade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
---

Remove the diagonal striped background pattern from the development-mode test card panels
12 changes: 3 additions & 9 deletions packages/ui/src/components/Checkout/CheckoutForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Drawer } from '@/ui/elements/Drawer';
import { LineItems } from '@/ui/elements/LineItems';
import { SegmentedControl } from '@/ui/elements/SegmentedControl';
import { Select, SelectButton, SelectOptionList } from '@/ui/elements/Select';
import { DevModeOverlay } from '@/ui/elements/DevModeNotice';
import { Tooltip } from '@/ui/elements/Tooltip';
import {
getCheckoutSeatUnitTotal,
Expand Down Expand Up @@ -370,17 +371,10 @@ export const PayWithTestPaymentMethod = () => {
flexDirection: 'column',
rowGap: t.space.$2,
position: 'relative',
overflow: 'hidden',
})}
>
<Box
sx={t => ({
position: 'absolute',
inset: 0,
background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`,
maskImage: `linear-gradient(transparent 20%, black)`,
pointerEvents: 'none',
})}
/>
<DevModeOverlay />
<Flex
sx={t => ({
alignItems: 'center',
Expand Down
12 changes: 3 additions & 9 deletions packages/ui/src/components/PaymentMethods/TestPaymentMethod.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DevModeOverlay } from '@/ui/elements/DevModeNotice';
import { LineItems } from '@/ui/elements/LineItems';

import { Box, localizationKeys, Text, useLocalizations } from '../../customizables';
Expand All @@ -17,17 +18,10 @@ export const TestPaymentMethod = () => {
flexDirection: 'column',
rowGap: t.space.$2,
position: 'relative',
overflow: 'hidden',
})}
>
<Box
sx={t => ({
position: 'absolute',
inset: 0,
background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`,
maskImage: `linear-gradient(transparent 20%, black)`,
pointerEvents: 'none',
})}
/>
<DevModeOverlay />
<Box
sx={{
display: 'flex',
Expand Down
6 changes: 2 additions & 4 deletions packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,18 @@ export const CardClerkAndPagesTag = React.memo(
{
width: '100%',
position: 'relative',
isolation: 'isolate',
},
outerSx,
]}
>
{withDevOverlay && <DevModeOverlay gradient={0} />}
{withDevOverlay && <DevModeOverlay />}
<Col
sx={t => ({
gap: displayConfig.branded || withFooterPages ? t.space.$2 : 0,
gap: displayConfig.branded || withFooterPages ? t.space.$4 : 0,
marginInline: 'auto',
width: '100%',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
position: 'relative',
})}
>
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/elements/Card/CardFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>((pro
withFooterPages={showSponsorAndLinks && !isProfileFooter}
devModeNoticeSx={t => ({
padding: t.space.$none,
// The footer adds $4 (16px) below this section; pull the notice down so it sits ~12px from the bottom.
marginBottom: `calc(${t.space.$1} * -1)`,
})}
outerSx={isProfileFooter ? profileCardFooterStyles : undefined}
withDevOverlay
Expand Down
10 changes: 10 additions & 0 deletions packages/ui/src/elements/Card/CardRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ const getFlushElements = (t: InternalTheme) => ({
borderTopWidth: 0,
padding: 0,
},
'& [data-clerk-dev-mode-overlay]': {
display: 'none',
},
'& [data-clerk-dev-mode-notice]': {
alignSelf: 'center',
marginBottom: `calc(${t.space.$2} * -1)`,
marginInline: 'auto',
padding: 0,
textAlign: 'center',
},
},
});

Expand Down
61 changes: 60 additions & 1 deletion packages/ui/src/elements/Card/__tests__/CardRoot.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { render } from '@testing-library/react';
import { ClerkInstanceContext } from '@clerk/shared/react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { beforeEach, describe, expect, it } from 'vitest';

import { EnvironmentProvider } from '@/contexts';
import { AppearanceProvider, useAppearance } from '@/customizables';
import type { ParsedAppearance } from '@/customizables/parseAppearance';
import { FlowMetadataProvider } from '@/elements/contexts';
import { ModalContext } from '@/elements/Modal';
import { InternalThemeProvider } from '@/styledSystem';

import { CardFooter } from '../CardFooter';
import { CardRoot } from '../CardRoot';

let captured: ParsedAppearance;
Expand Down Expand Up @@ -48,6 +51,35 @@ const renderCard = ({ elevation, globalAppearance, appearance, withModal }: Rend
);
};

const devEnvironment = {
displayConfig: {
branded: false,
showDevModeWarning: true,
},
isDevelopmentOrStaging: () => true,
};

const renderCardWithFooter = ({ appearance }: Pick<RenderCardOptions, 'appearance'> = {}) => {
return render(
<ClerkInstanceContext.Provider value={{ value: { client: {}, user: {} } as any }}>
<EnvironmentProvider value={devEnvironment as any}>
<AppearanceProvider
appearanceKey='signIn'
appearance={appearance as any}
>
<InternalThemeProvider>
<FlowMetadataProvider flow='signIn'>
<CardRoot>
<CardFooter />
</CardRoot>
</FlowMetadataProvider>
</InternalThemeProvider>
</AppearanceProvider>
</EnvironmentProvider>
</ClerkInstanceContext.Provider>,
);
};

describe('CardRoot augmentedAppearance — raised (default)', () => {
beforeEach(() => {
captured = undefined as any;
Expand Down Expand Up @@ -160,6 +192,33 @@ describe('CardRoot augmentedAppearance — flush', () => {
expect(captured.parsedElements[2].footer?.background).toBe('blue');
expect(captured.parsedElements[2].footer?.['>:first-of-type']?.padding).toBe('16px');
});

it('adds flush-only dev mode indicator overrides', () => {
renderCard({ elevation: 'flush' });
expect(captured.parsedElements[1].footer?.['& [data-clerk-dev-mode-overlay]']?.display).toBe('none');
expect(captured.parsedElements[1].footer?.['& [data-clerk-dev-mode-notice]']?.textAlign).toBe('center');
expect(captured.parsedElements[1].footer?.['& [data-clerk-dev-mode-notice]']?.padding).toBe(0);
});
});

describe('CardRoot dev mode indicator rendering', () => {
it('raised cards render both the patterned overlay and development mode text markers', () => {
const { container } = renderCardWithFooter();
const notice = screen.getByText('Development mode');

expect(notice.hasAttribute('data-clerk-dev-mode-notice')).toBe(true);
expect(container.querySelector('[data-clerk-dev-mode-overlay]')).not.toBeNull();
});

it('flush cards keep the development mode text and hide the patterned overlay through flush styling', () => {
const { container } = renderCardWithFooter({ appearance: { options: { elevation: 'flush' } } });
const notice = screen.getByText('Development mode');
const overlay = container.querySelector('[data-clerk-dev-mode-overlay]');

expect(notice.hasAttribute('data-clerk-dev-mode-notice')).toBe(true);
expect(overlay).not.toBeNull();
expect(getComputedStyle(overlay as HTMLElement).display).toBe('none');
});
});

describe('CardRoot elevation resolution priority', () => {
Expand Down
100 changes: 89 additions & 11 deletions packages/ui/src/elements/DevModeNotice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,52 @@ import type { ThemableCssProp } from '@/ui/styledSystem';
import { Box, Text } from '../customizables';
import { useDevMode } from '../hooks/useDevMode';

type DevModeOverlayProps = {
gradient?: number;
const DEV_MODE_GRID = {
width: 400,
height: 60,
squareSize: 1.5,
gap: 2,
minOpacity: 0.1,
maxOpacity: 0.7,
contrast: 2,
fadeWidth: 45, // % of width; horizontal radius of the oval — smaller pulls the sides in / curves them more
fadeHeight: 20, // px; vertical radius of the oval — smaller makes the top edge curve down more sharply
fadeCenterY: 85, // % of height; vertical center of the oval — 100 = on the bottom line; >100 pushes it below for a gentler top arc
fadeStrength: 0.98, // 0–1; how strongly the oval fade dims toward its edge (1 = fully transparent, 0 = no fade)
fadeCenter: 0.82, // 0–1; mask opacity at the very center — below 1 fades the middle a touch so the falloff is gentler
lineHeight: 1,
} as const;

const cellRandom = (x: number, y: number) => {
let h = (x * 374761393 + y * 668265263 + 2246822519) | 0;
h = (h ^ (h >>> 13)) * 1274126177;
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
};

const buildDevModeGridTile = (options: typeof DEV_MODE_GRID) => {
const step = options.squareSize + options.gap;
const tileWidth = Math.max(1, Math.round(options.width / step)) * step;
const tileHeight = options.height - options.lineHeight;
let rects = '';

for (let y = 0; y < tileHeight; y += step) {
for (let x = 0; x < tileWidth; x += step) {
const opacity =
options.minOpacity + (options.maxOpacity - options.minOpacity) * Math.pow(cellRandom(x, y), options.contrast);
rects += `<rect x="${x}" y="${y}" width="${options.squareSize}" height="${options.squareSize}" fill="#fff" opacity="${opacity.toFixed(2)}"/>`;
}
}

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${tileWidth}" height="${tileHeight}">${rects}</svg>`;
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
};

export const DevModeOverlay = (props: DevModeOverlayProps) => {
const { gradient = 60 } = props;
const gridTile = buildDevModeGridTile(DEV_MODE_GRID);
// A single oval mask: its curve fades the grid on the top AND the sides, so the top edge curves down toward the corners.
const gridMask = `radial-gradient(ellipse ${DEV_MODE_GRID.fadeWidth}% ${(DEV_MODE_GRID.fadeHeight / DEV_MODE_GRID.height) * 100}% at 50% ${DEV_MODE_GRID.fadeCenterY}%, rgba(0,0,0,${DEV_MODE_GRID.fadeCenter}) 0%, rgba(0,0,0,${1 - DEV_MODE_GRID.fadeStrength}) 100%)`;
const lineMask = 'linear-gradient(to right, transparent 2%, #000 50%, transparent 98%)';

export const DevModeOverlay = () => {
const { showDevModeNotice } = useDevMode();

if (!showDevModeNotice) {
Expand All @@ -17,15 +57,52 @@ export const DevModeOverlay = (props: DevModeOverlayProps) => {

return (
<Box
sx={t => ({
data-clerk-dev-mode-overlay=''
sx={{
userSelect: 'none',
pointerEvents: 'none',
inset: 0,
insetInline: 0,
bottom: 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)`,
})}
/>
height: DEV_MODE_GRID.height,
}}
>
<Box
sx={{
position: 'absolute',
inset: 0,
maskImage: gridMask,
WebkitMaskImage: gridMask,
}}
>
<Box
sx={t => ({
position: 'absolute',
top: 0,
insetInline: 0,
bottom: DEV_MODE_GRID.lineHeight,
backgroundColor: t.colors.$warning500,
maskImage: gridTile,
maskRepeat: 'repeat-x',
maskPosition: 'left bottom',
WebkitMaskImage: gridTile,
WebkitMaskRepeat: 'repeat-x',
WebkitMaskPosition: 'left bottom',
})}
/>
</Box>
<Box
sx={t => ({
position: 'absolute',
insetInline: 0,
bottom: 0,
height: DEV_MODE_GRID.lineHeight,
backgroundColor: t.colors.$warning500,
maskImage: lineMask,
WebkitMaskImage: lineMask,
})}
/>
</Box>
);
};

Expand All @@ -40,10 +117,11 @@ export const DevModeNotice = (props: DevModeNoticeProps) => {

return (
<Text
data-clerk-dev-mode-notice=''
variant='buttonSmall'
sx={[
t => ({
color: t.colors.$warning500,
fontWeight: t.fontWeights.$semibold,
padding: t.space.$1x5,
}),
sx,
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/elements/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ const NavbarContainer = (
sx={{
width: 'fit-content',
}}
devModeNoticeSx={t => ({
// Match the card footer so the notice sits the same distance from the bottom as sign-in/sign-up.
padding: t.space.$none,
marginBottom: `calc(${t.space.$1} * -1)`,
})}
/>
</Col>
);
Expand Down
Loading