diff --git a/.changeset/short-news-enjoy.md b/.changeset/short-news-enjoy.md new file mode 100644 index 00000000000..11230f82d56 --- /dev/null +++ b/.changeset/short-news-enjoy.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Improve accessibility of the Keyless prompt. diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 664f087bf91..5bab893c45a 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -18,6 +18,6 @@ { "path": "./dist/userverification*.js", "maxSize": "5KB" }, { "path": "./dist/onetap*.js", "maxSize": "1KB" }, { "path": "./dist/waitlist*.js", "maxSize": "1.3KB" }, - { "path": "./dist/keylessPrompt*.js", "maxSize": "4.5KB" } + { "path": "./dist/keylessPrompt*.js", "maxSize": "4.9KB" } ] } diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index 7e0e7321491..499c6d9de37 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -1,9 +1,10 @@ -import { useClerk } from '@clerk/shared/react'; // eslint-disable-next-line no-restricted-imports import { css } from '@emotion/react'; -import { useState } from 'react'; +import type { PropsWithChildren } from 'react'; +import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; -import { descriptors, Flex, Link, Spinner } from '../../customizables'; +import { Flex, Link } from '../../customizables'; import { Portal } from '../../elements/Portal'; import { InternalThemeProvider } from '../../styledSystem'; import { ClerkLogoIcon } from './ClerkLogoIcon'; @@ -15,25 +16,23 @@ type KeylessPromptProps = { copyKeysUrl: string; }; +const buttonIdentifierPrefix = `--clerk-keyless-prompt`; +const buttonIdentifier = `${buttonIdentifierPrefix}-button`; +const contentIdentifier = `${buttonIdentifierPrefix}-content`; + const _KeylessPrompt = (_props: KeylessPromptProps) => { const [isExpanded, setIsExpanded] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const handleFocus = () => setIsExpanded(true); - const claimed = Boolean(useRevalidateEnvironment().authConfig.claimedAt); - const clerk = useClerk(); return ( setIsExpanded(true)} data-expanded={isExpanded} + align='center' sx={t => ({ position: 'fixed', - bottom: '3.125rem', - right: '3.125rem', + bottom: '1.25rem', + right: '1.25rem', zIndex: t.zIndices.$fab, height: `${t.sizes.$10}`, minWidth: '18.5625rem', @@ -44,8 +43,7 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { 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)', + transition: 'all 325ms cubic-bezier(0.18, 0.98, 0.1, 1)', '&[data-expanded="true"]': { flexDirection: 'column', @@ -57,16 +55,22 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { gap: `${t.space.$1x5}`, 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)', + transition: 'all 205ms cubic-bezier(0.4, 1, 0.20, 0.9)', }, })} > - !claimed && setIsExpanded(prev => !prev)} + css={css` + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + `} > ({ @@ -99,26 +103,29 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { width: 1rem; height: 1rem; transform-style: preserve-3d; - animation: ${isExpanded ? 'coinFlipAnimation 6s infinite linear' : ' none'}; + animation: ${isExpanded ? 'coinFlipAnimation 12s infinite linear' : ' none'}; @keyframes coinFlipAnimation { 0%, - 40% { + 70% { transform: rotateY(0); } - 50%, - 90% { + 75%, + 95% { transform: rotateY(180deg); } 100% { transform: rotateY(0); } } + @media (prefers-reduced-motion: reduce) { + animation: none; + } `} > - {isExpanded && !claimed && ( - - )} - + to { + transform: scaleX(1); + opacity: 1; + } + } + `} + > + + + - {isExpanded && ( + - + {claimed ? 'Get API keys' : 'Claim keys'} + + + + Skip to Clerk keyless mode content + + ); }; @@ -456,3 +498,21 @@ export const KeylessPrompt = (props: KeylessPromptProps) => ( <_KeylessPrompt {...props} /> ); + +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); + } + }; + }, []); + + // Render the children inside the dynamically created div + return portalContainer ? createPortal(children, portalContainer) : null; +};