From 96107cd9516f38d13e20ab11bfb5ac34837814da Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Sat, 24 Jan 2026 10:20:37 -0500 Subject: [PATCH 01/19] init --- .../devPrompts/KeylessPrompt/index.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index 9844b8ef814..df81c4c7b85 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -76,14 +76,6 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { }); }, [_props.copyKeysUrl]); - const getKeysUrlFromLastActive = useMemo(() => { - return withLastActiveFallback(() => { - const redirectUrlParts = handleDashboardUrlParsing(_props.copyKeysUrl); - const url = new URL(`${redirectUrlParts.baseDomain}/last-active?path=api-keys`); - return url.href; - }); - }, [_props.copyKeysUrl]); - const mainCTAStyles = css` ${basePromptElementStyles}; display: flex; @@ -131,10 +123,11 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { '&[data-expanded="true"]': { flexDirection: 'column', - alignItems: 'flex-center', - justifyContent: 'flex-center', - height: claimed || success ? 'fit-content' : isSignedIn ? '8.5rem' : '12rem', - overflow: 'hidden', + alignItems: 'flex-start', + justifyContent: 'flex-start', + // Use fit-content for height - this will be animated with interpolate-size in modern browsers + height: 'fit-content', + overflow: 'clip', width: 'fit-content', minWidth: '16.125rem', gap: `${t.space.$1x5}`, @@ -142,6 +135,11 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { borderRadius: `${t.radii.$xl}`, transition: 'all 230ms cubic-bezier(0.28, 1, 0.32, 1)', }, + + // Progressive enhancement for height: auto/fit-content transitions + '@supports (interpolate-size: allow-keywords)': { + interpolateSize: 'allow-keywords', + }, })} > + ) : ( + ({ + flexDirection: 'column', + alignItems: 'center', + gap: t.space.$2x5, + })} + > + - Dismiss - - ) : ( - ({ - flexDirection: 'column', - alignItems: 'center', - gap: t.space.$2x5, - })} - > - - {claimed ? 'Get API keys' : 'Claim application'} - - - ))} + } + `} + > + {claimed ? 'Get API keys' : 'Claim application'} + + + )} From 0a730a5a5ab76835abe552195ac84af291c83730 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Sat, 24 Jan 2026 10:59:07 -0500 Subject: [PATCH 05/19] wip --- packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index 1b9e6b1b022..d63524ffa3a 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -315,7 +315,8 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { flexDirection: 'column', gap: t.space.$3, opacity: isForcedExpanded ? 1 : 0, - transition: 'opacity 150ms ease-out', + // Fade in during expansion, finishing slightly after container completes + transition: 'opacity 250ms ease-out', transitionDelay: isForcedExpanded ? '50ms' : '0ms', })} > From 7f1a2f1b5f2986353ecbaa5864124a54289876ac Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Sat, 24 Jan 2026 11:00:25 -0500 Subject: [PATCH 06/19] wip --- .../ui/src/components/devPrompts/KeylessPrompt/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index d63524ffa3a..d9f32520419 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -127,9 +127,9 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { padding: `${t.space.$2} ${t.space.$3}`, borderRadius: '1.25rem', - // Transition all morphing properties at same rate + // Transition all morphing properties at same rate - snappier timing transition: - 'width 200ms ease-out, height 200ms ease-out, padding 200ms ease-out, border-radius 200ms ease-out, gap 200ms ease-out, background 200ms ease-out', + 'width 150ms ease-out, height 150ms ease-out, padding 150ms ease-out, border-radius 150ms ease-out, gap 150ms ease-out, background 150ms ease-out', '&[data-expanded="false"]:hover': { background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 0%, rgba(255, 255, 255, 0) 100%), #1f1f1f', @@ -316,8 +316,8 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { gap: t.space.$3, opacity: isForcedExpanded ? 1 : 0, // Fade in during expansion, finishing slightly after container completes - transition: 'opacity 250ms ease-out', - transitionDelay: isForcedExpanded ? '50ms' : '0ms', + transition: 'opacity 180ms ease-out', + transitionDelay: isForcedExpanded ? '30ms' : '0ms', })} >
Date: Sat, 24 Jan 2026 11:25:16 -0500 Subject: [PATCH 07/19] wip --- .../devPrompts/KeylessPrompt/index.tsx | 332 +++++++++--------- 1 file changed, 175 insertions(+), 157 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index d9f32520419..3347ff39ed0 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -28,6 +28,11 @@ const buttonIdentifierPrefix = `--clerk-keyless-prompt`; const buttonIdentifier = `${buttonIdentifierPrefix}-button`; const contentIdentifier = `${buttonIdentifierPrefix}-content`; +// Animation timing constants +const ANIMATION_DURATION = '150ms'; +const CONTENT_FADE_DURATION = '180ms'; +const CONTENT_FADE_DELAY = '30ms'; + /** * If we cannot reconstruct the url properly, then simply fallback to Clerk Dashboard */ @@ -55,6 +60,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { const appName = environment.displayConfig.applicationName; const isForcedExpanded = claimed || success || isExpanded; + const claimUrlToDashboard = useMemo(() => { if (claimed) { return _props.copyKeysUrl; @@ -76,6 +82,9 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { }); }, [_props.copyKeysUrl]); + // Determine CTA button color based on state + const ctaButtonColor = claimed || success ? 'white' : '#fde047'; + const mainCTAStyles = css` ${basePromptElementStyles}; display: flex; @@ -89,7 +98,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { font-size: 0.75rem; font-weight: 500; letter-spacing: 0.12px; - color: ${claimed ? 'white' : success ? 'white' : '#fde047'}; + color: ${ctaButtonColor}; text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.32); white-space: nowrap; user-select: none; @@ -103,6 +112,144 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { 0px 0px 4px 0px rgba(243, 107, 22, 0) inset; `; + // Determine CTA button hover styles + const ctaButtonHoverStyles = claimed + ? 'background: #4B4B4B; transition: all 120ms ease-in-out;' + : `box-shadow: + 0px 0px 6px 0px rgba(253, 224, 71, 0.24) inset, + 0px 0px 0px 1px rgba(255, 255, 255, 0.04) inset, + 0px 1px 0px 0px rgba(255, 255, 255, 0.04) inset, + 0px 0px 0px 1px rgba(0, 0, 0, 0.12), + 0px 1.5px 2px 0px rgba(0, 0, 0, 0.48);`; + + // Render the appropriate icon based on state + function renderStatusIcon() { + if (success) { + return ( + + ); + } + + if (claimed) { + return ( + + + + ); + } + + return ( +
+ + + + + + + +
+ ); + } + + // Get the status text based on state + function getStatusText() { + if (success) { + return 'Claim completed'; + } + if (claimed) { + return 'Missing environment keys'; + } + return 'Clerk is in keyless mode'; + } + + // Common paragraph styles for content text + const contentParagraphStyles = css` + ${basePromptElementStyles}; + color: #b4b4b4; + font-size: 0.8125rem; + font-weight: 400; + line-height: 1rem; + `; + + // Title text styles + const titleTextStyles = css` + ${basePromptElementStyles}; + color: #d9d9d9; + font-size: 0.875rem; + font-weight: 500; + white-space: nowrap; + cursor: pointer; + `; + return ( { padding: `${t.space.$2} ${t.space.$3}`, borderRadius: '1.25rem', - // Transition all morphing properties at same rate - snappier timing - transition: - 'width 150ms ease-out, height 150ms ease-out, padding 150ms ease-out, border-radius 150ms ease-out, gap 150ms ease-out, background 150ms ease-out', + // AIM transition - interpolate-size handles fit-content smoothly + transition: `width ${ANIMATION_DURATION} cubic-bezier(0.4, 0, 0.2, 1), + height ${ANIMATION_DURATION} cubic-bezier(0.4, 0, 0.2, 1), + padding ${ANIMATION_DURATION} cubic-bezier(0.4, 0, 0.2, 1), + border-radius ${ANIMATION_DURATION} cubic-bezier(0.4, 0, 0.2, 1), + gap ${ANIMATION_DURATION} cubic-bezier(0.4, 0, 0.2, 1), + background ${ANIMATION_DURATION} cubic-bezier(0.4, 0, 0.2, 1)`, '&[data-expanded="false"]:hover': { background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 0%, rgba(255, 255, 255, 0) 100%), #1f1f1f', }, '&[data-expanded="true"]': { - // Expanded: static width, dynamic height via fit-content + // Expanded: static width, dynamic height via fit-content (AIM technique) + // interpolate-size: allow-keywords enables smooth transition to fit-content width: '16.125rem', height: 'fit-content', gap: `${t.space.$1x5}`, @@ -165,108 +317,14 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { gap: t.space.$2, })} > - {success ? ( - - ) : claimed ? ( - - - - ) : ( -
- - - - - - - -
- )} + {renderStatusIcon()}

