Refactor frontend system primitives and surfaces
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
# Frontend System Audit
|
||||
|
||||
## 1. UI libraries actually used
|
||||
|
||||
- `next` App Router is the app shell and routing layer.
|
||||
- `tailwindcss` is the styling system.
|
||||
- `@radix-ui/react-*` primitives are used through local wrappers in:
|
||||
- `components/ui/accordion.tsx`
|
||||
- `components/ui/checkbox.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/dropdown-menu.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `class-variance-authority` is used for variants in:
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/badge.tsx`
|
||||
- `components/ui/app-card.tsx`
|
||||
- `components/layout/container.tsx`
|
||||
- `clsx` + `tailwind-merge` are used through `cn()` in `lib/utils.ts`.
|
||||
- `next-themes` is used for theme switching in:
|
||||
- `components/theme-provider.tsx`
|
||||
- `components/theme-toggle.tsx`
|
||||
- `framer-motion` is used for custom motion in:
|
||||
- `components/motion-fade.tsx`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/site-logo.tsx`
|
||||
- `components/layout/hero-motion-backdrop.tsx`
|
||||
- `components/layout/hero-atmosphere.tsx`
|
||||
- `components/layout/site-ambient-backdrop.tsx`
|
||||
- `components/layout/ascii-water-ripple.tsx`
|
||||
- `components/layout/animated-logo.tsx`
|
||||
- `components/layout/stacked-marquee-section.tsx`
|
||||
- `tailwindcss-animate` is enabled in `tailwind.config.ts` and is used by state classes in `dialog`, `dropdown-menu`, `select`, `sheet`, and `accordion`.
|
||||
|
||||
## 2. Which library is the primary source of UI primitives
|
||||
|
||||
The primary primitive source is the local
|
||||
`components/ui`
|
||||
layer.
|
||||
|
||||
That layer is mostly built on top of
|
||||
`@radix-ui/react-*`
|
||||
plus Tailwind classes.
|
||||
|
||||
The app code imports local primitives like:
|
||||
|
||||
- `@/components/ui/button`
|
||||
- `@/components/ui/card`
|
||||
- `@/components/ui/input`
|
||||
- `@/components/ui/dialog`
|
||||
- `@/components/ui/select`
|
||||
|
||||
It does not import Radix primitives directly from route files and feature components.
|
||||
|
||||
## 3. Whether shadcn/ui is really used or only partially used
|
||||
|
||||
`shadcn/ui`
|
||||
is present, but partially.
|
||||
|
||||
Evidence that it is present:
|
||||
|
||||
- `components.json` exists and points to `app/globals.css`, `tailwind.config.ts`, and `@/components/ui`.
|
||||
- The local primitive structure matches the common shadcn pattern:
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/card.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/dropdown-menu.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `lib/utils.ts` exposes the standard `cn()` helper.
|
||||
|
||||
Evidence that it is partial/customized rather than stock:
|
||||
|
||||
- There is no runtime `shadcn-ui` package in `package.json`.
|
||||
- Several components are customized to project tokens:
|
||||
- `rounded-surface`
|
||||
- `rounded-nested`
|
||||
- `shadow-card`
|
||||
- `shadow-panel`
|
||||
- `components/ui/app-card.tsx` is a project-specific wrapper over `Card`.
|
||||
- `components/ui/tabs.tsx` is a custom React context implementation, not a Radix wrapper.
|
||||
- Usage is selective. Import counts in the current codebase show:
|
||||
- `app-card`: `23`
|
||||
- `card`: `26`
|
||||
- `button`: `19`
|
||||
- `dialog`: `1`
|
||||
- `dropdown-menu`: `1`
|
||||
- `sheet`: `1`
|
||||
- `table`: `1`
|
||||
- `tabs`: `1`
|
||||
|
||||
## 4. Repeated layout/container patterns found in code
|
||||
|
||||
### Global container pattern
|
||||
|
||||
The main reusable container is `components/layout/container.tsx`:
|
||||
|
||||
```tsx
|
||||
mx-auto w-full px-4 sm:px-6 lg:px-8
|
||||
```
|
||||
|
||||
Container sizes come from Tailwind extensions:
|
||||
|
||||
- `max-w-layout`
|
||||
- `max-w-narrow`
|
||||
- `max-w-wide`
|
||||
- `max-w-admin`
|
||||
|
||||
These map to CSS vars in `app/globals.css`:
|
||||
|
||||
- `--container-narrow: 48rem`
|
||||
- `--container-default: 72rem`
|
||||
- `--container-wide: 80rem`
|
||||
- `--container-admin: 88rem`
|
||||
|
||||
### Public site shell pattern
|
||||
|
||||
Repeated in:
|
||||
|
||||
- `app/[locale]/(site)/layout.tsx`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/home-hero.tsx`
|
||||
- `components/layout/page-hero.tsx`
|
||||
- `app/[locale]/(site)/contact/page.tsx`
|
||||
|
||||
Patterns:
|
||||
|
||||
- `relative flex min-h-screen flex-col overflow-hidden`
|
||||
- `max-w-[72rem] px-4 sm:px-6 lg:px-8`
|
||||
- hero sections with:
|
||||
- `pt-24`
|
||||
- `sm:pt-28`
|
||||
- `lg:pt-28`
|
||||
- `pb-10`
|
||||
- `lg:pb-12`
|
||||
- `lg:pb-16`
|
||||
|
||||
### Admin layout pattern
|
||||
|
||||
Repeated in:
|
||||
|
||||
- `components/dashboard/dashboard-layout.tsx`
|
||||
- `components/root/root-dashboard-shell.tsx`
|
||||
- many `app/root/*/page.tsx`
|
||||
|
||||
Patterns:
|
||||
|
||||
- `min-h-screen bg-muted/30`
|
||||
- `grid min-h-screen lg:grid-cols-[280px_minmax(0,1fr)]`
|
||||
- sticky sidebar:
|
||||
- `lg:sticky lg:top-0 lg:h-screen`
|
||||
- sticky header:
|
||||
- `sticky top-0 z-30 ... bg-background/95 ... backdrop-blur`
|
||||
- main content spacing:
|
||||
- `p-4 lg:p-6`
|
||||
- page stack wrapper:
|
||||
- `space-y-6`
|
||||
|
||||
### Editor / split workspace pattern
|
||||
|
||||
Repeated in:
|
||||
|
||||
- `components/root/site-settings-form.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
- `app/root/page.tsx`
|
||||
|
||||
Patterns:
|
||||
|
||||
- `grid gap-6 xl:grid-cols-[minmax(0,3fr)_minmax(320px,1fr)]`
|
||||
- `grid gap-6 xl:grid-cols-[280px_minmax(0,1fr)]`
|
||||
- sticky side panel:
|
||||
- `xl:sticky xl:top-6 xl:self-start`
|
||||
|
||||
## 5. Repeated radius values found in code
|
||||
|
||||
### Tokenized radius values
|
||||
|
||||
Defined in `app/globals.css`:
|
||||
|
||||
- `--radius-surface: 24px`
|
||||
- `--radius-nested: 16px`
|
||||
- `--radius-pill: 9999px`
|
||||
|
||||
Mapped in `tailwind.config.ts`:
|
||||
|
||||
- `rounded-surface`
|
||||
- `rounded-nested`
|
||||
- `rounded-pill`
|
||||
|
||||
### Repeated radius class patterns
|
||||
|
||||
Most repeated token usage:
|
||||
|
||||
- `rounded-surface`
|
||||
- `components/ui/card.tsx`
|
||||
- `components/ui/app-card.tsx`
|
||||
- `components/ui/accordion.tsx`
|
||||
- `components/ui/dropdown-menu.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/tabs.tsx`
|
||||
- `rounded-nested`
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/input.tsx`
|
||||
- `components/ui/textarea.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/dashboard/sidebar.tsx`
|
||||
- `components/root/contact-protection-form.tsx`
|
||||
- `rounded-[var(--radius-pill)]`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
- `rounded-full`
|
||||
- used heavily for decorative circles and icon pills in site chrome:
|
||||
- `components/layout/site-ambient-backdrop.tsx`
|
||||
- `components/layout/hero-atmosphere.tsx`
|
||||
- `components/layout/ascii-water-ripple.tsx`
|
||||
|
||||
### Non-token radius values also present
|
||||
|
||||
- `rounded-md`
|
||||
- several content blocks and previews
|
||||
- examples:
|
||||
- `app/root/page.tsx`
|
||||
- `components/root/site-settings-form.tsx`
|
||||
- `components/root/media-field-picker.tsx`
|
||||
- `rounded-lg`
|
||||
- examples:
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/dashboard/dashboard-layout.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
|
||||
## 6. Repeated spacing patterns found in code
|
||||
|
||||
### Global repeated spacing
|
||||
|
||||
Most repeated stack and gap patterns:
|
||||
|
||||
- `space-y-6`
|
||||
- `gap-6`
|
||||
- `space-y-2`
|
||||
- `space-y-3`
|
||||
- `p-6`
|
||||
- `p-4`
|
||||
- `px-4 py-3`
|
||||
- `px-4 py-4`
|
||||
- `px-3 py-2`
|
||||
|
||||
### Core primitive spacing
|
||||
|
||||
- `CardHeader`: `flex flex-col gap-1.5 p-6`
|
||||
- `components/ui/card.tsx`
|
||||
- `CardContent`: `p-6 pt-0`
|
||||
- `components/ui/card.tsx`
|
||||
- `Button` sizes:
|
||||
- `default`: `h-10 px-4 py-2`
|
||||
- `sm`: `h-9 px-3`
|
||||
- `lg`: `h-11 px-6`
|
||||
- `icon`: `h-10 w-10`
|
||||
- `components/ui/button.tsx`
|
||||
- `Input` and `Textarea`:
|
||||
- `px-3 py-2`
|
||||
- `components/ui/input.tsx`
|
||||
- `components/ui/textarea.tsx`
|
||||
- `SelectTrigger`:
|
||||
- `px-3 py-2`
|
||||
- `components/ui/select.tsx`
|
||||
|
||||
### Layout spacing
|
||||
|
||||
- container padding:
|
||||
- `px-4 sm:px-6 lg:px-8`
|
||||
- admin page wrapper:
|
||||
- `p-4 lg:p-6`
|
||||
- admin section wrapper:
|
||||
- `py-6 lg:py-8`
|
||||
- `components/layout/app-shell.tsx`
|
||||
- hero wrappers:
|
||||
- `pt-24`
|
||||
- `sm:pt-28`
|
||||
- `lg:pt-28`
|
||||
|
||||
## 7. Repeated color/token usage found in code
|
||||
|
||||
### Semantic tokens defined
|
||||
|
||||
Defined in `app/globals.css` and exposed in `tailwind.config.ts`:
|
||||
|
||||
- base semantic:
|
||||
- `background`
|
||||
- `foreground`
|
||||
- `card`
|
||||
- `popover`
|
||||
- `primary`
|
||||
- `secondary`
|
||||
- `muted`
|
||||
- `accent`
|
||||
- `destructive`
|
||||
- `border`
|
||||
- `border-strong`
|
||||
- `input`
|
||||
- `ring`
|
||||
- custom semantic:
|
||||
- `surface-1`
|
||||
- `surface-2`
|
||||
- `surface-3`
|
||||
- `surface-inverse`
|
||||
- `brand-primary`
|
||||
- `brand-secondary`
|
||||
- `status-success`
|
||||
- `status-success-soft`
|
||||
- `status-warning`
|
||||
- `status-warning-soft`
|
||||
- `sidebar-*`
|
||||
- `hero-*`
|
||||
|
||||
### Repeated utility combinations
|
||||
|
||||
Very common combinations:
|
||||
|
||||
- `border border-border/80 bg-card text-card-foreground shadow-card`
|
||||
- `components/ui/card.tsx`
|
||||
- `border border-input bg-background`
|
||||
- `components/ui/input.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/root/portfolio-categories-manager.tsx`
|
||||
- `components/root/portfolio-projects-overview.tsx`
|
||||
- `bg-primary text-primary-foreground`
|
||||
- active nav and badges/buttons:
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/badge.tsx`
|
||||
- `components/dashboard/sidebar.tsx`
|
||||
- `components/root/portfolio-subnav.tsx`
|
||||
- `app/[locale]/(site)/portfolio/page.tsx`
|
||||
- `text-muted-foreground`
|
||||
- repeated throughout forms, cards, table headers, descriptions
|
||||
- `hover:bg-accent`
|
||||
- repeated in `button`, `dropdown-menu`, select items, and selection cards
|
||||
- `bg-muted/40` and `bg-muted/50`
|
||||
- repeated in admin surfaces and tables
|
||||
|
||||
### Site-specific color usage outside semantic surface tokens
|
||||
|
||||
Public-facing chrome also uses many arbitrary values:
|
||||
|
||||
- `border-black/6`
|
||||
- `bg-white/24`
|
||||
- `bg-white/32`
|
||||
- `text-[hsl(var(--hero-ink)/0.76)]`
|
||||
- custom shadow values with raw `rgba(...)`
|
||||
|
||||
Examples:
|
||||
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
- `app/[locale]/(site)/page.tsx`
|
||||
|
||||
## 8. Motion/animation approach actually used
|
||||
|
||||
There are two real motion layers.
|
||||
|
||||
### `framer-motion`
|
||||
|
||||
Used for custom movement and page chrome:
|
||||
|
||||
- reveal-on-scroll wrapper:
|
||||
- `components/motion-fade.tsx`
|
||||
- header nav active pill and mobile menu:
|
||||
- `components/layout/site-header.tsx`
|
||||
- logo animation:
|
||||
- `components/layout/site-logo.tsx`
|
||||
- `components/layout/animated-logo.tsx`
|
||||
- ambient / backdrop motion:
|
||||
- `components/layout/hero-motion-backdrop.tsx`
|
||||
- `components/layout/hero-atmosphere.tsx`
|
||||
- `components/layout/site-ambient-backdrop.tsx`
|
||||
- `components/layout/ascii-water-ripple.tsx`
|
||||
- `components/layout/stacked-marquee-section.tsx`
|
||||
|
||||
### `tailwindcss-animate`
|
||||
|
||||
Used through state classes for Radix-style overlays and disclosures:
|
||||
|
||||
- `data-[state=open]:animate-in`
|
||||
- `data-[state=closed]:animate-out`
|
||||
- `fade-in-0`
|
||||
- `zoom-in-95`
|
||||
- `slide-in-from-top-2`
|
||||
- `animate-accordion-down`
|
||||
- `animate-accordion-up`
|
||||
|
||||
Files:
|
||||
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/dropdown-menu.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `components/ui/accordion.tsx`
|
||||
|
||||
## 9. Icon system actually used
|
||||
|
||||
The icon system is
|
||||
`lucide-react`
|
||||
.
|
||||
|
||||
Evidence:
|
||||
|
||||
- dependency in `package.json`
|
||||
- broad usage across app and components
|
||||
- examples:
|
||||
- `components/root/root-dashboard-shell.tsx`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/theme-toggle.tsx`
|
||||
- `app/root/page.tsx`
|
||||
- `app/[locale]/(site)/page.tsx`
|
||||
|
||||
No second React icon library is present in `package.json`.
|
||||
|
||||
## 10. Component architecture currently used
|
||||
|
||||
### Routing structure
|
||||
|
||||
- `app` is the real route layer.
|
||||
- public site routes live under:
|
||||
- `app/[locale]/(site)/*`
|
||||
- admin routes live under:
|
||||
- `app/root/*`
|
||||
- `src` exists but is empty in the current codebase.
|
||||
|
||||
### Component folders
|
||||
|
||||
- `components/ui`
|
||||
- primitive and near-primitive reusable controls
|
||||
- `components/layout`
|
||||
- site shell, hero, header, footer, container, backdrops
|
||||
- `components/root`
|
||||
- admin feature components and forms
|
||||
- `components/dashboard`
|
||||
- admin navigation and dashboard layout shell
|
||||
- `components/site`
|
||||
- site feature forms such as contact form
|
||||
|
||||
### Helper layer
|
||||
|
||||
- `lib/utils.ts`
|
||||
- `cn()`
|
||||
- `lib/root-navigation.ts`
|
||||
- navigation config for admin shell
|
||||
- other `lib/*`
|
||||
- app settings, metadata, locale, media, portfolio, auth
|
||||
|
||||
### Wrappers around primitives
|
||||
|
||||
Confirmed custom wrappers around local primitive layer:
|
||||
|
||||
- `components/ui/app-card.tsx`
|
||||
- wraps `Card`
|
||||
- `components/theme-toggle.tsx`
|
||||
- wraps `Button`
|
||||
- `components/layout/locale-toggle.tsx`
|
||||
- uses `Button` and `DropdownMenu`
|
||||
- `components/root/form-save-button.tsx`
|
||||
- uses `Button`
|
||||
|
||||
## 11. Violations or inconsistencies found in the current codebase
|
||||
|
||||
These are current inconsistencies visible in the implementation.
|
||||
|
||||
### Token system is mixed with raw one-off styling
|
||||
|
||||
Admin and primitive components strongly use semantic tokens:
|
||||
|
||||
- `bg-card`
|
||||
- `bg-background`
|
||||
- `border-input`
|
||||
- `shadow-card`
|
||||
- `rounded-surface`
|
||||
|
||||
But public chrome often bypasses them with raw arbitrary values:
|
||||
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
- `app/[locale]/(site)/page.tsx`
|
||||
|
||||
Examples:
|
||||
|
||||
- `bg-white/24`
|
||||
- `border-black/6`
|
||||
- `shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)]`
|
||||
|
||||
### Radius usage is not fully normalized
|
||||
|
||||
Tokenized radius classes exist, but these also appear:
|
||||
|
||||
- `rounded-md`
|
||||
- `rounded-lg`
|
||||
- `rounded-full`
|
||||
|
||||
Examples:
|
||||
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/dashboard/dashboard-layout.tsx`
|
||||
- `components/root/site-settings-form.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
|
||||
### Some primitives follow the local system, some keep stock shadcn-style values
|
||||
|
||||
- tokenized:
|
||||
- `components/ui/card.tsx`
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/input.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- less tokenized:
|
||||
- `components/ui/dialog.tsx`
|
||||
- `sm:rounded-lg`
|
||||
- `shadow-lg`
|
||||
- less tokenized:
|
||||
- `components/ui/sheet.tsx`
|
||||
- `shadow-lg`
|
||||
|
||||
### `AppCard` surface levels are not all visually distinct
|
||||
|
||||
In `components/ui/app-card.tsx`:
|
||||
|
||||
- `level: 1` and `level: 2` both use:
|
||||
- `border border-border/80 bg-card text-card-foreground shadow-card`
|
||||
|
||||
So the file defines multiple levels, but two of them are currently identical.
|
||||
|
||||
### Primitive implementation style is not uniform
|
||||
|
||||
Most interactive primitives use Radix wrappers, but `components/ui/tabs.tsx` is a custom React context implementation instead.
|
||||
|
||||
### Tailwind scans `src`, but `src` is empty
|
||||
|
||||
`tailwind.config.ts`
|
||||
includes:
|
||||
|
||||
- `./src/**/*.{js,ts,jsx,tsx,mdx}`
|
||||
|
||||
Current project state:
|
||||
|
||||
- `src` contains no files.
|
||||
|
||||
## 12. Codex rules derived from the existing system
|
||||
|
||||
These rules are derived from what the codebase already does.
|
||||
|
||||
- Use local primitives from `components/ui` before introducing direct Radix usage.
|
||||
- Use `cn()` from `lib/utils.ts` for class composition.
|
||||
- Use `cva()` for reusable variant APIs where the project already does that:
|
||||
- `Button`
|
||||
- `Badge`
|
||||
- `AppCard`
|
||||
- `Container`
|
||||
- For shared admin surfaces, prefer tokenized classes already present:
|
||||
- `rounded-surface`
|
||||
- `rounded-nested`
|
||||
- `bg-card`
|
||||
- `bg-background`
|
||||
- `border-input`
|
||||
- `border-border/80`
|
||||
- `shadow-card`
|
||||
- `shadow-panel`
|
||||
- For main width constraints, follow the existing container system:
|
||||
- `Container`
|
||||
- `max-w-layout`
|
||||
- `max-w-admin`
|
||||
- `px-4 sm:px-6 lg:px-8`
|
||||
- For admin pages and forms, the repeated layout rhythm is:
|
||||
- `space-y-6`
|
||||
- `gap-6`
|
||||
- `space-y-2`
|
||||
- `p-6`
|
||||
- `p-4`
|
||||
- For public site hero/layout sections, the repeated shell is:
|
||||
- `hero-surface`
|
||||
- `max-w-[72rem]`
|
||||
- `px-4 sm:px-6 lg:px-8`
|
||||
- `pt-24 sm:pt-28`
|
||||
- Use `lucide-react` for icons.
|
||||
- Use `framer-motion` for custom motion and `tailwindcss-animate` for Radix state transitions.
|
||||
- Treat `shadcn/ui` in this repo as a local customized base, not as a stock untouched library.
|
||||
@@ -0,0 +1,536 @@
|
||||
# Frontend System Current State
|
||||
|
||||
## 1. UI primitive source of truth
|
||||
|
||||
The current primitive source of truth is:
|
||||
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/card.tsx`
|
||||
- `components/ui/app-card.tsx`
|
||||
- `components/ui/input.tsx`
|
||||
- `components/ui/textarea.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `components/ui/tabs.tsx`
|
||||
- `components/ui/dropdown-menu.tsx`
|
||||
- `components/ui/accordion.tsx`
|
||||
- `components/ui/badge.tsx`
|
||||
- `components/ui/checkbox.tsx`
|
||||
- `components/ui/table.tsx`
|
||||
- `components/ui/label.tsx`
|
||||
- `components/ui/separator.tsx`
|
||||
|
||||
Feature code currently reuses these primitives directly.
|
||||
|
||||
## 2. Libraries currently used for UI, motion, icons, and class composition
|
||||
|
||||
UI primitives and wrappers:
|
||||
|
||||
- `@radix-ui/react-accordion`
|
||||
- `@radix-ui/react-checkbox`
|
||||
- `@radix-ui/react-dialog`
|
||||
- `@radix-ui/react-dropdown-menu`
|
||||
- `@radix-ui/react-select`
|
||||
- `@radix-ui/react-slot`
|
||||
|
||||
Motion:
|
||||
|
||||
- `framer-motion`
|
||||
- `tailwindcss-animate`
|
||||
|
||||
Icons:
|
||||
|
||||
- `lucide-react`
|
||||
|
||||
Class composition:
|
||||
|
||||
- `class-variance-authority`
|
||||
- `clsx`
|
||||
- `tailwind-merge`
|
||||
|
||||
Shared helper:
|
||||
|
||||
- `lib/utils.ts`
|
||||
- `cn(...inputs)`
|
||||
|
||||
Source files:
|
||||
|
||||
- `package.json`
|
||||
- `lib/utils.ts`
|
||||
|
||||
## 3. Current token system in use
|
||||
|
||||
The active semantic token layer is defined in:
|
||||
|
||||
- `app/globals.css`
|
||||
- `tailwind.config.ts`
|
||||
|
||||
Current color token groups exposed in Tailwind:
|
||||
|
||||
- `background`
|
||||
- `foreground`
|
||||
- `card`
|
||||
- `card-foreground`
|
||||
- `popover`
|
||||
- `popover-foreground`
|
||||
- `primary`
|
||||
- `primary-foreground`
|
||||
- `secondary`
|
||||
- `secondary-foreground`
|
||||
- `muted`
|
||||
- `muted-foreground`
|
||||
- `accent`
|
||||
- `accent-foreground`
|
||||
- `destructive`
|
||||
- `destructive-foreground`
|
||||
- `border`
|
||||
- `border-strong`
|
||||
- `input`
|
||||
- `ring`
|
||||
- `surface.1`
|
||||
- `surface.2`
|
||||
- `surface.3`
|
||||
- `surface.inverse`
|
||||
- `brand.primary`
|
||||
- `brand.secondary`
|
||||
- `status.success`
|
||||
- `status.success-soft`
|
||||
- `status.warning`
|
||||
- `status.warning-soft`
|
||||
- `sidebar.*`
|
||||
|
||||
Current tokenized utility usage in shared UI is based on:
|
||||
|
||||
- `bg-card`
|
||||
- `bg-background`
|
||||
- `bg-surface-2`
|
||||
- `text-card-foreground`
|
||||
- `text-foreground`
|
||||
- `text-muted-foreground`
|
||||
- `border-border/80`
|
||||
- `border-border/70`
|
||||
- `border-input`
|
||||
- `hover:bg-accent`
|
||||
|
||||
## 4. Current radius system in use
|
||||
|
||||
Defined in:
|
||||
|
||||
- `app/globals.css`
|
||||
- `tailwind.config.ts`
|
||||
|
||||
Current radius tokens:
|
||||
|
||||
- `--radius-surface: 24px`
|
||||
- `--radius-nested: 16px`
|
||||
- `--radius-pill: 9999px`
|
||||
|
||||
Current mapped utilities:
|
||||
|
||||
- `rounded-surface`
|
||||
- `rounded-nested`
|
||||
- `rounded-pill`
|
||||
|
||||
Current shared primitive usage:
|
||||
|
||||
- surface containers:
|
||||
- `rounded-surface`
|
||||
- `components/ui/card.tsx`
|
||||
- `components/ui/app-card.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/tabs.tsx`
|
||||
- controls and nested blocks:
|
||||
- `rounded-nested`
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/input.tsx`
|
||||
- `components/ui/textarea.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- pill controls:
|
||||
- `rounded-pill`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
|
||||
## 5. Current surface/card/panel rules in use
|
||||
|
||||
### Base `Card`
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/card.tsx`
|
||||
|
||||
Current base rule:
|
||||
|
||||
```tsx
|
||||
rounded-surface border border-border/80 bg-card text-card-foreground shadow-card
|
||||
```
|
||||
|
||||
### `AppCard`
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/app-card.tsx`
|
||||
|
||||
Current visual levels:
|
||||
|
||||
- `level={1}`
|
||||
- `border border-border/80 bg-card text-card-foreground shadow-card`
|
||||
- `level={2}`
|
||||
- `border border-input bg-background text-foreground shadow-xs`
|
||||
- `level={3}`
|
||||
- `border border-border/80 bg-surface-2 text-foreground shadow-panel`
|
||||
- `level="inverse"`
|
||||
- `border-transparent bg-foreground text-background shadow-card`
|
||||
|
||||
Current padding variants:
|
||||
|
||||
- `none`
|
||||
- `sm`
|
||||
- `p-4`
|
||||
- `md`
|
||||
- `p-6`
|
||||
- `lg`
|
||||
- `p-8`
|
||||
|
||||
Current interactive rule:
|
||||
|
||||
- `hover:bg-accent/30 hover:text-accent-foreground`
|
||||
|
||||
### Current surface usage in feature code
|
||||
|
||||
Nested surface blocks currently reuse `AppCard` directly in:
|
||||
|
||||
- `components/root/media-field-picker.tsx`
|
||||
- `components/root/portfolio-projects-overview.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
- `components/root/portfolio-categories-manager.tsx`
|
||||
- `components/root/site-settings-form.tsx`
|
||||
|
||||
## 6. Current input/button/select/dialog/sheet rules in use
|
||||
|
||||
### Button
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/button.tsx`
|
||||
|
||||
Base:
|
||||
|
||||
```tsx
|
||||
inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-nested text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50
|
||||
```
|
||||
|
||||
Current variants:
|
||||
|
||||
- `default`
|
||||
- `bg-primary text-primary-foreground shadow-sm hover:bg-primary/92`
|
||||
- `secondary`
|
||||
- `bg-secondary text-secondary-foreground hover:bg-secondary/90`
|
||||
- `outline`
|
||||
- `border border-input bg-background hover:border-border-strong hover:bg-accent hover:text-accent-foreground`
|
||||
- `ghost`
|
||||
- `text-muted-foreground hover:bg-muted hover:text-foreground`
|
||||
- `link`
|
||||
- `rounded-none text-primary underline-offset-4 hover:underline`
|
||||
- `destructive`
|
||||
- `bg-destructive text-destructive-foreground hover:bg-destructive/90`
|
||||
|
||||
Current sizes:
|
||||
|
||||
- `default`
|
||||
- `h-10 px-4 py-2`
|
||||
- `sm`
|
||||
- `h-9 px-3`
|
||||
- `lg`
|
||||
- `h-11 px-6`
|
||||
- `icon`
|
||||
- `h-10 w-10`
|
||||
|
||||
### Input
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/input.tsx`
|
||||
|
||||
Current rule:
|
||||
|
||||
```tsx
|
||||
flex h-10 w-full rounded-nested border border-input bg-background px-3 py-2 text-base text-foreground shadow-xs ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30 focus-visible:ring-offset-0
|
||||
```
|
||||
|
||||
### Select
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/select.tsx`
|
||||
|
||||
Current trigger rule:
|
||||
|
||||
```tsx
|
||||
flex h-10 w-full items-center justify-between rounded-nested border border-input bg-background px-3 py-2 text-sm shadow-xs
|
||||
```
|
||||
|
||||
Current content rule:
|
||||
|
||||
```tsx
|
||||
rounded-surface border border-input bg-background text-foreground shadow-panel
|
||||
```
|
||||
|
||||
Current item rule:
|
||||
|
||||
```tsx
|
||||
rounded-nested ... focus:bg-accent focus:text-accent-foreground
|
||||
```
|
||||
|
||||
### Dialog
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/dialog.tsx`
|
||||
|
||||
Current overlay rule:
|
||||
|
||||
```tsx
|
||||
fixed inset-0 z-50 bg-foreground/30 backdrop-blur-sm
|
||||
```
|
||||
|
||||
Current content rule:
|
||||
|
||||
```tsx
|
||||
rounded-surface border border-border/80 bg-card p-6 text-card-foreground shadow-panel
|
||||
```
|
||||
|
||||
Current close button rule:
|
||||
|
||||
```tsx
|
||||
inline-flex h-8 w-8 items-center justify-center rounded-nested text-muted-foreground hover:bg-accent hover:text-foreground
|
||||
```
|
||||
|
||||
### Sheet
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/sheet.tsx`
|
||||
|
||||
Current overlay rule matches `Dialog`:
|
||||
|
||||
```tsx
|
||||
fixed inset-0 z-50 bg-foreground/30 backdrop-blur-sm
|
||||
```
|
||||
|
||||
Current content rule:
|
||||
|
||||
```tsx
|
||||
border-border/80 bg-card p-6 text-card-foreground shadow-panel
|
||||
```
|
||||
|
||||
Current side radius rules:
|
||||
|
||||
- `sm:rounded-l-surface`
|
||||
- `sm:rounded-r-surface`
|
||||
- `rounded-b-surface`
|
||||
- `rounded-t-surface`
|
||||
|
||||
### Tabs
|
||||
|
||||
Source:
|
||||
|
||||
- `components/ui/tabs.tsx`
|
||||
|
||||
Current list rule:
|
||||
|
||||
```tsx
|
||||
inline-flex ... gap-2 rounded-surface border border-border/80 bg-muted/40 p-1.5
|
||||
```
|
||||
|
||||
Current trigger rule:
|
||||
|
||||
```tsx
|
||||
rounded-nested px-3 py-2 text-sm font-medium text-muted-foreground
|
||||
```
|
||||
|
||||
Current active rule:
|
||||
|
||||
```tsx
|
||||
border border-border/80 bg-background text-foreground shadow-xs
|
||||
```
|
||||
|
||||
## 7. Current layout/container rules in use
|
||||
|
||||
### Container
|
||||
|
||||
Source:
|
||||
|
||||
- `components/layout/container.tsx`
|
||||
|
||||
Current base container rule:
|
||||
|
||||
```tsx
|
||||
mx-auto w-full px-4 sm:px-6 lg:px-8
|
||||
```
|
||||
|
||||
Current size variants:
|
||||
|
||||
- `default`
|
||||
- `max-w-layout`
|
||||
- `narrow`
|
||||
- `max-w-narrow`
|
||||
- `wide`
|
||||
- `max-w-wide`
|
||||
- `admin`
|
||||
- `max-w-admin`
|
||||
|
||||
### Shared public chrome
|
||||
|
||||
Current shared top controls use:
|
||||
|
||||
- `rounded-pill`
|
||||
- `border-border/70`
|
||||
- `bg-background/80`
|
||||
- `shadow-panel`
|
||||
- `backdrop-blur-chrome`
|
||||
|
||||
Files:
|
||||
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
|
||||
### Admin shell
|
||||
|
||||
Current admin layout still uses:
|
||||
|
||||
- `min-h-screen bg-muted/30`
|
||||
- `grid min-h-screen lg:grid-cols-[280px_minmax(0,1fr)]`
|
||||
- `sticky top-0`
|
||||
- `p-4 lg:p-6`
|
||||
|
||||
Source:
|
||||
|
||||
- `components/dashboard/dashboard-layout.tsx`
|
||||
|
||||
## 8. Current spacing rhythm in use
|
||||
|
||||
Current repeated spacing values across primitives and layout:
|
||||
|
||||
- `p-4`
|
||||
- `p-6`
|
||||
- `p-8`
|
||||
- `px-3 py-2`
|
||||
- `px-4 py-2`
|
||||
- `px-4 py-3`
|
||||
- `px-4 py-4`
|
||||
- `gap-2`
|
||||
- `gap-3`
|
||||
- `gap-4`
|
||||
- `gap-6`
|
||||
- `space-y-2`
|
||||
- `space-y-3`
|
||||
- `space-y-4`
|
||||
- `space-y-6`
|
||||
|
||||
Current primitive spacing sources:
|
||||
|
||||
- `CardHeader`
|
||||
- `gap-1.5 p-6`
|
||||
- `CardContent`
|
||||
- `p-6 pt-0`
|
||||
- `AppCard padding="sm"`
|
||||
- `p-4`
|
||||
- `AppCard padding="md"`
|
||||
- `p-6`
|
||||
- `AppCard padding="lg"`
|
||||
- `p-8`
|
||||
- `Container`
|
||||
- `px-4 sm:px-6 lg:px-8`
|
||||
|
||||
## 9. Current motion rules in use
|
||||
|
||||
Primary motion library:
|
||||
|
||||
- `framer-motion`
|
||||
|
||||
State animation utility layer:
|
||||
|
||||
- `tailwindcss-animate`
|
||||
|
||||
Current motion patterns:
|
||||
|
||||
- reveal-on-scroll wrapper
|
||||
- `components/motion-fade.tsx`
|
||||
- `initial: { opacity: 0, y: 20 }`
|
||||
- `whileInView: { opacity: 1, y: 0 }`
|
||||
- `transition: { duration: 0.45, ease: "easeOut" }`
|
||||
- shared overlay open/close classes
|
||||
- `dialog`
|
||||
- `sheet`
|
||||
- `select`
|
||||
- `dropdown-menu`
|
||||
- `accordion`
|
||||
- animated nav active pill and mobile menu
|
||||
- `components/layout/site-header.tsx`
|
||||
|
||||
## 10. Current icon rules in use
|
||||
|
||||
Current icon library:
|
||||
|
||||
- `lucide-react`
|
||||
|
||||
Current shared rule:
|
||||
|
||||
Icons are imported directly from `lucide-react` and passed through local primitives or feature components.
|
||||
|
||||
Examples:
|
||||
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/theme-toggle.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
|
||||
No second React icon library is currently present in `package.json`.
|
||||
|
||||
## 11. Current forbidden drift or remaining transitional areas, if any
|
||||
|
||||
The shared primitive system is currently tokenized.
|
||||
|
||||
Remaining transitional areas still present in the codebase:
|
||||
|
||||
- public hero / atmospheric visuals still use raw gradients and `rgba(...)` decorative backgrounds
|
||||
- `components/layout/home-hero.tsx`
|
||||
- `components/layout/site-ambient-backdrop.tsx`
|
||||
- `components/layout/hero-atmosphere.tsx`
|
||||
- `components/layout/site-logo.tsx`
|
||||
- `coming-soon` page still uses older raw translucent chrome styles
|
||||
- `app/[locale]/coming-soon/page.tsx`
|
||||
- some public route-level content blocks still use direct local classes instead of shared primitive wrappers
|
||||
- `app/[locale]/(site)/portfolio/page.tsx`
|
||||
- `app/[locale]/(site)/portfolio/[slug]/page.tsx`
|
||||
- one preview block in site settings still uses a custom dark preview surface
|
||||
- `components/root/site-settings-form.tsx`
|
||||
- `bg-slate-950`
|
||||
- `text-white/55`
|
||||
- `text-white/60`
|
||||
|
||||
These files exist in the current codebase and should be treated as remaining transitional areas, not as shared-system rules.
|
||||
|
||||
## 12. Codex rules derived from the current system
|
||||
|
||||
- Use `components/ui` as the primitive source of truth.
|
||||
- Use `cn()` from `lib/utils.ts` for class composition.
|
||||
- Use `cva()` where the primitive already exposes variants.
|
||||
- Use semantic tokens already defined in `app/globals.css` and mapped in `tailwind.config.ts`.
|
||||
- Use `Card` for the base surface:
|
||||
- `rounded-surface border border-border/80 bg-card text-card-foreground shadow-card`
|
||||
- Use `AppCard` when feature code needs explicit shared surface levels.
|
||||
- Use `AppCard level={1}` for primary card surfaces.
|
||||
- Use `AppCard level={2}` for nested panels and inner blocks.
|
||||
- Use `AppCard level={3}` for elevated grouped panels.
|
||||
- Use `rounded-surface`, `rounded-nested`, and `rounded-pill` as the shared radius system.
|
||||
- Use `Input`, `Textarea`, `Select`, `Dialog`, `Sheet`, and `Tabs` from `components/ui` instead of re-creating those patterns in feature code.
|
||||
- Use `Container` for width and horizontal padding rules.
|
||||
- Use `lucide-react` for icons.
|
||||
- Use `framer-motion` for custom motion and `tailwindcss-animate` for primitive state transitions.
|
||||
- Do not treat transitional decorative hero files or remaining route-local raw blocks as shared-system primitives.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Frontend System Refactor Summary
|
||||
|
||||
## Scope
|
||||
|
||||
This refactor was applied directly in the codebase.
|
||||
|
||||
It focused on:
|
||||
|
||||
- radius normalization
|
||||
- primitive normalization
|
||||
- removing arbitrary visual drift from shared UI
|
||||
- standardizing shared surface patterns
|
||||
- fixing the false abstraction in `components/ui/app-card.tsx`
|
||||
|
||||
## Architecture Preserved
|
||||
|
||||
- `components/ui` remains the primitive source of truth
|
||||
- `cn()` in `lib/utils.ts` remains unchanged and is still the shared class composition helper
|
||||
- existing semantic token naming remains unchanged
|
||||
- no new UI library was introduced
|
||||
|
||||
Current primitive layer remains in:
|
||||
|
||||
- `components/ui/button.tsx`
|
||||
- `components/ui/card.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/select.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `components/ui/tabs.tsx`
|
||||
- `components/ui/app-card.tsx`
|
||||
|
||||
## What Changed
|
||||
|
||||
### 1. Radius normalization
|
||||
|
||||
Shared system areas were normalized toward:
|
||||
|
||||
- `rounded-surface`
|
||||
- `rounded-nested`
|
||||
- `rounded-pill`
|
||||
|
||||
This removed inconsistent `rounded-md` and `rounded-lg` usage from the shared admin and primitive system areas.
|
||||
|
||||
### 2. Primitive normalization
|
||||
|
||||
The following primitives were aligned with the project token system:
|
||||
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `components/ui/tabs.tsx`
|
||||
|
||||
Changes included:
|
||||
|
||||
- tokenized surface colors
|
||||
- tokenized borders
|
||||
- tokenized radius
|
||||
- tokenized shadows
|
||||
- unified close button treatment in `dialog` and `sheet`
|
||||
|
||||
### 3. Shared UI drift removal
|
||||
|
||||
Raw visual values were replaced with semantic tokens in:
|
||||
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
- `app/[locale]/(site)/page.tsx`
|
||||
|
||||
This removed raw usage patterns such as:
|
||||
|
||||
- hardcoded white/black alpha surfaces
|
||||
- raw `rgba(...)` shadows
|
||||
- raw `text-[hsl(...)]` color usage for shared controls
|
||||
|
||||
### 4. Shared surface standardization
|
||||
|
||||
Repeated nested panels and stat blocks in feature code were moved toward shared local primitives, especially `AppCard`.
|
||||
|
||||
Refactored areas include:
|
||||
|
||||
- `components/root/media-field-picker.tsx`
|
||||
- `components/root/portfolio-projects-overview.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
- `components/root/portfolio-categories-manager.tsx`
|
||||
- `components/root/site-settings-form.tsx`
|
||||
|
||||
### 5. False abstraction fixed
|
||||
|
||||
`components/ui/app-card.tsx` previously declared multiple levels where `level 1` and `level 2` were visually identical.
|
||||
|
||||
That was corrected.
|
||||
|
||||
Current `AppCard` levels are now meaningfully distinct:
|
||||
|
||||
- `level 1`
|
||||
- `bg-card`
|
||||
- `shadow-card`
|
||||
- `level 2`
|
||||
- `bg-background`
|
||||
- `border-input`
|
||||
- `shadow-xs`
|
||||
- `level 3`
|
||||
- `bg-surface-2`
|
||||
- `shadow-panel`
|
||||
- `inverse`
|
||||
- preserved as contrast surface
|
||||
|
||||
## Files Updated
|
||||
|
||||
- `app/[locale]/(site)/page.tsx`
|
||||
- `app/root/media/page.tsx`
|
||||
- `app/root/page.tsx`
|
||||
- `components/dashboard/dashboard-card.tsx`
|
||||
- `components/dashboard/dashboard-layout.tsx`
|
||||
- `components/layout/floating-preferences.tsx`
|
||||
- `components/layout/site-header.tsx`
|
||||
- `components/root/flash-message.tsx`
|
||||
- `components/root/media-field-picker.tsx`
|
||||
- `components/root/portfolio-categories-manager.tsx`
|
||||
- `components/root/portfolio-project-form.tsx`
|
||||
- `components/root/portfolio-projects-overview.tsx`
|
||||
- `components/root/portfolio-subnav.tsx`
|
||||
- `components/root/site-settings-form.tsx`
|
||||
- `components/ui/app-card.tsx`
|
||||
- `components/ui/dialog.tsx`
|
||||
- `components/ui/sheet.tsx`
|
||||
- `components/ui/tabs.tsx`
|
||||
- `components/ui/ui-kit-showcase.tsx`
|
||||
|
||||
## Validation
|
||||
|
||||
Validation run:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
- passed with no ESLint errors or warnings
|
||||
Reference in New Issue
Block a user