@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,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test suite for Badge component
|
|
3
|
+
*
|
|
4
|
+
* @constela/ui Badge component tests following TDD methodology.
|
|
5
|
+
* These tests verify the Badge component structure, params, and styles.
|
|
6
|
+
*
|
|
7
|
+
* Coverage:
|
|
8
|
+
* - Component structure validation
|
|
9
|
+
* - Params definition validation
|
|
10
|
+
* - Style preset validation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
14
|
+
import {
|
|
15
|
+
loadComponentForTesting,
|
|
16
|
+
assertValidComponent,
|
|
17
|
+
assertValidStylePreset,
|
|
18
|
+
hasParams,
|
|
19
|
+
isOptionalParam,
|
|
20
|
+
hasParamType,
|
|
21
|
+
getRootTag,
|
|
22
|
+
hasVariants,
|
|
23
|
+
hasVariantOptions,
|
|
24
|
+
hasDefaultVariants,
|
|
25
|
+
hasSlot,
|
|
26
|
+
findPropInView,
|
|
27
|
+
type ComponentTestContext,
|
|
28
|
+
} from '../../tests/helpers/test-utils.js';
|
|
29
|
+
|
|
30
|
+
describe('Badge Component', () => {
|
|
31
|
+
let ctx: ComponentTestContext;
|
|
32
|
+
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
ctx = loadComponentForTesting('badge');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ==================== Component Structure Tests ====================
|
|
38
|
+
|
|
39
|
+
describe('Component Structure', () => {
|
|
40
|
+
it('should have valid component structure', () => {
|
|
41
|
+
assertValidComponent(ctx.component);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should have div as root element', () => {
|
|
45
|
+
const rootTag = getRootTag(ctx.component);
|
|
46
|
+
expect(rootTag).toBe('div');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should contain a slot for badge content', () => {
|
|
50
|
+
expect(hasSlot(ctx.component.view)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should have className using StyleExpr with preset badgeStyles', () => {
|
|
54
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
55
|
+
expect(className).not.toBeNull();
|
|
56
|
+
// StyleExpr should have expr: 'style' and preset reference
|
|
57
|
+
expect(className).toMatchObject({
|
|
58
|
+
expr: 'style',
|
|
59
|
+
preset: 'badgeStyles',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ==================== Params Validation Tests ====================
|
|
65
|
+
|
|
66
|
+
describe('Params Validation', () => {
|
|
67
|
+
const expectedParams = ['variant'];
|
|
68
|
+
|
|
69
|
+
it('should have all expected params', () => {
|
|
70
|
+
expect(hasParams(ctx.component, expectedParams)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('param: variant', () => {
|
|
74
|
+
it('should be optional', () => {
|
|
75
|
+
expect(isOptionalParam(ctx.component, 'variant')).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should have type string', () => {
|
|
79
|
+
expect(hasParamType(ctx.component, 'variant', 'string')).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ==================== Style Preset Tests ====================
|
|
85
|
+
|
|
86
|
+
describe('Style Preset', () => {
|
|
87
|
+
it('should have valid style preset structure', () => {
|
|
88
|
+
const badgeStyles = ctx.styles['badgeStyles'];
|
|
89
|
+
expect(badgeStyles).toBeDefined();
|
|
90
|
+
assertValidStylePreset(badgeStyles);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should have base classes for common badge styles', () => {
|
|
94
|
+
const badgeStyles = ctx.styles['badgeStyles'];
|
|
95
|
+
expect(badgeStyles.base).toBeDefined();
|
|
96
|
+
expect(typeof badgeStyles.base).toBe('string');
|
|
97
|
+
expect(badgeStyles.base.length).toBeGreaterThan(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('variant options', () => {
|
|
101
|
+
const variantOptions = ['default', 'secondary', 'destructive', 'outline'];
|
|
102
|
+
|
|
103
|
+
it('should have variant variants', () => {
|
|
104
|
+
const badgeStyles = ctx.styles['badgeStyles'];
|
|
105
|
+
expect(hasVariants(badgeStyles, ['variant'])).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it.each(variantOptions)('should have %s variant option', (option) => {
|
|
109
|
+
const badgeStyles = ctx.styles['badgeStyles'];
|
|
110
|
+
expect(hasVariantOptions(badgeStyles, 'variant', [option])).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('default variants', () => {
|
|
115
|
+
it('should have default variant set to default', () => {
|
|
116
|
+
const badgeStyles = ctx.styles['badgeStyles'];
|
|
117
|
+
expect(hasDefaultVariants(badgeStyles, { variant: 'default' })).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ==================== View Props Tests ====================
|
|
123
|
+
|
|
124
|
+
describe('View Props', () => {
|
|
125
|
+
it('should pass variant to StyleExpr', () => {
|
|
126
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
127
|
+
expect(className).toMatchObject({
|
|
128
|
+
expr: 'style',
|
|
129
|
+
props: expect.objectContaining({
|
|
130
|
+
variant: expect.objectContaining({ expr: 'param', name: 'variant' }),
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"params": {
|
|
3
|
+
"separator": { "type": "string", "required": false, "default": "/" }
|
|
4
|
+
},
|
|
5
|
+
"view": {
|
|
6
|
+
"kind": "element",
|
|
7
|
+
"tag": "nav",
|
|
8
|
+
"props": {
|
|
9
|
+
"aria-label": { "expr": "lit", "value": "Breadcrumb" },
|
|
10
|
+
"className": {
|
|
11
|
+
"expr": "style",
|
|
12
|
+
"preset": "breadcrumbStyles"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"children": [{ "kind": "slot" }]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test suite for Breadcrumb component
|
|
3
|
+
*
|
|
4
|
+
* @constela/ui Breadcrumb component tests following TDD methodology.
|
|
5
|
+
* These tests verify the Breadcrumb 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
|
+
hasSlot,
|
|
24
|
+
findPropInView,
|
|
25
|
+
hasAriaAttribute,
|
|
26
|
+
type ComponentTestContext,
|
|
27
|
+
} from '../../tests/helpers/test-utils.js';
|
|
28
|
+
|
|
29
|
+
describe('Breadcrumb Component', () => {
|
|
30
|
+
let ctx: ComponentTestContext;
|
|
31
|
+
|
|
32
|
+
beforeAll(() => {
|
|
33
|
+
ctx = loadComponentForTesting('breadcrumb');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ==================== Component Structure Tests ====================
|
|
37
|
+
|
|
38
|
+
describe('Component Structure', () => {
|
|
39
|
+
it('should have valid component structure', () => {
|
|
40
|
+
assertValidComponent(ctx.component);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should have nav as root element', () => {
|
|
44
|
+
const rootTag = getRootTag(ctx.component);
|
|
45
|
+
expect(rootTag).toBe('nav');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should have aria-label="Breadcrumb" attribute', () => {
|
|
49
|
+
const ariaLabel = findPropInView(ctx.component.view, 'aria-label');
|
|
50
|
+
expect(ariaLabel).not.toBeNull();
|
|
51
|
+
expect(ariaLabel).toMatchObject({
|
|
52
|
+
expr: 'lit',
|
|
53
|
+
value: 'Breadcrumb',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should contain a slot for breadcrumb items', () => {
|
|
58
|
+
expect(hasSlot(ctx.component.view)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should have className using StyleExpr with preset breadcrumbStyles', () => {
|
|
62
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
63
|
+
expect(className).not.toBeNull();
|
|
64
|
+
expect(className).toMatchObject({
|
|
65
|
+
expr: 'style',
|
|
66
|
+
preset: 'breadcrumbStyles',
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ==================== Params Validation Tests ====================
|
|
72
|
+
|
|
73
|
+
describe('Params Validation', () => {
|
|
74
|
+
const expectedParams = ['separator'];
|
|
75
|
+
|
|
76
|
+
it('should have all expected params', () => {
|
|
77
|
+
expect(hasParams(ctx.component, expectedParams)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('param: separator', () => {
|
|
81
|
+
it('should be optional', () => {
|
|
82
|
+
expect(isOptionalParam(ctx.component, 'separator')).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should have type string', () => {
|
|
86
|
+
expect(hasParamType(ctx.component, 'separator', 'string')).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should have default value of "/"', () => {
|
|
90
|
+
const separatorParam = ctx.component.params?.['separator'];
|
|
91
|
+
expect(separatorParam).toBeDefined();
|
|
92
|
+
expect(separatorParam?.default).toBe('/');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ==================== Style Preset Tests ====================
|
|
98
|
+
|
|
99
|
+
describe('Style Preset', () => {
|
|
100
|
+
it('should have valid style preset structure', () => {
|
|
101
|
+
const breadcrumbStyles = ctx.styles['breadcrumbStyles'];
|
|
102
|
+
expect(breadcrumbStyles).toBeDefined();
|
|
103
|
+
assertValidStylePreset(breadcrumbStyles);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should have base classes for common breadcrumb styles', () => {
|
|
107
|
+
const breadcrumbStyles = ctx.styles['breadcrumbStyles'];
|
|
108
|
+
expect(breadcrumbStyles.base).toBeDefined();
|
|
109
|
+
expect(typeof breadcrumbStyles.base).toBe('string');
|
|
110
|
+
expect(breadcrumbStyles.base.length).toBeGreaterThan(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should include flex layout classes in base', () => {
|
|
114
|
+
const breadcrumbStyles = ctx.styles['breadcrumbStyles'];
|
|
115
|
+
expect(breadcrumbStyles.base).toMatch(/flex/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should include items-center class in base', () => {
|
|
119
|
+
const breadcrumbStyles = ctx.styles['breadcrumbStyles'];
|
|
120
|
+
expect(breadcrumbStyles.base).toMatch(/items-center/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should include gap class in base', () => {
|
|
124
|
+
const breadcrumbStyles = ctx.styles['breadcrumbStyles'];
|
|
125
|
+
expect(breadcrumbStyles.base).toMatch(/gap/);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ==================== Accessibility Tests ====================
|
|
130
|
+
|
|
131
|
+
describe('Accessibility', () => {
|
|
132
|
+
it('should have aria-label attribute for screen readers', () => {
|
|
133
|
+
expect(hasAriaAttribute(ctx.component.view, 'aria-label')).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should have aria-label value of "Breadcrumb"', () => {
|
|
137
|
+
const ariaLabel = findPropInView(ctx.component.view, 'aria-label');
|
|
138
|
+
expect(ariaLabel).toMatchObject({
|
|
139
|
+
expr: 'lit',
|
|
140
|
+
value: 'Breadcrumb',
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should use nav element for semantic navigation', () => {
|
|
145
|
+
const rootTag = getRootTag(ctx.component);
|
|
146
|
+
expect(rootTag).toBe('nav');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
A customizable button component with multiple variants and sizes.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Copy `button.constela.json` and `button.styles.json` to your project's components directory.
|
|
8
|
+
|
|
9
|
+
### Basic Usage
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"kind": "component",
|
|
14
|
+
"name": "Button",
|
|
15
|
+
"children": [
|
|
16
|
+
{ "kind": "text", "value": { "expr": "lit", "value": "Click me" } }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### With Variants
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"kind": "component",
|
|
26
|
+
"name": "Button",
|
|
27
|
+
"props": {
|
|
28
|
+
"variant": { "expr": "lit", "value": "destructive" }
|
|
29
|
+
},
|
|
30
|
+
"children": [
|
|
31
|
+
{ "kind": "text", "value": { "expr": "lit", "value": "Delete" } }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### With Size
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"kind": "component",
|
|
41
|
+
"name": "Button",
|
|
42
|
+
"props": {
|
|
43
|
+
"size": { "expr": "lit", "value": "lg" }
|
|
44
|
+
},
|
|
45
|
+
"children": [
|
|
46
|
+
{ "kind": "text", "value": { "expr": "lit", "value": "Large Button" } }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Disabled State
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"kind": "component",
|
|
56
|
+
"name": "Button",
|
|
57
|
+
"props": {
|
|
58
|
+
"disabled": { "expr": "lit", "value": true }
|
|
59
|
+
},
|
|
60
|
+
"children": [
|
|
61
|
+
{ "kind": "text", "value": { "expr": "lit", "value": "Disabled" } }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With ARIA Label
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"kind": "component",
|
|
71
|
+
"name": "Button",
|
|
72
|
+
"props": {
|
|
73
|
+
"ariaLabel": { "expr": "lit", "value": "Close dialog" },
|
|
74
|
+
"variant": { "expr": "lit", "value": "ghost" },
|
|
75
|
+
"size": { "expr": "lit", "value": "icon" }
|
|
76
|
+
},
|
|
77
|
+
"children": [
|
|
78
|
+
{ "kind": "text", "value": { "expr": "lit", "value": "X" } }
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Props
|
|
84
|
+
|
|
85
|
+
| Prop | Type | Required | Default | Description |
|
|
86
|
+
|------|------|----------|---------|-------------|
|
|
87
|
+
| `variant` | string | No | `"default"` | Button style variant |
|
|
88
|
+
| `size` | string | No | `"default"` | Button size |
|
|
89
|
+
| `disabled` | boolean | No | `false` | Whether the button is disabled |
|
|
90
|
+
| `type` | string | No | - | HTML button type attribute (browser default: `"submit"`) |
|
|
91
|
+
| `ariaLabel` | string | No | - | ARIA label for accessibility |
|
|
92
|
+
|
|
93
|
+
## Variants
|
|
94
|
+
|
|
95
|
+
| Variant | Description |
|
|
96
|
+
|---------|-------------|
|
|
97
|
+
| `default` | Primary button style with solid background |
|
|
98
|
+
| `destructive` | Red/danger button for destructive actions |
|
|
99
|
+
| `outline` | Button with border and transparent background |
|
|
100
|
+
| `secondary` | Secondary button with muted background |
|
|
101
|
+
| `ghost` | Minimal button with no background until hover |
|
|
102
|
+
| `link` | Text-only button styled as a link |
|
|
103
|
+
|
|
104
|
+
## Sizes
|
|
105
|
+
|
|
106
|
+
| Size | Description |
|
|
107
|
+
|------|-------------|
|
|
108
|
+
| `default` | Standard button size (h-10) |
|
|
109
|
+
| `sm` | Small button (h-9) |
|
|
110
|
+
| `lg` | Large button (h-11) |
|
|
111
|
+
| `icon` | Square icon button (h-10 w-10) |
|
|
112
|
+
|
|
113
|
+
## Accessibility
|
|
114
|
+
|
|
115
|
+
- Uses semantic `<button>` element
|
|
116
|
+
- Supports `aria-label` for screen readers
|
|
117
|
+
- Disabled state is properly communicated via `disabled` attribute
|
|
118
|
+
- Focus ring visible on keyboard navigation
|
|
119
|
+
- Disabled buttons have reduced opacity
|
|
120
|
+
|
|
121
|
+
## Customization
|
|
122
|
+
|
|
123
|
+
### Modifying Styles
|
|
124
|
+
|
|
125
|
+
Edit `button.styles.json` to customize the appearance:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"buttonStyles": {
|
|
130
|
+
"base": "your-base-classes",
|
|
131
|
+
"variants": {
|
|
132
|
+
"variant": {
|
|
133
|
+
"custom": "your-custom-variant-classes"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Adding New Variants
|
|
141
|
+
|
|
142
|
+
1. Add the variant to `button.styles.json`:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"variants": {
|
|
147
|
+
"variant": {
|
|
148
|
+
"success": "bg-green-500 text-white hover:bg-green-600"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
2. Use it in your component:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"kind": "component",
|
|
159
|
+
"name": "Button",
|
|
160
|
+
"props": {
|
|
161
|
+
"variant": { "expr": "lit", "value": "success" }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"params": {
|
|
3
|
+
"variant": { "type": "string", "required": false },
|
|
4
|
+
"size": { "type": "string", "required": false },
|
|
5
|
+
"disabled": { "type": "boolean", "required": false },
|
|
6
|
+
"type": { "type": "string", "required": false },
|
|
7
|
+
"ariaLabel": { "type": "string", "required": false }
|
|
8
|
+
},
|
|
9
|
+
"view": {
|
|
10
|
+
"kind": "element",
|
|
11
|
+
"tag": "button",
|
|
12
|
+
"props": {
|
|
13
|
+
"className": {
|
|
14
|
+
"expr": "style",
|
|
15
|
+
"preset": "buttonStyles",
|
|
16
|
+
"props": {
|
|
17
|
+
"variant": { "expr": "param", "name": "variant" },
|
|
18
|
+
"size": { "expr": "param", "name": "size" }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"disabled": { "expr": "param", "name": "disabled" },
|
|
22
|
+
"type": { "expr": "param", "name": "type" },
|
|
23
|
+
"aria-label": { "expr": "param", "name": "ariaLabel" }
|
|
24
|
+
},
|
|
25
|
+
"children": [{ "kind": "slot" }]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"buttonStyles": {
|
|
3
|
+
"base": "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
4
|
+
"variants": {
|
|
5
|
+
"variant": {
|
|
6
|
+
"default": "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
7
|
+
"destructive": "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
8
|
+
"outline": "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
9
|
+
"secondary": "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
10
|
+
"ghost": "hover:bg-accent hover:text-accent-foreground",
|
|
11
|
+
"link": "text-primary underline-offset-4 hover:underline"
|
|
12
|
+
},
|
|
13
|
+
"size": {
|
|
14
|
+
"default": "h-10 px-4 py-2",
|
|
15
|
+
"sm": "h-9 rounded-md px-3",
|
|
16
|
+
"lg": "h-11 rounded-md px-8",
|
|
17
|
+
"icon": "h-10 w-10"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"defaultVariants": {
|
|
21
|
+
"variant": "default",
|
|
22
|
+
"size": "default"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|