From 8db72d100fb3835a4efba4347f0450028311abff Mon Sep 17 00:00:00 2001 From: Meryem Kaftar Date: Tue, 10 Dec 2024 03:21:44 -0800 Subject: [PATCH] feat(clerk-js): Add static toast UI for Keyless mode (#4658) Co-authored-by: Alex Carpenter --- .changeset/afraid-pumas-design.md | 2 + .../KeylessPrompt/ClerkLogoIcon.tsx | 74 +++ .../components/KeylessPrompt/KeySlashIcon.tsx | 22 + .../src/ui/components/KeylessPrompt/index.tsx | 513 +++++++++++++----- 4 files changed, 468 insertions(+), 143 deletions(-) create mode 100644 .changeset/afraid-pumas-design.md create mode 100644 packages/clerk-js/src/ui/components/KeylessPrompt/ClerkLogoIcon.tsx create mode 100644 packages/clerk-js/src/ui/components/KeylessPrompt/KeySlashIcon.tsx 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 + +

+ )} +
);