@constela/ui 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/components/alert/alert.constela.json +22 -0
- package/components/alert/alert.styles.json +14 -0
- package/components/alert/alert.test.ts +173 -0
- package/components/avatar/avatar.constela.json +32 -0
- package/components/avatar/avatar.styles.json +15 -0
- package/components/avatar/avatar.test.ts +197 -0
- package/components/badge/badge.constela.json +19 -0
- package/components/badge/badge.styles.json +16 -0
- package/components/badge/badge.test.ts +135 -0
- package/components/breadcrumb/breadcrumb.constela.json +17 -0
- package/components/breadcrumb/breadcrumb.styles.json +5 -0
- package/components/breadcrumb/breadcrumb.test.ts +149 -0
- package/components/button/README.md +164 -0
- package/components/button/button.constela.json +27 -0
- package/components/button/button.styles.json +25 -0
- package/components/button/button.test.ts +233 -0
- package/components/card/card.constela.json +21 -0
- package/components/card/card.styles.json +14 -0
- package/components/card/card.test.ts +154 -0
- package/components/checkbox/checkbox.constela.json +33 -0
- package/components/checkbox/checkbox.styles.json +15 -0
- package/components/checkbox/checkbox.test.ts +275 -0
- package/components/container/container.constela.json +21 -0
- package/components/container/container.styles.json +18 -0
- package/components/container/container.test.ts +164 -0
- package/components/dialog/dialog.constela.json +19 -0
- package/components/dialog/dialog.styles.json +5 -0
- package/components/dialog/dialog.test.ts +139 -0
- package/components/grid/grid.constela.json +23 -0
- package/components/grid/grid.styles.json +25 -0
- package/components/grid/grid.test.ts +193 -0
- package/components/input/input.constela.json +34 -0
- package/components/input/input.styles.json +19 -0
- package/components/input/input.test.ts +301 -0
- package/components/pagination/pagination.constela.json +22 -0
- package/components/pagination/pagination.styles.json +15 -0
- package/components/pagination/pagination.test.ts +170 -0
- package/components/popover/popover.constela.json +20 -0
- package/components/popover/popover.styles.json +16 -0
- package/components/popover/popover.test.ts +165 -0
- package/components/radio/radio.constela.json +31 -0
- package/components/radio/radio.styles.json +15 -0
- package/components/radio/radio.test.ts +253 -0
- package/components/select/select.constela.json +32 -0
- package/components/select/select.styles.json +15 -0
- package/components/select/select.test.ts +257 -0
- package/components/skeleton/skeleton.constela.json +24 -0
- package/components/skeleton/skeleton.styles.json +5 -0
- package/components/skeleton/skeleton.test.ts +164 -0
- package/components/stack/stack.constela.json +27 -0
- package/components/stack/stack.styles.json +33 -0
- package/components/stack/stack.test.ts +261 -0
- package/components/switch/switch.constela.json +29 -0
- package/components/switch/switch.styles.json +15 -0
- package/components/switch/switch.test.ts +224 -0
- package/components/tabs/tabs.constela.json +21 -0
- package/components/tabs/tabs.styles.json +14 -0
- package/components/tabs/tabs.test.ts +163 -0
- package/components/textarea/textarea.constela.json +34 -0
- package/components/textarea/textarea.styles.json +15 -0
- package/components/textarea/textarea.test.ts +290 -0
- package/components/toast/toast.constela.json +23 -0
- package/components/toast/toast.styles.json +17 -0
- package/components/toast/toast.test.ts +183 -0
- package/components/tooltip/tooltip.constela.json +21 -0
- package/components/tooltip/tooltip.styles.json +16 -0
- package/components/tooltip/tooltip.test.ts +164 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +83 -0
- package/package.json +39 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test suite for Toast component
|
|
3
|
+
*
|
|
4
|
+
* @constela/ui Toast component tests following TDD methodology.
|
|
5
|
+
* These tests verify the Toast component structure, params, styles, and accessibility.
|
|
6
|
+
*
|
|
7
|
+
* Coverage:
|
|
8
|
+
* - Component structure validation
|
|
9
|
+
* - Params definition validation
|
|
10
|
+
* - Style preset validation
|
|
11
|
+
* - Accessibility attributes
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
15
|
+
import {
|
|
16
|
+
loadComponentForTesting,
|
|
17
|
+
assertValidComponent,
|
|
18
|
+
assertValidStylePreset,
|
|
19
|
+
hasParams,
|
|
20
|
+
isOptionalParam,
|
|
21
|
+
hasParamType,
|
|
22
|
+
getRootTag,
|
|
23
|
+
hasVariants,
|
|
24
|
+
hasVariantOptions,
|
|
25
|
+
hasDefaultVariants,
|
|
26
|
+
hasSlot,
|
|
27
|
+
findPropInView,
|
|
28
|
+
hasRole,
|
|
29
|
+
type ComponentTestContext,
|
|
30
|
+
} from '../../tests/helpers/test-utils.js';
|
|
31
|
+
|
|
32
|
+
describe('Toast Component', () => {
|
|
33
|
+
let ctx: ComponentTestContext;
|
|
34
|
+
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
ctx = loadComponentForTesting('toast');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ==================== Component Structure Tests ====================
|
|
40
|
+
|
|
41
|
+
describe('Component Structure', () => {
|
|
42
|
+
it('should have valid component structure', () => {
|
|
43
|
+
assertValidComponent(ctx.component);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should have div as root element', () => {
|
|
47
|
+
const rootTag = getRootTag(ctx.component);
|
|
48
|
+
expect(rootTag).toBe('div');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should have role="alert" attribute', () => {
|
|
52
|
+
expect(hasRole(ctx.component.view, 'alert')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should contain a slot for toast content', () => {
|
|
56
|
+
expect(hasSlot(ctx.component.view)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should have className using StyleExpr with preset toastStyles', () => {
|
|
60
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
61
|
+
expect(className).not.toBeNull();
|
|
62
|
+
// StyleExpr should have expr: 'style' and preset reference
|
|
63
|
+
expect(className).toMatchObject({
|
|
64
|
+
expr: 'style',
|
|
65
|
+
preset: 'toastStyles',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ==================== Params Validation Tests ====================
|
|
71
|
+
|
|
72
|
+
describe('Params Validation', () => {
|
|
73
|
+
const expectedParams = ['variant', 'title', 'description'];
|
|
74
|
+
|
|
75
|
+
it('should have all expected params', () => {
|
|
76
|
+
expect(hasParams(ctx.component, expectedParams)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('param: variant', () => {
|
|
80
|
+
it('should be optional', () => {
|
|
81
|
+
expect(isOptionalParam(ctx.component, 'variant')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should have type string', () => {
|
|
85
|
+
expect(hasParamType(ctx.component, 'variant', 'string')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('param: title', () => {
|
|
90
|
+
it('should be optional', () => {
|
|
91
|
+
expect(isOptionalParam(ctx.component, 'title')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should have type string', () => {
|
|
95
|
+
expect(hasParamType(ctx.component, 'title', 'string')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('param: description', () => {
|
|
100
|
+
it('should be optional', () => {
|
|
101
|
+
expect(isOptionalParam(ctx.component, 'description')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should have type string', () => {
|
|
105
|
+
expect(hasParamType(ctx.component, 'description', 'string')).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ==================== Style Preset Tests ====================
|
|
111
|
+
|
|
112
|
+
describe('Style Preset', () => {
|
|
113
|
+
it('should have valid style preset structure', () => {
|
|
114
|
+
const toastStyles = ctx.styles['toastStyles'];
|
|
115
|
+
expect(toastStyles).toBeDefined();
|
|
116
|
+
assertValidStylePreset(toastStyles);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should have base classes for common toast styles', () => {
|
|
120
|
+
const toastStyles = ctx.styles['toastStyles'];
|
|
121
|
+
expect(toastStyles.base).toBeDefined();
|
|
122
|
+
expect(typeof toastStyles.base).toBe('string');
|
|
123
|
+
expect(toastStyles.base.length).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('variant options', () => {
|
|
127
|
+
const variantOptions = ['default', 'success', 'error', 'warning', 'info'];
|
|
128
|
+
|
|
129
|
+
it('should have variant variants', () => {
|
|
130
|
+
const toastStyles = ctx.styles['toastStyles'];
|
|
131
|
+
expect(hasVariants(toastStyles, ['variant'])).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it.each(variantOptions)('should have %s variant option', (option) => {
|
|
135
|
+
const toastStyles = ctx.styles['toastStyles'];
|
|
136
|
+
expect(hasVariantOptions(toastStyles, 'variant', [option])).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('default variants', () => {
|
|
141
|
+
it('should have default variant set to default', () => {
|
|
142
|
+
const toastStyles = ctx.styles['toastStyles'];
|
|
143
|
+
expect(hasDefaultVariants(toastStyles, { variant: 'default' })).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ==================== Accessibility Tests ====================
|
|
149
|
+
|
|
150
|
+
describe('Accessibility', () => {
|
|
151
|
+
it('should have role="alert" for screen readers', () => {
|
|
152
|
+
const role = findPropInView(ctx.component.view, 'role');
|
|
153
|
+
expect(role).not.toBeNull();
|
|
154
|
+
expect(role).toMatchObject({
|
|
155
|
+
expr: 'lit',
|
|
156
|
+
value: 'alert',
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should have aria-live="polite" for non-intrusive notifications', () => {
|
|
161
|
+
const ariaLive = findPropInView(ctx.component.view, 'aria-live');
|
|
162
|
+
expect(ariaLive).not.toBeNull();
|
|
163
|
+
expect(ariaLive).toMatchObject({
|
|
164
|
+
expr: 'lit',
|
|
165
|
+
value: 'polite',
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ==================== View Props Tests ====================
|
|
171
|
+
|
|
172
|
+
describe('View Props', () => {
|
|
173
|
+
it('should pass variant to StyleExpr', () => {
|
|
174
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
175
|
+
expect(className).toMatchObject({
|
|
176
|
+
expr: 'style',
|
|
177
|
+
props: expect.objectContaining({
|
|
178
|
+
variant: expect.objectContaining({ expr: 'param', name: 'variant' }),
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"params": {
|
|
3
|
+
"content": { "type": "string", "required": false },
|
|
4
|
+
"position": { "type": "string", "required": false }
|
|
5
|
+
},
|
|
6
|
+
"view": {
|
|
7
|
+
"kind": "element",
|
|
8
|
+
"tag": "div",
|
|
9
|
+
"props": {
|
|
10
|
+
"role": { "expr": "lit", "value": "tooltip" },
|
|
11
|
+
"className": {
|
|
12
|
+
"expr": "style",
|
|
13
|
+
"preset": "tooltipStyles",
|
|
14
|
+
"props": {
|
|
15
|
+
"position": { "expr": "param", "name": "position" }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"children": [{ "kind": "slot" }]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tooltipStyles": {
|
|
3
|
+
"base": "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
4
|
+
"variants": {
|
|
5
|
+
"position": {
|
|
6
|
+
"top": "bottom-full mb-2",
|
|
7
|
+
"right": "left-full ml-2",
|
|
8
|
+
"bottom": "top-full mt-2",
|
|
9
|
+
"left": "right-full mr-2"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"defaultVariants": {
|
|
13
|
+
"position": "top"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test suite for Tooltip component
|
|
3
|
+
*
|
|
4
|
+
* @constela/ui Tooltip component tests following TDD methodology.
|
|
5
|
+
* These tests verify the Tooltip component structure, params, styles, and accessibility.
|
|
6
|
+
*
|
|
7
|
+
* Coverage:
|
|
8
|
+
* - Component structure validation
|
|
9
|
+
* - Params definition validation
|
|
10
|
+
* - Style preset validation
|
|
11
|
+
* - Accessibility attributes
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
15
|
+
import {
|
|
16
|
+
loadComponentForTesting,
|
|
17
|
+
assertValidComponent,
|
|
18
|
+
assertValidStylePreset,
|
|
19
|
+
hasParams,
|
|
20
|
+
isOptionalParam,
|
|
21
|
+
hasParamType,
|
|
22
|
+
getRootTag,
|
|
23
|
+
hasVariants,
|
|
24
|
+
hasVariantOptions,
|
|
25
|
+
hasDefaultVariants,
|
|
26
|
+
hasSlot,
|
|
27
|
+
findPropInView,
|
|
28
|
+
hasRole,
|
|
29
|
+
type ComponentTestContext,
|
|
30
|
+
} from '../../tests/helpers/test-utils.js';
|
|
31
|
+
|
|
32
|
+
describe('Tooltip Component', () => {
|
|
33
|
+
let ctx: ComponentTestContext;
|
|
34
|
+
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
ctx = loadComponentForTesting('tooltip');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ==================== Component Structure Tests ====================
|
|
40
|
+
|
|
41
|
+
describe('Component Structure', () => {
|
|
42
|
+
it('should have valid component structure', () => {
|
|
43
|
+
assertValidComponent(ctx.component);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should have div as root element', () => {
|
|
47
|
+
const rootTag = getRootTag(ctx.component);
|
|
48
|
+
expect(rootTag).toBe('div');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should have role="tooltip" attribute', () => {
|
|
52
|
+
expect(hasRole(ctx.component.view, 'tooltip')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should contain a slot for trigger element', () => {
|
|
56
|
+
expect(hasSlot(ctx.component.view)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should have className using StyleExpr with preset tooltipStyles', () => {
|
|
60
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
61
|
+
expect(className).not.toBeNull();
|
|
62
|
+
// StyleExpr should have expr: 'style' and preset reference
|
|
63
|
+
expect(className).toMatchObject({
|
|
64
|
+
expr: 'style',
|
|
65
|
+
preset: 'tooltipStyles',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ==================== Params Validation Tests ====================
|
|
71
|
+
|
|
72
|
+
describe('Params Validation', () => {
|
|
73
|
+
const expectedParams = ['content', 'position'];
|
|
74
|
+
|
|
75
|
+
it('should have all expected params', () => {
|
|
76
|
+
expect(hasParams(ctx.component, expectedParams)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('param: content', () => {
|
|
80
|
+
it('should be optional', () => {
|
|
81
|
+
expect(isOptionalParam(ctx.component, 'content')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should have type string', () => {
|
|
85
|
+
expect(hasParamType(ctx.component, 'content', 'string')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('param: position', () => {
|
|
90
|
+
it('should be optional', () => {
|
|
91
|
+
expect(isOptionalParam(ctx.component, 'position')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should have type string', () => {
|
|
95
|
+
expect(hasParamType(ctx.component, 'position', 'string')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ==================== Style Preset Tests ====================
|
|
101
|
+
|
|
102
|
+
describe('Style Preset', () => {
|
|
103
|
+
it('should have valid style preset structure', () => {
|
|
104
|
+
const tooltipStyles = ctx.styles['tooltipStyles'];
|
|
105
|
+
expect(tooltipStyles).toBeDefined();
|
|
106
|
+
assertValidStylePreset(tooltipStyles);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should have base classes for common tooltip styles', () => {
|
|
110
|
+
const tooltipStyles = ctx.styles['tooltipStyles'];
|
|
111
|
+
expect(tooltipStyles.base).toBeDefined();
|
|
112
|
+
expect(typeof tooltipStyles.base).toBe('string');
|
|
113
|
+
expect(tooltipStyles.base.length).toBeGreaterThan(0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('position variants', () => {
|
|
117
|
+
const positionOptions = ['top', 'right', 'bottom', 'left'];
|
|
118
|
+
|
|
119
|
+
it('should have position variants', () => {
|
|
120
|
+
const tooltipStyles = ctx.styles['tooltipStyles'];
|
|
121
|
+
expect(hasVariants(tooltipStyles, ['position'])).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it.each(positionOptions)('should have %s position option', (option) => {
|
|
125
|
+
const tooltipStyles = ctx.styles['tooltipStyles'];
|
|
126
|
+
expect(hasVariantOptions(tooltipStyles, 'position', [option])).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('default variants', () => {
|
|
131
|
+
it('should have default position set to top', () => {
|
|
132
|
+
const tooltipStyles = ctx.styles['tooltipStyles'];
|
|
133
|
+
expect(hasDefaultVariants(tooltipStyles, { position: 'top' })).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ==================== Accessibility Tests ====================
|
|
139
|
+
|
|
140
|
+
describe('Accessibility', () => {
|
|
141
|
+
it('should have role="tooltip" for screen readers', () => {
|
|
142
|
+
const role = findPropInView(ctx.component.view, 'role');
|
|
143
|
+
expect(role).not.toBeNull();
|
|
144
|
+
expect(role).toMatchObject({
|
|
145
|
+
expr: 'lit',
|
|
146
|
+
value: 'tooltip',
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ==================== View Props Tests ====================
|
|
152
|
+
|
|
153
|
+
describe('View Props', () => {
|
|
154
|
+
it('should pass position to StyleExpr', () => {
|
|
155
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
156
|
+
expect(className).toMatchObject({
|
|
157
|
+
expr: 'style',
|
|
158
|
+
props: expect.objectContaining({
|
|
159
|
+
position: expect.objectContaining({ expr: 'param', name: 'position' }),
|
|
160
|
+
}),
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export { ComponentDef, Expression, ParamDef, StylePreset, ViewNode, validateAst } from '@constela/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @constela/ui - Copy-paste UI components for Constela
|
|
5
|
+
*
|
|
6
|
+
* A shadcn/ui style component library providing pre-built,
|
|
7
|
+
* accessible UI components written in Constela JSON DSL.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Component validation result
|
|
12
|
+
*/
|
|
13
|
+
interface ComponentValidationResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Style preset validation result
|
|
19
|
+
*/
|
|
20
|
+
interface StyleValidationResult {
|
|
21
|
+
valid: boolean;
|
|
22
|
+
errors: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validate a Constela component definition
|
|
26
|
+
*/
|
|
27
|
+
declare function validateComponent(component: unknown): ComponentValidationResult;
|
|
28
|
+
/**
|
|
29
|
+
* Validate a style preset definition
|
|
30
|
+
*/
|
|
31
|
+
declare function validateStylePreset(preset: unknown): StyleValidationResult;
|
|
32
|
+
/**
|
|
33
|
+
* Button component variant types
|
|
34
|
+
*/
|
|
35
|
+
type ButtonVariant = 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
36
|
+
type ButtonSize = 'default' | 'sm' | 'lg' | 'icon';
|
|
37
|
+
/**
|
|
38
|
+
* Input component type
|
|
39
|
+
*/
|
|
40
|
+
type InputType = 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search';
|
|
41
|
+
/**
|
|
42
|
+
* Alert component variant types
|
|
43
|
+
*/
|
|
44
|
+
type AlertVariant = 'default' | 'destructive';
|
|
45
|
+
/**
|
|
46
|
+
* Badge component variant types
|
|
47
|
+
*/
|
|
48
|
+
type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline';
|
|
49
|
+
/**
|
|
50
|
+
* Toast component variant types
|
|
51
|
+
*/
|
|
52
|
+
type ToastVariant = 'default' | 'success' | 'error' | 'warning' | 'info';
|
|
53
|
+
|
|
54
|
+
export { type AlertVariant, type BadgeVariant, type ButtonSize, type ButtonVariant, type ComponentValidationResult, type InputType, type StyleValidationResult, type ToastVariant, validateComponent, validateStylePreset };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { validateAst } from "@constela/core";
|
|
3
|
+
function validateComponent(component) {
|
|
4
|
+
const errors = [];
|
|
5
|
+
if (!component || typeof component !== "object") {
|
|
6
|
+
return { valid: false, errors: ["Component must be an object"] };
|
|
7
|
+
}
|
|
8
|
+
const comp = component;
|
|
9
|
+
if (!comp["view"]) {
|
|
10
|
+
errors.push("Component must have a view property");
|
|
11
|
+
}
|
|
12
|
+
if (comp["params"] !== void 0) {
|
|
13
|
+
if (typeof comp["params"] !== "object" || comp["params"] === null) {
|
|
14
|
+
errors.push("params must be an object");
|
|
15
|
+
} else {
|
|
16
|
+
const params = comp["params"];
|
|
17
|
+
for (const [name, def] of Object.entries(params)) {
|
|
18
|
+
if (!def || typeof def !== "object") {
|
|
19
|
+
errors.push(`Param "${name}" must be an object`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const paramDef = def;
|
|
23
|
+
if (!paramDef["type"] || typeof paramDef["type"] !== "string") {
|
|
24
|
+
errors.push(`Param "${name}" must have a string type property`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
valid: errors.length === 0,
|
|
31
|
+
errors
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function validateStylePreset(preset) {
|
|
35
|
+
const errors = [];
|
|
36
|
+
if (!preset || typeof preset !== "object") {
|
|
37
|
+
return { valid: false, errors: ["Style preset must be an object"] };
|
|
38
|
+
}
|
|
39
|
+
const style = preset;
|
|
40
|
+
if (typeof style["base"] !== "string") {
|
|
41
|
+
errors.push("Style preset must have a string base property");
|
|
42
|
+
}
|
|
43
|
+
if (style["variants"] !== void 0) {
|
|
44
|
+
if (typeof style["variants"] !== "object" || style["variants"] === null) {
|
|
45
|
+
errors.push("variants must be an object");
|
|
46
|
+
} else {
|
|
47
|
+
const variants = style["variants"];
|
|
48
|
+
for (const [variantName, variantOptions] of Object.entries(variants)) {
|
|
49
|
+
if (typeof variantOptions !== "object" || variantOptions === null) {
|
|
50
|
+
errors.push(`Variant "${variantName}" must be an object`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const options = variantOptions;
|
|
54
|
+
for (const [optionName, optionValue] of Object.entries(options)) {
|
|
55
|
+
if (typeof optionValue !== "string") {
|
|
56
|
+
errors.push(`Variant "${variantName}.${optionName}" must be a string`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (style["defaultVariants"] !== void 0) {
|
|
63
|
+
if (typeof style["defaultVariants"] !== "object" || style["defaultVariants"] === null) {
|
|
64
|
+
errors.push("defaultVariants must be an object");
|
|
65
|
+
} else {
|
|
66
|
+
const defaults = style["defaultVariants"];
|
|
67
|
+
for (const [name, value] of Object.entries(defaults)) {
|
|
68
|
+
if (typeof value !== "string") {
|
|
69
|
+
errors.push(`defaultVariants.${name} must be a string`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
valid: errors.length === 0,
|
|
76
|
+
errors
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
validateAst,
|
|
81
|
+
validateComponent,
|
|
82
|
+
validateStylePreset
|
|
83
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@constela/ui",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Copy-paste UI components for Constela - shadcn/ui style component library",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"components"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@constela/core": "0.15.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.10.0",
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.3.0",
|
|
25
|
+
"vitest": "^2.0.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
34
|
+
"type-check": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"clean": "rm -rf dist"
|
|
38
|
+
}
|
|
39
|
+
}
|