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>
This commit is contained in:
Michael Chihlas
2026-03-08 01:03:41 -05:00
parent 94b428d168
commit 905e16de8b
15 changed files with 87 additions and 192 deletions

View File

@@ -1,7 +1,8 @@
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { Modal } from '@/components/common/Modal'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
interface CreateCategoryModalProps {
isOpen: boolean
@@ -93,7 +94,7 @@ export function CreateCategoryModal({
<label htmlFor="name" className="mb-1 block text-sm font-medium text-foreground">
Category Name <span className="text-red-400">*</span>
</label>
<input
<Input
id="name"
type="text"
value={name}
@@ -102,12 +103,6 @@ export function CreateCategoryModal({
maxLength={100}
placeholder="e.g., Network Troubleshooting"
required
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">
{name.length}/100 characters
@@ -118,19 +113,13 @@ export function CreateCategoryModal({
<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)}
disabled={isSaving}
rows={3}
placeholder="Brief description of this category..."
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>
</form>

View File

@@ -1,8 +1,9 @@
import { useState } from 'react'
import { cn } from '@/lib/utils'
import type { StepCategoryListItem } from '@/types'
import { Modal } from '@/components/common/Modal'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
interface EditCategoryModalProps {
isOpen: boolean
@@ -105,7 +106,7 @@ export function EditCategoryModal({
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-foreground">
Category Name <span className="text-red-400">*</span>
</label>
<input
<Input
id="edit-name"
type="text"
value={name}
@@ -114,12 +115,6 @@ export function EditCategoryModal({
maxLength={100}
placeholder="e.g., Network Troubleshooting"
required
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">
{name.length}/100 characters
@@ -130,19 +125,13 @@ export function EditCategoryModal({
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
<Textarea
id="edit-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
disabled={isSaving}
rows={3}
placeholder="Brief description of this category..."
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>
</form>

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

@@ -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
@@ -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

View File

@@ -4,6 +4,8 @@ import { cn } from '@/lib/utils'
import { stepCategoriesApi } from '@/api/stepCategories'
import type { StepCreate, StepCategory, StepCommand } from '@/types/step'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
interface StepFormProps {
onSubmit: (data: StepCreate) => void
@@ -173,20 +175,14 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
<label htmlFor="title" className="mb-2 block text-sm font-medium text-foreground">
Title <span className="text-red-400">*</span>
</label>
<input
<Input
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter step title"
className={cn(
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
errors.title ? 'border-red-400/50' : 'border-border'
)}
error={errors.title}
/>
{errors.title && (
<p className="mt-1 text-xs text-red-400">{errors.title}</p>
)}
</div>
{/* Instructions */}
@@ -195,20 +191,14 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
Instructions <span className="text-red-400">*</span>
<span className="ml-2 text-xs font-normal text-muted-foreground">(Markdown supported)</span>
</label>
<textarea
<Textarea
id="instructions"
value={instructions}
onChange={(e) => setInstructions(e.target.value)}
placeholder="Describe what to do in this step..."
rows={6}
className={cn(
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
errors.instructions ? 'border-red-400/50' : 'border-border'
)}
error={errors.instructions}
/>
{errors.instructions && (
<p className="mt-1 text-xs text-red-400">{errors.instructions}</p>
)}
</div>
{/* Help Text */}
@@ -216,13 +206,12 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
<label htmlFor="helpText" className="mb-2 block text-sm font-medium text-foreground">
Help Text <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
</label>
<textarea
<Textarea
id="helpText"
value={helpText}
onChange={(e) => setHelpText(e.target.value)}
placeholder="Additional context or tips..."
rows={3}
className="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>
@@ -256,31 +245,22 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
</button>
</div>
<div className="space-y-2">
<input
<Input
type="text"
value={cmd.label}
onChange={(e) => updateCommand(index, 'label', e.target.value)}
placeholder="Command label (e.g., 'Restart service')"
className={cn(
'w-full rounded-md border bg-card px-3 py-1.5 text-sm text-foreground',
errors[`command_${index}_label`] ? 'border-red-400/50' : 'border-border'
)}
className="py-1.5"
error={errors[`command_${index}_label`]}
/>
<input
<Input
type="text"
value={cmd.command}
onChange={(e) => updateCommand(index, 'command', e.target.value)}
placeholder="Command (e.g., 'systemctl restart nginx')"
className={cn(
'w-full rounded-md border bg-card px-3 py-1.5 font-mono text-sm text-foreground',
errors[`command_${index}_command`] ? 'border-red-400/50' : 'border-border'
)}
className="py-1.5 font-mono"
error={errors[`command_${index}_command`]}
/>
{(errors[`command_${index}_label`] || errors[`command_${index}_command`]) && (
<p className="text-xs text-red-400">
{errors[`command_${index}_label`] || errors[`command_${index}_command`]}
</p>
)}
</div>
</div>
))}
@@ -312,14 +292,14 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
Tags <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
</label>
<div className="flex gap-2">
<input
<Input
id="tagInput"
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleTagInputKeyDown}
placeholder="Type tag and press Enter"
className="flex-1 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"
className="flex-1"
/>
<button
type="button"