Files
myeasycms-v2/apps/dev-tool/app/components/components/button-group-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

370 lines
10 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import { Filter, Plus, Settings } from 'lucide-react';
import { Button } from '@kit/ui/button';
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from '@kit/ui/button-group';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@kit/ui/card';
import { Label } from '@kit/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@kit/ui/select';
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 ButtonGroupControls {
orientation: 'horizontal' | 'vertical';
size: 'sm' | 'default' | 'lg';
withLabel: boolean;
withSeparator: boolean;
withFilter: boolean;
withPrimary: boolean;
withSelect: boolean;
fullWidth: boolean;
}
const orientationOptions = [
{
value: 'horizontal',
label: 'Horizontal',
description: 'Buttons arranged side-by-side',
},
{
value: 'vertical',
label: 'Vertical',
description: 'Stack buttons vertically',
},
] as const;
const sizeOptions = [
{
value: 'sm',
label: 'Small',
description: 'Compact 36px controls',
},
{
value: 'default',
label: 'Default',
description: 'Standard 40px controls',
},
{
value: 'lg',
label: 'Large',
description: 'Spacious 44px controls',
},
] as const;
export function ButtonGroupStory() {
const { controls, updateControl } = useStoryControls<ButtonGroupControls>({
orientation: 'horizontal',
size: 'sm',
withLabel: false,
withSeparator: false,
withFilter: false,
withPrimary: false,
withSelect: false,
fullWidth: false,
});
const buttonGroupPropsString = useMemo(
() =>
generatePropsString(
{
orientation: controls.orientation,
className: controls.fullWidth ? 'w-full' : undefined,
},
{
orientation: 'horizontal',
className: undefined,
},
),
[controls.fullWidth, controls.orientation],
);
const generatedCode = useMemo(() => {
const separatorOrientation =
controls.orientation === 'vertical' ? 'horizontal' : 'vertical';
const buttonSizeProp =
controls.size === 'default' ? '' : ` size="${controls.size}"`;
const selectTriggerClasses = [
'w-[140px] justify-between',
controls.size === 'sm' ? 'h-9 text-sm' : null,
controls.size === 'lg' ? 'h-11 text-base' : null,
]
.filter(Boolean)
.join(' ');
const labelClasses = [
'min-w-[120px] justify-between',
controls.size === 'sm' ? 'text-sm' : null,
controls.size === 'lg' ? 'text-base' : null,
'gap-2',
]
.filter(Boolean)
.join(' ');
let code = `<ButtonGroup${buttonGroupPropsString}>`;
if (controls.withLabel) {
code += `\n <ButtonGroupText className="${labelClasses}">`;
code += `\n Views`;
code += `\n <Settings className="h-4 w-4" />`;
code += `\n </ButtonGroupText>`;
}
code += `\n <Button variant="outline"${buttonSizeProp}>Overview</Button>`;
code += `\n <Button variant="outline"${buttonSizeProp}>Activity</Button>`;
code += `\n <Button variant="outline"${buttonSizeProp}>Calendar</Button>`;
if (controls.withSeparator) {
code += `\n <ButtonGroupSeparator orientation="${separatorOrientation}" />`;
}
if (controls.withFilter) {
code += `\n <Button variant="ghost" className="gap-2"${buttonSizeProp}>`;
code += `\n <Filter className="h-4 w-4" />`;
code += `\n Filters`;
code += `\n </Button>`;
}
if (controls.withSelect) {
code += `\n <Select defaultValue="all">`;
code += `\n <SelectTrigger data-slot="select-trigger" className="${selectTriggerClasses}">`;
code += `\n <SelectValue placeholder="Segment" />`;
code += `\n </SelectTrigger>`;
code += `\n <SelectContent>`;
code += `\n <SelectItem value="all">All tasks</SelectItem>`;
code += `\n <SelectItem value="mine">Assigned to me</SelectItem>`;
code += `\n <SelectItem value="review">Needs review</SelectItem>`;
code += `\n </SelectContent>`;
code += `\n </Select>`;
}
if (controls.withPrimary) {
code += `\n <Button${buttonSizeProp}>`;
code += `\n <Plus className="mr-2 h-4 w-4" />`;
code += `\n New view`;
code += `\n </Button>`;
}
code += `\n</ButtonGroup>`;
return formatCodeBlock(code, [
"import { Filter, Plus, Settings } from 'lucide-react';",
"import { Button } from '@kit/ui/button';",
"import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from '@kit/ui/button-group';",
"import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kit/ui/select';",
]);
}, [buttonGroupPropsString, controls]);
const separatorOrientation =
controls.orientation === 'vertical' ? 'horizontal' : 'vertical';
const preview = (
<div
className={cn(
'bg-background flex min-h-[220px] items-center justify-center rounded-xl border p-6',
controls.fullWidth && 'items-start',
)}
>
<ButtonGroup
orientation={controls.orientation}
className={cn(controls.fullWidth && 'w-full justify-between')}
>
{controls.withLabel && (
<ButtonGroupText
className={cn(
'min-w-[120px] justify-between gap-2',
controls.size === 'sm' && 'text-sm',
controls.size === 'lg' && 'text-base',
)}
>
<span>Views</span>
<Settings className="h-4 w-4" />
</ButtonGroupText>
)}
<Button variant="outline" size={controls.size}>
Overview
</Button>
<Button variant="outline" size={controls.size}>
Activity
</Button>
<Button variant="outline" size={controls.size}>
Calendar
</Button>
{controls.withSeparator && (
<ButtonGroupSeparator orientation={separatorOrientation} />
)}
{controls.withFilter && (
<Button variant="ghost" size={controls.size} className="gap-2">
<Filter className="h-4 w-4" />
Filters
</Button>
)}
{controls.withPrimary && (
<Button size={controls.size} className="gap-2">
<Plus className="h-4 w-4" />
New view
</Button>
)}
</ButtonGroup>
</div>
);
const controlsPanel = (
<>
<div className="space-y-2">
<Label htmlFor="orientation">Orientation</Label>
<SimpleStorySelect
value={controls.orientation}
onValueChange={(value) => updateControl('orientation', value)}
options={orientationOptions}
/>
</div>
<div className="space-y-2">
<Label htmlFor="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="withLabel" className="text-sm font-medium">
Show label
</Label>
<Switch
id="withLabel"
checked={controls.withLabel}
onCheckedChange={(checked) => updateControl('withLabel', checked)}
/>
</div>
<div className="flex items-center justify-between gap-3">
<Label htmlFor="withFilter" className="text-sm font-medium">
Show filter button
</Label>
<Switch
id="withFilter"
checked={controls.withFilter}
onCheckedChange={(checked) => updateControl('withFilter', checked)}
/>
</div>
<div className="flex items-center justify-between gap-3">
<Label htmlFor="withPrimary" className="text-sm font-medium">
Show primary action
</Label>
<Switch
id="withPrimary"
checked={controls.withPrimary}
onCheckedChange={(checked) => updateControl('withPrimary', checked)}
/>
</div>
</>
);
const examples = (
<Card>
<CardHeader>
<CardTitle>Button group sizes</CardTitle>
<CardDescription>
Mirror the documentation examples with small, default, and large
buttons.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<ButtonGroup>
<Button variant="outline" size="sm">
Small
</Button>
<Button variant="outline" size="sm">
Button
</Button>
<Button variant="outline" size="sm">
Group
</Button>
<Button variant="outline" size="sm">
<Plus className="h-4 w-4" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Default</Button>
<Button variant="outline">Button</Button>
<Button variant="outline">Group</Button>
<Button variant="outline">
<Plus className="h-4 w-4" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="lg">
Large
</Button>
<Button variant="outline" size="lg">
Button
</Button>
<Button variant="outline" size="lg">
Group
</Button>
<Button variant="outline" size="lg">
<Plus className="h-4 w-4" />
</Button>
</ButtonGroup>
</CardContent>
</Card>
);
return (
<ComponentStoryLayout
preview={preview}
controls={controlsPanel}
generatedCode={generatedCode}
examples={examples}
previewTitle="Interactive button group"
previewDescription="Coordinate related actions, filters, and dropdowns in a single control cluster."
controlsTitle="Configuration"
controlsDescription="Toggle layout options and auxiliary actions to compose the group."
codeTitle="Usage"
codeDescription="Copy the configuration that matches your toolbar requirements."
/>
);
}
export default ButtonGroupStory;