diff --git a/.changeset/afraid-pumas-design.md b/.changeset/afraid-pumas-design.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/afraid-pumas-design.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/ClerkLogoIcon.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/ClerkLogoIcon.tsx new file mode 100644 index 0000000000..c963d1bfd7 --- /dev/null +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/ClerkLogoIcon.tsx @@ -0,0 +1,74 @@ +// Custom icon specifically used for the Keyless toast. Not intended to be used anywhere else + +export function ClerkLogoIcon() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/KeySlashIcon.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/KeySlashIcon.tsx new file mode 100644 index 0000000000..4cb57e3593 --- /dev/null +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/KeySlashIcon.tsx @@ -0,0 +1,22 @@ +// Custom icon specifically used for the Keyless toast. Not intended to be used anywhere else + +export function KeySlashIcon() { + return ( + + + + ); +} diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index 3c391ab441..3fa6ffd4ce 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -1,169 +1,396 @@ -import type { PointerEventHandler } from 'react'; -import { useCallback, useEffect, useRef } from 'react'; +// eslint-disable-next-line no-restricted-imports +import { css } from '@emotion/react'; +import { useState } from 'react'; -import type { LocalizationKey } from '../../customizables'; -import { Col, descriptors, Flex, Link, Text } from '../../customizables'; +import { descriptors, Flex, Link } from '../../customizables'; import { Portal } from '../../elements/Portal'; -import { InternalThemeProvider, mqu } from '../../styledSystem'; +import { InternalThemeProvider } from '../../styledSystem'; +import { ClerkLogoIcon } from './ClerkLogoIcon'; +import { KeySlashIcon } from './KeySlashIcon'; type KeylessPromptProps = { url?: string; }; -type FabContentProps = { title?: LocalizationKey | string; signOutText: LocalizationKey | string; url: string }; - -const FabContent = ({ title, signOutText, url }: FabContentProps) => { - return ( - ({ - width: '100%', - paddingLeft: t.sizes.$4, - paddingRight: t.sizes.$6, - whiteSpace: 'nowrap', - })} - > - - ({ - alignSelf: 'flex-start', - color: t.colors.$primary500, - ':hover': { - cursor: 'pointer', - }, - })} - localizationKey={signOutText} - onClick={ - () => (window.location.href = url) - // clerk-js has been loaded at this point so we can safely access session - // handleSignOutSessionClicked(session!) - } - /> - - ); -}; - -const _KeylessPrompt = (props: KeylessPromptProps) => { - // const { parsedInternalTheme } = useAppearance(); - const containerRef = useRef(null); - - //essentials for calcs - // const eyeWidth = parsedInternalTheme.sizes.$16; - // const eyeHeight = eyeWidth; - const topProperty = '--cl-impersonation-fab-top'; - const rightProperty = '--cl-impersonation-fab-right'; - const defaultTop = 109; - const defaultRight = 23; - - const handleResize = () => { - const current = containerRef.current; - if (!current) { - return; - } - - const offsetRight = window.innerWidth - current.offsetLeft - current.offsetWidth; - const offsetBottom = window.innerHeight - current.offsetTop - current.offsetHeight; - - const outsideViewport = [current.offsetLeft, offsetRight, current.offsetTop, offsetBottom].some(o => o < 0); - - if (outsideViewport) { - document.documentElement.style.setProperty(rightProperty, `${defaultRight}px`); - document.documentElement.style.setProperty(topProperty, `${defaultTop}px`); - } - }; - - const onPointerDown: PointerEventHandler = () => { - window.addEventListener('pointermove', onPointerMove); - window.addEventListener( - 'pointerup', - () => { - window.removeEventListener('pointermove', onPointerMove); - handleResize(); - }, - { once: true }, - ); - }; - - const onPointerMove = useCallback((e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - const current = containerRef.current; - if (!current) { - return; - } - const rightOffestBasedOnViewportAndContent = `${ - window.innerWidth - current.offsetLeft - current.offsetWidth - e.movementX - }px`; - document.documentElement.style.setProperty(rightProperty, rightOffestBasedOnViewportAndContent); - document.documentElement.style.setProperty(topProperty, `${current.offsetTop - -e.movementY}px`); - }, []); - - const repositionFabOnResize = () => { - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); - }; - }; - - useEffect(repositionFabOnResize, []); - - if (!props.url) { - return null; - } +const _KeylessPrompt = (_props: KeylessPromptProps) => { + const [isExpanded, setIsExpanded] = useState(true); + const handleFocus = () => setIsExpanded(true); return ( setIsExpanded(true)} + data-expanded={isExpanded} sx={t => ({ - touchAction: 'none', //for drag to work on mobile consistently position: 'fixed', - overflow: 'hidden', - top: `var(${topProperty}, ${defaultTop}px)`, - right: `var(${rightProperty}, ${defaultRight}px)`, - padding: `10px`, + bottom: '3.125rem', + right: '3.125rem', zIndex: t.zIndices.$fab, - boxShadow: t.shadows.$fabShadow, - borderRadius: t.radii.$halfHeight, //to match the circular eye perfectly - backgroundColor: t.colors.$white, + height: `${t.sizes.$10}`, + minWidth: '18.5625rem', + maxWidth: 'fit-content', + padding: `${t.space.$1x5} ${t.space.$1x5} ${t.space.$1x5} ${t.space.$3}`, + borderRadius: '1.25rem', fontFamily: t.fonts.$main, - ':hover': { - cursor: 'grab', - }, - ':hover #cl-impersonationText': { - transition: `max-width ${t.transitionDuration.$slowest} ease, opacity 0ms ease ${t.transitionDuration.$slowest}`, - maxWidth: `min(calc(50vw - 2 * ${defaultRight}px), ${15}ch)`, - [mqu.md]: { - maxWidth: `min(calc(100vw - 2 * ${defaultRight}px), ${15}ch)`, - }, - opacity: 1, + background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 100%), #1f1f1f', + boxShadow: + '0px 0px 0px 0.5px #2f3037 inset, 0px 1px 0px 0px rgba(255, 255, 255, 0.08) inset, 0px 0px 1px 1px rgba(255, 255, 255, 0.15) inset, 0px 0px 1px 0px rgba(255, 255, 255, 0.72), 0px 16px 36px -6px rgba(0, 0, 0, 0.36), 0px 6px 16px -2px rgba(0, 0, 0, 0.2)', + + transition: 'all 200ms cubic-bezier(0.3, 0.5, 0.1, 1)', + + '&[data-expanded="true"]': { + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'flex-start', + height: 'fit-content', + width: 'fit-content', + minWidth: '16.125rem', + gap: `${t.space.$1}`, + padding: `${t.space.$2x5} ${t.space.$3} 3.25rem ${t.space.$3}`, + borderRadius: `${t.radii.$xl}`, + transition: 'all 210ms cubic-bezier(0.4, 1, 0.20, 0.9)', }, })} > - 🔓Keyless Mode ({ - transition: `max-width ${t.transitionDuration.$slowest} ease, opacity ${t.transitionDuration.$fast} ease`, - maxWidth: '0px', - opacity: 1, - })} + sx={{ + width: '100%', + justifyContent: 'space-between', + alignItems: 'center', + }} > - + ({ + alignItems: 'center', + gap: t.space.$2, + })} + > +
+ + + +
+ +

+ Clerk is in keyless mode +

+
+ + {isExpanded && ( + + )}
+ + {isExpanded && ( +

+ We noticed your app was running without API Keys. Claim this instance by linking a Clerk account.{' '} + ({ + color: t.colors.$whiteAlpha600, + textDecoration: 'underline solid', + transition: `${t.transitionTiming.$common} ${t.transitionDuration.$fast}`, + ':hover': { + color: t.colors.$whiteAlpha800, + }, + })} + > + Learn more + +

+ )} +
);