Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/components/tabs/tabs.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite';
import { ref } from 'vue';
import Text from '../text/text.vue';
import Tabs from './tabs.vue';

const meta = {
title: 'Common/Tabs',
component: Tabs,
argTypes: {
modelValue: {
control: 'text',
},
tabs: {
control: 'object',
},
},
args: {
modelValue: 'home',
tabs: [
{ value: 'home', label: 'Home' },
{ value: 'profile', label: 'Profile' },
{ value: 'settings', label: 'Settings' },
],
},
render: args => ({
components: { Tabs },
setup() {
const activeTab = ref(args.modelValue);
return { args, activeTab };
},
template: `<Tabs v-model="activeTab" :tabs="args.tabs" />`,
}),
} satisfies Meta<typeof Tabs>;

export default meta;

type Story = StoryObj<typeof meta>;

export const DefaultTabs: Story = {};

export const TabsWithIcons: Story = {
args: {
tabs: [
{ value: 'home', label: 'Home', icon: 'i-ri-home-line' },
{ value: 'profile', label: 'Profile', icon: 'i-ri-user-line' },
{ value: 'settings', label: 'Settings', icon: 'i-ri-settings-line' },
],
},
};

export const IconOnlyTabs: Story = {
args: {
tabs: [
{ value: 'home', icon: 'i-ri-home-line' },
{ value: 'profile', icon: 'i-ri-user-line' },
{ value: 'settings', icon: 'i-ri-settings-line' },
],
},
};

export const TabsWithContent: Story = {
render: () => ({
components: { Tabs, Text },
setup() {
const tabs = [
{ value: 'home', label: 'Home' },
{ value: 'profile', label: 'Profile' },
{ value: 'settings', label: 'Settings' },
];

const selection = ref(tabs[0].value);

return { selection, tabs };
},
template: `
<Tabs v-model="selection" :tabs="tabs" />

<Text v-if="selection === 'home'">
<p>This is the home content. Welcome to the home tab!</p>
</Text>

<Text v-if="selection === 'profile'">
<p>This is the profile content. Here you can manage your profile settings.</p>
</Text>

<Text v-if="selection === 'settings'">
<p>This is the settings content. Configure your preferences here.</p>
</Text>
`,
}),
};
36 changes: 36 additions & 0 deletions src/components/tabs/tabs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup lang="ts">
import Icon from '../../components/icon/icon.vue';

defineProps<{
modelValue?: string | number;
tabs: Array<{
value: string | number;
label?: string;
icon?: string;
}>;
}>();

const emit = defineEmits<{
'update:modelValue': [value: string | number];
}>();
</script>

<template>
<div class="flex flex-row gap-0.5rem items-center bg-gray-100 dark:bg-gray-900 rounded-full p-0.25rem">
<button
v-for="tab in tabs"
:key="tab.value"
:data-active="modelValue === tab.value"
:data-has-label="!!tab.label"
:data-has-icon="!!tab.icon"
class="flex flex-row gap-0.125em items-center rounded-full text-1.25rem cursor-pointer data-[active=true]:(layer-theme:(text-gray-900 bg-white) dark:layer-theme:(text-gray-100 bg-gray-800)) data-[active=false]:(text-gray-600 hover:text-gray-900 dark:(text-gray-400 hover:text-gray-100)) data-[has-label=true]:p-r-1rem data-[has-label=true]:data-[has-icon=false]:p-l-1rem"
@click="emit('update:modelValue', tab.value)"
>
<Icon v-if="tab.icon" :name="tab.icon" :scale="1.25" :class="{ 'opacity-50': tab.label }" class="p-0.5rem shrink-0 grow-0" />

<span v-if="tab.label" class="font-500 font-serif leading-none shrink-0 grow-0 p-y-0.5em">
{{ tab.label }}
</span>
</button>
</div>
</template>