@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,18 @@
1
+ import type { StorybookConfig } from '@storybook/vue3-vite'
2
+
3
+ const config: StorybookConfig = {
4
+ stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
5
+ addons: [
6
+ '@storybook/addon-essentials',
7
+ '@storybook/addon-links'
8
+ ],
9
+ framework: {
10
+ name: '@storybook/vue3-vite',
11
+ options: {}
12
+ },
13
+ docs: {
14
+ autodocs: 'tag'
15
+ }
16
+ }
17
+
18
+ export default config
@@ -0,0 +1,14 @@
1
+ import type { Preview } from '@storybook/vue3'
2
+
3
+ const preview: Preview = {
4
+ parameters: {
5
+ controls: {
6
+ matchers: {
7
+ color: /(background|color)$/i,
8
+ date: /Date$/i
9
+ }
10
+ }
11
+ }
12
+ }
13
+
14
+ export default preview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidbirchall/core",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Vue 3 component library with TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -17,9 +17,6 @@
17
17
  },
18
18
  "./core.css": "./dist/core.css"
19
19
  },
20
- "files": [
21
- "dist"
22
- ],
23
20
  "scripts": {
24
21
  "dev": "vite build --watch",
25
22
  "build": "vite build && tsc --emitDeclarationOnly",
@@ -0,0 +1,147 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import Badge from './Badge.vue'
3
+
4
+ const meta = {
5
+ title: 'Components/Badge',
6
+ component: Badge,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ variant: {
10
+ control: 'select',
11
+ options: ['primary', 'secondary', 'success', 'warning', 'danger', 'info'],
12
+ description: 'Badge color variant'
13
+ },
14
+ size: {
15
+ control: 'select',
16
+ options: ['small', 'medium', 'large'],
17
+ description: 'Badge size'
18
+ }
19
+ },
20
+ args: {
21
+ variant: 'primary',
22
+ size: 'medium'
23
+ }
24
+ } satisfies Meta<typeof Badge>
25
+
26
+ export default meta
27
+ type Story = StoryObj<typeof meta>
28
+
29
+ export const Primary: Story = {
30
+ args: {
31
+ variant: 'primary'
32
+ },
33
+ render: (args: any) => ({
34
+ components: { Badge },
35
+ setup() {
36
+ return { args }
37
+ },
38
+ template: '<Badge v-bind="args">Primary</Badge>'
39
+ })
40
+ }
41
+
42
+ export const Success: Story = {
43
+ args: {
44
+ variant: 'success'
45
+ },
46
+ render: (args: any) => ({
47
+ components: { Badge },
48
+ setup() {
49
+ return { args }
50
+ },
51
+ template: '<Badge v-bind="args">Success</Badge>'
52
+ })
53
+ }
54
+
55
+ export const Warning: Story = {
56
+ args: {
57
+ variant: 'warning'
58
+ },
59
+ render: (args: any) => ({
60
+ components: { Badge },
61
+ setup() {
62
+ return { args }
63
+ },
64
+ template: '<Badge v-bind="args">Warning</Badge>'
65
+ })
66
+ }
67
+
68
+ export const Danger: Story = {
69
+ args: {
70
+ variant: 'danger'
71
+ },
72
+ render: (args: any) => ({
73
+ components: { Badge },
74
+ setup() {
75
+ return { args }
76
+ },
77
+ template: '<Badge v-bind="args">Danger</Badge>'
78
+ })
79
+ }
80
+
81
+ export const Info: Story = {
82
+ args: {
83
+ variant: 'info'
84
+ },
85
+ render: (args: any) => ({
86
+ components: { Badge },
87
+ setup() {
88
+ return { args }
89
+ },
90
+ template: '<Badge v-bind="args">Info</Badge>'
91
+ })
92
+ }
93
+
94
+ export const Small: Story = {
95
+ args: {
96
+ size: 'small'
97
+ },
98
+ render: (args: any) => ({
99
+ components: { Badge },
100
+ setup() {
101
+ return { args }
102
+ },
103
+ template: '<Badge v-bind="args">Small Badge</Badge>'
104
+ })
105
+ }
106
+
107
+ export const Large: Story = {
108
+ args: {
109
+ size: 'large'
110
+ },
111
+ render: (args: any) => ({
112
+ components: { Badge },
113
+ setup() {
114
+ return { args }
115
+ },
116
+ template: '<Badge v-bind="args">Large Badge</Badge>'
117
+ })
118
+ }
119
+
120
+ export const AllVariants: Story = {
121
+ render: () => ({
122
+ components: { Badge },
123
+ template: `
124
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
125
+ <Badge variant="primary">Primary</Badge>
126
+ <Badge variant="secondary">Secondary</Badge>
127
+ <Badge variant="success">Success</Badge>
128
+ <Badge variant="warning">Warning</Badge>
129
+ <Badge variant="danger">Danger</Badge>
130
+ <Badge variant="info">Info</Badge>
131
+ </div>
132
+ `
133
+ })
134
+ }
135
+
136
+ export const AllSizes: Story = {
137
+ render: () => ({
138
+ components: { Badge },
139
+ template: `
140
+ <div style="display: flex; gap: 0.5rem; align-items: center;">
141
+ <Badge size="small">Small</Badge>
142
+ <Badge size="medium">Medium</Badge>
143
+ <Badge size="large">Large</Badge>
144
+ </div>
145
+ `
146
+ })
147
+ }
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Badge from './Badge.vue'
4
+
5
+ describe('Badge', () => {
6
+ it('renders slot content', () => {
7
+ const wrapper = mount(Badge, {
8
+ slots: {
9
+ default: 'New'
10
+ }
11
+ })
12
+ expect(wrapper.text()).toBe('New')
13
+ })
14
+
15
+ it('applies primary variant by default', () => {
16
+ const wrapper = mount(Badge)
17
+ expect(wrapper.classes()).toContain('badge--primary')
18
+ })
19
+
20
+ it('applies correct variant class', () => {
21
+ const variants = ['primary', 'secondary', 'success', 'warning', 'danger', 'info'] as const
22
+
23
+ variants.forEach(variant => {
24
+ const wrapper = mount(Badge, {
25
+ props: { variant }
26
+ })
27
+ expect(wrapper.classes()).toContain(`badge--${variant}`)
28
+ })
29
+ })
30
+
31
+ it('applies medium size by default', () => {
32
+ const wrapper = mount(Badge)
33
+ expect(wrapper.classes()).toContain('badge--medium')
34
+ })
35
+
36
+ it('applies correct size class', () => {
37
+ const sizes = ['small', 'medium', 'large'] as const
38
+
39
+ sizes.forEach(size => {
40
+ const wrapper = mount(Badge, {
41
+ props: { size }
42
+ })
43
+ expect(wrapper.classes()).toContain(`badge--${size}`)
44
+ })
45
+ })
46
+
47
+ it('combines variant and size classes', () => {
48
+ const wrapper = mount(Badge, {
49
+ props: {
50
+ variant: 'success',
51
+ size: 'large'
52
+ }
53
+ })
54
+ expect(wrapper.classes()).toContain('badge--success')
55
+ expect(wrapper.classes()).toContain('badge--large')
56
+ })
57
+ })
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <span :class="['badge', `badge--${variant}`, `badge--${size}`]">
3
+ <slot />
4
+ </span>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ interface Props {
9
+ variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'
10
+ size?: 'small' | 'medium' | 'large'
11
+ }
12
+
13
+ withDefaults(defineProps<Props>(), {
14
+ variant: 'primary',
15
+ size: 'medium'
16
+ })
17
+ </script>
18
+
19
+ <style scoped>
20
+ .badge {
21
+ display: inline-flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ font-weight: 500;
25
+ border-radius: 0.375rem;
26
+ white-space: nowrap;
27
+ transition: all 0.2s;
28
+ }
29
+
30
+ /* Sizes */
31
+ .badge--small {
32
+ padding: 0.125rem 0.5rem;
33
+ font-size: 0.75rem;
34
+ line-height: 1.25;
35
+ }
36
+
37
+ .badge--medium {
38
+ padding: 0.25rem 0.75rem;
39
+ font-size: 0.875rem;
40
+ line-height: 1.25;
41
+ }
42
+
43
+ .badge--large {
44
+ padding: 0.375rem 1rem;
45
+ font-size: 0.9375rem;
46
+ line-height: 1.5;
47
+ }
48
+
49
+ /* Variants */
50
+ .badge--primary {
51
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
52
+ color: white;
53
+ }
54
+
55
+ .badge--secondary {
56
+ background: #f3f4f6;
57
+ color: #374151;
58
+ }
59
+
60
+ .badge--success {
61
+ background: #dcfce7;
62
+ color: #166534;
63
+ }
64
+
65
+ .badge--warning {
66
+ background: #fef3c7;
67
+ color: #92400e;
68
+ }
69
+
70
+ .badge--danger {
71
+ background: #fee2e2;
72
+ color: #991b1b;
73
+ }
74
+
75
+ .badge--info {
76
+ background: #dbeafe;
77
+ color: #1e40af;
78
+ }
79
+ </style>
@@ -0,0 +1,80 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import Button from './Button.vue'
3
+
4
+ const meta = {
5
+ title: 'Components/Button',
6
+ component: Button,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ variant: {
10
+ control: 'select',
11
+ options: ['primary', 'secondary', 'danger'],
12
+ description: 'Button variant style'
13
+ },
14
+ disabled: {
15
+ control: 'boolean',
16
+ description: 'Disabled state'
17
+ },
18
+ onClick: { action: 'clicked' }
19
+ },
20
+ args: {
21
+ variant: 'primary',
22
+ disabled: false
23
+ }
24
+ } satisfies Meta<typeof Button>
25
+
26
+ export default meta
27
+ type Story = StoryObj<typeof meta>
28
+
29
+ export const Primary: Story = {
30
+ args: {
31
+ variant: 'primary'
32
+ },
33
+ render: (args: any) => ({
34
+ components: { Button },
35
+ setup() {
36
+ return { args }
37
+ },
38
+ template: '<Button v-bind="args">Click me</Button>'
39
+ })
40
+ }
41
+
42
+ export const Secondary: Story = {
43
+ args: {
44
+ variant: 'secondary'
45
+ },
46
+ render: (args: any) => ({
47
+ components: { Button },
48
+ setup() {
49
+ return { args }
50
+ },
51
+ template: '<Button v-bind="args">Click me</Button>'
52
+ })
53
+ }
54
+
55
+ export const Danger: Story = {
56
+ args: {
57
+ variant: 'danger'
58
+ },
59
+ render: (args: any) => ({
60
+ components: { Button },
61
+ setup() {
62
+ return { args }
63
+ },
64
+ template: '<Button v-bind="args">Delete</Button>'
65
+ })
66
+ }
67
+
68
+ export const Disabled: Story = {
69
+ args: {
70
+ variant: 'primary',
71
+ disabled: true
72
+ },
73
+ render: (args: any) => ({
74
+ components: { Button },
75
+ setup() {
76
+ return { args }
77
+ },
78
+ template: '<Button v-bind="args">Disabled</Button>'
79
+ })
80
+ }
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { render, screen } from '@testing-library/vue'
3
+ import userEvent from '@testing-library/user-event'
4
+ import Button from './Button.vue'
5
+
6
+ describe('Button', () => {
7
+ describe('rendering', () => {
8
+ it('renders slot content', () => {
9
+ render(Button, {
10
+ slots: {
11
+ default: 'Click me'
12
+ }
13
+ })
14
+
15
+ expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument()
16
+ })
17
+
18
+ it('applies primary variant class by default', () => {
19
+ render(Button, {
20
+ slots: { default: 'Button' }
21
+ })
22
+
23
+ const button = screen.getByRole('button')
24
+ expect(button).toHaveClass('btn--primary')
25
+ })
26
+
27
+ it('applies correct variant class', () => {
28
+ render(Button, {
29
+ props: {
30
+ variant: 'secondary'
31
+ },
32
+ slots: { default: 'Button' }
33
+ })
34
+
35
+ const button = screen.getByRole('button')
36
+ expect(button).toHaveClass('btn--secondary')
37
+ expect(button).not.toHaveClass('btn--primary')
38
+ })
39
+
40
+ it('applies danger variant class', () => {
41
+ render(Button, {
42
+ props: {
43
+ variant: 'danger'
44
+ },
45
+ slots: { default: 'Button' }
46
+ })
47
+
48
+ const button = screen.getByRole('button')
49
+ expect(button).toHaveClass('btn--danger')
50
+ })
51
+
52
+ it('applies disabled class when disabled prop is true', () => {
53
+ render(Button, {
54
+ props: {
55
+ disabled: true
56
+ },
57
+ slots: { default: 'Button' }
58
+ })
59
+
60
+ const button = screen.getByRole('button')
61
+ expect(button).toHaveClass('btn--disabled')
62
+ })
63
+
64
+ it('sets disabled attribute when disabled prop is true', () => {
65
+ render(Button, {
66
+ props: {
67
+ disabled: true
68
+ },
69
+ slots: { default: 'Button' }
70
+ })
71
+
72
+ const button = screen.getByRole('button')
73
+ expect(button).toBeDisabled()
74
+ })
75
+ })
76
+
77
+ describe('interactions', () => {
78
+ it('emits click event when clicked', async () => {
79
+ const user = userEvent.setup()
80
+ const clickHandler = vi.fn()
81
+
82
+ render(Button, {
83
+ props: {
84
+ onClick: clickHandler
85
+ },
86
+ slots: { default: 'Click me' }
87
+ })
88
+
89
+ const button = screen.getByRole('button')
90
+ await user.click(button)
91
+
92
+ expect(clickHandler).toHaveBeenCalledTimes(1)
93
+ expect(clickHandler).toHaveBeenCalledWith(expect.any(MouseEvent))
94
+ })
95
+
96
+ it('does not call click handler when disabled', async () => {
97
+ const user = userEvent.setup()
98
+ const clickHandler = vi.fn()
99
+
100
+ render(Button, {
101
+ props: {
102
+ disabled: true,
103
+ onClick: clickHandler
104
+ },
105
+ slots: { default: 'Button' }
106
+ })
107
+
108
+ const button = screen.getByRole('button')
109
+ await user.click(button)
110
+
111
+ expect(clickHandler).not.toHaveBeenCalled()
112
+ })
113
+ })
114
+
115
+ describe('props validation', () => {
116
+ it('accepts all valid variant values', () => {
117
+ const variants = ['primary', 'secondary', 'danger'] as const
118
+
119
+ variants.forEach(variant => {
120
+ const { container } = render(Button, {
121
+ props: {
122
+ variant
123
+ },
124
+ slots: { default: 'Button' }
125
+ })
126
+
127
+ const button = container.querySelector('button')
128
+ expect(button).toHaveClass(`btn--${variant}`)
129
+ })
130
+ })
131
+
132
+ it('handles boolean disabled prop', () => {
133
+ render(Button, {
134
+ props: {
135
+ disabled: false
136
+ },
137
+ slots: { default: 'Button' }
138
+ })
139
+
140
+ const button = screen.getByRole('button')
141
+ expect(button).not.toHaveClass('btn--disabled')
142
+ expect(button).not.toBeDisabled()
143
+ })
144
+ })
145
+ })
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <component
3
+ :is="url ? 'a' : 'button'"
4
+ :href="url"
5
+ :target="url ? target : undefined"
6
+ :rel="url && target === '_blank' ? 'noopener noreferrer' : undefined"
7
+ :class="['btn', `btn--${variant}`, { 'btn--disabled': disabled }]"
8
+ :disabled="!url && disabled"
9
+ :aria-disabled="disabled"
10
+ v-on="!url ? { click: handleClick } : {}"
11
+ >
12
+ <slot />
13
+ <span v-if="url && target === '_blank'" class="sr-only">(opens in new window)</span>
14
+ </component>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ export interface ButtonProps {
19
+ variant?: 'primary' | 'secondary' | 'danger'
20
+ disabled?: boolean
21
+ url?: string
22
+ target?: '_self' | '_blank'
23
+ }
24
+
25
+ const props = withDefaults(defineProps<ButtonProps>(), {
26
+ variant: 'primary',
27
+ disabled: false,
28
+ target: '_self'
29
+ })
30
+
31
+ const emit = defineEmits<{
32
+ click: [event: MouseEvent]
33
+ }>()
34
+
35
+ const handleClick = (event: MouseEvent) => {
36
+ if (props.disabled) {
37
+ event.preventDefault()
38
+ event.stopImmediatePropagation()
39
+ } else {
40
+ emit('click', event)
41
+ }
42
+ }
43
+ </script>
44
+
45
+ <style scoped>
46
+ .btn {
47
+ padding: 0.5rem 1rem;
48
+ border: none;
49
+ border-radius: 0.25rem;
50
+ font-size: 1rem;
51
+ font-weight: 500;
52
+ cursor: pointer;
53
+ transition: all 0.2s ease-in-out;
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ text-decoration: none;
58
+ }
59
+
60
+ .btn:hover:not(.btn--disabled) {
61
+ transform: translateY(-1px);
62
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
63
+ }
64
+
65
+ .btn--primary {
66
+ background-color: #3b82f6;
67
+ color: white;
68
+ }
69
+
70
+ .btn--primary:hover:not(.btn--disabled) {
71
+ background-color: #2563eb;
72
+ }
73
+
74
+ .btn--secondary {
75
+ background-color: #6b7280;
76
+ color: white;
77
+ }
78
+
79
+ .btn--secondary:hover:not(.btn--disabled) {
80
+ background-color: #4b5563;
81
+ }
82
+
83
+ .btn--danger {
84
+ background-color: #ef4444;
85
+ color: white;
86
+ }
87
+
88
+ .btn--danger:hover:not(.btn--disabled) {
89
+ background-color: #dc2626;
90
+ }
91
+
92
+ .btn--disabled {
93
+ opacity: 0.5;
94
+ cursor: not-allowed;
95
+ pointer-events: none;
96
+ }
97
+
98
+ .sr-only {
99
+ position: absolute;
100
+ width: 1px;
101
+ height: 1px;
102
+ padding: 0;
103
+ overflow: hidden;
104
+ clip: rect(0, 0, 0, 0);
105
+ white-space: nowrap;
106
+ border: 0;
107
+ }
108
+ </style>
@@ -0,0 +1,4 @@
1
+ export interface ButtonProps {
2
+ variant?: 'primary' | 'secondary' | 'danger'
3
+ disabled?: boolean
4
+ }