Skip to content

Commit a34a6ce

Browse files
feat: Coin flip animation (#7276)
1 parent a7cdb01 commit a34a6ce

File tree

1 file changed

+143
-123
lines changed
  • packages/clerk-js/src/ui/components/devPrompts/EnableOrganizationsPrompt

1 file changed

+143
-123
lines changed

packages/clerk-js/src/ui/components/devPrompts/EnableOrganizationsPrompt/index.tsx

Lines changed: 143 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { __internal_EnableOrganizationsPromptProps, EnableEnvironmentSettin
44
import type { SerializedStyles } from '@emotion/react';
55
// eslint-disable-next-line no-restricted-imports
66
import { css, type Theme } from '@emotion/react';
7-
import { forwardRef, useId, useMemo, useRef, useState } from 'react';
7+
import { forwardRef, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
88

99
import { useEnvironment } from '@/ui/contexts';
1010
import { Modal } from '@/ui/elements/Modal';
@@ -122,128 +122,7 @@ const EnableOrganizationsPromptInternal = ({
122122
gap: t.sizes.$2,
123123
})}
124124
>
125-
<div
126-
css={css`
127-
perspective: 1000px;
128-
position: relative;
129-
width: 1.25rem;
130-
height: 1.25rem;
131-
transform-style: preserve-3d;
132-
transition: transform 0.6s ease-in-out;
133-
transform: ${isEnabled ? 'rotateY(180deg)' : 'rotateY(0)'};
134-
animation: ${!isEnabled ? 'coinFlipAnimation 6s infinite linear' : 'none'};
135-
136-
@keyframes coinFlipAnimation {
137-
0%,
138-
55% {
139-
transform: rotateY(0);
140-
}
141-
60%,
142-
95% {
143-
transform: rotateY(180deg);
144-
}
145-
100% {
146-
transform: rotateY(0);
147-
}
148-
}
149-
150-
@media (prefers-reduced-motion: reduce) {
151-
transition: none;
152-
animation: none;
153-
transform: ${isEnabled ? 'rotateY(180deg)' : 'rotateY(0)'};
154-
}
155-
`}
156-
>
157-
{isEnabled ? (
158-
<span
159-
aria-hidden
160-
css={css`
161-
position: absolute;
162-
width: 100%;
163-
height: 100%;
164-
backface-visibility: hidden;
165-
transform: rotateY(180deg);
166-
display: flex;
167-
align-items: center;
168-
justify-content: center;
169-
`}
170-
>
171-
<PromptSuccessIcon
172-
css={css`
173-
width: 1.25rem;
174-
height: 1.25rem;
175-
`}
176-
/>
177-
</span>
178-
) : (
179-
<>
180-
<span
181-
className='coin-flip-front'
182-
aria-hidden
183-
css={css`
184-
position: absolute;
185-
width: 100%;
186-
height: 100%;
187-
backface-visibility: hidden;
188-
display: flex;
189-
align-items: center;
190-
justify-content: center;
191-
`}
192-
>
193-
<ClerkLogoIcon />
194-
</span>
195-
196-
<span
197-
className='coin-flip-back'
198-
aria-hidden
199-
css={css`
200-
position: absolute;
201-
width: 100%;
202-
height: 100%;
203-
backface-visibility: hidden;
204-
transform: rotateY(180deg);
205-
display: flex;
206-
align-items: center;
207-
justify-content: center;
208-
`}
209-
>
210-
<svg
211-
css={css`
212-
width: 1.25rem;
213-
height: 1.25rem;
214-
`}
215-
viewBox='0 0 20 20'
216-
fill='none'
217-
xmlns='http://www.w3.org/2000/svg'
218-
>
219-
<path
220-
opacity='0.2'
221-
d='M17.25 10C17.25 14.0041 14.0041 17.25 10 17.25C5.99594 17.25 2.75 14.0041 2.75 10C2.75 5.99594 5.99594 2.75 10 2.75C14.0041 2.75 17.25 5.99594 17.25 10Z'
222-
fill='#EAB308'
223-
/>
224-
<path
225-
fillRule='evenodd'
226-
clipRule='evenodd'
227-
d='M10 3.5C6.41015 3.5 3.5 6.41015 3.5 10C3.5 13.5899 6.41015 16.5 10 16.5C13.5899 16.5 16.5 13.5899 16.5 10C16.5 6.41015 13.5899 3.5 10 3.5ZM2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10Z'
228-
fill='#EAB308'
229-
/>
230-
<path
231-
fillRule='evenodd'
232-
clipRule='evenodd'
233-
d='M10 6C10.5523 6 11 6.44772 11 7V9C11 9.55228 10.5523 10 10 10C9.44772 10 9 9.55228 9 9V7C9 6.44772 9.44772 6 10 6Z'
234-
fill='#EAB308'
235-
/>
236-
<path
237-
fillRule='evenodd'
238-
clipRule='evenodd'
239-
d='M10 12C10.5523 12 11 12.4477 11 13V13.01C11 13.5623 10.5523 14.01 10 14.01C9.44772 14.01 9 13.5623 9 13.01V13C9 12.4477 9.44772 12 10 12Z'
240-
fill='#EAB308'
241-
/>
242-
</svg>
243-
</span>
244-
</>
245-
)}
246-
</div>
125+
<CoinFlip isEnabled={isEnabled} />
247126

248127
<h1
249128
css={[
@@ -679,3 +558,144 @@ const Link = forwardRef<HTMLAnchorElement, React.ComponentProps<'a'> & { css?: S
679558
);
680559
},
681560
);
561+
562+
const CoinFlip = ({ isEnabled }: { isEnabled: boolean }) => {
563+
const [rotation, setRotation] = useState(0);
564+
565+
useLayoutEffect(() => {
566+
if (isEnabled) {
567+
setRotation(r => (r === 0 ? 180 : 0));
568+
return;
569+
}
570+
571+
const interval = setInterval(() => {
572+
setRotation(r => (r === 0 ? 180 : 0));
573+
}, 2000);
574+
575+
return () => clearInterval(interval);
576+
}, [isEnabled]);
577+
578+
let frontFaceType: 'idle' | 'success' = 'idle';
579+
let backFaceType: 'warning' | 'success' = 'warning';
580+
581+
if (isEnabled) {
582+
if (rotation === 0) {
583+
frontFaceType = 'success';
584+
backFaceType = 'warning';
585+
} else {
586+
backFaceType = 'success';
587+
frontFaceType = 'idle';
588+
}
589+
}
590+
591+
const renderContent = (type: 'idle' | 'warning' | 'success') => {
592+
switch (type) {
593+
case 'idle':
594+
return <ClerkLogoIcon />;
595+
case 'success':
596+
return (
597+
<PromptSuccessIcon
598+
css={css`
599+
width: 1.25rem;
600+
height: 1.25rem;
601+
`}
602+
/>
603+
);
604+
case 'warning':
605+
return (
606+
<svg
607+
css={css`
608+
width: 1.25rem;
609+
height: 1.25rem;
610+
`}
611+
viewBox='0 0 20 20'
612+
fill='none'
613+
xmlns='http://www.w3.org/2000/svg'
614+
>
615+
<path
616+
opacity='0.2'
617+
d='M17.25 10C17.25 14.0041 14.0041 17.25 10 17.25C5.99594 17.25 2.75 14.0041 2.75 10C2.75 5.99594 5.99594 2.75 10 2.75C14.0041 2.75 17.25 5.99594 17.25 10Z'
618+
fill='#EAB308'
619+
/>
620+
<path
621+
fillRule='evenodd'
622+
clipRule='evenodd'
623+
d='M10 3.5C6.41015 3.5 3.5 6.41015 3.5 10C3.5 13.5899 6.41015 16.5 10 16.5C13.5899 16.5 16.5 13.5899 16.5 10C16.5 6.41015 13.5899 3.5 10 3.5ZM2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10Z'
624+
fill='#EAB308'
625+
/>
626+
<path
627+
fillRule='evenodd'
628+
clipRule='evenodd'
629+
d='M10 6C10.5523 6 11 6.44772 11 7V9C11 9.55228 10.5523 10 10 10C9.44772 10 9 9.55228 9 9V7C9 6.44772 9.44772 6 10 6Z'
630+
fill='#EAB308'
631+
/>
632+
<path
633+
fillRule='evenodd'
634+
clipRule='evenodd'
635+
d='M10 12C10.5523 12 11 12.4477 11 13V13.01C11 13.5623 10.5523 14.01 10 14.01C9.44772 14.01 9 13.5623 9 13.01V13C9 12.4477 9.44772 12 10 12Z'
636+
fill='#EAB308'
637+
/>
638+
</svg>
639+
);
640+
}
641+
};
642+
643+
return (
644+
<div
645+
css={css`
646+
perspective: 1000px;
647+
width: 1.25rem;
648+
height: 1.25rem;
649+
`}
650+
>
651+
<div
652+
css={css`
653+
position: relative;
654+
width: 100%;
655+
height: 100%;
656+
transform-style: preserve-3d;
657+
transition: transform 0.6s ease-in-out;
658+
transform: rotateY(${rotation}deg);
659+
660+
@media (prefers-reduced-motion: reduce) {
661+
transition: none;
662+
}
663+
`}
664+
>
665+
<span
666+
aria-hidden
667+
css={css`
668+
position: absolute;
669+
width: 100%;
670+
height: 100%;
671+
backface-visibility: hidden;
672+
display: flex;
673+
align-items: center;
674+
justify-content: center;
675+
-webkit-font-smoothing: antialiased;
676+
transform: rotateY(0deg);
677+
`}
678+
>
679+
{renderContent(frontFaceType)}
680+
</span>
681+
682+
<span
683+
aria-hidden
684+
css={css`
685+
position: absolute;
686+
width: 100%;
687+
height: 100%;
688+
backface-visibility: hidden;
689+
transform: rotateY(180deg);
690+
display: flex;
691+
align-items: center;
692+
justify-content: center;
693+
-webkit-font-smoothing: antialiased;
694+
`}
695+
>
696+
{renderContent(backFaceType)}
697+
</span>
698+
</div>
699+
</div>
700+
);
701+
};

0 commit comments

Comments
 (0)