@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,173 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/vue'
3
+ import Card from './Card.vue'
4
+
5
+ describe('Card', () => {
6
+ describe('rendering', () => {
7
+ it('renders default slot content', () => {
8
+ render(Card, {
9
+ slots: {
10
+ default: '<p>Card content</p>'
11
+ }
12
+ })
13
+
14
+ expect(screen.getByText('Card content')).toBeInTheDocument()
15
+ })
16
+
17
+ it('renders header slot when provided', () => {
18
+ const { container } = render(Card, {
19
+ slots: {
20
+ header: 'Card Header',
21
+ default: 'Card content'
22
+ }
23
+ })
24
+
25
+ const header = container.querySelector('.card__header')
26
+ expect(header).toBeInTheDocument()
27
+ expect(header).toHaveTextContent('Card Header')
28
+ })
29
+
30
+ it('does not render header section when slot is not provided', () => {
31
+ const { container } = render(Card, {
32
+ slots: {
33
+ default: 'Card content'
34
+ }
35
+ })
36
+
37
+ const header = container.querySelector('.card__header')
38
+ expect(header).not.toBeInTheDocument()
39
+ })
40
+
41
+ it('renders footer slot when provided', () => {
42
+ const { container } = render(Card, {
43
+ slots: {
44
+ default: 'Card content',
45
+ footer: 'Card Footer'
46
+ }
47
+ })
48
+
49
+ const footer = container.querySelector('.card__footer')
50
+ expect(footer).toBeInTheDocument()
51
+ expect(footer).toHaveTextContent('Card Footer')
52
+ })
53
+
54
+ it('does not render footer section when slot is not provided', () => {
55
+ const { container } = render(Card, {
56
+ slots: {
57
+ default: 'Card content'
58
+ }
59
+ })
60
+
61
+ const footer = container.querySelector('.card__footer')
62
+ expect(footer).not.toBeInTheDocument()
63
+ })
64
+
65
+ it('renders all slots together', () => {
66
+ const { container } = render(Card, {
67
+ slots: {
68
+ header: 'Header',
69
+ default: 'Body',
70
+ footer: 'Footer'
71
+ }
72
+ })
73
+
74
+ expect(container.querySelector('.card__header')).toBeInTheDocument()
75
+ expect(container.querySelector('.card__body')).toBeInTheDocument()
76
+ expect(container.querySelector('.card__footer')).toBeInTheDocument()
77
+ })
78
+ })
79
+
80
+ describe('hoverable prop', () => {
81
+ it('does not apply hoverable class by default', () => {
82
+ const { container } = render(Card, {
83
+ slots: {
84
+ default: 'Content'
85
+ }
86
+ })
87
+
88
+ const card = container.querySelector('.card')
89
+ expect(card).not.toHaveClass('card--hoverable')
90
+ })
91
+
92
+ it('applies hoverable class when prop is true', () => {
93
+ const { container } = render(Card, {
94
+ props: {
95
+ hoverable: true
96
+ },
97
+ slots: {
98
+ default: 'Content'
99
+ }
100
+ })
101
+
102
+ const card = container.querySelector('.card')
103
+ expect(card).toHaveClass('card--hoverable')
104
+ })
105
+
106
+ it('does not apply hoverable class when prop is false', () => {
107
+ const { container } = render(Card, {
108
+ props: {
109
+ hoverable: false
110
+ },
111
+ slots: {
112
+ default: 'Content'
113
+ }
114
+ })
115
+
116
+ const card = container.querySelector('.card')
117
+ expect(card).not.toHaveClass('card--hoverable')
118
+ })
119
+ })
120
+
121
+ describe('styling', () => {
122
+ it('always applies base card class', () => {
123
+ const { container } = render(Card, {
124
+ slots: {
125
+ default: 'Content'
126
+ }
127
+ })
128
+
129
+ const card = container.querySelector('.card')
130
+ expect(card).toHaveClass('card')
131
+ })
132
+
133
+ it('maintains card structure with body wrapper', () => {
134
+ const { container } = render(Card, {
135
+ slots: {
136
+ default: 'Test content'
137
+ }
138
+ })
139
+
140
+ const body = container.querySelector('.card__body')
141
+ expect(body).toBeInTheDocument()
142
+ expect(body).toHaveTextContent('Test content')
143
+ })
144
+ })
145
+
146
+ describe('complex content', () => {
147
+ it('handles complex HTML in slots', () => {
148
+ const { container } = render(Card, {
149
+ slots: {
150
+ header: '<h2>Title</h2>',
151
+ default: '<div><p>Paragraph 1</p><p>Paragraph 2</p></div>',
152
+ footer: '<button>Action</button>'
153
+ }
154
+ })
155
+
156
+ expect(container.querySelector('.card__header h2')).toBeInTheDocument()
157
+ expect(container.querySelectorAll('.card__body p')).toHaveLength(2)
158
+ expect(screen.getByRole('button', { name: 'Action' })).toBeInTheDocument()
159
+ })
160
+
161
+ it('handles nested components in slots', () => {
162
+ const { container } = render(Card, {
163
+ slots: {
164
+ default: '<div class="custom-content">Nested</div>'
165
+ }
166
+ })
167
+
168
+ const customContent = container.querySelector('.custom-content')
169
+ expect(customContent).toBeInTheDocument()
170
+ expect(customContent).toHaveTextContent('Nested')
171
+ })
172
+ })
173
+ })
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div :class="['card', { 'card--hoverable': hoverable }]">
3
+ <div v-if="$slots.header" class="card__header">
4
+ <slot name="header" />
5
+ </div>
6
+ <div class="card__body">
7
+ <slot />
8
+ </div>
9
+ <div v-if="$slots.footer" class="card__footer">
10
+ <slot name="footer" />
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ export interface CardProps {
17
+ hoverable?: boolean
18
+ }
19
+
20
+ withDefaults(defineProps<CardProps>(), {
21
+ hoverable: false
22
+ })
23
+ </script>
24
+
25
+ <style scoped>
26
+ .card {
27
+ background: white;
28
+ border: 1px solid #e5e7eb;
29
+ border-radius: 0.5rem;
30
+ overflow: hidden;
31
+ }
32
+
33
+ .card--hoverable {
34
+ transition: all 0.2s ease-in-out;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .card--hoverable:hover {
39
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
40
+ transform: translateY(-2px);
41
+ }
42
+
43
+ .card__header {
44
+ padding: 1rem 1.5rem;
45
+ border-bottom: 1px solid #e5e7eb;
46
+ font-weight: 600;
47
+ font-size: 1.125rem;
48
+ }
49
+
50
+ .card__body {
51
+ padding: 1.5rem;
52
+ }
53
+
54
+ .card__footer {
55
+ padding: 1rem 1.5rem;
56
+ border-top: 1px solid #e5e7eb;
57
+ background-color: #f9fafb;
58
+ }
59
+ </style>
@@ -1,3 +1,3 @@
1
1
  export interface CardProps {
2
- hoverable?: boolean;
2
+ hoverable?: boolean
3
3
  }
@@ -0,0 +1,126 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { ref } from 'vue'
3
+ import Checkbox from './Checkbox.vue'
4
+
5
+ const meta = {
6
+ title: 'Form Fields/Checkbox',
7
+ component: Checkbox,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ modelValue: {
11
+ control: 'boolean',
12
+ description: 'Checked state'
13
+ },
14
+ label: {
15
+ control: 'text',
16
+ description: 'Label text'
17
+ },
18
+ disabled: {
19
+ control: 'boolean',
20
+ description: 'Disabled state'
21
+ },
22
+ required: {
23
+ control: 'boolean',
24
+ description: 'Required field'
25
+ },
26
+ error: {
27
+ control: 'text',
28
+ description: 'Error message'
29
+ }
30
+ },
31
+ args: {
32
+ modelValue: false,
33
+ disabled: false,
34
+ required: false
35
+ }
36
+ } satisfies Meta<typeof Checkbox>
37
+
38
+ export default meta
39
+ type Story = StoryObj<typeof meta>
40
+
41
+ export const Default: Story = {
42
+ args: {
43
+ label: 'Accept terms and conditions'
44
+ },
45
+ render: (args: any) => ({
46
+ components: { Checkbox },
47
+ setup() {
48
+ const checked = ref(args.modelValue ?? false)
49
+ return { args, checked }
50
+ },
51
+ template: '<Checkbox v-bind="args" v-model="checked" />'
52
+ })
53
+ }
54
+
55
+ export const Checked: Story = {
56
+ args: {
57
+ label: 'Subscribe to newsletter',
58
+ modelValue: true
59
+ },
60
+ render: (args: any) => ({
61
+ components: { Checkbox },
62
+ setup() {
63
+ const checked = ref(args.modelValue ?? false)
64
+ return { args, checked }
65
+ },
66
+ template: '<Checkbox v-bind="args" v-model="checked" />'
67
+ })
68
+ }
69
+
70
+ export const Disabled: Story = {
71
+ args: {
72
+ label: 'Disabled option',
73
+ disabled: true
74
+ },
75
+ render: (args: any) => ({
76
+ components: { Checkbox },
77
+ setup() {
78
+ const checked = ref(false)
79
+ return { args, checked }
80
+ },
81
+ template: '<Checkbox v-bind="args" v-model="checked" />'
82
+ })
83
+ }
84
+
85
+ export const DisabledChecked: Story = {
86
+ args: {
87
+ label: 'Disabled checked option',
88
+ disabled: true,
89
+ modelValue: true
90
+ },
91
+ render: (args: any) => ({
92
+ components: { Checkbox },
93
+ setup() {
94
+ const checked = ref(args.modelValue ?? false)
95
+ return { args, checked }
96
+ },
97
+ template: '<Checkbox v-bind="args" v-model="checked" />'
98
+ })
99
+ }
100
+
101
+ export const WithError: Story = {
102
+ args: {
103
+ label: 'I agree to the terms',
104
+ error: 'You must accept the terms to continue'
105
+ },
106
+ render: (args: any) => ({
107
+ components: { Checkbox },
108
+ setup() {
109
+ const checked = ref(false)
110
+ return { args, checked }
111
+ },
112
+ template: '<Checkbox v-bind="args" v-model="checked" />'
113
+ })
114
+ }
115
+
116
+ export const WithoutLabel: Story = {
117
+ args: {},
118
+ render: (args: any) => ({
119
+ components: { Checkbox },
120
+ setup() {
121
+ const checked = ref(false)
122
+ return { args, checked }
123
+ },
124
+ template: '<Checkbox v-bind="args" v-model="checked" />'
125
+ })
126
+ }
@@ -0,0 +1,155 @@
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 Checkbox from './Checkbox.vue'
5
+
6
+ describe('Checkbox', () => {
7
+ describe('rendering', () => {
8
+ it('renders with label', () => {
9
+ render(Checkbox, {
10
+ props: {
11
+ label: 'Accept terms'
12
+ }
13
+ })
14
+
15
+ expect(screen.getByText('Accept terms')).toBeInTheDocument()
16
+ })
17
+
18
+ it('renders without label', () => {
19
+ const { container } = render(Checkbox)
20
+
21
+ expect(container.querySelector('.checkbox-text')).not.toBeInTheDocument()
22
+ })
23
+
24
+ it('renders error message', () => {
25
+ render(Checkbox, {
26
+ props: {
27
+ error: 'This field is required'
28
+ }
29
+ })
30
+
31
+ expect(screen.getByText('This field is required')).toBeInTheDocument()
32
+ })
33
+
34
+ it('applies disabled class when disabled', () => {
35
+ render(Checkbox, {
36
+ props: {
37
+ label: 'Disabled',
38
+ disabled: true
39
+ }
40
+ })
41
+
42
+ const label = screen.getByText('Disabled').closest('.checkbox-label')
43
+ expect(label).toHaveClass('checkbox-label--disabled')
44
+ })
45
+ })
46
+
47
+ describe('checked state', () => {
48
+ it('is unchecked by default', () => {
49
+ render(Checkbox, {
50
+ props: {
51
+ label: 'Checkbox'
52
+ }
53
+ })
54
+
55
+ const checkbox = screen.getByRole('checkbox')
56
+ expect(checkbox).not.toBeChecked()
57
+ })
58
+
59
+ it('renders as checked when modelValue is true', () => {
60
+ render(Checkbox, {
61
+ props: {
62
+ label: 'Checkbox',
63
+ modelValue: true
64
+ }
65
+ })
66
+
67
+ const checkbox = screen.getByRole('checkbox')
68
+ expect(checkbox).toBeChecked()
69
+ })
70
+
71
+ it('can be toggled', async () => {
72
+ const user = userEvent.setup()
73
+ const { emitted } = render(Checkbox, {
74
+ props: {
75
+ label: 'Toggle me'
76
+ }
77
+ })
78
+
79
+ const checkbox = screen.getByRole('checkbox')
80
+ await user.click(checkbox)
81
+
82
+ expect(emitted()['update:modelValue']).toBeTruthy()
83
+ expect(emitted()['update:modelValue'][0]).toEqual([true])
84
+ })
85
+ })
86
+
87
+ describe('disabled state', () => {
88
+ it('disables the checkbox when disabled prop is true', () => {
89
+ render(Checkbox, {
90
+ props: {
91
+ label: 'Disabled',
92
+ disabled: true
93
+ }
94
+ })
95
+
96
+ const checkbox = screen.getByRole('checkbox')
97
+ expect(checkbox).toBeDisabled()
98
+ })
99
+
100
+ it('does not emit events when disabled', async () => {
101
+ const user = userEvent.setup()
102
+ const { emitted } = render(Checkbox, {
103
+ props: {
104
+ label: 'Disabled',
105
+ disabled: true
106
+ }
107
+ })
108
+
109
+ const checkbox = screen.getByRole('checkbox')
110
+ await user.click(checkbox)
111
+
112
+ expect(emitted()['update:modelValue']).toBeFalsy()
113
+ })
114
+ })
115
+
116
+ describe('required attribute', () => {
117
+ it('sets required attribute when required prop is true', () => {
118
+ render(Checkbox, {
119
+ props: {
120
+ label: 'Required',
121
+ required: true
122
+ }
123
+ })
124
+
125
+ const checkbox = screen.getByRole('checkbox')
126
+ expect(checkbox).toBeRequired()
127
+ })
128
+
129
+ it('does not set required attribute by default', () => {
130
+ render(Checkbox, {
131
+ props: {
132
+ label: 'Optional'
133
+ }
134
+ })
135
+
136
+ const checkbox = screen.getByRole('checkbox')
137
+ expect(checkbox).not.toBeRequired()
138
+ })
139
+ })
140
+
141
+ describe('accessibility', () => {
142
+ it('associates label with checkbox', () => {
143
+ render(Checkbox, {
144
+ props: {
145
+ label: 'Accept terms'
146
+ }
147
+ })
148
+
149
+ const checkbox = screen.getByRole('checkbox')
150
+ const label = screen.getByText('Accept terms')
151
+
152
+ expect(checkbox.closest('label')).toContainElement(label)
153
+ })
154
+ })
155
+ })
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div class="checkbox-wrapper">
3
+ <label :class="['checkbox-label', { 'checkbox-label--disabled': disabled }]">
4
+ <input
5
+ type="checkbox"
6
+ class="checkbox-input"
7
+ :checked="modelValue"
8
+ :disabled="disabled"
9
+ :required="required"
10
+ @change="handleChange"
11
+ />
12
+ <span class="checkbox-box"></span>
13
+ <span v-if="label" class="checkbox-text">{{ label }}</span>
14
+ </label>
15
+ <span v-if="error" class="checkbox-error">{{ error }}</span>
16
+ </div>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+
21
+ export interface CheckboxProps {
22
+ modelValue?: boolean
23
+ label?: string
24
+ disabled?: boolean
25
+ required?: boolean
26
+ error?: string
27
+ }
28
+
29
+ withDefaults(defineProps<CheckboxProps>(), {
30
+ modelValue: false,
31
+ disabled: false,
32
+ required: false
33
+ })
34
+
35
+ const emit = defineEmits<{
36
+ 'update:modelValue': [value: boolean]
37
+ }>()
38
+
39
+ const handleChange = (event: Event) => {
40
+ const target = event.target as HTMLInputElement
41
+ emit('update:modelValue', target.checked)
42
+ }
43
+ </script>
44
+
45
+ <style scoped>
46
+ .checkbox-wrapper {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: 0.25rem;
50
+ }
51
+
52
+ .checkbox-label {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: 0.5rem;
56
+ cursor: pointer;
57
+ position: relative;
58
+ }
59
+
60
+ .checkbox-label--disabled {
61
+ cursor: not-allowed;
62
+ opacity: 0.6;
63
+ }
64
+
65
+ .checkbox-input {
66
+ position: absolute;
67
+ opacity: 0;
68
+ width: 0;
69
+ height: 0;
70
+ }
71
+
72
+ .checkbox-box {
73
+ width: 1.25rem;
74
+ height: 1.25rem;
75
+ border: 2px solid #d1d5db;
76
+ border-radius: 0.25rem;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ transition: all 0.2s ease-in-out;
81
+ background: white;
82
+ flex-shrink: 0;
83
+ }
84
+
85
+ .checkbox-input:checked + .checkbox-box {
86
+ background: #3b82f6;
87
+ border-color: #3b82f6;
88
+ }
89
+
90
+ .checkbox-input:checked + .checkbox-box::after {
91
+ content: '';
92
+ width: 0.375rem;
93
+ height: 0.75rem;
94
+ border: solid white;
95
+ border-width: 0 2px 2px 0;
96
+ transform: rotate(45deg);
97
+ margin-bottom: 0.125rem;
98
+ }
99
+
100
+ .checkbox-input:focus + .checkbox-box {
101
+ outline: 2px solid #3b82f6;
102
+ outline-offset: 2px;
103
+ }
104
+
105
+ .checkbox-input:disabled + .checkbox-box {
106
+ background: #f3f4f6;
107
+ cursor: not-allowed;
108
+ }
109
+
110
+ .checkbox-text {
111
+ font-size: 0.875rem;
112
+ color: #374151;
113
+ user-select: none;
114
+ }
115
+
116
+ .checkbox-error {
117
+ font-size: 0.75rem;
118
+ color: #ef4444;
119
+ margin-left: 1.75rem;
120
+ }
121
+ </style>
@@ -0,0 +1,7 @@
1
+ export interface CheckboxProps {
2
+ modelValue?: boolean
3
+ label?: string
4
+ disabled?: boolean
5
+ required?: boolean
6
+ error?: string
7
+ }