From 7070989a14b36b3fd94df047183d6ee8caa59f10 Mon Sep 17 00:00:00 2001 From: Joe Bell <7349341+joe-bell@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:35:17 +0300 Subject: [PATCH] feat(ui): Button `busy` states (#3502) Co-authored-by: Alex Carpenter --- .changeset/brave-bees-kick.md | 2 + package-lock.json | 8 +- .../ui/src/components/sign-in/sign-in.tsx | 28 +++- .../ui/src/components/sign-up/sign-up.tsx | 28 +++- packages/ui/src/global.css | 11 ++ packages/ui/src/primitives/button.tsx | 57 +++++--- packages/ui/src/primitives/connection.tsx | 49 ++++--- packages/ui/src/primitives/field.tsx | 8 +- packages/ui/src/primitives/spinner.tsx | 135 ++++++++++++++++++ packages/ui/src/tailwind.config.ts | 3 + 10 files changed, 278 insertions(+), 51 deletions(-) create mode 100644 .changeset/brave-bees-kick.md create mode 100644 packages/ui/src/primitives/spinner.tsx diff --git a/.changeset/brave-bees-kick.md b/.changeset/brave-bees-kick.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/brave-bees-kick.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/package-lock.json b/package-lock.json index f8ee87c6c1..a119d48920 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38920,18 +38920,18 @@ "devDependencies": { "@clerk/eslint-config-custom": "*", "@cloudflare/workers-types": "^3.18.0", - "@types/chai": "^4.3.16", + "@types/chai": "^4.3.3", "@types/cookie": "^0.5.1", "@types/node": "^18.19.33", "@types/qunit": "^2.19.7", - "@types/sinon": "^10.0.20", + "@types/sinon": "^10.0.13", "chai": "^4.3.6", "edge-runtime": "^2.5.9", "esbuild": "^0.15.12", "esbuild-register": "^3.3.3", "miniflare": "^2.14.2", "npm-run-all": "^4.1.5", - "qunit": "^2.21.0", + "qunit": "^2.19.3", "sinon": "^14.0.1", "tsup": "*", "typescript": "*", @@ -40566,7 +40566,7 @@ "@clerk/eslint-config-custom": "*", "@types/express": "4.17.14", "@types/node": "^18.19.33", - "nock": "^13.5.4", + "nock": "^13.0.7", "npm-run-all": "^4.1.5", "prettier": "^2.5.0", "tsup": "*", diff --git a/packages/ui/src/components/sign-in/sign-in.tsx b/packages/ui/src/components/sign-in/sign-in.tsx index e01e9cbc6f..b6faf132d8 100644 --- a/packages/ui/src/components/sign-in/sign-in.tsx +++ b/packages/ui/src/components/sign-in/sign-in.tsx @@ -1,5 +1,6 @@ import * as Common from '@clerk/elements/common'; import * as SignIn from '@clerk/elements/sign-in'; +import * as React from 'react'; import { PROVIDERS } from '~/constants'; import { Button } from '~/primitives/button'; @@ -9,7 +10,14 @@ import * as Field from '~/primitives/field'; import * as Icon from '~/primitives/icon'; import { Seperator } from '~/primitives/seperator'; +type ConnectionName = (typeof PROVIDERS)[number]['name']; + export function SignInComponent() { + const [busyConnectionName, setBusyConnectionName] = React.useState(null); + const [isContinuing, setIsContinuing] = React.useState(false); + + const hasBusyConnection = busyConnectionName !== null; + return ( @@ -29,8 +37,13 @@ export function SignInComponent() { name={provider.id} asChild > - - + } + onClick={() => setBusyConnectionName(provider.name)} + key={provider.name} + > {provider.name} @@ -47,7 +60,7 @@ export function SignInComponent() { Email address - + {({ message }) => { @@ -61,7 +74,14 @@ export function SignInComponent() { submit asChild > - + diff --git a/packages/ui/src/components/sign-up/sign-up.tsx b/packages/ui/src/components/sign-up/sign-up.tsx index 5e194bc0eb..48d5dfd27f 100644 --- a/packages/ui/src/components/sign-up/sign-up.tsx +++ b/packages/ui/src/components/sign-up/sign-up.tsx @@ -1,5 +1,6 @@ import * as Common from '@clerk/elements/common'; import * as SignUp from '@clerk/elements/sign-up'; +import * as React from 'react'; import { PROVIDERS } from '~/constants'; import { Button } from '~/primitives/button'; @@ -9,7 +10,14 @@ import * as Field from '~/primitives/field'; import * as Icon from '~/primitives/icon'; import { Seperator } from '~/primitives/seperator'; +type ConnectionName = (typeof PROVIDERS)[number]['name']; + export function SignUpComponent() { + const [busyConnectionName, setBusyConnectionName] = React.useState(null); + const [isContinuing, setIsContinuing] = React.useState(false); + + const hasBusyConnection = busyConnectionName !== null; + return ( @@ -28,8 +36,13 @@ export function SignUpComponent() { key={provider.id} name={provider.id} > - - + } + onClick={() => setBusyConnectionName(provider.name)} + key={provider.name} + > {provider.name} @@ -46,7 +59,7 @@ export function SignUpComponent() { Email address - + {({ message }) => { @@ -60,7 +73,14 @@ export function SignUpComponent() { submit asChild > - + diff --git a/packages/ui/src/global.css b/packages/ui/src/global.css index a1e3953a6d..0e4d7c008d 100644 --- a/packages/ui/src/global.css +++ b/packages/ui/src/global.css @@ -1,9 +1,19 @@ +@keyframes cl-spin { + to { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + :where(:root) { --cl-font-family: inherit; --cl-color-destructive: 0 84% 60%; --cl-radius: 0.375rem; --cl-spacing-unit: 1rem; } + *, ::before, ::after { @@ -12,6 +22,7 @@ border-style: solid; border-color: #e5e7eb; } + ::before, ::after { --cl-content: ''; diff --git a/packages/ui/src/primitives/button.tsx b/packages/ui/src/primitives/button.tsx index ad1f81751a..90e9009856 100644 --- a/packages/ui/src/primitives/button.tsx +++ b/packages/ui/src/primitives/button.tsx @@ -1,24 +1,41 @@ import cn from 'clsx'; import * as React from 'react'; -import * as Icon from './icon'; +import { Spinner } from './spinner'; -export const Button = React.forwardRef>( - function Button({ children, className, ...props }, forwardedRef) { - return ( - // eslint-disable-next-line react/button-has-type - - ); - }, -); +export const Button = React.forwardRef(function Button( + { + busy, + children, + className, + disabled, + icon, + ...props + }: React.ButtonHTMLAttributes & { busy?: boolean; icon?: React.ReactNode }, + forwardedRef: React.ForwardedRef, +) { + return ( + // eslint-disable-next-line react/button-has-type + + ); +}); diff --git a/packages/ui/src/primitives/connection.tsx b/packages/ui/src/primitives/connection.tsx index 3535c92b6d..04067b8e7c 100644 --- a/packages/ui/src/primitives/connection.tsx +++ b/packages/ui/src/primitives/connection.tsx @@ -1,6 +1,8 @@ import cn from 'clsx'; import * as React from 'react'; +import { Spinner } from './spinner'; + export const Root = React.forwardRef>(function Root( { children, className, ...props }, forwardedRef, @@ -16,20 +18,33 @@ export const Root = React.forwardRef, 'type'>>( - function Button({ children, className, ...props }, forwardedRef) { - return ( - - ); - }, -); +export const Button = React.forwardRef(function Button( + { + busy, + children, + className, + disabled, + icon, + ...props + }: React.ButtonHTMLAttributes & { icon?: React.ReactNode; busy?: boolean }, + forwardedRef: React.ForwardedRef, +) { + return ( + // eslint-disable-next-line react/button-has-type + + ); +}); diff --git a/packages/ui/src/primitives/field.tsx b/packages/ui/src/primitives/field.tsx index 9f45d5a4ab..da17fd956d 100644 --- a/packages/ui/src/primitives/field.tsx +++ b/packages/ui/src/primitives/field.tsx @@ -9,7 +9,7 @@ export const Root = React.forwardRef {children} @@ -24,7 +24,10 @@ export const Label = React.forwardRef {children} @@ -37,6 +40,7 @@ export const Input = React.forwardRef + * Some other loading text… + * + * ); + * } + * ``` + */ +export const Spinner = forwardRef(function Spinner( + { + children, + className, + }: Pick, 'className'> & { + children: string; + }, + ref: React.ForwardedRef, +) { + return ( + + {children} + + + + ( + + + + + + + + + ) + + + + + + ); +}); diff --git a/packages/ui/src/tailwind.config.ts b/packages/ui/src/tailwind.config.ts index 0a8e1bb09b..2119b38ef0 100644 --- a/packages/ui/src/tailwind.config.ts +++ b/packages/ui/src/tailwind.config.ts @@ -68,6 +68,9 @@ const config = { DEFAULT: 'hsl(var(--cl-color-destructive))', }, }, + animation: { + spin: 'cl-spin linear infinite', + }, }, }, } satisfies Config;