import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { Monitor, Smartphone, Tablet, Apple, Globe, Shield } from 'lucide-react';
import { useState } from 'react';
import { TabSelector, type TabSelectorItem } from '../components/ui/tab-selector';
const meta = {
title: 'UI/TabSelector',
component: TabSelector,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'A tab-style selector with primary (accent) and secondary (soft grey) variants. Supports icons, disabled states, and badges.',
},
},
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary'],
description: 'Visual variant',
},
label: {
control: 'text',
description: 'Optional label above the selector',
},
},
decorators: [
(Story) => (
),
],
} satisfies Meta;
export default meta;
type Story = StoryObj;
const basicItems: TabSelectorItem[] = [
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3' },
];
const fiveItems: TabSelectorItem[] = [
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3' },
{ id: 'tab4', label: 'Tab 4' },
{ id: 'tab5', label: 'Tab 5' },
];
const itemsWithIcons: TabSelectorItem[] = [
{ id: 'desktop', label: 'Desktop', icon: },
{ id: 'tablet', label: 'Tablet', icon: },
{ id: 'mobile', label: 'Mobile', icon: },
];
/**
* Primary variant with 3 tabs — active tab uses accent background.
*/
export const Primary: Story = {
args: {
value: 'tab1',
items: basicItems,
variant: 'primary',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Secondary variant — active tab uses soft grey background.
*/
export const Secondary: Story = {
args: {
value: 'tab1',
items: basicItems,
variant: 'secondary',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Five tabs matching the full Figma design spec.
*/
export const FiveTabs: Story = {
args: {
value: 'tab1',
items: fiveItems,
variant: 'primary',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Tabs with icons.
*/
export const WithIcons: Story = {
args: {
value: 'desktop',
items: itemsWithIcons,
variant: 'primary',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* With a label above the selector.
*/
export const WithLabel: Story = {
args: {
value: 'desktop',
items: itemsWithIcons,
variant: 'primary',
label: 'Select Device',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Tab with a disabled item.
*/
export const WithDisabled: Story = {
args: {
value: 'tab1',
items: [
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3', disabled: true },
],
variant: 'primary',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Entire selector disabled via the top-level `disabled` prop.
*/
export const Disabled: Story = {
args: {
value: 'tab1',
items: basicItems,
variant: 'primary',
disabled: true,
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Two tabs only.
*/
export const TwoTabs: Story = {
args: {
value: 'on',
items: [
{ id: 'on', label: 'Enabled' },
{ id: 'off', label: 'Disabled' },
],
variant: 'primary',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Real-world example: platform selector with icons.
*/
export const PlatformExample: Story = {
args: {
value: 'macos',
items: [
{ id: 'windows', label: 'Windows', icon: },
{ id: 'macos', label: 'macOS', icon: },
{ id: 'linux', label: 'Linux', icon: },
],
variant: 'primary',
label: 'Select Platform',
onValueChange: () => {},
},
render: function Render(args) {
const [value, setValue] = useState(args.value);
return ;
},
};
/**
* Side-by-side comparison of both variants.
*/
export const VariantComparison: Story = {
args: {
value: 'tab2',
items: fiveItems,
onValueChange: () => {},
},
render: function Render() {
const [primaryValue, setPrimaryValue] = useState('tab2');
const [secondaryValue, setSecondaryValue] = useState('tab2');
return (
);
},
};
/**
* Real-world example with badges and disabled tabs.
*/
export const WithBadges: Story = {
args: {
value: 'basic',
items: [],
onValueChange: () => {},
},
render: function Render() {
const [value, setValue] = useState('basic');
const items: TabSelectorItem[] = [
{ id: 'basic', label: 'Basic', icon: },
{ id: 'pro', label: 'Pro', icon: },
{
id: 'enterprise',
label: 'Enterprise',
icon: ,
disabled: true,
badge: (
Soon
),
},
];
return ;
},
};