Files
myeasycms-v2/apps/dev-tool/app/components/components/item-story.tsx
Giancarlo Buomprisco 2e20d3e76f 2.18.0: New Invitation flow, refactored Database Webhooks, new ShadCN UI Components (#384)
* Streamlined invitations flow
* Removed web hooks in favor of handling logic directly in server actions
* Added new Shadcn UI Components
2025-10-05 17:54:16 +08:00

413 lines
12 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import { ArrowUpRight, Calendar, CheckCircle2, Clock3 } from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@kit/ui/card';
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemFooter,
ItemGroup,
ItemHeader,
ItemMedia,
ItemSeparator,
ItemTitle,
} from '@kit/ui/item';
import { Label } from '@kit/ui/label';
import { Separator } from '@kit/ui/separator';
import { Switch } from '@kit/ui/switch';
import { cn } from '@kit/ui/utils';
import {
formatCodeBlock,
generatePropsString,
useStoryControls,
} from '../lib/story-utils';
import { ComponentStoryLayout } from './story-layout';
import { SimpleStorySelect } from './story-select';
interface ItemControls {
variant: 'default' | 'outline' | 'muted';
size: 'default' | 'sm';
showMedia: boolean;
showDescription: boolean;
showActions: boolean;
showFooter: boolean;
showSeparator: boolean;
}
const variantOptions = [
{ value: 'default', label: 'Default', description: 'Unstyled surface' },
{ value: 'outline', label: 'Outline', description: 'Bordered list item' },
{ value: 'muted', label: 'Muted', description: 'Soft background highlight' },
] as const;
const sizeOptions = [
{ value: 'default', label: 'Default', description: 'Spacious item' },
{ value: 'sm', label: 'Small', description: 'Compact height' },
] as const;
export function ItemStory() {
const { controls, updateControl } = useStoryControls<ItemControls>({
variant: 'default',
size: 'default',
showMedia: true,
showDescription: true,
showActions: true,
showFooter: true,
showSeparator: true,
});
const itemPropsString = useMemo(
() =>
generatePropsString(
{
variant: controls.variant,
size: controls.size,
},
{
variant: 'default',
size: 'default',
},
),
[controls.variant, controls.size],
);
const generatedCode = useMemo(() => {
const lines: string[] = [];
lines.push(`<Item${itemPropsString}>`);
if (controls.showMedia) {
lines.push(
' <ItemMedia variant="icon">',
' <Calendar className="h-4 w-4" />',
' </ItemMedia>',
);
}
lines.push(' <ItemContent>');
lines.push(' <ItemHeader>');
lines.push(' <ItemTitle>Weekly planning</ItemTitle>');
if (controls.showActions) {
lines.push(
' <ItemActions>',
' <Badge variant="secondary">12 tasks</Badge>',
' <Button variant="ghost" size="sm" className="gap-1">',
' View',
' <ArrowUpRight className="h-4 w-4" />',
' </Button>',
' </ItemActions>',
);
}
lines.push(' </ItemHeader>');
if (controls.showDescription) {
lines.push(
' <ItemDescription>Plan upcoming sprints and capture blockers from the team sync.</ItemDescription>',
);
}
if (controls.showFooter) {
lines.push(
' <ItemFooter>',
' <div className="flex items-center gap-2 text-xs text-muted-foreground">',
' <Clock3 className="h-3.5 w-3.5" />',
' Updated 2 hours ago',
' </div>',
' <Badge variant="outline">In progress</Badge>',
' </ItemFooter>',
);
}
lines.push(' </ItemContent>');
lines.push('</Item>');
return formatCodeBlock(lines.join('\n'), [
"import { ArrowUpRight, Calendar, Clock3 } from 'lucide-react';",
"import { Badge } from '@kit/ui/badge';",
"import { Button } from '@kit/ui/button';",
"import { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemMedia, ItemTitle } from '@kit/ui/item';",
]);
}, [controls, itemPropsString]);
const preview = (
<ItemGroup className="gap-3">
<Item
variant={controls.variant}
size={controls.size}
className={cn('w-full')}
>
{controls.showMedia && (
<ItemMedia variant="icon">
<Calendar className="h-4 w-4" />
</ItemMedia>
)}
<ItemContent>
<ItemHeader>
<ItemTitle className="gap-2">
Weekly planning
<Badge variant="secondary">Sprint</Badge>
</ItemTitle>
{controls.showActions && (
<ItemActions className="gap-2">
<Badge variant="outline">12 tasks</Badge>
<Button variant="ghost" size="sm" className="gap-1">
View
<ArrowUpRight className="h-4 w-4" />
</Button>
</ItemActions>
)}
</ItemHeader>
{controls.showDescription && (
<ItemDescription>
Plan upcoming sprints and capture blockers from the team sync.
</ItemDescription>
)}
{controls.showFooter && (
<ItemFooter>
<div className="text-muted-foreground flex items-center gap-2 text-xs">
<Clock3 className="h-3.5 w-3.5" />
Updated 2 hours ago
</div>
<Badge variant="outline">In progress</Badge>
</ItemFooter>
)}
</ItemContent>
</Item>
{controls.showSeparator && <ItemSeparator />}
<Item variant="muted" size="sm">
<ItemMedia>
<Avatar className="h-9 w-9">
<AvatarImage src="https://daily.jstor.org/wp-content/uploads/2019/10/ada_lovelace_pioneer_1050x700.jpg" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemHeader>
<ItemTitle className="gap-2">
Ada Lovelace
<Badge variant="outline">Owner</Badge>
</ItemTitle>
{controls.showActions && (
<ItemActions className="gap-2">
<Button size="sm" variant="ghost" className="gap-1">
Message
<ArrowUpRight className="h-4 w-4" />
</Button>
</ItemActions>
)}
</ItemHeader>
{controls.showDescription && (
<ItemDescription>
Building the analytics module. Next milestone due Friday.
</ItemDescription>
)}
{controls.showFooter && (
<ItemFooter>
<Badge variant="secondary" className="gap-1">
<CheckCircle2 className="h-3.5 w-3.5" />
Active
</Badge>
<span className="text-muted-foreground text-xs">Joined 2023</span>
</ItemFooter>
)}
</ItemContent>
</Item>
</ItemGroup>
);
const controlsPanel = (
<>
<div className="space-y-2">
<Label htmlFor="item-variant">Variant</Label>
<SimpleStorySelect
value={controls.variant}
onValueChange={(value) => updateControl('variant', value)}
options={variantOptions}
/>
</div>
<div className="space-y-2">
<Label htmlFor="item-size">Size</Label>
<SimpleStorySelect
value={controls.size}
onValueChange={(value) => updateControl('size', value)}
options={sizeOptions}
/>
</div>
<Separator />
<div className="flex items-center justify-between gap-3">
<Label htmlFor="show-media" className="text-sm font-medium">
Show media
</Label>
<Switch
id="show-media"
checked={controls.showMedia}
onCheckedChange={(checked) => updateControl('showMedia', checked)}
/>
</div>
<div className="flex items-center justify-between gap-3">
<Label htmlFor="show-description" className="text-sm font-medium">
Show description
</Label>
<Switch
id="show-description"
checked={controls.showDescription}
onCheckedChange={(checked) =>
updateControl('showDescription', checked)
}
/>
</div>
<div className="flex items-center justify-between gap-3">
<Label htmlFor="show-actions" className="text-sm font-medium">
Show actions
</Label>
<Switch
id="show-actions"
checked={controls.showActions}
onCheckedChange={(checked) => updateControl('showActions', checked)}
/>
</div>
<div className="flex items-center justify-between gap-3">
<Label htmlFor="show-footer" className="text-sm font-medium">
Show footer
</Label>
<Switch
id="show-footer"
checked={controls.showFooter}
onCheckedChange={(checked) => updateControl('showFooter', checked)}
/>
</div>
<div className="flex items-center justify-between gap-3">
<Label htmlFor="show-separator" className="text-sm font-medium">
Show separator
</Label>
<Switch
id="show-separator"
checked={controls.showSeparator}
onCheckedChange={(checked) => updateControl('showSeparator', checked)}
/>
</div>
</>
);
const examples = (
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Checklist</CardTitle>
<CardDescription>
Stack compact items for task summaries or changelog entries.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<ItemGroup className="gap-y-4">
<Item size="sm" variant="outline">
<ItemContent>
<ItemHeader>
<ItemTitle className="gap-2">
Deployment checklist
<Badge variant="secondary">Today</Badge>
</ItemTitle>
</ItemHeader>
<ItemDescription>
Review release notes, QA smoke tests, and notify support.
</ItemDescription>
</ItemContent>
</Item>
<ItemSeparator />
<Item size="sm" variant="outline">
<ItemContent>
<ItemTitle>QA sign-off</ItemTitle>
<ItemFooter>
<Badge variant="outline">Pending</Badge>
<span className="text-muted-foreground text-xs">Due 5pm</span>
</ItemFooter>
</ItemContent>
</Item>
</ItemGroup>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Directory</CardTitle>
<CardDescription>
Combine avatar media with actions to build list views.
</CardDescription>
</CardHeader>
<CardContent>
<ItemGroup className="gap-3">
<Item variant="muted" size="sm">
<ItemMedia>
<Avatar className="h-9 w-9">
<AvatarFallback>GH</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemHeader>
<ItemTitle>Grace Hopper</ItemTitle>
<ItemActions>
<Button variant="ghost" size="sm" className="gap-1">
Profile
<ArrowUpRight className="h-4 w-4" />
</Button>
</ItemActions>
</ItemHeader>
<ItemDescription>Staff engineer · Platform</ItemDescription>
</ItemContent>
</Item>
</ItemGroup>
</CardContent>
</Card>
</div>
);
return (
<ComponentStoryLayout
preview={preview}
controls={controlsPanel}
generatedCode={generatedCode}
examples={examples}
previewTitle="Composable list items"
previewDescription="Mix media, headers, and actions to create rich list presentations."
controlsTitle="Configuration"
controlsDescription="Toggle structural primitives to match your layout requirements."
codeTitle="Usage"
codeDescription="Start from a base item and add media, actions, or metadata as needed."
/>
);
}
export default ItemStory;