Initial commit
This commit is contained in:
980
skills/backstage-style-web/assets/component_templates.md
Normal file
980
skills/backstage-style-web/assets/component_templates.md
Normal file
@@ -0,0 +1,980 @@
|
||||
# Backstage Style Component Templates
|
||||
|
||||
## Core Page Templates
|
||||
|
||||
### 1. Dashboard Page Template
|
||||
|
||||
```typescript
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardAction } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { MoreHorizontal, Plus, Search, Filter, Download } from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
interface DashboardData {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "active" | "inactive" | "pending";
|
||||
value: number;
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [data, setData] = useState<DashboardData[]>([
|
||||
// Sample data
|
||||
{ id: "1", name: "Project Alpha", status: "active", value: 12450, lastUpdated: "2 hours ago" },
|
||||
{ id: "2", name: "Project Beta", status: "pending", value: 8900, lastUpdated: "1 day ago" },
|
||||
{ id: "3", name: "Project Gamma", status: "inactive", value: 3200, lastUpdated: "3 days ago" },
|
||||
]);
|
||||
|
||||
const filteredData = data.filter(item =>
|
||||
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const statusVariant = (status: string) => {
|
||||
switch (status) {
|
||||
case "active": return "default";
|
||||
case "pending": return "secondary";
|
||||
case "inactive": return "outline";
|
||||
default: return "secondary";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your projects and monitor performance
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Export
|
||||
</Button>
|
||||
<Button size="sm">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Add Project
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Projects</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">24</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+2 from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Active Projects</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">18</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+4 from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Value</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">$24,550</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+12% from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Completion Rate</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">89%</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+3% from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Data Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Projects</CardTitle>
|
||||
<CardAction>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search projects..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8 w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
Filter
|
||||
</Button>
|
||||
</div>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Value</TableHead>
|
||||
<TableHead>Last Updated</TableHead>
|
||||
<TableHead className="w-[70px]">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredData.map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="font-medium">{item.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusVariant(item.status)}>
|
||||
{item.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>${item.value.toLocaleString()}</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{item.lastUpdated}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Duplicate</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Form Page Template
|
||||
|
||||
```typescript
|
||||
"use client";
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z.string().min(2, "Title must be at least 2 characters"),
|
||||
description: z.string().min(10, "Description must be at least 10 characters"),
|
||||
category: z.string().min(1, "Please select a category"),
|
||||
priority: z.enum(["low", "medium", "high"]),
|
||||
isPublic: z.boolean().default(false),
|
||||
tags: z.array(z.string()).optional(),
|
||||
email: z.string().email("Invalid email address"),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export default function FormPage() {
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
description: "",
|
||||
category: "",
|
||||
priority: "medium",
|
||||
isPublic: false,
|
||||
tags: [],
|
||||
email: "",
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(values: FormData) {
|
||||
toast({
|
||||
title: "Form submitted successfully!",
|
||||
description: "Your data has been saved.",
|
||||
});
|
||||
console.log(values);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">Create New Item</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Fill out the form below to create a new item
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{/* Main Form */}
|
||||
<div className="md:col-span-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Item Details</CardTitle>
|
||||
<CardDescription>
|
||||
Provide the basic information for your new item
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter a title" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This will be the display name for your item.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Enter a description"
|
||||
className="min-h-[100px]"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Provide a detailed description of your item.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="category"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Category</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a category" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="web">Web Development</SelectItem>
|
||||
<SelectItem value="mobile">Mobile Development</SelectItem>
|
||||
<SelectItem value="design">Design</SelectItem>
|
||||
<SelectItem value="marketing">Marketing</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter email address" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="priority"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Priority Level</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-row space-x-6"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="low" id="low" />
|
||||
<Label htmlFor="low">Low</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="medium" id="medium" />
|
||||
<Label htmlFor="medium">Medium</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="high" id="high" />
|
||||
<Label htmlFor="high">High</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="isPublic"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">
|
||||
Make Public
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
Allow others to view this item publicly.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button type="submit">Create Item</Button>
|
||||
<Button type="button" variant="outline">
|
||||
Save as Draft
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Info */}
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Help</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Need help? Check out our documentation or contact support.
|
||||
</p>
|
||||
<div className="mt-4 space-y-2">
|
||||
<Button variant="outline" size="sm" className="w-full">
|
||||
View Documentation
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="w-full">
|
||||
Contact Support
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Stats</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Total Items</span>
|
||||
<span className="text-sm font-medium">247</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">This Month</span>
|
||||
<span className="text-sm font-medium">18</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Published</span>
|
||||
<span className="text-sm font-medium">156</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Settings Page Template
|
||||
|
||||
```typescript
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Bell, Shield, User, Palette, Globe } from "lucide-react";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [notifications, setNotifications] = useState({
|
||||
email: true,
|
||||
push: false,
|
||||
marketing: true,
|
||||
});
|
||||
|
||||
const [theme, setTheme] = useState("system");
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">Settings</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your account settings and preferences
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="general" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="general" className="flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
General
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="notifications" className="flex items-center gap-2">
|
||||
<Bell className="h-4 w-4" />
|
||||
Notifications
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="appearance" className="flex items-center gap-2">
|
||||
<Palette className="h-4 w-4" />
|
||||
Appearance
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="privacy" className="flex items-center gap-2">
|
||||
<Shield className="h-4 w-4" />
|
||||
Privacy
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="advanced" className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4" />
|
||||
Advanced
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="general" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Profile Information</CardTitle>
|
||||
<CardDescription>
|
||||
Update your personal information and profile details
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar className="h-20 w-20">
|
||||
<AvatarImage src="/avatars/01.png" />
|
||||
<AvatarFallback>JD</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="space-y-2">
|
||||
<Button variant="outline" size="sm">
|
||||
Change Avatar
|
||||
</Button>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
JPG, GIF or PNG. 1MB max.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">First Name</Label>
|
||||
<Input id="firstName" defaultValue="John" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lastName">Last Name</Label>
|
||||
<Input id="lastName" defaultValue="Doe" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" type="email" defaultValue="john@example.com" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="bio">Bio</Label>
|
||||
<Input id="bio" defaultValue="Software developer and designer" />
|
||||
</div>
|
||||
|
||||
<Button>Save Changes</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="notifications" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notification Preferences</CardTitle>
|
||||
<CardDescription>
|
||||
Choose what notifications you want to receive
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Email Notifications</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Receive notifications via email
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={notifications.email}
|
||||
onCheckedChange={(checked) =>
|
||||
setNotifications(prev => ({ ...prev, email: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Push Notifications</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Receive push notifications on your devices
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={notifications.push}
|
||||
onCheckedChange={(checked) =>
|
||||
setNotifications(prev => ({ ...prev, push: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Marketing Emails</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Receive emails about new features and updates
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={notifications.marketing}
|
||||
onCheckedChange={(checked) =>
|
||||
setNotifications(prev => ({ ...prev, marketing: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="appearance" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Theme Preferences</CardTitle>
|
||||
<CardDescription>
|
||||
Customize the appearance of your interface
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label>Theme</Label>
|
||||
<Select value={theme} onValueChange={setTheme}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Choose your preferred theme or sync with your system
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Language</Label>
|
||||
<Select defaultValue="en">
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="en">English</SelectItem>
|
||||
<SelectItem value="zh">中文</SelectItem>
|
||||
<SelectItem value="es">Español</SelectItem>
|
||||
<SelectItem value="fr">Français</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="privacy" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Privacy Settings</CardTitle>
|
||||
<CardDescription>
|
||||
Control your privacy and data sharing preferences
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Profile Visibility</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Make your profile visible to other users
|
||||
</p>
|
||||
</div>
|
||||
<Switch defaultChecked />
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Analytics</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Help us improve by sharing usage analytics
|
||||
</p>
|
||||
</div>
|
||||
<Switch defaultChecked />
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
<Label className="text-base">Data Export</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Download a copy of your data
|
||||
</p>
|
||||
<Button variant="outline">Request Data Export</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="advanced" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Advanced Settings</CardTitle>
|
||||
<CardDescription>
|
||||
Advanced configuration options for power users
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label>API Access</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input
|
||||
readOnly
|
||||
value="sk-1234567890abcdef"
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
<Button variant="outline" size="sm">
|
||||
Copy
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Regenerate
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Use this API key to access our services programmatically
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
<Label className="text-base text-red-600">Danger Zone</Label>
|
||||
<div className="border border-red-200 rounded-lg p-4 space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium">Delete Account</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Permanently delete your account and all data
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="destructive" size="sm">
|
||||
Delete Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Component Building Blocks
|
||||
|
||||
### 1. Data Display Card
|
||||
|
||||
```typescript
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface DataCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
description?: string;
|
||||
trend?: "up" | "down" | "neutral";
|
||||
trendValue?: string;
|
||||
action?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function DataCard({
|
||||
title,
|
||||
value,
|
||||
description,
|
||||
trend,
|
||||
trendValue,
|
||||
action
|
||||
}: DataCardProps) {
|
||||
const trendColor = {
|
||||
up: "text-green-600",
|
||||
down: "text-red-600",
|
||||
neutral: "text-muted-foreground",
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{title}</CardTitle>
|
||||
{action}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{value}</div>
|
||||
{(description || trendValue) && (
|
||||
<p className={`text-xs ${trend ? trendColor[trend] : 'text-muted-foreground'}`}>
|
||||
{trendValue && <span>{trendValue} </span>}
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Action Header
|
||||
|
||||
```typescript
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Search, Plus, Filter, Download } from "lucide-react";
|
||||
|
||||
interface ActionHeaderProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
searchPlaceholder?: string;
|
||||
onSearch?: (value: string) => void;
|
||||
actions?: Array<{
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
variant?: "default" | "outline" | "secondary";
|
||||
onClick?: () => void;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function ActionHeader({
|
||||
title,
|
||||
description,
|
||||
searchPlaceholder,
|
||||
onSearch,
|
||||
actions = []
|
||||
}: ActionHeaderProps) {
|
||||
return (
|
||||
<div className="flex flex-col space-y-4 md:flex-row md:items-center md:justify-between md:space-y-0">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">{title}</h2>
|
||||
{description && (
|
||||
<p className="text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{searchPlaceholder && onSearch && (
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => onSearch(e.target.value)}
|
||||
className="pl-8 w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{actions.map((action, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant={action.variant || "default"}
|
||||
size="sm"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.icon}
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Status Badge
|
||||
|
||||
```typescript
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
const statusVariants = cva("", {
|
||||
variants: {
|
||||
status: {
|
||||
active: "bg-green-100 text-green-800 hover:bg-green-100/80",
|
||||
inactive: "bg-gray-100 text-gray-800 hover:bg-gray-100/80",
|
||||
pending: "bg-yellow-100 text-yellow-800 hover:bg-yellow-100/80",
|
||||
error: "bg-red-100 text-red-800 hover:bg-red-100/80",
|
||||
success: "bg-emerald-100 text-emerald-800 hover:bg-emerald-100/80",
|
||||
warning: "bg-amber-100 text-amber-800 hover:bg-amber-100/80",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
status: "inactive",
|
||||
},
|
||||
});
|
||||
|
||||
interface StatusBadgeProps extends VariantProps<typeof statusVariants> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function StatusBadge({ status, children, className }: StatusBadgeProps) {
|
||||
return (
|
||||
<Badge variant="secondary" className={statusVariants({ status, className })}>
|
||||
{children}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Layout Wrapper
|
||||
|
||||
```typescript
|
||||
interface PageLayoutProps {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
actions?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PageLayout({ children, title, description, actions }: PageLayoutProps) {
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">{title}</h2>
|
||||
{description && (
|
||||
<p className="text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
{actions && <div className="flex items-center space-x-2">{actions}</div>}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
These templates provide a solid foundation for building Backstage-style applications with consistent patterns, proper TypeScript types, and responsive design.
|
||||
Reference in New Issue
Block a user