- {success ? 'Claim completed' : claimed ? 'Missing environment keys' : 'Clerk is in keyless mode'} + {getStatusText()}

@@ -315,9 +373,9 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { flexDirection: 'column', gap: t.space.$3, opacity: isForcedExpanded ? 1 : 0, - // Fade in during expansion, finishing slightly after container completes - transition: 'opacity 180ms ease-out', - transitionDelay: isForcedExpanded ? '30ms' : '0ms', + transition: `opacity ${CONTENT_FADE_DURATION} cubic-bezier(0.4, 0, 0.2, 1)`, + transitionDelay: isForcedExpanded ? CONTENT_FADE_DELAY : '0ms', + pointerEvents: isForcedExpanded ? 'auto' : 'none', })} >
{ `} > {success ? ( -

+

Your application{' '} {

) : claimed ? ( -

+

You claimed this application but haven't set keys in your environment. Get them from the Clerk Dashboard.

) : isSignedIn ? ( -

+

You've created your first user! Link this application to your Clerk account to explore the Dashboard. @@ -411,11 +445,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { <>

@@ -423,11 +453,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {

@@ -442,9 +468,11 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { {success ? ( + `} + > + + + - ({ - flexDirection: 'column', - gap: t.space.$3, - opacity: isForcedExpanded ? 1 : 0, - transition: `opacity ${CONTENT_FADE_DURATION} ${EASING_CURVE}`, - transitionDelay: isForcedExpanded ? CONTENT_FADE_DELAY : '0ms', - pointerEvents: isForcedExpanded ? 'auto' : 'none', - })} - > -

