@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,121 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import Heading from './Heading.vue'
3
+
4
+ const meta = {
5
+ title: 'Components/Heading',
6
+ component: Heading,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ tag: {
10
+ control: 'select',
11
+ options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
12
+ },
13
+ size: {
14
+ control: 'select',
15
+ options: ['1', '2', '3', '4', '5', '6']
16
+ },
17
+ weight: {
18
+ control: 'select',
19
+ options: ['light', 'normal', 'medium', 'semibold', 'bold']
20
+ },
21
+ align: {
22
+ control: 'select',
23
+ options: ['left', 'center', 'right']
24
+ }
25
+ }
26
+ } satisfies Meta<typeof Heading>
27
+
28
+ export default meta
29
+ type Story = StoryObj<typeof meta>
30
+
31
+ export const Default: Story = {
32
+ args: {
33
+ tag: 'h2',
34
+ size: '2',
35
+ weight: 'bold',
36
+ align: 'left'
37
+ },
38
+ render: (args: any) => ({
39
+ components: { Heading },
40
+ setup() {
41
+ return { args }
42
+ },
43
+ template: '<Heading v-bind="args">Welcome to Vue Design System</Heading>'
44
+ })
45
+ }
46
+
47
+ export const AllSizes: Story = {
48
+ render: () => ({
49
+ components: { Heading },
50
+ template: `
51
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
52
+ <Heading tag="h1" size="1">Heading Size 1</Heading>
53
+ <Heading tag="h2" size="2">Heading Size 2</Heading>
54
+ <Heading tag="h3" size="3">Heading Size 3</Heading>
55
+ <Heading tag="h4" size="4">Heading Size 4</Heading>
56
+ <Heading tag="h5" size="5">Heading Size 5</Heading>
57
+ <Heading tag="h6" size="6">Heading Size 6</Heading>
58
+ </div>
59
+ `
60
+ })
61
+ }
62
+
63
+ export const AllWeights: Story = {
64
+ render: () => ({
65
+ components: { Heading },
66
+ template: `
67
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
68
+ <Heading weight="light">Light Weight</Heading>
69
+ <Heading weight="normal">Normal Weight</Heading>
70
+ <Heading weight="medium">Medium Weight</Heading>
71
+ <Heading weight="semibold">Semibold Weight</Heading>
72
+ <Heading weight="bold">Bold Weight</Heading>
73
+ </div>
74
+ `
75
+ })
76
+ }
77
+
78
+ export const Alignment: Story = {
79
+ render: () => ({
80
+ components: { Heading },
81
+ template: `
82
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
83
+ <Heading align="left">Left Aligned Heading</Heading>
84
+ <Heading align="center">Center Aligned Heading</Heading>
85
+ <Heading align="right">Right Aligned Heading</Heading>
86
+ </div>
87
+ `
88
+ })
89
+ }
90
+
91
+ export const SemanticVsVisual: Story = {
92
+ render: () => ({
93
+ components: { Heading },
94
+ template: `
95
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
96
+ <Heading tag="h1" size="3">H1 tag with size 3 appearance</Heading>
97
+ <Heading tag="h3" size="1">H3 tag with size 1 appearance</Heading>
98
+ <Heading tag="h6" size="2">H6 tag with size 2 appearance</Heading>
99
+ </div>
100
+ `
101
+ })
102
+ }
103
+
104
+ export const CustomStyling: Story = {
105
+ render: () => ({
106
+ components: { Heading },
107
+ template: `
108
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
109
+ <Heading tag="h1" size="1" weight="light" align="center">
110
+ Light Centered Hero
111
+ </Heading>
112
+ <Heading tag="h2" size="2" weight="semibold">
113
+ Section Title with Semibold
114
+ </Heading>
115
+ <Heading tag="h3" size="4" weight="normal" align="right">
116
+ Small Right Aligned Subtitle
117
+ </Heading>
118
+ </div>
119
+ `
120
+ })
121
+ }
@@ -0,0 +1,184 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/vue'
3
+ import Heading from './Heading.vue'
4
+
5
+ describe('Heading', () => {
6
+ describe('Rendering', () => {
7
+ it('renders with default props', () => {
8
+ render(Heading, {
9
+ slots: {
10
+ default: 'Test Heading'
11
+ }
12
+ })
13
+
14
+ const heading = screen.getByText('Test Heading')
15
+ expect(heading).toBeInTheDocument()
16
+ expect(heading.tagName).toBe('H2')
17
+ })
18
+
19
+ it('renders the correct heading tag', () => {
20
+ render(Heading, {
21
+ props: { tag: 'h1' },
22
+ slots: { default: 'H1 Heading' }
23
+ })
24
+
25
+ const heading = screen.getByText('H1 Heading')
26
+ expect(heading.tagName).toBe('H1')
27
+ })
28
+
29
+ it('renders all heading levels correctly', () => {
30
+ const levels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const
31
+
32
+ levels.forEach(level => {
33
+ const { unmount } = render(Heading, {
34
+ props: { tag: level },
35
+ slots: { default: `${level.toUpperCase()} content` }
36
+ })
37
+
38
+ const heading = screen.getByText(`${level.toUpperCase()} content`)
39
+ expect(heading.tagName).toBe(level.toUpperCase())
40
+ unmount()
41
+ })
42
+ })
43
+ })
44
+
45
+ describe('Size Prop', () => {
46
+ it('applies the correct size class', () => {
47
+ render(Heading, {
48
+ props: { size: '3' },
49
+ slots: { default: 'Sized Heading' }
50
+ })
51
+
52
+ const heading = screen.getByText('Sized Heading')
53
+ expect(heading).toHaveClass('heading--3')
54
+ })
55
+
56
+ it('applies different size classes', () => {
57
+ const sizes = ['1', '2', '3', '4', '5', '6'] as const
58
+
59
+ sizes.forEach(size => {
60
+ const { unmount } = render(Heading, {
61
+ props: { size },
62
+ slots: { default: `Size ${size}` }
63
+ })
64
+
65
+ const heading = screen.getByText(`Size ${size}`)
66
+ expect(heading).toHaveClass(`heading--${size}`)
67
+ unmount()
68
+ })
69
+ })
70
+
71
+ it('allows different tag and size combinations', () => {
72
+ render(Heading, {
73
+ props: { tag: 'h1', size: '4' },
74
+ slots: { default: 'H1 with size 4' }
75
+ })
76
+
77
+ const heading = screen.getByText('H1 with size 4')
78
+ expect(heading.tagName).toBe('H1')
79
+ expect(heading).toHaveClass('heading--4')
80
+ })
81
+ })
82
+
83
+ describe('Weight Prop', () => {
84
+ it('applies default weight class', () => {
85
+ render(Heading, {
86
+ slots: { default: 'Default Weight' }
87
+ })
88
+
89
+ const heading = screen.getByText('Default Weight')
90
+ expect(heading).toHaveClass('heading--bold')
91
+ })
92
+
93
+ it('applies custom weight classes', () => {
94
+ const weights = ['light', 'normal', 'medium', 'semibold', 'bold'] as const
95
+
96
+ weights.forEach(weight => {
97
+ const { unmount } = render(Heading, {
98
+ props: { weight },
99
+ slots: { default: `${weight} weight` }
100
+ })
101
+
102
+ const heading = screen.getByText(`${weight} weight`)
103
+ expect(heading).toHaveClass(`heading--${weight}`)
104
+ unmount()
105
+ })
106
+ })
107
+ })
108
+
109
+ describe('Align Prop', () => {
110
+ it('applies default alignment class', () => {
111
+ render(Heading, {
112
+ slots: { default: 'Default Align' }
113
+ })
114
+
115
+ const heading = screen.getByText('Default Align')
116
+ expect(heading).toHaveClass('heading--left')
117
+ })
118
+
119
+ it('applies custom alignment classes', () => {
120
+ const alignments = ['left', 'center', 'right'] as const
121
+
122
+ alignments.forEach(align => {
123
+ const { unmount } = render(Heading, {
124
+ props: { align },
125
+ slots: { default: `${align} aligned` }
126
+ })
127
+
128
+ const heading = screen.getByText(`${align} aligned`)
129
+ expect(heading).toHaveClass(`heading--${align}`)
130
+ unmount()
131
+ })
132
+ })
133
+ })
134
+
135
+ describe('CSS Classes', () => {
136
+ it('applies base heading class', () => {
137
+ render(Heading, {
138
+ slots: { default: 'Base Class' }
139
+ })
140
+
141
+ const heading = screen.getByText('Base Class')
142
+ expect(heading).toHaveClass('heading')
143
+ })
144
+
145
+ it('combines multiple modifier classes', () => {
146
+ render(Heading, {
147
+ props: {
148
+ tag: 'h3',
149
+ size: '2',
150
+ weight: 'semibold',
151
+ align: 'center'
152
+ },
153
+ slots: { default: 'Multiple Modifiers' }
154
+ })
155
+
156
+ const heading = screen.getByText('Multiple Modifiers')
157
+ expect(heading).toHaveClass('heading')
158
+ expect(heading).toHaveClass('heading--2')
159
+ expect(heading).toHaveClass('heading--semibold')
160
+ expect(heading).toHaveClass('heading--center')
161
+ })
162
+ })
163
+
164
+ describe('Slot Content', () => {
165
+ it('renders simple text content', () => {
166
+ render(Heading, {
167
+ slots: { default: 'Simple Text' }
168
+ })
169
+
170
+ expect(screen.getByText('Simple Text')).toBeInTheDocument()
171
+ })
172
+
173
+ it('renders complex HTML content', () => {
174
+ render(Heading, {
175
+ slots: {
176
+ default: '<span>Complex</span> <strong>HTML</strong>'
177
+ }
178
+ })
179
+
180
+ expect(screen.getByText(/Complex/)).toBeInTheDocument()
181
+ expect(screen.getByText(/HTML/)).toBeInTheDocument()
182
+ })
183
+ })
184
+ })
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ :class="['heading', `heading--${size}`, `heading--${weight}`, { [`heading--${align}`]: align }]"
5
+ >
6
+ <slot />
7
+ </component>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+
12
+ export interface HeadingProps {
13
+ tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
14
+ size?: '1' | '2' | '3' | '4' | '5' | '6'
15
+ weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold'
16
+ align?: 'left' | 'center' | 'right'
17
+ }
18
+
19
+ withDefaults(defineProps<HeadingProps>(), {
20
+ tag: 'h2',
21
+ size: '2',
22
+ weight: 'bold',
23
+ align: 'left'
24
+ })
25
+ </script>
26
+
27
+ <style scoped>
28
+ .heading {
29
+ margin: 0;
30
+ line-height: 1.2;
31
+ color: #111827;
32
+ }
33
+
34
+ /* Size variations */
35
+ .heading--1 {
36
+ font-size: 3rem; /* 48px */
37
+ letter-spacing: -0.02em;
38
+ }
39
+
40
+ .heading--2 {
41
+ font-size: 2.25rem; /* 36px */
42
+ letter-spacing: -0.01em;
43
+ }
44
+
45
+ .heading--3 {
46
+ font-size: 1.875rem; /* 30px */
47
+ letter-spacing: -0.01em;
48
+ }
49
+
50
+ .heading--4 {
51
+ font-size: 1.5rem; /* 24px */
52
+ }
53
+
54
+ .heading--5 {
55
+ font-size: 1.25rem; /* 20px */
56
+ }
57
+
58
+ .heading--6 {
59
+ font-size: 1rem; /* 16px */
60
+ }
61
+
62
+ /* Weight variations */
63
+ .heading--light {
64
+ font-weight: 300;
65
+ }
66
+
67
+ .heading--normal {
68
+ font-weight: 400;
69
+ }
70
+
71
+ .heading--medium {
72
+ font-weight: 500;
73
+ }
74
+
75
+ .heading--semibold {
76
+ font-weight: 600;
77
+ }
78
+
79
+ .heading--bold {
80
+ font-weight: 700;
81
+ }
82
+
83
+ /* Alignment */
84
+ .heading--left {
85
+ text-align: left;
86
+ }
87
+
88
+ .heading--center {
89
+ text-align: center;
90
+ }
91
+
92
+ .heading--right {
93
+ text-align: right;
94
+ }
95
+ </style>
@@ -0,0 +1,6 @@
1
+ export interface HeadingProps {
2
+ tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
3
+ size?: '1' | '2' | '3' | '4' | '5' | '6'
4
+ weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold'
5
+ align?: 'left' | 'center' | 'right'
6
+ }
@@ -0,0 +1,172 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { ref } from 'vue'
3
+ import Input from './Input.vue'
4
+
5
+ const meta = {
6
+ title: 'Form Fields/Input',
7
+ component: Input,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ type: {
11
+ control: 'select',
12
+ options: ['text', 'email', 'password', 'number', 'tel', 'url'],
13
+ description: 'Input type'
14
+ },
15
+ label: {
16
+ control: 'text',
17
+ description: 'Label text'
18
+ },
19
+ placeholder: {
20
+ control: 'text',
21
+ description: 'Placeholder text'
22
+ },
23
+ disabled: {
24
+ control: 'boolean',
25
+ description: 'Disabled state'
26
+ },
27
+ required: {
28
+ control: 'boolean',
29
+ description: 'Required field'
30
+ },
31
+ error: {
32
+ control: 'text',
33
+ description: 'Error message'
34
+ },
35
+ hint: {
36
+ control: 'text',
37
+ description: 'Hint text'
38
+ }
39
+ }
40
+ } satisfies Meta<typeof Input>
41
+
42
+ export default meta
43
+ type Story = StoryObj<typeof meta>
44
+
45
+ export const Default: Story = {
46
+ args: {
47
+ label: 'Email',
48
+ type: 'email',
49
+ placeholder: 'Enter your email'
50
+ },
51
+ render: (args: any) => ({
52
+ components: { Input },
53
+ setup() {
54
+ const value = ref('')
55
+ return { args, value }
56
+ },
57
+ template: '<Input v-bind="args" v-model="value" />'
58
+ })
59
+ }
60
+
61
+ export const WithValue: Story = {
62
+ args: {
63
+ label: 'Name',
64
+ modelValue: 'John Doe'
65
+ },
66
+ render: (args: any) => ({
67
+ components: { Input },
68
+ setup() {
69
+ const value = ref(args.modelValue ?? '')
70
+ return { args, value }
71
+ },
72
+ template: '<Input v-bind="args" v-model="value" />'
73
+ })
74
+ }
75
+
76
+ export const WithHint: Story = {
77
+ args: {
78
+ label: 'Username',
79
+ placeholder: 'Choose a username',
80
+ hint: 'Must be 3-20 characters'
81
+ },
82
+ render: (args: any) => ({
83
+ components: { Input },
84
+ setup() {
85
+ const value = ref('')
86
+ return { args, value }
87
+ },
88
+ template: '<Input v-bind="args" v-model="value" />'
89
+ })
90
+ }
91
+
92
+ export const WithError: Story = {
93
+ args: {
94
+ label: 'Email',
95
+ type: 'email',
96
+ modelValue: 'invalid-email',
97
+ error: 'Please enter a valid email address'
98
+ },
99
+ render: (args: any) => ({
100
+ components: { Input },
101
+ setup() {
102
+ const value = ref(args.modelValue ?? '')
103
+ return { args, value }
104
+ },
105
+ template: '<Input v-bind="args" v-model="value" />'
106
+ })
107
+ }
108
+
109
+ export const Required: Story = {
110
+ args: {
111
+ label: 'Password',
112
+ type: 'password',
113
+ required: true,
114
+ placeholder: 'Enter your password'
115
+ },
116
+ render: (args: any) => ({
117
+ components: { Input },
118
+ setup() {
119
+ const value = ref('')
120
+ return { args, value }
121
+ },
122
+ template: '<Input v-bind="args" v-model="value" />'
123
+ })
124
+ }
125
+
126
+ export const Disabled: Story = {
127
+ args: {
128
+ label: 'Disabled Input',
129
+ modelValue: 'Cannot edit this',
130
+ disabled: true
131
+ },
132
+ render: (args: any) => ({
133
+ components: { Input },
134
+ setup() {
135
+ const value = ref(args.modelValue ?? '')
136
+ return { args, value }
137
+ },
138
+ template: '<Input v-bind="args" v-model="value" />'
139
+ })
140
+ }
141
+
142
+ export const Number: Story = {
143
+ args: {
144
+ label: 'Age',
145
+ type: 'number',
146
+ placeholder: 'Enter your age'
147
+ },
148
+ render: (args: any) => ({
149
+ components: { Input },
150
+ setup() {
151
+ const value = ref('')
152
+ return { args, value }
153
+ },
154
+ template: '<Input v-bind="args" v-model="value" />'
155
+ })
156
+ }
157
+
158
+ export const Password: Story = {
159
+ args: {
160
+ label: 'Password',
161
+ type: 'password',
162
+ placeholder: 'Enter password'
163
+ },
164
+ render: (args: any) => ({
165
+ components: { Input },
166
+ setup() {
167
+ const value = ref('')
168
+ return { args, value }
169
+ },
170
+ template: '<Input v-bind="args" v-model="value" />'
171
+ })
172
+ }