@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,99 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Modal from './Modal.vue'
4
+
5
+ describe('Modal', () => {
6
+ beforeEach(() => {
7
+ // Create a div with id 'app' for Teleport target
8
+ const app = document.createElement('div')
9
+ app.id = 'app'
10
+ document.body.appendChild(app)
11
+ })
12
+
13
+ afterEach(() => {
14
+ document.body.innerHTML = ''
15
+ })
16
+
17
+ it('renders when modelValue is true', () => {
18
+ mount(Modal, {
19
+ props: {
20
+ modelValue: true,
21
+ title: 'Test Modal'
22
+ },
23
+ attachTo: document.body
24
+ })
25
+ expect(document.querySelector('.modal-overlay')).toBeTruthy()
26
+ })
27
+
28
+ it('does not render when modelValue is false', () => {
29
+ mount(Modal, {
30
+ props: {
31
+ modelValue: false
32
+ },
33
+ attachTo: document.body
34
+ })
35
+ expect(document.querySelector('.modal-overlay')).toBeFalsy()
36
+ })
37
+
38
+ it('displays title', () => {
39
+ mount(Modal, {
40
+ props: {
41
+ modelValue: true,
42
+ title: 'My Modal Title'
43
+ },
44
+ attachTo: document.body
45
+ })
46
+ expect(document.querySelector('.modal-title')?.textContent).toBe('My Modal Title')
47
+ })
48
+
49
+ it('renders slot content', () => {
50
+ mount(Modal, {
51
+ props: {
52
+ modelValue: true
53
+ },
54
+ slots: {
55
+ default: '<p>Modal content</p>'
56
+ },
57
+ attachTo: document.body
58
+ })
59
+ expect(document.querySelector('.modal-body')?.textContent).toContain('Modal content')
60
+ })
61
+
62
+ it('emits close event when close button is clicked', async () => {
63
+ const wrapper = mount(Modal, {
64
+ props: {
65
+ modelValue: true
66
+ },
67
+ attachTo: document.body
68
+ })
69
+
70
+ const closeButton = document.querySelector('.modal-close') as HTMLElement
71
+ closeButton.click()
72
+ await wrapper.vm.$nextTick()
73
+
74
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([false])
75
+ expect(wrapper.emitted('close')).toBeTruthy()
76
+ })
77
+
78
+ it('applies size class correctly', () => {
79
+ mount(Modal, {
80
+ props: {
81
+ modelValue: true,
82
+ size: 'large'
83
+ },
84
+ attachTo: document.body
85
+ })
86
+ expect(document.querySelector('.modal-content--large')).toBeTruthy()
87
+ })
88
+
89
+ it('hides close button when closable is false', () => {
90
+ mount(Modal, {
91
+ props: {
92
+ modelValue: true,
93
+ closable: false
94
+ },
95
+ attachTo: document.body
96
+ })
97
+ expect(document.querySelector('.modal-close')).toBeFalsy()
98
+ })
99
+ })
@@ -0,0 +1,278 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <Transition name="modal">
4
+ <div
5
+ v-if="modelValue"
6
+ ref="overlayRef"
7
+ class="modal-overlay"
8
+ @pointerdown.self="handleOverlayPointerDown"
9
+ >
10
+ <div
11
+ class="modal-content"
12
+ :class="[`modal-content--${size}`]"
13
+ @click.stop
14
+ @pointerdown.stop="handleContentPointerDown"
15
+ @pointerup.stop
16
+ >
17
+ <div class="modal-header">
18
+ <slot name="header">
19
+ <h3 class="modal-title">{{ title }}</h3>
20
+ </slot>
21
+ <button
22
+ v-if="closable"
23
+ @click="close"
24
+ class="modal-close"
25
+ aria-label="Close modal"
26
+ >
27
+ ×
28
+ </button>
29
+ </div>
30
+
31
+ <div class="modal-body">
32
+ <slot />
33
+ </div>
34
+
35
+ <div v-if="$slots.footer" class="modal-footer">
36
+ <slot name="footer" />
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </Transition>
41
+ </Teleport>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { onBeforeUnmount, ref, watch } from 'vue'
46
+
47
+ interface Props {
48
+ modelValue: boolean
49
+ title?: string
50
+ size?: 'small' | 'medium' | 'large'
51
+ closable?: boolean
52
+ closeOnOverlay?: boolean
53
+ }
54
+
55
+ const props = withDefaults(defineProps<Props>(), {
56
+ title: '',
57
+ size: 'medium',
58
+ closable: true,
59
+ closeOnOverlay: true
60
+ })
61
+
62
+ const emit = defineEmits<{
63
+ 'update:modelValue': [value: boolean]
64
+ close: []
65
+ }>()
66
+
67
+ const close = () => {
68
+ emit('update:modelValue', false)
69
+ emit('close')
70
+ }
71
+
72
+ const overlayRef = ref<HTMLElement | null>(null)
73
+ const pointerDownOnOverlay = ref(false)
74
+ const draggedOnOverlay = ref(false)
75
+ const startX = ref(0)
76
+ const startY = ref(0)
77
+ const activePointerId = ref<number | null>(null)
78
+ const lastPointerDownTarget = ref<'overlay' | 'content' | null>(null)
79
+
80
+ const handleWindowPointerMove = (event: PointerEvent) => {
81
+ if (!pointerDownOnOverlay.value) return
82
+ const deltaX = Math.abs(event.clientX - startX.value)
83
+ const deltaY = Math.abs(event.clientY - startY.value)
84
+ if (deltaX > 5 || deltaY > 5) {
85
+ draggedOnOverlay.value = true
86
+ }
87
+ }
88
+
89
+ const handleWindowPointerUp = (event: PointerEvent) => {
90
+ window.removeEventListener('pointermove', handleWindowPointerMove)
91
+ window.removeEventListener('pointerup', handleWindowPointerUp)
92
+
93
+ if (!pointerDownOnOverlay.value || lastPointerDownTarget.value !== 'overlay') {
94
+ pointerDownOnOverlay.value = false
95
+ draggedOnOverlay.value = false
96
+ lastPointerDownTarget.value = null
97
+ return
98
+ }
99
+ const overlayEl = overlayRef.value
100
+ const isOverlayTarget = overlayEl && event.target === overlayEl
101
+
102
+ if (props.closeOnOverlay && isOverlayTarget && !draggedOnOverlay.value) {
103
+ close()
104
+ }
105
+
106
+ if (overlayEl && activePointerId.value !== null && overlayEl.hasPointerCapture(activePointerId.value)) {
107
+ overlayEl.releasePointerCapture(activePointerId.value)
108
+ }
109
+ activePointerId.value = null
110
+
111
+ pointerDownOnOverlay.value = false
112
+ draggedOnOverlay.value = false
113
+ lastPointerDownTarget.value = null
114
+ }
115
+
116
+ const handleOverlayPointerDown = (event: PointerEvent) => {
117
+ if (!props.closeOnOverlay) return
118
+ if (event.target !== event.currentTarget) return
119
+
120
+ pointerDownOnOverlay.value = true
121
+ draggedOnOverlay.value = false
122
+ lastPointerDownTarget.value = 'overlay'
123
+ startX.value = event.clientX
124
+ startY.value = event.clientY
125
+
126
+ const overlayEl = overlayRef.value
127
+ if (overlayEl) {
128
+ activePointerId.value = event.pointerId
129
+ overlayEl.setPointerCapture(event.pointerId)
130
+ }
131
+
132
+ window.addEventListener('pointermove', handleWindowPointerMove)
133
+ window.addEventListener('pointerup', handleWindowPointerUp)
134
+ }
135
+
136
+ const handleContentPointerDown = () => {
137
+ lastPointerDownTarget.value = 'content'
138
+ }
139
+
140
+ onBeforeUnmount(() => {
141
+ window.removeEventListener('pointermove', handleWindowPointerMove)
142
+ window.removeEventListener('pointerup', handleWindowPointerUp)
143
+ })
144
+
145
+ // Lock body scroll when modal is open
146
+ watch(() => props.modelValue, (isOpen) => {
147
+ if (isOpen) {
148
+ document.body.style.overflow = 'hidden'
149
+ } else {
150
+ document.body.style.overflow = ''
151
+ }
152
+ })
153
+ </script>
154
+
155
+ <style scoped>
156
+ .modal-overlay {
157
+ position: fixed;
158
+ top: 0;
159
+ left: 0;
160
+ right: 0;
161
+ bottom: 0;
162
+ background: rgba(0, 0, 0, 0.5);
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ z-index: 1000;
167
+ padding: 1rem;
168
+ overflow-y: auto;
169
+ }
170
+
171
+ .modal-content {
172
+ background: white;
173
+ border-radius: 1rem;
174
+ width: 100%;
175
+ max-height: 90vh;
176
+ display: flex;
177
+ flex-direction: column;
178
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
179
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04);
180
+ }
181
+
182
+ .modal-content--small {
183
+ max-width: 400px;
184
+ }
185
+
186
+ .modal-content--medium {
187
+ max-width: 600px;
188
+ }
189
+
190
+ .modal-content--large {
191
+ max-width: 900px;
192
+ }
193
+
194
+ .modal-header {
195
+ display: flex;
196
+ justify-content: space-between;
197
+ align-items: center;
198
+ padding: 1.5rem;
199
+ border-bottom: 2px solid #e5e7eb;
200
+ flex-shrink: 0;
201
+ }
202
+
203
+ .modal-title {
204
+ margin: 0;
205
+ font-size: 1.25rem;
206
+ font-weight: 600;
207
+ color: #1f2937;
208
+ }
209
+
210
+ .modal-close {
211
+ width: 2rem;
212
+ height: 2rem;
213
+ border: none;
214
+ background: #f3f4f6;
215
+ border-radius: 0.5rem;
216
+ font-size: 1.5rem;
217
+ cursor: pointer;
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ line-height: 1;
222
+ color: #6b7280;
223
+ transition: all 0.2s;
224
+ flex-shrink: 0;
225
+ }
226
+
227
+ .modal-close:hover {
228
+ background: #e5e7eb;
229
+ color: #374151;
230
+ }
231
+
232
+ .modal-body {
233
+ padding: 1.5rem;
234
+ overflow-y: auto;
235
+ flex: 1;
236
+ }
237
+
238
+ .modal-footer {
239
+ padding: 1.5rem;
240
+ border-top: 2px solid #e5e7eb;
241
+ display: flex;
242
+ gap: 1rem;
243
+ justify-content: flex-end;
244
+ flex-shrink: 0;
245
+ }
246
+
247
+ /* Transitions */
248
+ .modal-enter-active,
249
+ .modal-leave-active {
250
+ transition: opacity 0.3s ease;
251
+ }
252
+
253
+ .modal-enter-active .modal-content,
254
+ .modal-leave-active .modal-content {
255
+ transition: transform 0.3s ease;
256
+ }
257
+
258
+ .modal-enter-from,
259
+ .modal-leave-to {
260
+ opacity: 0;
261
+ }
262
+
263
+ .modal-enter-from .modal-content,
264
+ .modal-leave-to .modal-content {
265
+ transform: scale(0.95);
266
+ }
267
+
268
+ @media (max-width: 640px) {
269
+ .modal-content {
270
+ max-height: 100vh;
271
+ border-radius: 0;
272
+ }
273
+
274
+ .modal-overlay {
275
+ padding: 0;
276
+ }
277
+ }
278
+ </style>
@@ -0,0 +1,313 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { ref, onMounted } from 'vue'
3
+ import ProgressBar from './ProgressBar.vue'
4
+
5
+ const meta = {
6
+ title: 'Components/ProgressBar',
7
+ component: ProgressBar,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ percentage: {
11
+ control: { type: 'range', min: 0, max: 100, step: 1 },
12
+ description: 'Progress percentage (0-100)'
13
+ },
14
+ // label: {
15
+ // control: 'text',
16
+ // description: 'Progress bar label'
17
+ // },
18
+ showLabel: {
19
+ control: 'boolean',
20
+ description: 'Show label and percentage'
21
+ },
22
+ showPercentageInBar: {
23
+ control: 'boolean',
24
+ description: 'Show percentage inside the bar'
25
+ },
26
+ height: {
27
+ control: { type: 'range', min: 4, max: 40, step: 2 },
28
+ description: 'Progress bar height in pixels'
29
+ },
30
+ variant: {
31
+ control: 'select',
32
+ options: ['primary', 'success', 'warning', 'danger'],
33
+ description: 'Color variant'
34
+ },
35
+ animated: {
36
+ control: 'boolean',
37
+ description: 'Animated transition'
38
+ }
39
+ },
40
+ args: {
41
+ percentage: 50,
42
+ showLabel: true,
43
+ showPercentageInBar: false,
44
+ height: 12,
45
+ variant: 'primary',
46
+ animated: true
47
+ }
48
+ } satisfies Meta<typeof ProgressBar>
49
+
50
+ export default meta
51
+ type Story = StoryObj<typeof meta>
52
+
53
+ export const Default: Story = {
54
+ args: {
55
+ percentage: 65,
56
+ label: 'Progress'
57
+ }
58
+ }
59
+
60
+ export const Success: Story = {
61
+ args: {
62
+ percentage: 100,
63
+ label: 'Completed',
64
+ variant: 'success'
65
+ }
66
+ }
67
+
68
+ export const Warning: Story = {
69
+ args: {
70
+ percentage: 45,
71
+ label: 'Warning',
72
+ variant: 'warning'
73
+ }
74
+ }
75
+
76
+ export const Danger: Story = {
77
+ args: {
78
+ percentage: 20,
79
+ label: 'Critical',
80
+ variant: 'danger'
81
+ }
82
+ }
83
+
84
+ export const WithPercentageInBar: Story = {
85
+ args: {
86
+ percentage: 75,
87
+ label: 'Upload Progress',
88
+ showPercentageInBar: true,
89
+ height: 24
90
+ }
91
+ }
92
+
93
+ export const Thin: Story = {
94
+ args: {
95
+ percentage: 60,
96
+ label: 'Thin Progress Bar',
97
+ height: 4
98
+ }
99
+ }
100
+
101
+ export const Thick: Story = {
102
+ args: {
103
+ percentage: 80,
104
+ label: 'Thick Progress Bar',
105
+ height: 32,
106
+ showPercentageInBar: true
107
+ }
108
+ }
109
+
110
+ export const NoLabel: Story = {
111
+ args: {
112
+ percentage: 55,
113
+ showLabel: false
114
+ }
115
+ }
116
+
117
+ export const NotAnimated: Story = {
118
+ args: {
119
+ percentage: 70,
120
+ label: 'Static Progress',
121
+ animated: false
122
+ }
123
+ }
124
+
125
+ export const Animated: Story = {
126
+ render: () => ({
127
+ components: { ProgressBar },
128
+ setup() {
129
+ const progress = ref(0)
130
+
131
+ onMounted(() => {
132
+ const interval = setInterval(() => {
133
+ if (progress.value >= 100) {
134
+ progress.value = 0
135
+ } else {
136
+ progress.value += 1
137
+ }
138
+ }, 50)
139
+
140
+ return () => clearInterval(interval)
141
+ })
142
+
143
+ return { progress }
144
+ },
145
+ template: `
146
+ <ProgressBar
147
+ :percentage="progress"
148
+ label="Loading..."
149
+ variant="primary"
150
+ />
151
+ `
152
+ })
153
+ }
154
+
155
+ export const MultipleSteps: Story = {
156
+ render: () => ({
157
+ components: { ProgressBar },
158
+ setup() {
159
+ const steps = [
160
+ { label: 'Step 1: Information', percentage: 100, variant: 'success' },
161
+ { label: 'Step 2: Verification', percentage: 100, variant: 'success' },
162
+ { label: 'Step 3: Payment', percentage: 60, variant: 'primary' },
163
+ { label: 'Step 4: Confirmation', percentage: 0, variant: 'primary' }
164
+ ]
165
+
166
+ return { steps }
167
+ },
168
+ template: `
169
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
170
+ <ProgressBar
171
+ v-for="(step, index) in steps"
172
+ :key="index"
173
+ :label="step.label"
174
+ :percentage="step.percentage"
175
+ :variant="step.variant"
176
+ />
177
+ </div>
178
+ `
179
+ })
180
+ }
181
+
182
+ export const FileUpload: Story = {
183
+ render: () => ({
184
+ components: { ProgressBar },
185
+ setup() {
186
+ const uploads = ref([
187
+ { name: 'document.pdf', progress: 100, variant: 'success' },
188
+ { name: 'presentation.pptx', progress: 75, variant: 'primary' },
189
+ { name: 'spreadsheet.xlsx', progress: 45, variant: 'primary' },
190
+ { name: 'image.png', progress: 15, variant: 'warning' }
191
+ ])
192
+
193
+ return { uploads }
194
+ },
195
+ template: `
196
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
197
+ <ProgressBar
198
+ v-for="(file, index) in uploads"
199
+ :key="index"
200
+ :label="file.name"
201
+ :percentage="file.progress"
202
+ :variant="file.variant"
203
+ height="8"
204
+ />
205
+ </div>
206
+ `
207
+ })
208
+ }
209
+
210
+ export const AllVariants: Story = {
211
+ render: () => ({
212
+ components: { ProgressBar },
213
+ template: `
214
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
215
+ <ProgressBar
216
+ label="Primary"
217
+ :percentage="75"
218
+ variant="primary"
219
+ />
220
+ <ProgressBar
221
+ label="Success"
222
+ :percentage="100"
223
+ variant="success"
224
+ />
225
+ <ProgressBar
226
+ label="Warning"
227
+ :percentage="50"
228
+ variant="warning"
229
+ />
230
+ <ProgressBar
231
+ label="Danger"
232
+ :percentage="25"
233
+ variant="danger"
234
+ />
235
+ </div>
236
+ `
237
+ })
238
+ }
239
+
240
+ export const AllSizes: Story = {
241
+ render: () => ({
242
+ components: { ProgressBar },
243
+ template: `
244
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
245
+ <ProgressBar
246
+ label="Thin (4px)"
247
+ :percentage="70"
248
+ :height="4"
249
+ />
250
+ <ProgressBar
251
+ label="Small (8px)"
252
+ :percentage="70"
253
+ :height="8"
254
+ />
255
+ <ProgressBar
256
+ label="Medium (12px)"
257
+ :percentage="70"
258
+ :height="12"
259
+ />
260
+ <ProgressBar
261
+ label="Large (20px)"
262
+ :percentage="70"
263
+ :height="20"
264
+ show-percentage-in-bar
265
+ />
266
+ <ProgressBar
267
+ label="Extra Large (32px)"
268
+ :percentage="70"
269
+ :height="32"
270
+ show-percentage-in-bar
271
+ />
272
+ </div>
273
+ `
274
+ })
275
+ }
276
+
277
+ export const TaskCompletion: Story = {
278
+ render: () => ({
279
+ components: { ProgressBar },
280
+ setup() {
281
+ const completedTasks = ref(7)
282
+ const totalTasks = ref(10)
283
+ const percentage = ref((completedTasks.value / totalTasks.value) * 100)
284
+
285
+ return { completedTasks, totalTasks, percentage }
286
+ },
287
+ template: `
288
+ <div>
289
+ <h3 style="margin-bottom: 1rem;">Task Completion</h3>
290
+ <ProgressBar
291
+ :label="\`Tasks Completed: \${completedTasks}/\${totalTasks}\`"
292
+ :percentage="percentage"
293
+ variant="success"
294
+ height="16"
295
+ />
296
+ <div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
297
+ <button
298
+ @click="completedTasks = Math.min(completedTasks + 1, totalTasks); percentage = (completedTasks / totalTasks) * 100"
299
+ style="padding: 0.5rem 1rem; background: #10b981; color: white; border: none; border-radius: 0.375rem; cursor: pointer;"
300
+ >
301
+ Complete Task
302
+ </button>
303
+ <button
304
+ @click="completedTasks = Math.max(completedTasks - 1, 0); percentage = (completedTasks / totalTasks) * 100"
305
+ style="padding: 0.5rem 1rem; background: #6b7280; color: white; border: none; border-radius: 0.375rem; cursor: pointer;"
306
+ >
307
+ Undo
308
+ </button>
309
+ </div>
310
+ </div>
311
+ `
312
+ })
313
+ }