({ + flexDirection: 'column', + gap: t.space.$3, + opacity: isForcedExpanded ? 1 : 0, + transition: `opacity ${CONTENT_FADE_DURATION} ${EASING_CURVE}`, + transitionDelay: isForcedExpanded ? CONTENT_FADE_DELAY : '0ms', + pointerEvents: isForcedExpanded ? 'auto' : 'none', + // marginBlockEnd: `calc(-1 * ${t.space.$3})`, + // paddingBlockEnd: t.space.$3, + // maskImage: `linear-gradient(to bottom, black calc(100% - ${t.space.$3}), transparent)`, + })} >
- {success ? ( -

- Your application{' '} - - {appName} - {' '} - has been claimed. Configure settings from the{' '} - ({ - color: t.colors.$whiteAlpha600, - textDecoration: 'underline solid', - transition: `${t.transitionTiming.$common} ${t.transitionDuration.$fast}`, - ':hover': { - color: t.colors.$whiteAlpha800, - }, - })} - > - Clerk Dashboard - -

- ) : claimed ? ( -

- You claimed this application but haven't set keys in your environment. Get them from the Clerk - Dashboard. -

- ) : isSignedIn ? ( -

- - You've created your first user! Link this application to your Clerk account to explore the +

+ {success ? ( +

+ Your application{' '} + + {appName} + {' '} + has been claimed. Configure settings from the{' '} + ({ + color: t.colors.$whiteAlpha600, + textDecoration: 'underline solid', + transition: `${t.transitionTiming.$common} ${t.transitionDuration.$fast}`, + ':hover': { + color: t.colors.$whiteAlpha800, + }, + })} + > + Clerk Dashboard + +

+ ) : claimed ? ( +

+ You claimed this application but haven't set keys in your environment. Get them from the Clerk Dashboard. - -

- ) : ( - <> -

- Temporary API keys are enabled so you can get started immediately.

-

- Claim this application to access the Clerk Dashboard where you can manage auth settings and explore - more Clerk features. + ) : isSignedIn ? ( +

+ + You've created your first user! Link this application to your Clerk account to explore the + Dashboard. +

- - )} + ) : ( + <> +

+ Temporary API keys are enabled so you can get started immediately. +

+

+ Claim this application to access the Clerk Dashboard where you can manage auth settings and + explore more Clerk features. +

+ + )} +
-
- {success ? ( - - ) : ( - ({ - flexDirection: 'column', - alignItems: 'center', - gap: t.space.$2x5, - })} - > - { + void (async () => { + await _props.onDismiss?.(); + window.location.reload(); + })(); + }} css={css` ${mainCTAStyles}; - opacity: ${isForcedExpanded ? 1 : 0}; - ${isAnimating ? 'pointer-events: none; opacity: 0.6;' : ''} - transition: opacity ${isForcedExpanded ? CONTENT_FADE_DURATION : '80ms'} ${EASING_CURVE}; - transitiondelay: ${isForcedExpanded ? CONTENT_FADE_DELAY : '0ms'}; &:hover { - ${isAnimating ? '' : ctaButtonHoverStyles} + background: #4b4b4b; + transition: all 120ms ease-in-out; } `} > - {claimed ? 'Get API keys' : 'Claim application'} - - - )} + Dismiss + + ) : ( + ({ + flexDirection: 'column', + alignItems: 'center', + gap: t.space.$2x5, + })} + > + + {claimed ? 'Get API keys' : 'Claim application'} + + + )} + @@ -546,6 +553,315 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => { ); }; +function KeylessPromptInternal(_props: KeylessPromptProps) { + const [isOpen, setIsOpen] = useState(false); + const id = React.useId(); + return ( + + + {/* @property enables animating custom properties in gradients */} + +
+ + +
+
+
+
+
+

Temporary API keys are enabled so you can get started immediately.

+

+ Claim this application to access the Clerk Dashboard where you can manage auth settings and + explore more Clerk features. +

+
+ + + Claim application + + + +
+
+
+
+
+
+
+ ); +} + export const KeylessPrompt = (props: KeylessPromptProps) => ( From 7f368e1520ccdb39125fa28b3e3aada5c6ff45b5 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Sat, 24 Jan 2026 17:56:08 -0500 Subject: [PATCH 12/19] wip --- .../devPrompts/KeylessPrompt/index.tsx | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index 18bdf227d43..fa683c94790 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -556,18 +556,35 @@ const _KeylessPromptInternal = (_props: KeylessPromptProps) => { function KeylessPromptInternal(_props: KeylessPromptProps) { const [isOpen, setIsOpen] = useState(false); const id = React.useId(); + const containerRef = React.useRef(null); + + React.useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + setIsOpen(false); + } + }; + + const handleClickOutside = (e: MouseEvent) => { + if (isOpen && containerRef.current && !containerRef.current.contains(e.target as Node)) { + setIsOpen(false); + } + }; + + window.addEventListener('keydown', handleKeyDown); + document.addEventListener('mousedown', handleClickOutside); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); + return ( - {/* @property enables animating custom properties in gradients */} -
@@ -814,6 +843,7 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { rgb(108, 71, 255) 0px 0px 0px 1px, rgba(255, 255, 255, 0.07) 0px 1px 0px 0px inset, rgba(33, 33, 38, 0.2) 0px 1px 3px 0px; + outline: none; &::before { content: ''; position: absolute; @@ -822,6 +852,10 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { mix-blend-mode: overlay; border-radius: inherit; } + &:focus-visible { + outline: 2px solid #6c47ff; + outline-offset: 2px; + } `} > Date: Sat, 24 Jan 2026 18:03:09 -0500 Subject: [PATCH 13/19] wip --- .../components/devPrompts/KeylessPrompt/index.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index fa683c94790..93b43b2a334 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -381,7 +381,6 @@ const _KeylessPromptInternal = (_props: KeylessPromptProps) => { transition: `opacity ${CONTENT_FADE_DURATION} ${EASING_CURVE}`, transitionDelay: isForcedExpanded ? CONTENT_FADE_DELAY : '0ms', pointerEvents: isForcedExpanded ? 'auto' : 'none', - // marginBlockEnd: `calc(-1 * ${t.space.$3})`, // paddingBlockEnd: t.space.$3, // maskImage: `linear-gradient(to bottom, black calc(100% - ${t.space.$3}), transparent)`, })} @@ -736,6 +735,19 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { > Clerk is in keyless mode + + {isOpen ? 'Collapse' : 'Expand'} prompt content + Date: Sat, 24 Jan 2026 19:41:39 -0500 Subject: [PATCH 14/19] wip --- .../devPrompts/KeylessPrompt/index.tsx | 223 +++++++++--------- 1 file changed, 112 insertions(+), 111 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index 93b43b2a334..aca833b071e 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -585,17 +585,28 @@ function KeylessPromptInternal(_props: KeylessPromptProps) {
@@ -670,6 +680,7 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { --size: 1rem; width: var(--size); height: var(--size); + color: var(--foreground); `} viewBox='0 0 128 128' fill='none' @@ -679,36 +690,34 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { cx='64' cy='64' r='20' - fill='#fafafa' + fill='currentColor' /> @@ -756,7 +765,7 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { --size: 1rem; width: var(--size); height: var(--size); - color: white; + color: var(--foreground); margin-inline-start: auto; margin-inline-end: 0.75rem; opacity: ${isOpen ? 0.6 : 0}; @@ -783,7 +792,8 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { grid-template-rows: ${isOpen ? '1fr' : '0fr'}; contain: layout style; will-change: grid-template-rows; - transition: grid-template-rows ${isOpen ? '220ms' : '180ms'} var(--ease-bezier); + transition: grid-template-rows ${isOpen ? 'var(--duration-open)' : 'var(--duration-close)'} + var(--ease-bezier); @media (prefers-reduced-motion: reduce) { transition: none; } @@ -795,110 +805,101 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { css={css` overflow: hidden; min-height: 0; - mask-image: linear-gradient(to bottom, black calc(100% - 0.75rem), transparent); `} >
-
Temporary API keys are enabled so you can get started immediately.

+

+ Claim this application to access the Clerk Dashboard where you can manage auth settings and explore + more Clerk features. +

+
+ + -

Temporary API keys are enabled so you can get started immediately.

-

- Claim this application to access the Clerk Dashboard where you can manage auth settings and - explore more Clerk features. -

-
- + - Claim application - - - -
+ + +
From 435a1ca0136371fbacd9fc90998a80dae604de19 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Sat, 24 Jan 2026 19:58:42 -0500 Subject: [PATCH 15/19] wip --- .../devPrompts/KeylessPrompt/index.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index aca833b071e..d9c26028e6f 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -596,14 +596,14 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { --foreground-secondary: #b4b4b4; --accent: #6c47ff; --offset: 1.25rem; - --width-expanded: 18rem; - --width-collapsed: 13rem; + --width-opened: 18rem; + --width-closed: 13rem; position: fixed; bottom: var(--offset); right: var(--offset); z-index: 999999; height: auto; - width: ${isOpen ? 'var(--width-expanded)' : 'var(--width-collapsed)'}; + width: ${isOpen ? 'var(--width-opened)' : 'var(--width-closed)'}; interpolate-size: allow-keywords; background: linear-gradient(180deg, rgba(255, 255, 255, 0.01) 0%, rgba(255, 255, 255, 0) 100%), var(--background); @@ -615,16 +615,13 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { 0px 16px 36px -6px rgba(0, 0, 0, 0.36), 0px 6px 16px -2px rgba(0, 0, 0, 0.2); border-radius: ${isOpen ? '0.75rem' : '2.5rem'}; - contain: layout style paint; isolation: isolate; - will-change: width, height, border-radius; + will-change: width, border-radius; transform: translateZ(0); backface-visibility: hidden; transition: - height ${isOpen ? 'var(--duration-open)' : 'var(--duration-close)'} var(--ease-bezier), width ${isOpen ? 'var(--duration-open)' : 'var(--duration-close)'} var(--ease-bezier), - border-radius var(--duration-open) var(--ease-bezier), - background ${isOpen ? 'var(--duration-open)' : 'var(--duration-close)'} var(--ease-bezier); + border-radius var(--duration-open) var(--ease-bezier); @media (prefers-reduced-motion: reduce) { transition: none; } @@ -790,8 +787,6 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { css={css` display: grid; grid-template-rows: ${isOpen ? '1fr' : '0fr'}; - contain: layout style; - will-change: grid-template-rows; transition: grid-template-rows ${isOpen ? 'var(--duration-open)' : 'var(--duration-close)'} var(--ease-bezier); @media (prefers-reduced-motion: reduce) { @@ -805,11 +800,12 @@ function KeylessPromptInternal(_props: KeylessPromptProps) { css={css` overflow: hidden; min-height: 0; + mask-image: linear-gradient(to bottom, black calc(100% - 0.75rem), transparent); `} >
Date: Sat, 24 Jan 2026 20:16:40 -0500 Subject: [PATCH 16/19] slide in the line --- .../devPrompts/KeylessPrompt/index.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index d9c26028e6f..c13ffd7b427 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -675,6 +675,7 @@ function KeylessPromptInternal(_props: KeylessPromptProps) {
Date: Tue, 27 Jan 2026 15:56:21 -0500 Subject: [PATCH 17/19] feat(ui): Extract mosaic theme (#7676) --- .../devPrompts/KeylessPrompt/index.tsx | 1146 ++++++----------- packages/ui/src/mosaic/theme-provider.tsx | 19 + packages/ui/src/mosaic/theme.ts | 265 ++++ 3 files changed, 691 insertions(+), 739 deletions(-) create mode 100644 packages/ui/src/mosaic/theme-provider.tsx create mode 100644 packages/ui/src/mosaic/theme.ts diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index c13ffd7b427..429fba50636 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -1,21 +1,13 @@ import { useUser } from '@clerk/shared/react'; // eslint-disable-next-line no-restricted-imports import { css } from '@emotion/react'; -import type { PropsWithChildren } from 'react'; -import React, { useEffect, useMemo, useState } from 'react'; -import { createPortal } from 'react-dom'; +import React, { useMemo, useState } from 'react'; -import { Flex, Link } from '../../../customizables'; +import { Link } from '../../../customizables'; import { Portal } from '../../../elements/Portal'; +import { MosaicThemeProvider, useMosaicTheme } from '../../../mosaic/theme-provider'; import { InternalThemeProvider } from '../../../styledSystem'; -import { - basePromptElementStyles, - ClerkLogoIcon, - handleDashboardUrlParsing, - PromptContainer, - PromptSuccessIcon, -} from '../shared'; -import { KeySlashIcon } from './KeySlashIcon'; +import { handleDashboardUrlParsing } from '../shared'; import { useRevalidateEnvironment } from './use-revalidate-environment'; type KeylessPromptProps = { @@ -24,16 +16,6 @@ type KeylessPromptProps = { onDismiss: (() => Promise) | undefined | null; }; -const buttonIdentifierPrefix = `--clerk-keyless-prompt`; -const buttonIdentifier = `${buttonIdentifierPrefix}-button`; -const contentIdentifier = `${buttonIdentifierPrefix}-content`; - -// Animation timing constants -const ANIMATION_DURATION = '200ms'; -const CONTENT_FADE_DURATION = '200ms'; -const CONTENT_FADE_DELAY = '40ms'; -const EASING_CURVE = 'cubic-bezier(0.2, 0, 0, 1)'; - /** * If we cannot reconstruct the url properly, then simply fallback to Clerk Dashboard */ @@ -45,321 +27,310 @@ function withLastActiveFallback(cb: () => string): string { } } -const _KeylessPromptInternal = (_props: KeylessPromptProps) => { - const { isSignedIn } = useUser(); - const [isExpanded, setIsExpanded] = useState(false); - const [isAnimating, setIsAnimating] = useState(false); - - useEffect(() => { - if (isSignedIn) { - setIsExpanded(true); - } - }, [isSignedIn]); - - // Track animation state to prevent interactions during transition - useEffect(() => { - if (!isAnimating) { - return; - } - const timer = setTimeout(() => setIsAnimating(false), 200); - return () => clearTimeout(timer); - }, [isAnimating]); +function KeylessPromptInternal(props: KeylessPromptProps) { + return ( + + + + ); +} +function KeylessPromptContent(props: KeylessPromptProps) { + const { isSignedIn } = useUser(); const environment = useRevalidateEnvironment(); const claimed = Boolean(environment.authConfig.claimedAt); - const success = typeof _props.onDismiss === 'function' && claimed; + const success = typeof props.onDismiss === 'function' && claimed; const appName = environment.displayConfig.applicationName; + const isLocked = claimed || success; + + const [isOpen, setIsOpen] = useState(isSignedIn || isLocked); + const [hasMounted, setHasMounted] = useState(false); + const id = React.useId(); + const containerRef = React.useRef(null); + const theme = useMosaicTheme(); + + React.useEffect(() => { + setHasMounted(true); + }, []); + + React.useEffect(() => { + if (hasMounted && isSignedIn) { + setIsOpen(true); + } + }, [hasMounted, isSignedIn]); - const isForcedExpanded = claimed || success || isExpanded; + React.useEffect(() => { + if (isLocked) { + setIsOpen(true); + } + }, [isLocked]); const claimUrlToDashboard = useMemo(() => { if (claimed) { - return _props.copyKeysUrl; + return props.copyKeysUrl; } - const url = new URL(_props.claimUrl); + const url = new URL(props.claimUrl); // Clerk Dashboard accepts a `return_url` query param when visiting `/apps/claim`. url.searchParams.append('return_url', window.location.href); return url.href; - }, [claimed, _props.copyKeysUrl, _props.claimUrl]); + }, [claimed, props.copyKeysUrl, props.claimUrl]); const instanceUrlToDashboard = useMemo(() => { return withLastActiveFallback(() => { - const redirectUrlParts = handleDashboardUrlParsing(_props.copyKeysUrl); + const redirectUrlParts = handleDashboardUrlParsing(props.copyKeysUrl); const url = new URL( `${redirectUrlParts.baseDomain}/apps/${redirectUrlParts.appId}/instances/${redirectUrlParts.instanceId}/user-authentication/email-phone-username`, ); return url.href; }); - }, [_props.copyKeysUrl]); - - const ctaButtonColor = claimed || success ? 'white' : '#fde047'; + }, [props.copyKeysUrl]); - const mainCTAStyles = css` - ${basePromptElementStyles}; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 1.75rem; - max-width: 14.625rem; - padding: 0.25rem 0.625rem; - border-radius: 0.375rem; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.12px; - color: ${ctaButtonColor}; - text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.32); - white-space: nowrap; - user-select: none; - cursor: pointer; - background: linear-gradient(180deg, rgba(0, 0, 0, 0) 30.5%, rgba(0, 0, 0, 0.05) 100%), #454545; - box-shadow: - 0px 0px 0px 1px rgba(255, 255, 255, 0.04) inset, - 0px 1px 0px 0px rgba(255, 255, 255, 0.04) inset, - 0px 0px 0px 1px rgba(0, 0, 0, 0.12), - 0px 1.5px 2px 0px rgba(0, 0, 0, 0.48), - 0px 0px 4px 0px rgba(243, 107, 22, 0) inset; - `; - - const ctaButtonHoverStyles = claimed - ? 'background: #4B4B4B; transition: all 120ms ease-in-out;' - : `box-shadow: - 0px 0px 6px 0px rgba(253, 224, 71, 0.24) inset, - 0px 0px 0px 1px rgba(255, 255, 255, 0.04) inset, - 0px 1px 0px 0px rgba(255, 255, 255, 0.04) inset, - 0px 0px 0px 1px rgba(0, 0, 0, 0.12), - 0px 1.5px 2px 0px rgba(0, 0, 0, 0.48);`; - - function renderStatusIcon() { + function getStatusText() { if (success) { - return ( - - ); + return 'Claim completed'; } - if (claimed) { - return ( - - - - ); + return 'Missing environment keys'; + } + return 'Clerk is in keyless mode'; + } + + React.useEffect(() => { + if (isLocked) { + return; } - return ( + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + setIsOpen(false); + } + }; + + const handleClickOutside = (e: MouseEvent) => { + if (isOpen && containerRef.current && !containerRef.current.contains(e.target as Node)) { + setIsOpen(false); + } + }; + + window.addEventListener('keydown', handleKeyDown); + document.addEventListener('mousedown', handleClickOutside); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen, isLocked]); + + return ( +
- { + if (!isLocked) { + setIsOpen(prev => !prev); + } + }} + aria-expanded={isOpen} + aria-controls={id} css={css` - position: absolute; - width: 100%; - height: 100%; - backface-visibility: hidden; + box-sizing: border-box; + appearance: none; + border: none; + background-color: transparent; display: flex; align-items: center; - justify-content: center; - `} - > - - - - - - -
- ); - } - - function getStatusText() { - if (success) { - return 'Claim completed'; - } - if (claimed) { - return 'Missing environment keys'; - } - return 'Clerk is in keyless mode'; - } - - const contentParagraphStyles = css` - ${basePromptElementStyles}; - color: #b4b4b4; - font-size: 0.8125rem; - font-weight: 400; - line-height: 1rem; - `; - - const titleTextStyles = css` - ${basePromptElementStyles}; - color: #d9d9d9; - font-size: 0.875rem; - font-weight: 500; - white-space: nowrap; - cursor: pointer; - `; - - return ( - - ({ - interpolateSize: 'allow-keywords', - position: 'fixed', - bottom: '1.25rem', - right: '1.25rem', - flexDirection: 'column', - alignItems: 'flex-start', - justifyContent: 'flex-start', - overflow: 'clip', - width: '13.5rem', - height: '2.375rem', - borderRadius: '1.25rem', - transition: `width ${ANIMATION_DURATION} ${EASING_CURVE}, - height ${ANIMATION_DURATION} ${EASING_CURVE}, - padding ${ANIMATION_DURATION} ${EASING_CURVE}, - border-radius ${ANIMATION_DURATION} ${EASING_CURVE}, - background ${ANIMATION_DURATION} ${EASING_CURVE}`, - - '&[data-expanded="false"]:hover': { - background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 0%, rgba(255, 255, 255, 0) 100%), #1f1f1f', - }, - - '&[data-expanded="true"]': { - width: '16.125rem', - height: 'fit-content', - borderRadius: `${t.radii.$xl}`, - }, - })} - > - ({ - padding: `${t.space.$2} ${t.space.$3}`, - maskImage: `linear-gradient(to bottom, black calc(100% - ${t.space.$3}), transparent)`, - flexDirection: 'column', - })} - > - + )} + - ({ - flexDirection: 'column', - gap: t.space.$3, - opacity: isForcedExpanded ? 1 : 0, - transition: `opacity ${CONTENT_FADE_DURATION} ${EASING_CURVE}`, - transitionDelay: isForcedExpanded ? CONTENT_FADE_DELAY : '0ms', - pointerEvents: isForcedExpanded ? 'auto' : 'none', - // paddingBlockEnd: t.space.$3, - // maskImage: `linear-gradient(to bottom, black calc(100% - ${t.space.$3}), transparent)`, - })} +
+
p': { + boxSizing: 'border-box', + margin: 0, + padding: 0, + textBoxTrim: 'trim-both', + }, + }} > {success ? ( -

+

Your application{' '} {appName} @@ -437,439 +438,124 @@ const _KeylessPromptInternal = (_props: KeylessPromptProps) => {

) : claimed ? ( -

+

You claimed this application but haven't set keys in your environment. Get them from the Clerk Dashboard.

) : isSignedIn ? ( -

- - You've created your first user! Link this application to your Clerk account to explore the - Dashboard. - +

+ You've created your first user! Link this application to your Clerk account to explore the + Dashboard.

) : ( <> -

- Temporary API keys are enabled so you can get started immediately. -

-

+

Temporary API keys are enabled so you can get started immediately.

+

Claim this application to access the Clerk Dashboard where you can manage auth settings and explore more Clerk features.

)}
-
- - {success ? ( - - ) : ( - ({ - flexDirection: 'column', - alignItems: 'center', - gap: t.space.$2x5, - })} - > - { + void props.onDismiss?.().then(() => { + window.location.reload(); + }); + }} css={css` - ${mainCTAStyles}; - opacity: ${isForcedExpanded ? 1 : 0}; - ${isAnimating ? 'pointer-events: none; opacity: 0.6;' : ''} - transition: opacity ${isForcedExpanded ? CONTENT_FADE_DURATION : '80ms'} ${EASING_CURVE}; - transitiondelay: ${isForcedExpanded ? CONTENT_FADE_DELAY : '0ms'}; - + box-sizing: border-box; + position: relative; + display: flex; + align-items: center; + justify-content: center; + gap: ${theme.spacing[1]}; + width: 100%; + height: ${theme.spacing[7]}; + border-radius: ${theme.spacing[1.5]}; + font-size: ${theme.typography.label[3].fontSize}; + font-weight: ${theme.fontWeights.medium}; + color: var(--foreground); + background: var(--accent); + box-shadow: + ${theme.colors.white} 0px 0px 0px 0px, + var(--accent) 0px 0px 0px 1px, + ${theme.alpha(theme.colors.white, 7)} 0px 1px 0px 0px inset, + ${theme.alpha(theme.colors.gray[1300], 20)} 0px 1px 3px 0px; + outline: none; + border: none; + margin: 0; + padding: 0; + cursor: pointer; + &::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient( + 180deg, + ${theme.alpha(theme.colors.white, 16)} 46%, + ${theme.alpha(theme.colors.white, 0)} 54% + ); + mix-blend-mode: overlay; + border-radius: inherit; + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } &:hover { - ${isAnimating ? '' : ctaButtonHoverStyles} + background: ${theme.colors.gray[1200]}; + transition: all 120ms ease-in-out; } `} > - {claimed ? 'Get API keys' : 'Claim application'} - - - )} - - - - - - Skip to Clerk keyless mode content - - - - ); -}; - -function KeylessPromptInternal(_props: KeylessPromptProps) { - const [isOpen, setIsOpen] = useState(false); - const id = React.useId(); - const containerRef = React.useRef(null); - - React.useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape' && isOpen) { - setIsOpen(false); - } - }; - - const handleClickOutside = (e: MouseEvent) => { - if (isOpen && containerRef.current && !containerRef.current.contains(e.target as Node)) { - setIsOpen(false); - } - }; - - window.addEventListener('keydown', handleKeyDown); - document.addEventListener('mousedown', handleClickOutside); - - return () => { - window.removeEventListener('keydown', handleKeyDown); - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [isOpen]); - - return ( - - -
- - -
-
-
-
-

Temporary API keys are enabled so you can get started immediately.

-

- Claim this application to access the Clerk Dashboard where you can manage auth settings and explore - more Clerk features. -

-
+ + Dismiss + + + ) : ( - Claim application + {claimed ? 'Get API keys' : 'Claim application'} -
+ )}
-
-
+
+ ); } @@ -922,20 +607,3 @@ export const KeylessPrompt = (props: KeylessPromptProps) => ( ); - -const BodyPortal = ({ children }: PropsWithChildren) => { - const [portalContainer, setPortalContainer] = useState(null); - - useEffect(() => { - const container = document.createElement('div'); - setPortalContainer(container); - document.body.insertBefore(container, document.body.firstChild); - return () => { - if (container) { - document.body.removeChild(container); - } - }; - }, []); - - return portalContainer ? createPortal(children, portalContainer) : null; -}; diff --git a/packages/ui/src/mosaic/theme-provider.tsx b/packages/ui/src/mosaic/theme-provider.tsx new file mode 100644 index 00000000000..f8b25690e5f --- /dev/null +++ b/packages/ui/src/mosaic/theme-provider.tsx @@ -0,0 +1,19 @@ +// eslint-disable-next-line no-restricted-imports +import createCache from '@emotion/cache'; +// eslint-disable-next-line no-restricted-imports +import { CacheProvider, ThemeContext } from '@emotion/react'; +import React from 'react'; + +import { type MosaicTheme, mosaicTheme } from './theme'; + +const mosaicCache = createCache({ key: 'mosaic' }); + +export const MosaicThemeProvider = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +export const useMosaicTheme = () => React.useContext(ThemeContext) as MosaicTheme; diff --git a/packages/ui/src/mosaic/theme.ts b/packages/ui/src/mosaic/theme.ts new file mode 100644 index 00000000000..97a6a2bef71 --- /dev/null +++ b/packages/ui/src/mosaic/theme.ts @@ -0,0 +1,265 @@ +// Ceramic Colors (from CSS variables) +const gray = { + 50: '#fafafb', + 100: '#f6f6f7', + 200: '#ececee', + 300: '#dbdbe0', + 400: '#c7c7cf', + 500: '#adadb7', + 600: '#90909d', + 700: '#767684', + 800: '#5f5f6f', + 900: '#4c4c5c', + 1000: '#3d3d4a', + 1100: '#33333e', + 1200: '#2b2b34', + 1300: '#232328', + 1400: '#1b1b1f', + 1500: '#111113', +} as const; + +const purple = { + 50: '#f5f3ff', + 100: '#e3e0ff', + 200: '#ccc8ff', + 300: '#bab0ff', + 400: '#a698ff', + 500: '#9280ff', + 600: '#846bff', + 700: '#6c47ff', + 800: '#5f15fe', + 900: '#4d06d1', + 1000: '#3707a6', + 1100: '#27057c', + 1200: '#1c045f', + 1300: '#16034b', +} as const; + +const green = { + 50: '#effdf1', + 100: '#aff9bf', + 200: '#65f088', + 300: '#49dc6e', + 400: '#31c854', + 500: '#1eb43c', + 600: '#199d34', + 700: '#15892b', + 800: '#107524', + 900: '#09661c', + 1000: '#0b5619', + 1100: '#0c4919', + 1200: '#0c3c18', + 1300: '#053211', +} as const; + +const red = { + 50: '#fef8f8', + 100: '#fedddd', + 200: '#fec4c4', + 300: '#fca9a9', + 400: '#f98a8a', + 500: '#f86969', + 600: '#f73d3d', + 700: '#e02e2e', + 800: '#c22a2a', + 900: '#aa1b1b', + 1000: '#921414', + 1100: '#7a1313', + 1200: '#651414', + 1300: '#550e0e', + 1400: '#3d0101', + 1500: '#2d0101', +} as const; + +const orange = { + 50: '#fff8f2', + 100: '#ffe4c4', + 200: '#fecc9f', + 300: '#feb166', + 400: '#fd9357', + 500: '#fd7224', + 600: '#e06213', + 700: '#c3540f', + 800: '#a8470c', + 900: '#9d3405', + 1000: '#8a2706', + 1100: '#75220b', + 1200: '#5f1e0c', + 1300: '#50170a', +} as const; + +const yellow = { + 50: '#fefbdc', + 100: '#f7ed55', + 200: '#e5d538', + 300: '#d7be35', + 400: '#c0aa18', + 500: '#bd9005', + 600: '#a47c04', + 700: '#8d6b03', + 800: '#775902', + 900: '#674401', + 1000: '#563202', + 1100: '#412303', + 1200: '#321904', + 1300: '#2a1203', +} as const; + +const blue = { + 50: '#f6faff', + 100: '#daeafe', + 200: '#b4d5fe', + 300: '#8dc2fd', + 400: '#73acfa', + 500: '#6694f8', + 600: '#307ff6', + 700: '#236dd7', + 800: '#1c5bb6', + 900: '#1744a6', + 1000: '#0f318e', + 1100: '#0e2369', + 1200: '#0b1c49', + 1300: '#0c1637', +} as const; + +// Typography - Labels +const label = { + 1: { fontSize: '1rem', lineHeight: '1.375rem', fontWeight: 500 }, + 2: { fontSize: '0.875rem', lineHeight: '1.25rem', fontWeight: 500 }, + 3: { fontSize: '0.75rem', lineHeight: '1rem', fontWeight: 500 }, + 4: { + fontSize: '0.6875rem', + lineHeight: '0.875rem', + fontWeight: 500, + letterSpacing: '0.015em', + }, + 5: { fontSize: '0.625rem', lineHeight: '0.8125rem', fontWeight: 500 }, +} as const; + +// Typography - Headings +const heading = { + 1: { + fontSize: '2.25rem', + lineHeight: '2.5rem', + fontWeight: 500, + letterSpacing: '-0.02em', + }, + 2: { + fontSize: '2rem', + lineHeight: '2.25rem', + fontWeight: 500, + letterSpacing: '-0.02em', + }, + 3: { + fontSize: '1.75rem', + lineHeight: '2.125rem', + fontWeight: 500, + letterSpacing: '-0.015em', + }, + 4: { + fontSize: '1.5rem', + lineHeight: '2rem', + fontWeight: 500, + letterSpacing: '-0.01em', + }, + 5: { + fontSize: '1.25rem', + lineHeight: '1.75rem', + fontWeight: 500, + letterSpacing: '-0.01em', + }, + 6: { fontSize: '1.0625rem', lineHeight: '1.5rem', fontWeight: 500 }, +} as const; + +// Typography - Body +const body = { + 1: { fontSize: '1rem', lineHeight: '1.375rem', fontWeight: 400 }, + 2: { fontSize: '0.875rem', lineHeight: '1.25rem', fontWeight: 400 }, + 3: { + fontSize: '0.75rem', + lineHeight: '1rem', + fontWeight: 400, + letterSpacing: '0.01em', + }, + 4: { fontSize: '0.6875rem', lineHeight: '0.875rem', fontWeight: 400 }, +} as const; + +const fontWeights = { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, +} as const; + +const fontFamilies = { + sans: 'system-ui, sans-serif', + mono: 'ui-monospace, monospace', +} as const; + +// Spacing (Tailwind-compatible scale, 1 unit = 0.25rem = 4px) +const spacing = { + 0: '0', + px: '1px', + 0.5: '0.125rem', + 1: '0.25rem', + 1.5: '0.375rem', + 2: '0.5rem', + 2.5: '0.625rem', + 3: '0.75rem', + 3.5: '0.875rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', + 11: '2.75rem', + 12: '3rem', + 14: '3.5rem', + 16: '4rem', + 20: '5rem', + 24: '6rem', + 28: '7rem', + 32: '8rem', + 36: '9rem', + 40: '10rem', + 44: '11rem', + 48: '12rem', + 52: '13rem', + 56: '14rem', + 60: '15rem', + 64: '16rem', + 72: '18rem', + 80: '20rem', + 96: '24rem', +} as const; + +const alpha = (color: string, opacity: number) => `color-mix(in srgb, ${color} ${opacity}%, transparent)`; +const negative = (value: string) => `-${value}`; + +export const mosaicTheme = { + colors: { + gray, + purple, + green, + red, + orange, + yellow, + blue, + white: '#fff', + black: '#000', + }, + typography: { + label, + heading, + body, + }, + fontWeights, + fontFamilies, + spacing, + alpha, + negative, +} as const; + +export type MosaicTheme = typeof mosaicTheme; From af74016c8189ecdb87f3504a9b239b023c5ed030 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 27 Jan 2026 16:30:09 -0500 Subject: [PATCH 18/19] remove internalthemeprovider --- .../devPrompts/KeylessPrompt/index.tsx | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index 429fba50636..f4ee1f6bde4 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -3,10 +3,8 @@ import { useUser } from '@clerk/shared/react'; import { css } from '@emotion/react'; import React, { useMemo, useState } from 'react'; -import { Link } from '../../../customizables'; import { Portal } from '../../../elements/Portal'; import { MosaicThemeProvider, useMosaicTheme } from '../../../mosaic/theme-provider'; -import { InternalThemeProvider } from '../../../styledSystem'; import { handleDashboardUrlParsing } from '../shared'; import { useRevalidateEnvironment } from './use-revalidate-environment'; @@ -28,14 +26,6 @@ function withLastActiveFallback(cb: () => string): string { } function KeylessPromptInternal(props: KeylessPromptProps) { - return ( - - - - ); -} - -function KeylessPromptContent(props: KeylessPromptProps) { const { isSignedIn } = useUser(); const environment = useRevalidateEnvironment(); const claimed = Boolean(environment.authConfig.claimedAt); @@ -421,21 +411,16 @@ function KeylessPromptContent(props: KeylessPromptProps) { {appName} {' '} has been claimed. Configure settings from the{' '} - ({ - color: t.colors.$whiteAlpha600, - textDecoration: 'underline solid', - transition: `${t.transitionTiming.$common} ${t.transitionDuration.$fast}`, - ':hover': { - color: t.colors.$whiteAlpha800, - }, - })} + target='_blank' + rel='noopener noreferrer' + css={css` + text-decoration: underline; + `} > Clerk Dashboard - +

) : claimed ? (

@@ -603,7 +588,7 @@ function KeylessPromptContent(props: KeylessPromptProps) { } export const KeylessPrompt = (props: KeylessPromptProps) => ( - + - + ); From 07d970f5b2dabeabf416b44d6a76588f8b01a3fa Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 27 Jan 2026 16:37:21 -0500 Subject: [PATCH 19/19] fix font-smoothing --- packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx index f4ee1f6bde4..77be3ce743f 100644 --- a/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/KeylessPrompt/index.tsx @@ -128,6 +128,8 @@ function KeylessPromptInternal(props: KeylessPromptProps) { --offset: ${theme.spacing[5]}; --width-opened: 18rem; --width-closed: 13rem; + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; position: fixed; bottom: var(--offset); right: var(--offset);