From 9de64a8093325ba17150a16875abf4d7b97ae6de Mon Sep 17 00:00:00 2001 From: designartvis Date: Sat, 4 Jul 2026 23:32:28 -0400 Subject: [PATCH] docs: add UI design-tokens styleguide --- README.md | 6 +++ docs/STYLEGUIDE.md | 89 ++++++++++++++++++++++++++++++++++++++++ src/app/quote/Client.tsx | 17 ++++++-- 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 docs/STYLEGUIDE.md diff --git a/README.md b/README.md index 220a252..fadeebe 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ Next.js frontend application for [StableRoute](https://github.com/StableRoute-Or - **TailwindCSS** for styling - A comprehensive set of routing, management, and audit log pages integrated with the StableRoute backend. +## UI Styleguide + +Contributor-facing Badge variants, Button variants, focus-ring conventions, and +light/dark color pairs are documented in +[`docs/STYLEGUIDE.md`](docs/STYLEGUIDE.md). + ## Routes Each route is defined under `src/app` and connects to its respective UI page: diff --git a/docs/STYLEGUIDE.md b/docs/STYLEGUIDE.md new file mode 100644 index 0000000..271c5bc --- /dev/null +++ b/docs/STYLEGUIDE.md @@ -0,0 +1,89 @@ +# UI Styleguide + +This guide documents the shared UI tokens that contributors should reuse when +adding StableRoute surfaces. The current implementation keeps design tokens +close to the components with Tailwind utility classes, so prefer the existing +components before adding new variants. + +## Badge Variants + +Use `src/components/Badge.tsx` for short statuses, labels, and compact metadata. +Badges are rendered as rounded pills with `inline-flex items-center rounded-full +px-2 py-0.5 text-xs font-medium`. + +| Variant | Use for | Light mode | Dark mode | +|---------|---------|------------|-----------| +| `neutral` | Default metadata, inactive states, informational labels | `bg-neutral-100 text-neutral-700` | `dark:bg-neutral-800 dark:text-neutral-300` | +| `ok` | Healthy, connected, successful, or enabled states | `bg-emerald-100 text-emerald-800` | `dark:bg-emerald-950 dark:text-emerald-300` | +| `warning` | Pending, degraded, or attention-needed states | `bg-amber-100 text-amber-800` | `dark:bg-amber-950 dark:text-amber-300` | +| `danger` | Failed, destructive, revoked, or blocked states | `bg-rose-100 text-rose-800` | `dark:bg-rose-950 dark:text-rose-300` | + +Examples: + +```tsx +Draft +Active +Pending +Revoked +``` + +## Button Variants + +Use `src/components/Button.tsx` for native button actions. The base shape is a +rounded pill with `rounded-full px-5 py-2 text-sm font-medium`; disabled buttons +use `disabled:opacity-50 disabled:cursor-not-allowed`. + +| Variant | Use for | Classes | +|---------|---------|---------| +| `primary` | Main page action or form submission | `bg-black text-white hover:bg-neutral-800 dark:bg-white dark:text-black dark:hover:bg-neutral-200` | +| `secondary` | Lower-emphasis actions, filters, and navigation-style controls | `border border-neutral-300 hover:border-neutral-500 dark:border-neutral-700 dark:hover:border-neutral-500` | +| `danger` | Destructive or irreversible actions | `bg-rose-600 text-white hover:bg-rose-700` | + +Examples: + +```tsx + + + +``` + +## Focus Rings + +Interactive controls should keep the focus-visible convention from +`src/components/Button.tsx`: + +```tsx +focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 +``` + +Use this pattern on new custom controls so keyboard focus is visible without +adding a permanent outline for pointer users. + +## Theme Colors + +Global foreground and background colors live in `src/app/globals.css`: + +```css +:root { + --foreground: #0a0a0a; + --background: #fafafa; +} +``` + +The `body` reads those variables, while component-level light and dark pairs are +expressed with Tailwind classes such as `bg-neutral-100` with +`dark:bg-neutral-800`. Keep new component colors paired for both light and dark +mode when the component is visible in both themes. + +## Tailwind Scope + +`tailwind.config.ts` scans `src/pages`, `src/components`, and `src/app` for +`js`, `ts`, `jsx`, `tsx`, and `mdx` files. The repo does not currently extend +Tailwind theme tokens, so new reusable visual decisions should either reuse the +classes above or be documented when a shared component introduces them. + +## Motion + +`src/app/globals.css` also honors `prefers-reduced-motion: reduce` by minimizing +animation and transition duration. Do not rely on animation as the only way to +communicate state changes. diff --git a/src/app/quote/Client.tsx b/src/app/quote/Client.tsx index 4546213..c3b13c7 100644 --- a/src/app/quote/Client.tsx +++ b/src/app/quote/Client.tsx @@ -1,7 +1,8 @@ "use client"; -import { useState } from "react"; +import { type FormEvent, useState } from "react"; import type { ApiError } from "@/lib/apiClient"; +import { assetsDiffer, isValidAmount } from "@/lib/quote"; type Quote = { source_asset: string; @@ -32,17 +33,25 @@ export default function QuoteClient() { const [requestId, setRequestId] = useState(null); const [loading, setLoading] = useState(false); - const onSubmit = async (event: React.FormEvent) => { + const onSubmit = async (event: FormEvent) => { event.preventDefault(); setError(null); setRequestId(null); setQuote(null); - if (!assetsDiffer(sourceAsset, destAsset)) { + const normalizedSourceAsset = normalizeAssetCode(sourceAsset); + const normalizedDestAsset = normalizeAssetCode(destAsset); + const normalizedAmount = amount.trim(); + + if (!normalizedSourceAsset || !normalizedDestAsset) { + setError("Asset codes must be 1-12 letters or numbers."); + return; + } + if (!assetsDiffer(normalizedSourceAsset, normalizedDestAsset)) { setError("Source and destination assets must differ."); return; } - if (!isValidAmount(amount)) { + if (!isValidAmount(normalizedAmount)) { setError("Amount must be a positive integer (base units)."); return; }