refactor: adopt shared Input/Textarea components (#101)

* refactor: adopt shared Input/Textarea components across 15 files

Replace 42 raw <input>/<textarea> elements with <Input>/<Textarea>
from components/ui/. Consistent focus states, error handling, and
styling across all form fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: replace hardcoded rgba/hex colors with Tailwind tokens

- rgba(255,255,255,0.xx) → bg-white/[0.xx], border-white/[0.xx]
- rgba(6,182,212,0.3) → border-primary/30 (focus states)
- #0a0a0a → bg-background
- Inline style hex colors → var(--color-primary), var(--color-brand-gradient-to)
- 28 files updated, zero hardcoded rgba() patterns remaining

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add PageMeta to 16 pages for SEO and proper browser tab titles

Public pages (Login, Register, Forgot/Reset Password, Verify Email,
Survey Thank You) get descriptions for SEO. Authenticated pages
(Dashboard, Flow Library, My Flows, Session History, AI Assistant,
Account Settings, Step Library, My Shares, Feedback, Guides) get
proper tab titles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add page transitions and staggered list animations

- ViewTransitionOutlet: wraps Outlet with fade-in-up animation keyed
  to route path. Sidebar/topbar stay still, only content area animates.
- StaggerList: reusable component that cascades children with
  incremental delay (50ms default). Pure CSS via @utility stagger-item.
- Applied stagger to TreeGridView, MyTreesPage cards, SessionHistoryPage.
- New stagger-fade-in keyframe in @theme block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: ViewTransitionOutlet needs h-full for React Flow canvas

The wrapper div broke the height chain needed by TreeEditorPage's
h-full layout, causing React Flow canvas to collapse to zero height.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: main-content flex layout for tree editor + scrollable pages

Main content area is now flex-col so the ViewTransitionOutlet wrapper
gets an explicit computed height via flex-1 min-h-0. This makes h-full
resolve correctly in the tree editor (React Flow canvas) while still
allowing overflow-y-auto scrolling for normal pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve ESLint errors in Button and Skeleton components

- Button: suppress react-refresh/only-export-components for buttonVariants re-export
- Skeleton: replace empty interface with type alias, replace Math.random() with static widths array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add PageMeta, animation classes, and layout fixes to remaining pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #101.
This commit is contained in:
chihlasm
2026-03-09 16:12:21 -04:00
committed by GitHub
parent b28a096738
commit 5095b0d8df
65 changed files with 352 additions and 298 deletions

View File

@@ -1,8 +1,9 @@
import { useState } from 'react'
import { Modal } from '@/components/common/Modal'
import { GitFork } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
interface ForkTreeModalProps {
isOpen: boolean
@@ -83,16 +84,12 @@ export function ForkTreeModal({
<label htmlFor="tree-name" className="mb-1.5 block text-sm font-medium text-foreground">
Tree Name <span className="text-red-400">*</span>
</label>
<input
<Input
id="tree-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="My Custom Tree"
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -100,17 +97,13 @@ export function ForkTreeModal({
<label htmlFor="tree-description" className="mb-1.5 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
<Textarea
id="tree-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Describe what this tree helps troubleshoot..."
rows={3}
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
'resize-none'
)}
className="resize-none"
/>
</div>
</div>

View File

@@ -1,7 +1,8 @@
import { useState } from 'react'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
interface SaveSessionAsTreeModalProps {
isOpen: boolean
@@ -60,7 +61,7 @@ export function SaveSessionAsTreeModal({
<label htmlFor="treeName" className="mb-1 block text-sm font-medium text-foreground">
Tree Name <span className="text-muted-foreground">(optional)</span>
</label>
<input
<Input
id="treeName"
type="text"
value={treeName}
@@ -68,12 +69,6 @@ export function SaveSessionAsTreeModal({
placeholder={defaultTreeName || "Auto-generated if left blank"}
disabled={isSaving}
maxLength={255}
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
</div>
@@ -82,19 +77,13 @@ export function SaveSessionAsTreeModal({
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
<Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Add a description for this tree"
disabled={isSaving}
rows={3}
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
</div>

View File

@@ -160,7 +160,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
</button>
{showDatePicker && (
<div className="absolute left-0 top-full z-50 mt-2 rounded-lg border border-border bg-[#0a0a0a] p-4 shadow-lg">
<div className="absolute left-0 top-full z-50 mt-2 rounded-lg border border-border bg-background p-4 shadow-lg">
{/* Date Type Toggle */}
<div className="mb-3 flex gap-2">
<button

View File

@@ -8,6 +8,7 @@ import { toast } from '@/lib/toast'
import { Spinner } from '@/components/common/Spinner'
import { Modal } from '@/components/common/Modal'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
interface ShareSessionModalProps {
sessionId: string
@@ -246,15 +247,11 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
<label className="mb-2 block text-sm font-medium text-foreground">
Share Name <span className="text-muted-foreground">(optional)</span>
</label>
<input
<Input
type="text"
value={shareName}
onChange={(e) => setShareName(e.target.value.slice(0, 100))}
placeholder="e.g. Training link, Customer escalation"
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
maxLength={100}
/>
</div>

View File

@@ -4,6 +4,7 @@ import { StarRating } from '@/components/common/StarRating'
import { cn } from '@/lib/utils'
import type { Step } from '@/types'
import { Button } from '@/components/ui/Button'
import { Textarea } from '@/components/ui/Textarea'
interface StepRatingData {
rating: number
@@ -103,7 +104,7 @@ export function StepRatingModal({
{librarySteps.map((step) => {
const rating = getRating(step.id)
return (
<div key={step.id} className="rounded-lg border border-border bg-[#0a0a0a] p-4">
<div key={step.id} className="rounded-lg border border-border bg-background p-4">
{/* Step Title */}
<h3 className="font-medium text-foreground">{step.title}</h3>
<p className="mt-1 text-sm text-muted-foreground capitalize">{step.step_type}</p>
@@ -164,7 +165,7 @@ export function StepRatingModal({
<label htmlFor={`review-${step.id}`} className="mb-1 block text-sm font-medium text-foreground">
Review <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
<Textarea
id={`review-${step.id}`}
value={rating?.review || ''}
onChange={(e) => handleReviewChange(step.id, e.target.value)}
@@ -172,12 +173,6 @@ export function StepRatingModal({
maxLength={500}
rows={2}
placeholder="Share your experience with this step..."
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-muted-foreground text-right">
{rating?.review?.length || 0}/500