Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(clerk-js): Improve keyless prompt accessibility #4806

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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/short-news-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Improve keyless prompt accessibility based on feedback
7 changes: 7 additions & 0 deletions packages/clerk-js/sandbox/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@
</script>
</head>
<body class="flex min-h-full flex-col overflow-x-hidden bg-gray-50">
<a
href="#keyless-prompt-button"
class="fixed -left-[999px] top-4 z-[999999] rounded-md bg-white px-2 py-1 text-sm text-black underline focus:left-4 focus:outline focus:outline-2 focus:outline-offset-2"
>
Skip to keyless mode content
</a>

<div class="fixed inset-y-0 left-0 w-72 overflow-y-auto border-r border-gray-100 bg-white px-2 py-4">
<header class="mb-2 flex items-center justify-center gap-x-2 border-b border-gray-100 pb-4">
<svg
Expand Down
197 changes: 105 additions & 92 deletions packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useClerk } from '@clerk/shared/react';
// eslint-disable-next-line no-restricted-imports
import { css } from '@emotion/react';
import { useState } from 'react';

import { useEnvironment } from '../../contexts';
import { descriptors, Flex, Link, Spinner } from '../../customizables';
import { descriptors, Flex, Link } from '../../customizables';
import { Portal } from '../../elements/Portal';
import { InternalThemeProvider } from '../../styledSystem';
import { ClerkLogoIcon } from './ClerkLogoIcon';
Expand All @@ -17,23 +16,19 @@ type KeylessPromptProps = {

const _KeylessPrompt = (_props: KeylessPromptProps) => {
const [isExpanded, setIsExpanded] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const handleFocus = () => setIsExpanded(true);

const claimed = Boolean(useEnvironment().authConfig.claimedAt);
const clerk = useClerk();

return (
<Portal>
<Flex
elementDescriptor={descriptors.impersonationFab}
align='center'
onMouseEnter={() => setIsExpanded(true)}
data-expanded={isExpanded}
align='center'
elementDescriptor={descriptors.impersonationFab}
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',
Expand All @@ -44,8 +39,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 290ms cubic-bezier(0.2, 0.98, 0.1, 1)',

'&[data-expanded="true"]': {
flexDirection: 'column',
Expand All @@ -57,16 +51,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)',
},
})}
>
<Flex
sx={{
width: '100%',
justifyContent: 'space-between',
alignItems: 'center',
}}
<button
type='button'
aria-expanded={isExpanded}
aria-controls='keyless-prompt-content'
id='keyless-prompt-button'
onClick={() => !claimed && setIsExpanded(prev => !prev)}
css={css`
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
`}
>
<Flex
sx={t => ({
Expand Down Expand Up @@ -99,26 +99,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;
}
`}
>
<span
className='coin-flip-front'
aria-hidden='true'
aria-hidden
css={css`
position: absolute;
width: 100%;
Expand All @@ -134,7 +137,7 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {

<span
className='coin-flip-back'
aria-hidden='true'
aria-hidden
css={css`
position: absolute;
width: 100%;
Expand All @@ -153,14 +156,15 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {

<p
data-text='Clerk is in keyless mode'
aria-label={claimed && isExpanded ? 'Missing environment keys' : 'Clerk is in keyless mode'}
LekoArts marked this conversation as resolved.
Show resolved Hide resolved
css={css`
color: #d9d9d9;
font-size: 0.875rem;
font-weight: 400;
font-weight: 500;
position: relative;
isolation: isolate;
white-space: nowrap;
animation: show-title 180ms ease-out forwards;
animation: show-title 160ms ease-out forwards;

${!claimed &&
`&::after {
Expand All @@ -187,6 +191,9 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
? 'text-shimmer-expanded 3s infinite ease-out forwards'
: 'text-shimmer 3s infinite ease-out forwards'
};
speak: none;
-webkit-user-select: none;
user-select: none;
}

&::before {
Expand Down Expand Up @@ -214,6 +221,17 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
? 'text-shimmer-expanded 3s infinite ease-out forwards'
: 'text-shimmer 3s infinite ease-out forwards'
};
speak: none;
-webkit-user-select: none;
user-select: none;
}

@media (prefers-reduced-motion: reduce) {
&::after,
&::before {
animation: none;
background: transparent;
}
}

@keyframes text-shimmer {
Expand Down Expand Up @@ -252,67 +270,63 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
</p>
</Flex>

{isExpanded && !claimed && (
<button
onClick={() => setIsExpanded(false)}
aria-label='Close'
type='button'
css={css`
cursor: pointer;
margin-left: 0.75rem;
color: #8c8c8c;
transition: color 130ms ease-out;
:hover {
color: #eeeeee;
}
animation: show-button 200ms cubic-bezier(0.4, 0, 0, 1.1) forwards;
<svg
width='1rem'
height='1rem'
viewBox='0 0 16 16'
fill='none'
aria-hidden
xmlns='http://www.w3.org/2000/svg'
css={css`
color: #8c8c8c;
transition: color 130ms ease-out;
visibility: ${isExpanded && !claimed ? 'visible' : 'hidden'};
:hover {
color: #eeeeee;
}
animation: show-button 200ms cubic-bezier(0.4, 0, 0, 1.1) forwards;

@keyframes show-button {
from {
transform: scaleX(0.9);
opacity: 0;
}
to {
transform: scaleX(1);
opacity: 1;
}
@keyframes show-button {
from {
transform: scaleX(0.9);
opacity: 0;
}
`}
>
<svg
width='1rem'
height='1rem'
viewBox='0 0 16 16'
fill='none'
aria-hidden
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M3.75 8H12.25'
stroke='currentColor'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
</button>
)}
</Flex>
to {
transform: scaleX(1);
opacity: 1;
}
}
`}
>
<path
d='M3.75 8H12.25'
stroke='currentColor'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
</button>

{isExpanded && (
<div
role='region'
id='keyless-prompt-content'
aria-labelledby='keyless-prompt-button'
hidden={!isExpanded}
>
<p
css={css`
color: #b4b4b4;
font-size: 0.75rem;
font-size: 0.8125rem;
font-weight: 400;
line-height: 1rem;
max-width: 14.625rem;
min-height: 2rem;
animation: show-description 260ms ease-out forwards;
animation: show-description 210ms ease forwards;

@keyframes show-description {
from {
transform: translateY(-1.8px);
transform: translateY(-1.5px);
opacity: 0;
}
to {
Expand All @@ -324,14 +338,14 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
>
{claimed ? (
<>
You claimed this application, but haven&apos;t set keys in your environment. Get your keys from the
Clerk Dashboard.
You claimed this application but haven&apos;t set keys in your environment. Get them from the Clerk
Dashboard.
</>
) : (
<>
API keys were missing so we generated them for you. Link this instance to your Clerk account to make
configuration changes.{' '}
We generated temporary API keys for you. Link this instance to your Clerk account to configure it.{' '}
<Link
aria-label='Learn more about Clerk keyless mode'
href='https://clerk.com/docs/keyless'
sx={t => ({
color: t.colors.$whiteAlpha600,
Expand All @@ -347,16 +361,12 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
</>
)}
</p>
)}
</div>

<button
type='button'
onFocus={handleFocus}
<a
href={claimed ? _props.copyKeysUrl : _props.claimUrl}
kaftarmery marked this conversation as resolved.
Show resolved Hide resolved
rel='noopener noreferrer'
data-expanded={isExpanded}
onClick={() => {
setIsLoading(true);
void clerk.navigate(claimed ? _props.copyKeysUrl : _props.claimUrl);
}}
css={css`
display: flex;
align-items: center;
Expand Down Expand Up @@ -385,18 +395,21 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
0px 1.5px 2px 0px rgba(0, 0, 0, 0.48),
0px 0px 4px 0px rgba(243, 107, 22, 0) inset;

transition: all 80ms cubic-bezier(0.3, 0.5, 0.1, 1);
transition: all 120ms cubic-bezier(0.1, 0.7, 0.1, 1);
animation: small-btn-glow 3s infinite 500ms;

@media (prefers-reduced-motion: reduce) {
animation: none;
}

&[data-expanded='true'] {
right: 0.75rem;
bottom: 0.75rem;
width: calc(100% - 1.5rem);
color: ${claimed ? 'white' : '#fde047'};
border-radius: 0.375rem;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 30.5%, rgba(0, 0, 0, 0.05) 100%), #454545;

transition: all 175ms cubic-bezier(0.6, 0.5, 0.1, 1);
transition: all 200ms cubic-bezier(0.4, 0.8, 0.2, 1);
animation: none;

&:hover {
Expand Down Expand Up @@ -444,8 +457,8 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {
}
`}
>
{isLoading ? <Spinner size={'sm'} /> : <> {claimed ? 'Get API keys' : 'Claim keys'}</>}
</button>
{claimed ? 'Get API keys' : 'Claim keys'}
</a>
</Flex>
</Portal>
);
Expand Down
Loading