@davidbirchall/core 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.storybook/main.ts +18 -0
  2. package/.storybook/preview.ts +14 -0
  3. package/package.json +1 -4
  4. package/src/components/Badge/Badge.stories.ts +147 -0
  5. package/src/components/Badge/Badge.test.ts +57 -0
  6. package/src/components/Badge/Badge.vue +79 -0
  7. package/src/components/Button/Button.stories.ts +80 -0
  8. package/src/components/Button/Button.test.ts +145 -0
  9. package/src/components/Button/Button.vue +108 -0
  10. package/src/components/Button/types.ts +4 -0
  11. package/src/components/Calendar/Calendar.stories.ts +261 -0
  12. package/src/components/Calendar/Calendar.test.ts +119 -0
  13. package/src/components/Calendar/Calendar.vue +528 -0
  14. package/src/components/Calendar/types.ts +20 -0
  15. package/src/components/Card/Card.stories.ts +88 -0
  16. package/src/components/Card/Card.test.ts +173 -0
  17. package/src/components/Card/Card.vue +59 -0
  18. package/{dist/Card/types.d.ts → src/components/Card/types.ts} +1 -1
  19. package/src/components/Checkbox/Checkbox.stories.ts +126 -0
  20. package/src/components/Checkbox/Checkbox.test.ts +155 -0
  21. package/src/components/Checkbox/Checkbox.vue +121 -0
  22. package/src/components/Checkbox/types.ts +7 -0
  23. package/src/components/DataTable/DataTable.stories.ts +156 -0
  24. package/src/components/DataTable/DataTable.test.ts +185 -0
  25. package/src/components/DataTable/DataTable.vue +177 -0
  26. package/src/components/DataTable/types.ts +12 -0
  27. package/src/components/DatePicker/DatePicker.stories.ts +172 -0
  28. package/src/components/DatePicker/DatePicker.test.ts +87 -0
  29. package/src/components/DatePicker/DatePicker.vue +302 -0
  30. package/src/components/Dropdown/Dropdown.stories.ts +231 -0
  31. package/src/components/Dropdown/Dropdown.vue +314 -0
  32. package/src/components/Dropdown/types.ts +14 -0
  33. package/src/components/EmptyState/EmptyState.stories.ts +189 -0
  34. package/src/components/EmptyState/EmptyState.vue +215 -0
  35. package/src/components/EmptyState/types.ts +8 -0
  36. package/src/components/ErrorSummary/ErrorSummary.vue +78 -0
  37. package/src/components/ErrorSummary/types.ts +4 -0
  38. package/src/components/FormGroup/FormGroup.stories.ts +264 -0
  39. package/src/components/FormGroup/FormGroup.test.ts +63 -0
  40. package/src/components/FormGroup/FormGroup.vue +58 -0
  41. package/src/components/Heading/Heading.stories.ts +121 -0
  42. package/src/components/Heading/Heading.test.ts +184 -0
  43. package/src/components/Heading/Heading.vue +95 -0
  44. package/src/components/Heading/types.ts +6 -0
  45. package/src/components/Input/Input.stories.ts +172 -0
  46. package/src/components/Input/Input.test.ts +213 -0
  47. package/src/components/Input/Input.vue +121 -0
  48. package/src/components/Input/types.ts +11 -0
  49. package/src/components/Modal/Modal.stories.ts +341 -0
  50. package/src/components/Modal/Modal.test.ts +99 -0
  51. package/src/components/Modal/Modal.vue +278 -0
  52. package/src/components/ProgressBar/ProgressBar.stories.ts +313 -0
  53. package/src/components/ProgressBar/ProgressBar.test.ts +98 -0
  54. package/src/components/ProgressBar/ProgressBar.vue +117 -0
  55. package/src/components/Select/Select.stories.ts +177 -0
  56. package/src/components/Select/Select.test.ts +225 -0
  57. package/src/components/Select/Select.vue +147 -0
  58. package/src/components/Select/types.ts +16 -0
  59. package/src/components/StatCard/StatCard.stories.ts +274 -0
  60. package/src/components/StatCard/StatCard.vue +226 -0
  61. package/src/components/StatCard/types.ts +12 -0
  62. package/src/components/Tag/Tag.stories.ts +78 -0
  63. package/src/components/Tag/Tag.test.ts +50 -0
  64. package/src/components/Tag/Tag.vue +71 -0
  65. package/src/components/Tag/types.ts +4 -0
  66. package/src/components/TextArea/TextArea.stories.ts +171 -0
  67. package/src/components/TextArea/TextArea.test.ts +202 -0
  68. package/src/components/TextArea/TextArea.vue +122 -0
  69. package/src/components/TextArea/types.ts +11 -0
  70. package/src/components/index.ts +5 -0
  71. package/src/test/setup.ts +1 -0
  72. package/src/vite-env.d.ts +6 -0
  73. package/tsconfig.json +29 -0
  74. package/vite.config.ts +33 -0
  75. package/vitest.config.ts +28 -0
  76. package/dist/Button/types.d.ts +0 -4
  77. package/dist/Calendar/types.d.ts +0 -22
  78. package/dist/Checkbox/types.d.ts +0 -7
  79. package/dist/DataTable/types.d.ts +0 -11
  80. package/dist/Dropdown/types.d.ts +0 -13
  81. package/dist/EmptyState/types.d.ts +0 -8
  82. package/dist/ErrorSummary/types.d.ts +0 -4
  83. package/dist/Heading/types.d.ts +0 -6
  84. package/dist/Input/types.d.ts +0 -11
  85. package/dist/Select/types.d.ts +0 -15
  86. package/dist/StatCard/types.d.ts +0 -12
  87. package/dist/Tag/types.d.ts +0 -4
  88. package/dist/TextArea/types.d.ts +0 -11
  89. package/dist/core.css +0 -1
  90. package/dist/core.js +0 -24
  91. package/dist/core.js.map +0 -1
  92. package/dist/core.umd.cjs +0 -2
  93. package/dist/core.umd.cjs.map +0 -1
  94. package/dist/index.d.ts +0 -2
  95. package/dist/package.json +0 -27
