@davidbirchall/core 1.0.8 → 1.0.9

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/dist/Button/types.d.ts +4 -0
  2. package/dist/Calendar/types.d.ts +22 -0
  3. package/{src/components/Card/types.ts → dist/Card/types.d.ts} +1 -1
  4. package/dist/Checkbox/types.d.ts +7 -0
  5. package/dist/DataTable/types.d.ts +11 -0
  6. package/dist/Dropdown/types.d.ts +13 -0
  7. package/dist/EmptyState/types.d.ts +8 -0
  8. package/dist/ErrorSummary/types.d.ts +4 -0
  9. package/dist/Heading/types.d.ts +6 -0
  10. package/dist/Input/types.d.ts +11 -0
  11. package/dist/Select/types.d.ts +15 -0
  12. package/dist/StatCard/types.d.ts +12 -0
  13. package/dist/Tag/types.d.ts +4 -0
  14. package/dist/TextArea/types.d.ts +11 -0
  15. package/dist/core.css +1 -0
  16. package/dist/core.js +24 -0
  17. package/dist/core.js.map +1 -0
  18. package/dist/core.umd.cjs +2 -0
  19. package/dist/core.umd.cjs.map +1 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/package.json +27 -0
  22. package/package.json +4 -1
  23. package/.storybook/main.ts +0 -18
  24. package/.storybook/preview.ts +0 -14
  25. package/src/components/Badge/Badge.stories.ts +0 -147
  26. package/src/components/Badge/Badge.test.ts +0 -57
  27. package/src/components/Badge/Badge.vue +0 -79
  28. package/src/components/Button/Button.stories.ts +0 -80
  29. package/src/components/Button/Button.test.ts +0 -145
  30. package/src/components/Button/Button.vue +0 -108
  31. package/src/components/Button/types.ts +0 -4
  32. package/src/components/Calendar/Calendar.stories.ts +0 -261
  33. package/src/components/Calendar/Calendar.test.ts +0 -119
  34. package/src/components/Calendar/Calendar.vue +0 -528
  35. package/src/components/Calendar/types.ts +0 -20
  36. package/src/components/Card/Card.stories.ts +0 -88
  37. package/src/components/Card/Card.test.ts +0 -173
  38. package/src/components/Card/Card.vue +0 -59
  39. package/src/components/Checkbox/Checkbox.stories.ts +0 -126
  40. package/src/components/Checkbox/Checkbox.test.ts +0 -155
  41. package/src/components/Checkbox/Checkbox.vue +0 -121
  42. package/src/components/Checkbox/types.ts +0 -7
  43. package/src/components/DataTable/DataTable.stories.ts +0 -156
  44. package/src/components/DataTable/DataTable.test.ts +0 -185
  45. package/src/components/DataTable/DataTable.vue +0 -177
  46. package/src/components/DataTable/types.ts +0 -12
  47. package/src/components/DatePicker/DatePicker.stories.ts +0 -172
  48. package/src/components/DatePicker/DatePicker.test.ts +0 -87
  49. package/src/components/DatePicker/DatePicker.vue +0 -302
  50. package/src/components/Dropdown/Dropdown.stories.ts +0 -231
  51. package/src/components/Dropdown/Dropdown.vue +0 -314
  52. package/src/components/Dropdown/types.ts +0 -14
  53. package/src/components/EmptyState/EmptyState.stories.ts +0 -189
  54. package/src/components/EmptyState/EmptyState.vue +0 -215
  55. package/src/components/EmptyState/types.ts +0 -8
  56. package/src/components/ErrorSummary/ErrorSummary.vue +0 -78
  57. package/src/components/ErrorSummary/types.ts +0 -4
  58. package/src/components/FormGroup/FormGroup.stories.ts +0 -264
  59. package/src/components/FormGroup/FormGroup.test.ts +0 -63
  60. package/src/components/FormGroup/FormGroup.vue +0 -58
  61. package/src/components/Heading/Heading.stories.ts +0 -121
  62. package/src/components/Heading/Heading.test.ts +0 -184
  63. package/src/components/Heading/Heading.vue +0 -95
  64. package/src/components/Heading/types.ts +0 -6
  65. package/src/components/Input/Input.stories.ts +0 -172
  66. package/src/components/Input/Input.test.ts +0 -213
  67. package/src/components/Input/Input.vue +0 -121
  68. package/src/components/Input/types.ts +0 -11
  69. package/src/components/Modal/Modal.stories.ts +0 -341
  70. package/src/components/Modal/Modal.test.ts +0 -99
  71. package/src/components/Modal/Modal.vue +0 -278
  72. package/src/components/ProgressBar/ProgressBar.stories.ts +0 -313
  73. package/src/components/ProgressBar/ProgressBar.test.ts +0 -98
  74. package/src/components/ProgressBar/ProgressBar.vue +0 -117
  75. package/src/components/Select/Select.stories.ts +0 -177
  76. package/src/components/Select/Select.test.ts +0 -225
  77. package/src/components/Select/Select.vue +0 -147
  78. package/src/components/Select/types.ts +0 -16
  79. package/src/components/StatCard/StatCard.stories.ts +0 -274
  80. package/src/components/StatCard/StatCard.vue +0 -226
  81. package/src/components/StatCard/types.ts +0 -12
  82. package/src/components/Tag/Tag.stories.ts +0 -78
  83. package/src/components/Tag/Tag.test.ts +0 -50
  84. package/src/components/Tag/Tag.vue +0 -71
  85. package/src/components/Tag/types.ts +0 -4
  86. package/src/components/TextArea/TextArea.stories.ts +0 -171
  87. package/src/components/TextArea/TextArea.test.ts +0 -202
  88. package/src/components/TextArea/TextArea.vue +0 -122
  89. package/src/components/TextArea/types.ts +0 -11
  90. package/src/components/index.ts +0 -5
  91. package/src/test/setup.ts +0 -1
  92. package/src/vite-env.d.ts +0 -6
  93. package/tsconfig.json +0 -29
  94. package/vite.config.ts +0 -33
  95. package/vitest.config.ts +0 -28
@@ -1,99 +0,0 @@
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
- })
@@ -1,278 +0,0 @@
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>
@@ -1,313 +0,0 @@
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
- }