import type { Meta, StoryObj } from '@storybook/vue3-vite' import { action } from 'storybook/actions' import { computed, ref } from 'vue' import CpTable from '@/components/CpTable.vue' const meta = { title: 'Organisms/CpTable', component: CpTable, parameters: { backgrounds: { default: 'dark', }, layout: 'padded', }, decorators: [ () => ({ template: '
' }), ], } satisfies Meta export default meta type Story = StoryObj type TableStoryArgs = NonNullable const sampleColumns = ['name', 'age', 'email', 'status'] const sampleData = [ { name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active', date: '2026-01-01' }, { name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive', date: '2026-01-02' }, { name: 'Bob Johnson', age: 35, email: 'bob@example.com', status: 'Active', date: '2026-01-03' }, { name: 'Alice Brown', age: 28, email: 'alice@example.com', status: 'Active', date: '2026-01-04' }, { name: 'Charlie Wilson', age: 42, email: 'charlie@example.com', status: 'Inactive', date: '2026-01-05' }, ] /** * Default table with sample columns and rows. Use the controls to * experiment with each prop in isolation. */ export const Default: Story = { args: { columns: sampleColumns, data: sampleData, caption: 'User Management Table', areRowsClickable: false, emptyCellPlaceholder: 'n/a', noResultPlaceholder: 'No results found', isLoading: false, enableRowOptions: false, quickOptionsLimit: 3, pagination: { enabled: true, }, onOnRowClick: action('on-row-click'), onOnPreviousClick: action('on-previous-click'), onOnNextClick: action('on-next-click'), onOnColumnsChanged: action('on-columns-changed'), }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { return { args } }, template: `
`, }), } /** * Enable the column manager so users can show/hide, pin and reorder * columns. Individual columns expose `isHidden`, `isProtected` and * `isFull` flags to control the initial layout. */ export const EnableColumnEdition: Story = { args: { ...Default.args, columns: [ { id: 'name', name: 'Name', isProtected: false }, { id: 'age', name: 'Age', isHidden: false, isProtected: false, isFull: true }, { id: 'email', name: 'Email', isHidden: false }, { id: 'status', name: 'Status', isFull: false }, { id: 'date', name: 'Date', isHidden: false, isProtected: true }, ], enableColumnEdition: true, }, } /** * Rows become interactive — hover highlights and the `onRowClick` event * fires on click. */ export const ClickableRows: Story = { args: { ...Default.args, areRowsClickable: true, }, } /** * Loading state — skeleton rows are displayed while the data is fetched. */ export const Loading: Story = { args: { ...Default.args, isLoading: true, }, } /** * Empty dataset — the `noResultPlaceholder` copy replaces the rows. */ export const Empty: Story = { args: { ...Default.args, data: [], }, } /** * Configure pagination through the `pagination` prop. `format` switches * between `PAGES` and alternative formats. */ export const WithPagination: Story = { args: { ...Default.args, data: [...sampleData, ...sampleData, ...sampleData], pagination: { enabled: true, limit: 12, }, }, } /** * Override the default footer details through the `#footer-details` slot. */ export const WithCustomFooterDetails: Story = { args: { ...WithPagination.args, }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { return { args } }, template: ` `, }), } /** * Group rows by providing an array of `{ groupBy, rows }` objects. A * custom `#groupBy` slot renders the section header. */ export const WithGroupBy: Story = { args: { ...Default.args, data: [ { groupBy: 'Active Users', rows: [ { name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active' }, { name: 'Bob Johnson', age: 35, email: 'bob@example.com', status: 'Active' }, ], }, { groupBy: 'Inactive Users', rows: [{ name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive' }], }, ], }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { return { args } }, template: ` `, }), } /** * Attach per-row actions through `rowOptions`. `quickOptionsLimit` * controls how many actions appear inline before the overflow menu; each * action can be async (`isAsync` + `isLoading`) or critical * (`isCritical`). */ export const WithCustomRowOptions: Story = { args: { ...Default.args, enableRowOptions: true, enableColumnEdition: true, }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { const isEditLoading = ref(false) const rowOptions = computed(() => [ { id: 'see', label: 'See', icon: 'eye', action: () => console.log('See'), }, { id: 'edit', label: 'Edit', icon: 'edit-2', isAsync: true, isLoading: isEditLoading.value, action: async (payload: Record) => { isEditLoading.value = true console.log('Edit', payload) await new Promise((resolve) => setTimeout(resolve, 2000)) isEditLoading.value = false }, }, { id: 'disable', label: 'Disable', icon: 'history', isDisabled: true, action: (payload: Record) => console.log('History', payload), }, { id: 'delete', label: 'Delete', icon: 'trash', isCritical: true, action: () => console.log('Delete'), }, ]) return { args, isEditLoading, rowOptions } }, template: ` `, }), } /** * Rows without a "primary" action — every option lives in the overflow * menu. Useful when actions are secondary to the row click. */ export const WithoutDefaultAction: Story = { args: { ...Default.args, enableRowOptions: true, quickOptionsLimit: 2, enableColumnEdition: true, }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { const isEditLoading = ref(false) const rowOptions = computed(() => [ { id: 'see', label: 'See', icon: 'eye', action: () => console.log('See'), }, { id: 'delete', label: 'Delete', icon: 'trash', isCritical: true, action: () => console.log('Delete'), }, ]) return { args, isEditLoading, rowOptions } }, template: ` `, }), } /** * Setting `quickOptionsLimit: 0` forces the primary action to appear * inline without any overflow menu. */ export const WithOnlyDefaultAction: Story = { args: { ...Default.args, enableRowOptions: true, enableColumnEdition: true, quickOptionsLimit: 0, }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { const isEditLoading = ref(false) const rowOptions = computed(() => [ { id: 'see', label: 'See', icon: 'eye', action: () => console.log('See'), }, { id: 'edit', label: 'Edit', icon: 'edit-2', isAsync: true, isLoading: isEditLoading.value, action: async (payload: Record) => { isEditLoading.value = true console.log('Edit', payload) await new Promise((resolve) => setTimeout(resolve, 2000)) isEditLoading.value = false }, }, ]) return { args, isEditLoading, rowOptions } }, template: ` `, }), } /** * When the table overflows horizontally, a subtle scroll cue appears on * the right edge to hint at the extra content. */ export const ScrollCue: Story = { args: { ...Default.args, columns: ['name', 'age', 'email', 'status', 'company', 'role', 'city', 'country'], data: [ { name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active', company: 'CitizenPlane', role: 'Analyst', city: 'Paris', country: 'France', }, { name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive', company: 'Kiwi', role: 'Manager', city: 'Prague', country: 'Czechia', }, { name: 'Bob Johnson', age: 35, email: 'bob@example.com', status: 'Active', company: 'Atlas', role: 'Engineer', city: 'Berlin', country: 'Germany', }, ], }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { return { args } }, template: `
`, }), } /** * A row can override the column layout with `{ fullWidth: '...' }` to * spread a single cell across the whole table (e.g. summaries or notes). */ export const FullWidthRow: Story = { args: { ...Default.args, data: [ { name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active' }, { fullWidth: 'Full-width row content (e.g. summary or note)' }, { name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive' }, ], }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { return { args } }, template: `
`, }), } /** * Mark a row as selected by adding `isSelected: true`. The row adopts a * highlighted background. */ export const SelectedRow: Story = { args: { ...Default.args, data: [ { name: 'John Doe', age: 30, email: 'john@example.com', status: 'Active' }, { name: 'Jane Smith', age: 25, email: 'jane@example.com', status: 'Inactive', isSelected: true }, { name: 'Bob Johnson', age: 35, email: 'bob@example.com', status: 'Active' }, ], }, render: (args: TableStoryArgs) => ({ components: { CpTable }, setup() { return { args } }, template: `
`, }), }