@@ -0,0 +1,156 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import DataTable from './DataTable.vue'
3
+ import type { DataTableColumn } from './types'
4
+
5
+ const meta = {
6
+ title: 'Components/DataTable',
7
+ component: DataTable,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ hoverable: {
11
+ control: 'boolean',
12
+ description: 'Enable hover effect on rows'
13
+ },
14
+ emptyText: {
15
+ control: 'text',
16
+ description: 'Text to display when no data is available'
17
+ }
18
+ },
19
+ args: {
20
+ hoverable: true,
21
+ emptyText: 'No data available'
22
+ }
23
+ } satisfies Meta<typeof DataTable>
24
+
25
+ export default meta
26
+ type Story = StoryObj<typeof meta>
27
+
28
+ const sampleColumns: DataTableColumn[] = [
29
+ { key: 'id', label: 'ID', sortable: true },
30
+ { key: 'name', label: 'Name', sortable: true },
31
+ { key: 'email', label: 'Email', sortable: true },
32
+ { key: 'role', label: 'Role', sortable: false }
33
+ ]
34
+
35
+ const sampleData = [
36
+ { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
37
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
38
+ { id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' },
39
+ { id: 4, name: 'Alice Williams', email: 'alice@example.com', role: 'User' },
40
+ { id: 5, name: 'Charlie Brown', email: 'charlie@example.com', role: 'Admin' }
41
+ ]
42
+
43
+ export const Basic: Story = {
44
+ args: {
45
+ columns: sampleColumns,
46
+ data: sampleData
47
+ }
48
+ }
49
+
50
+ export const Empty: Story = {
51
+ args: {
52
+ columns: sampleColumns,
53
+ data: []
54
+ }
55
+ }
56
+
57
+ export const CustomEmptyText: Story = {
58
+ args: {
59
+ columns: sampleColumns,
60
+ data: [],
61
+ emptyText: 'No users found'
62
+ }
63
+ }
64
+
65
+ export const NoHover: Story = {
66
+ args: {
67
+ columns: sampleColumns,
68
+ data: sampleData,
69
+ hoverable: false
70
+ }
71
+ }
72
+
73
+ export const ProductTable: Story = {
74
+ args: {
75
+ columns: [
76
+ { key: 'product', label: 'Product', sortable: true },
77
+ { key: 'category', label: 'Category', sortable: true },
78
+ { key: 'price', label: 'Price', sortable: true },
79
+ { key: 'stock', label: 'Stock', sortable: true },
80
+ { key: 'status', label: 'Status', sortable: false }
81
+ ],
82
+ data: [
83
+ { product: 'Laptop', category: 'Electronics', price: 999, stock: 45, status: 'In Stock' },
84
+ { product: 'Mouse', category: 'Accessories', price: 25, stock: 120, status: 'In Stock' },
85
+ { product: 'Keyboard', category: 'Accessories', price: 75, stock: 0, status: 'Out of Stock' },
86
+ { product: 'Monitor', category: 'Electronics', price: 350, stock: 23, status: 'In Stock' },
87
+ { product: 'Webcam', category: 'Electronics', price: 89, stock: 8, status: 'Low Stock' }
88
+ ]
89
+ }
90
+ }
91
+
92
+ export const WithCustomSlots: Story = {
93
+ args: {
94
+ columns: [
95
+ { key: 'name', label: 'Name', sortable: true },
96
+ { key: 'status', label: 'Status', sortable: false },
97
+ { key: 'actions', label: 'Actions', sortable: false }
98
+ ],
99
+ data: [
100
+ { name: 'John Doe', status: 'active', actions: '' },
101
+ { name: 'Jane Smith', status: 'inactive', actions: '' },
102
+ { name: 'Bob Johnson', status: 'active', actions: '' }
103
+ ]
104
+ },
105
+ render: (args: any) => ({
106
+ components: { DataTable },
107
+ setup() {
108
+ return { args }
109
+ },
110
+ template: `
111
+ <DataTable v-bind="args">
112
+ <template #cell-status="{ value }">
113
+ <span :style="{
114
+ color: value === 'active' ? '#10b981' : '#ef4444',
115
+ fontWeight: 600
116
+ }">
117
+ {{ value.toUpperCase() }}
118
+ </span>
119
+ </template>
120
+ <template #cell-actions>
121
+ <button style="
122
+ padding: 0.25rem 0.75rem;
123
+ background: #3b82f6;
124
+ color: white;
125
+ border: none;
126
+ border-radius: 0.25rem;
127
+ cursor: pointer;
128
+ ">
129
+ Edit
130
+ </button>
131
+ </template>
132
+ </DataTable>
133
+ `
134
+ })
135
+ }
136
+
137
+ export const LargeDataset: Story = {
138
+ args: {
139
+ columns: [
140
+ { key: 'id', label: 'ID', sortable: true },
141
+ { key: 'firstName', label: 'First Name', sortable: true },
142
+ { key: 'lastName', label: 'Last Name', sortable: true },
143
+ { key: 'email', label: 'Email', sortable: true },
144
+ { key: 'department', label: 'Department', sortable: true },
145
+ { key: 'salary', label: 'Salary', sortable: true }
146
+ ],
147
+ data: Array.from({ length: 50 }, (_, i) => ({
148
+ id: i + 1,
149
+ firstName: ['John', 'Jane', 'Bob', 'Alice', 'Charlie'][i % 5],
150
+ lastName: ['Doe', 'Smith', 'Johnson', 'Williams', 'Brown'][i % 5],
151
+ email: `user${i + 1}@example.com`,
152
+ department: ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'][i % 5],
153
+ salary: 50000 + (i * 1000)
154
+ }))
155
+ }
156
+ }
@@ -0,0 +1,185 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/vue'
3
+ import userEvent from '@testing-library/user-event'
4
+ import DataTable from './DataTable.vue'
5
+ import type { DataTableColumn } from './types'
6
+
7
+ describe('DataTable', () => {
8
+ const columns: DataTableColumn[] = [
9
+ { key: 'name', label: 'Name', sortable: true },
10
+ { key: 'age', label: 'Age', sortable: true },
11
+ { key: 'email', label: 'Email' }
12
+ ]
13
+
14
+ const data = [
15
+ { name: 'John Doe', age: 30, email: 'john@example.com' },
16
+ { name: 'Jane Smith', age: 25, email: 'jane@example.com' },
17
+ { name: 'Bob Johnson', age: 35, email: 'bob@example.com' }
18
+ ]
19
+
20
+ describe('rendering', () => {
21
+ it('renders table headers', () => {
22
+ render(DataTable, {
23
+ props: {
24
+ columns,
25
+ data
26
+ }
27
+ })
28
+
29
+ expect(screen.getByText('Name')).toBeInTheDocument()
30
+ expect(screen.getByText('Age')).toBeInTheDocument()
31
+ expect(screen.getByText('Email')).toBeInTheDocument()
32
+ })
33
+
34
+ it('renders table data', () => {
35
+ render(DataTable, {
36
+ props: {
37
+ columns,
38
+ data
39
+ }
40
+ })
41
+
42
+ expect(screen.getByText('John Doe')).toBeInTheDocument()
43
+ expect(screen.getByText('30')).toBeInTheDocument()
44
+ expect(screen.getByText('john@example.com')).toBeInTheDocument()
45
+ })
46
+
47
+ it('renders all rows', () => {
48
+ const { container } = render(DataTable, {
49
+ props: {
50
+ columns,
51
+ data
52
+ }
53
+ })
54
+
55
+ const rows = container.querySelectorAll('tbody tr')
56
+ expect(rows).toHaveLength(3)
57
+ })
58
+
59
+ it('shows empty text when no data', () => {
60
+ render(DataTable, {
61
+ props: {
62
+ columns,
63
+ data: []
64
+ }
65
+ })
66
+
67
+ expect(screen.getByText('No data available')).toBeInTheDocument()
68
+ })
69
+
70
+ it('shows custom empty text', () => {
71
+ render(DataTable, {
72
+ props: {
73
+ columns,
74
+ data: [],
75
+ emptyText: 'Custom empty message'
76
+ }
77
+ })
78
+
79
+ expect(screen.getByText('Custom empty message')).toBeInTheDocument()
80
+ })
81
+ })
82
+
83
+ describe('sorting', () => {
84
+ it('sorts data in ascending order when clicking sortable column', async () => {
85
+ const user = userEvent.setup()
86
+ const { container } = render(DataTable, {
87
+ props: {
88
+ columns,
89
+ data
90
+ }
91
+ })
92
+
93
+ const nameHeader = screen.getByText('Name').closest('th')
94
+ await user.click(nameHeader!)
95
+
96
+ const cells = container.querySelectorAll('tbody td:first-child')
97
+ expect(cells[0]).toHaveTextContent('Bob Johnson')
98
+ expect(cells[1]).toHaveTextContent('Jane Smith')
99
+ expect(cells[2]).toHaveTextContent('John Doe')
100
+ })
101
+
102
+ it('sorts data in descending order when clicking sortable column twice', async () => {
103
+ const user = userEvent.setup()
104
+ const { container } = render(DataTable, {
105
+ props: {
106
+ columns,
107
+ data
108
+ }
109
+ })
110
+
111
+ const nameHeader = screen.getByText('Name').closest('th')
112
+ await user.click(nameHeader!)
113
+ await user.click(nameHeader!)
114
+
115
+ const cells = container.querySelectorAll('tbody td:first-child')
116
+ expect(cells[0]).toHaveTextContent('John Doe')
117
+ expect(cells[1]).toHaveTextContent('Jane Smith')
118
+ expect(cells[2]).toHaveTextContent('Bob Johnson')
119
+ })
120
+
121
+ it('sorts numeric columns correctly', async () => {
122
+ const user = userEvent.setup()
123
+ const { container } = render(DataTable, {
124
+ props: {
125
+ columns,
126
+ data
127
+ }
128
+ })
129
+
130
+ const ageHeader = screen.getByText('Age').closest('th')
131
+ await user.click(ageHeader!)
132
+
133
+ const cells = container.querySelectorAll('tbody td:nth-child(2)')
134
+ expect(cells[0]).toHaveTextContent('25')
135
+ expect(cells[1]).toHaveTextContent('30')
136
+ expect(cells[2]).toHaveTextContent('35')
137
+ })
138
+
139
+ it('does not sort non-sortable columns', async () => {
140
+ const user = userEvent.setup()
141
+ const { container } = render(DataTable, {
142
+ props: {
143
+ columns,
144
+ data
145
+ }
146
+ })
147
+
148
+ const emailHeader = screen.getByText('Email').closest('th')
149
+ await user.click(emailHeader!)
150
+
151
+ // Data should remain in original order
152
+ const cells = container.querySelectorAll('tbody td:first-child')
153
+ expect(cells[0]).toHaveTextContent('John Doe')
154
+ expect(cells[1]).toHaveTextContent('Jane Smith')
155
+ expect(cells[2]).toHaveTextContent('Bob Johnson')
156
+ })
157
+ })
158
+
159
+ describe('hoverable', () => {
160
+ it('applies hoverable class by default', () => {
161
+ const { container } = render(DataTable, {
162
+ props: {
163
+ columns,
164
+ data
165
+ }
166
+ })
167
+
168
+ const firstRow = container.querySelector('tbody tr')
169
+ expect(firstRow).toHaveClass('data-table__row--hoverable')
170
+ })
171
+
172
+ it('does not apply hoverable class when disabled', () => {
173
+ const { container } = render(DataTable, {
174
+ props: {
175
+ columns,
176
+ data,
177
+ hoverable: false
178
+ }
179
+ })
180
+
181
+ const firstRow = container.querySelector('tbody tr')
182
+ expect(firstRow).not.toHaveClass('data-table__row--hoverable')
183
+ })
184
+ })
185
+ })
@@ -0,0 +1,177 @@
1
+ <template>
2
+ <div class="data-table">
3
+ <div class="data-table__wrapper">
4
+ <table class="data-table__table">
5
+ <thead class="data-table__head">
6
+ <tr>
7
+ <th
8
+ v-for="column in columns"
9
+ :key="column.key"
10
+ class="data-table__header"
11
+ :class="{ 'data-table__header--sortable': column.sortable }"
12
+ @click="column.sortable ? handleSort(column.key) : undefined"
13
+ >
14
+ <div class="data-table__header-content">
15
+ {{ column.label }}
16
+ <span v-if="column.sortable" class="data-table__sort-icon">
17
+ <span v-if="sortKey === column.key">
18
+ {{ sortOrder === 'asc' ? '↑' : '↓' }}
19
+ </span>
20
+ <span v-else class="data-table__sort-icon--inactive">↕</span>
21
+ </span>
22
+ </div>
23
+ </th>
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ <tr
28
+ v-for="(row, index) in sortedData"
29
+ :key="index"
30
+ class="data-table__row"
31
+ >
32
+ <td
33
+ v-for="column in columns"
34
+ :key="column.key"
35
+ class="data-table__cell"
36
+ >
37
+ <slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]">
38
+ {{ row[column.key] }}
39
+ </slot>
40
+ </td>
41
+ </tr>
42
+ <tr v-if="sortedData.length === 0">
43
+ <td :colspan="columns.length" class="data-table__cell data-table__cell--empty">
44
+ {{ emptyText }}
45
+ </td>
46
+ </tr>
47
+ </tbody>
48
+ </table>
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+ <script setup lang="ts">
54
+ import { ref, computed } from 'vue'
55
+
56
+ export interface DataTableColumn {
57
+ key: string
58
+ label: string
59
+ sortable?: boolean
60
+ }
61
+
62
+ export interface DataTableProps {
63
+ columns: DataTableColumn[]
64
+ data: Record<string, any>[]
65
+ hoverable?: boolean
66
+ emptyText?: string
67
+ }
68
+
69
+ const props = withDefaults(defineProps<DataTableProps>(), {
70
+ hoverable: true,
71
+ emptyText: 'No data available'
72
+ })
73
+
74
+ const sortKey = ref<string | null>(null)
75
+ const sortOrder = ref<'asc' | 'desc'>('asc')
76
+
77
+ const handleSort = (key: string) => {
78
+ if (sortKey.value === key) {
79
+ sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
80
+ } else {
81
+ sortKey.value = key
82
+ sortOrder.value = 'asc'
83
+ }
84
+ }
85
+
86
+ const sortedData = computed(() => {
87
+ if (!sortKey.value) {
88
+ return props.data
89
+ }
90
+
91
+ return [...props.data].sort((a, b) => {
92
+ const aVal = a[sortKey.value!]
93
+ const bVal = b[sortKey.value!]
94
+
95
+ if (aVal === bVal) return 0
96
+
97
+ let comparison = 0
98
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
99
+ comparison = aVal - bVal
100
+ } else {
101
+ comparison = String(aVal).localeCompare(String(bVal))
102
+ }
103
+
104
+ return sortOrder.value === 'asc' ? comparison : -comparison
105
+ })
106
+ })
107
+ </script>
108
+
109
+ <style scoped>
110
+ .data-table {
111
+ width: 100%;
112
+ }
113
+
114
+ .data-table__wrapper {
115
+ overflow-x: auto;
116
+ }
117
+
118
+ .data-table__table {
119
+ min-width: 100%;
120
+ border-collapse: collapse;
121
+ }
122
+
123
+ .data-table__head {
124
+ background-color: #f9fafb;
125
+ }
126
+
127
+ .data-table__header {
128
+ padding: 0.75rem 1.5rem;
129
+ text-align: left;
130
+ font-size: 0.75rem;
131
+ font-weight: 500;
132
+ color: #6b7280;
133
+ text-transform: uppercase;
134
+ letter-spacing: 0.05em;
135
+ border-bottom: 1px solid #e5e7eb;
136
+ }
137
+
138
+ .data-table__header--sortable {
139
+ cursor: pointer;
140
+ user-select: none;
141
+ }
142
+
143
+ .data-table__header--sortable:hover {
144
+ background-color: #f3f4f6;
145
+ }
146
+
147
+ .data-table__header-content {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 0.5rem;
151
+ }
152
+
153
+ .data-table__sort-icon {
154
+ font-size: 0.75rem;
155
+ color: #6b7280;
156
+ }
157
+
158
+ .data-table__sort-icon--inactive {
159
+ color: #d1d5db;
160
+ }
161
+
162
+ .data-table__row {
163
+ border-bottom: 1px solid #e5e7eb;
164
+ }
165
+
166
+ .data-table__cell {
167
+ padding: 1rem 1.5rem;
168
+ font-size: 0.875rem;
169
+ color: #6b7280;
170
+ }
171
+
172
+ .data-table__cell--empty {
173
+ text-align: center;
174
+ color: #9ca3af;
175
+ font-style: italic;
176
+ }
177
+ </style>
@@ -0,0 +1,12 @@
1
+ export interface DataTableColumn {
2
+ key: string
3
+ label: string
4
+ sortable?: boolean
5
+ }
6
+
7
+ export interface DataTableProps {
8
+ columns: DataTableColumn[]
9
+ data: Record<string, any>[]
10
+ hoverable?: boolean
11
+ emptyText?: string
12
+ }
@@ -0,0 +1,172 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { ref } from 'vue'
3
+ import DatePicker from './DatePicker.vue'
4
+
5
+ const meta = {
6
+ title: 'Components/DatePicker',
7
+ component: DatePicker,
8
+ tags: ['autodocs']
9
+ } satisfies Meta<typeof DatePicker>
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof meta>
13
+
14
+ export const Default: Story = {
15
+ render: (args: any) => ({
16
+ components: { DatePicker },
17
+ setup() {
18
+ const date = ref<string>('')
19
+ return { args, date }
20
+ },
21
+ template: `
22
+ <div>
23
+ <DatePicker v-bind="args" v-model="date" />
24
+ <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
25
+ Selected: {{ date || 'None' }}
26
+ </div>
27
+ </div>
28
+ `
29
+ })
30
+ }
31
+
32
+ export const WithValue: Story = {
33
+ render: (args: any) => ({
34
+ components: { DatePicker },
35
+ setup() {
36
+ const date = ref<string>('2026-03-15')
37
+ return { args, date }
38
+ },
39
+ template: `
40
+ <div>
41
+ <DatePicker v-bind="args" v-model="date" />
42
+ <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
43
+ Selected: {{ date }}
44
+ </div>
45
+ </div>
46
+ `
47
+ })
48
+ }
49
+
50
+ export const DateRange: Story = {
51
+ render: (args: any) => ({
52
+ components: { DatePicker },
53
+ setup() {
54
+ const dateRange = ref<{ start: string; end: string }>({ start: '', end: '' })
55
+ return { args, dateRange }
56
+ },
57
+ template: `
58
+ <div>
59
+ <DatePicker v-bind="args" v-model="dateRange" mode="range" placeholder="Select date range" />
60
+ <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
61
+ Start: {{ dateRange.start || 'Not set' }}<br />
62
+ End: {{ dateRange.end || 'Not set' }}
63
+ </div>
64
+ </div>
65
+ `
66
+ })
67
+ }
68
+
69
+ export const WithMinMaxDates: Story = {
70
+ render: (args: any) => ({
71
+ components: { DatePicker },
72
+ setup() {
73
+ const date = ref<string>('')
74
+ const today = new Date()
75
+ const minDate = today.toISOString().split('T')[0]
76
+ const maxDate = new Date(today.getFullYear(), today.getMonth() + 3, today.getDate()).toISOString().split('T')[0]
77
+
78
+ return { args, date, minDate, maxDate }
79
+ },
80
+ template: `
81
+ <div>
82
+ <DatePicker
83
+ v-bind="args"
84
+ v-model="date"
85
+ :min-date="minDate"
86
+ :max-date="maxDate"
87
+ placeholder="Future dates only"
88
+ />
89
+ <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
90
+ Selected: {{ date || 'None' }}<br />
91
+ <small style="color: #6b7280;">Min: {{ minDate }} | Max: {{ maxDate }}</small>
92
+ </div>
93
+ </div>
94
+ `
95
+ })
96
+ }
97
+
98
+ export const FormExample: Story = {
99
+ render: (args: any) => ({
100
+ components: { DatePicker },
101
+ setup() {
102
+ const passportNumber = ref('')
103
+ const expiryDate = ref<string>('')
104
+
105
+ return { args, passportNumber, expiryDate }
106
+ },
107
+ template: `
108
+ <div style="max-width: 400px;">
109
+ <div style="margin-bottom: 1rem;">
110
+ <label style="display: block; margin-bottom: 0.5rem; font-weight: 600;">Passport Number</label>
111
+ <input
112
+ v-model="passportNumber"
113
+ type="text"
114
+ placeholder="e.g., X12345678"
115
+ style="width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem;"
116
+ />
117
+ </div>
118
+
119
+ <div style="margin-bottom: 1rem;">
120
+ <label style="display: block; margin-bottom: 0.5rem; font-weight: 600;">Expiry Date</label>
121
+ <DatePicker
122
+ v-bind="args"
123
+ v-model="expiryDate"
124
+ placeholder="Select expiry date"
125
+ />
126
+ </div>
127
+
128
+ <div style="padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
129
+ <strong>Form Data:</strong><br />
130
+ Passport: {{ passportNumber || 'Not entered' }}<br />
131
+ Expiry: {{ expiryDate || 'Not selected' }}
132
+ </div>
133
+ </div>
134
+ `
135
+ })
136
+ }
137
+
138
+ export const LongFormat: Story = {
139
+ render: (args: any) => ({
140
+ components: { DatePicker },
141
+ setup() {
142
+ const date = ref<string>('2026-06-15')
143
+ return { args, date }
144
+ },
145
+ template: `
146
+ <div>
147
+ <DatePicker v-bind="args" v-model="date" format="long" />
148
+ <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
149
+ Selected: {{ date }}
150
+ </div>
151
+ </div>
152
+ `
153
+ })
154
+ }
155
+
156
+ export const Disabled: Story = {
157
+ render: (args: any) => ({
158
+ components: { DatePicker },
159
+ setup() {
160
+ const date = ref<string>('2026-02-07')
161
+ return { args, date }
162
+ },
163
+ template: `
164
+ <div>
165
+ <DatePicker v-bind="args" v-model="date" disabled />
166
+ <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
167
+ Selected: {{ date }}
168
+ </div>
169
+ </div>
170
+ `
171
+ })
172
+ }