diff --git a/CHANGELOG.md b/CHANGELOG.md index 19694452..db811b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### ๐ŸŽจ ๋ณ€๊ฒฝ ์‚ฌํ•ญ (UX / ์ ‘๊ทผ์„ฑ) +- ๋กœ๊ทธ์ธยท๊ด€๋ฆฌ์ž ๋กœ๊ทธ์ธยท๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •์˜ ์ œ์ถœ ๋Œ€๊ธฐ ์ƒํƒœ์— ๊ธฐ์กด ์ƒํƒœ ๋ฌธ๊ตฌ๋ฅผ ๋ณด์กฐํ•˜๋Š” ์žฅ์‹ ๋กœ๋”ฉ ์•„์ด์ฝ˜์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์•„์ด์ฝ˜์€ ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ์—์„œ ์ˆจ๊ธฐ๊ณ , ์šด์˜์ฒด์ œ์˜ reduced-motion ์„ ํ˜ธ๊ฐ€ ์ผœ์ง„ ๊ฒฝ์šฐ ํšŒ์ „ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค. - ์„ธ์…˜ ํ™œ๋™ ๋ฆฌ๋ณธ์€ ์‹œ๊ฐ์ ์œผ๋กœ ํ˜„์žฌ์ธ ์ด๋ฒคํŠธ๋ฅผ ํ† ๊ธ€ ๋ฒ„ํŠผ์œผ๋กœ ์˜ค์ธ์‹œํ‚ค์ง€ ์•Š๊ณ  `aria-current`๋กœ ๋…ธ์ถœํ•˜๋ฉฐ, ๋ณ‘ํ•ฉ๋œ ๋„๊ตฌ ์‹คํ–‰์€ `Expand โ€ฆ group` ๋‹ค์Œ ํ–‰๋™๊ณผ ํฌํ•จ ์ด๋ฒคํŠธ ์ˆ˜๋ฅผ ๋ช…ํ™•ํžˆ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค. - ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ ๋ณ€๊ฒฝยท์‚ญ์ œ ๋ฒ„ํŠผ๊ณผ ํ…์ŠคํŠธ๊ฐ€ ์ด๋ฆ„์„ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋ฒ„ํŠผ์˜ ์žฅ์‹ ์•„์ด์ฝ˜์„ ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ์—์„œ ์ˆจ๊ธฐ๊ณ , ๋ฒ„ํŠผ์˜ ๊ธฐ์กด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์ด ์œ ์ง€๋˜๋Š”์ง€ ๋ Œ๋”๋ง ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. - ์›น ๋Œ€์‹œ๋ณด๋“œ์˜ ๊ฐ์ข… ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ(`org-sidebar.tsx`, `org-header.tsx`, `no-organization-state.tsx`)์— ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์šฉ `aria-label="Log out of your account"` (๋˜๋Š” `Sign out of your account`) ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ ‘๊ทผ์„ฑ์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. diff --git a/packages/web/src/app/login/page.tsx b/packages/web/src/app/login/page.tsx index 75d1858a..1f5e011b 100644 --- a/packages/web/src/app/login/page.tsx +++ b/packages/web/src/app/login/page.tsx @@ -3,7 +3,7 @@ import { useState } from 'react' import { signIn } from 'next-auth/react' import { useRouter } from 'next/navigation' -import { Button } from '@/components/ui/button' +import { Button, ButtonLoadingContent } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' @@ -75,7 +75,11 @@ export default function LoginPage() {

{error}

)}
diff --git a/packages/web/src/components/admin/admin-login-form.tsx b/packages/web/src/components/admin/admin-login-form.tsx index b1dbdadf..d541ae7c 100644 --- a/packages/web/src/components/admin/admin-login-form.tsx +++ b/packages/web/src/components/admin/admin-login-form.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import { Lock } from 'lucide-react' import { useRouter } from 'next/navigation' -import { Button } from '@/components/ui/button' +import { Button, ButtonLoadingContent } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' @@ -76,7 +76,11 @@ export function AdminLoginForm() {
{error &&

{error}

} diff --git a/packages/web/src/components/auth/reset-password-form.tsx b/packages/web/src/components/auth/reset-password-form.tsx index 99066a69..f4f28bfa 100644 --- a/packages/web/src/components/auth/reset-password-form.tsx +++ b/packages/web/src/components/auth/reset-password-form.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import Link from 'next/link' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { Button, buttonVariants } from '@/components/ui/button' +import { Button, ButtonLoadingContent, buttonVariants } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' @@ -110,7 +110,11 @@ export function ResetPasswordForm({ token, email }: ResetPasswordFormProps) { )} diff --git a/packages/web/src/components/ui/button.test.tsx b/packages/web/src/components/ui/button.test.tsx new file mode 100644 index 00000000..7568197d --- /dev/null +++ b/packages/web/src/components/ui/button.test.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it } from 'vitest' + +import { ButtonLoadingContent } from './button' + +describe('ButtonLoadingContent', () => { + it('keeps loading copy readable while the decorative spinner respects reduced motion', () => { + const markup = renderToStaticMarkup( + Signing in..., + ) + + expect(markup).toContain('aria-hidden="true"') + expect(markup).toContain('animate-spin') + expect(markup).toContain('motion-reduce:animate-none') + expect(markup).toContain('Signing in...') + }) +}) diff --git a/packages/web/src/components/ui/button.tsx b/packages/web/src/components/ui/button.tsx index 09df7536..4f10d3c4 100644 --- a/packages/web/src/components/ui/button.tsx +++ b/packages/web/src/components/ui/button.tsx @@ -1,3 +1,6 @@ +import React from 'react' +import { Loader2 } from 'lucide-react' +import type { ReactNode } from 'react' import { Button as ButtonPrimitive } from "@base-ui/react/button" import { cva, type VariantProps } from "class-variance-authority" @@ -55,4 +58,17 @@ function Button({ ) } -export { Button, buttonVariants } + +function ButtonLoadingContent({ children }: { children: ReactNode }) { + return ( + <> +