@starwind-ui/core 1.12.0 → 1.12.2-next.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/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/src/components/checkbox/Checkbox.astro +1 -0
- package/dist/src/components/dropzone/Dropzone.astro +1 -0
- package/dist/src/components/input/Input.astro +1 -0
- package/dist/src/components/radio-group/RadioGroupItem.astro +2 -2
- package/dist/src/components/select/Select.astro +51 -1
- package/dist/src/components/select/SelectTrigger.astro +2 -6
- package/dist/src/components/textarea/Textarea.astro +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,11 +32,11 @@ var registry_default = {
|
|
|
32
32
|
version: "1.0.1",
|
|
33
33
|
dependencies: ["@starwind-ui/core/button@^2.1.0", "embla-carousel@^8.6.0"]
|
|
34
34
|
},
|
|
35
|
-
{ name: "checkbox", type: "component", version: "1.4.
|
|
35
|
+
{ name: "checkbox", type: "component", version: "1.4.1", dependencies: [] },
|
|
36
36
|
{ name: "dialog", type: "component", version: "1.4.1", dependencies: [] },
|
|
37
37
|
{ name: "dropdown", type: "component", version: "1.2.2", dependencies: [] },
|
|
38
|
-
{ name: "dropzone", type: "component", version: "1.2.
|
|
39
|
-
{ name: "input", type: "component", version: "1.3.
|
|
38
|
+
{ name: "dropzone", type: "component", version: "1.2.1", dependencies: [] },
|
|
39
|
+
{ name: "input", type: "component", version: "1.3.1", dependencies: [] },
|
|
40
40
|
{
|
|
41
41
|
name: "item",
|
|
42
42
|
type: "component",
|
|
@@ -47,8 +47,8 @@ var registry_default = {
|
|
|
47
47
|
{ name: "label", type: "component", version: "1.2.0", dependencies: [] },
|
|
48
48
|
{ name: "pagination", type: "component", version: "3.0.2", dependencies: [] },
|
|
49
49
|
{ name: "progress", type: "component", version: "1.1.0", dependencies: [] },
|
|
50
|
-
{ name: "radio-group", type: "component", version: "1.2.
|
|
51
|
-
{ name: "select", type: "component", version: "1.
|
|
50
|
+
{ name: "radio-group", type: "component", version: "1.2.3", dependencies: [] },
|
|
51
|
+
{ name: "select", type: "component", version: "1.8.0", dependencies: [] },
|
|
52
52
|
{ name: "separator", type: "component", version: "1.0.0", dependencies: [] },
|
|
53
53
|
{
|
|
54
54
|
name: "sheet",
|
|
@@ -61,7 +61,7 @@ var registry_default = {
|
|
|
61
61
|
{ name: "switch", type: "component", version: "1.3.0", dependencies: [] },
|
|
62
62
|
{ name: "table", type: "component", version: "1.1.0", dependencies: [] },
|
|
63
63
|
{ name: "tabs", type: "component", version: "1.4.0", dependencies: [] },
|
|
64
|
-
{ name: "textarea", type: "component", version: "1.3.
|
|
64
|
+
{ name: "textarea", type: "component", version: "1.3.1", dependencies: [] },
|
|
65
65
|
{ name: "toggle", type: "component", version: "1.0.0", dependencies: [] },
|
|
66
66
|
{ name: "tooltip", type: "component", version: "1.3.0", dependencies: [] }
|
|
67
67
|
]
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/registry.json"],"sourcesContent":["import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport componentRegistry from \"./registry.json\" with { type: \"json\" };\n\n/**\n * Component metadata interface describing a Starwind UI component\n */\nexport interface ComponentMeta {\n name: string;\n version: string;\n type: \"component\";\n dependencies: string[];\n}\n\n/**\n * Registry interface containing all available components\n */\nexport interface Registry {\n components: ComponentMeta[];\n}\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\n\n/**\n * Get the absolute path to a component file\n * @param {string} componentName - The name of the component\n * @param {string} fileName - The name of the file within the component\n * @returns {string} The absolute path to the component file\n */\nexport const getComponentPath = (componentName: string, fileName: string): string => {\n // In production (when installed as a dependency), the components will be in dist/src/components\n // In development, they will be in src/components\n const componentsDir = __dirname.includes(\"dist\") ? \"src/components\" : \"src/components\";\n return join(__dirname, componentsDir, componentName, fileName);\n};\n\n/**\n * Map of all components and their metadata from registry\n */\nexport const registry = componentRegistry.components as ComponentMeta[];\n","{\n \"$schema\": \"https://starwind.dev/registry-schema.json\",\n \"components\": [\n { \"name\": \"accordion\", \"type\": \"component\", \"version\": \"1.3.2\", \"dependencies\": [] },\n { \"name\": \"alert\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"alert-dialog\",\n \"type\": \"component\",\n \"version\": \"1.0.2\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\"]\n },\n { \"name\": \"aspect-ratio\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"avatar\", \"type\": \"component\", \"version\": \"1.2.1\", \"dependencies\": [] },\n { \"name\": \"badge\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"breadcrumb\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n {\n \"name\": \"button-group\",\n \"type\": \"component\",\n \"version\": \"1.0.0\",\n \"dependencies\": [\"@starwind-ui/core/separator@^1.0.0\"]\n },\n { \"name\": \"button\", \"type\": \"component\", \"version\": \"2.2.0\", \"dependencies\": [] },\n { \"name\": \"card\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"carousel\",\n \"type\": \"component\",\n \"version\": \"1.0.1\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\", \"embla-carousel@^8.6.0\"]\n },\n { \"name\": \"checkbox\", \"type\": \"component\", \"version\": \"1.4.
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/registry.json"],"sourcesContent":["import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport componentRegistry from \"./registry.json\" with { type: \"json\" };\n\n/**\n * Component metadata interface describing a Starwind UI component\n */\nexport interface ComponentMeta {\n name: string;\n version: string;\n type: \"component\";\n dependencies: string[];\n}\n\n/**\n * Registry interface containing all available components\n */\nexport interface Registry {\n components: ComponentMeta[];\n}\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\n\n/**\n * Get the absolute path to a component file\n * @param {string} componentName - The name of the component\n * @param {string} fileName - The name of the file within the component\n * @returns {string} The absolute path to the component file\n */\nexport const getComponentPath = (componentName: string, fileName: string): string => {\n // In production (when installed as a dependency), the components will be in dist/src/components\n // In development, they will be in src/components\n const componentsDir = __dirname.includes(\"dist\") ? \"src/components\" : \"src/components\";\n return join(__dirname, componentsDir, componentName, fileName);\n};\n\n/**\n * Map of all components and their metadata from registry\n */\nexport const registry = componentRegistry.components as ComponentMeta[];\n","{\n \"$schema\": \"https://starwind.dev/registry-schema.json\",\n \"components\": [\n { \"name\": \"accordion\", \"type\": \"component\", \"version\": \"1.3.2\", \"dependencies\": [] },\n { \"name\": \"alert\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"alert-dialog\",\n \"type\": \"component\",\n \"version\": \"1.0.2\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\"]\n },\n { \"name\": \"aspect-ratio\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"avatar\", \"type\": \"component\", \"version\": \"1.2.1\", \"dependencies\": [] },\n { \"name\": \"badge\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"breadcrumb\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n {\n \"name\": \"button-group\",\n \"type\": \"component\",\n \"version\": \"1.0.0\",\n \"dependencies\": [\"@starwind-ui/core/separator@^1.0.0\"]\n },\n { \"name\": \"button\", \"type\": \"component\", \"version\": \"2.2.0\", \"dependencies\": [] },\n { \"name\": \"card\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"carousel\",\n \"type\": \"component\",\n \"version\": \"1.0.1\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\", \"embla-carousel@^8.6.0\"]\n },\n { \"name\": \"checkbox\", \"type\": \"component\", \"version\": \"1.4.1\", \"dependencies\": [] },\n { \"name\": \"dialog\", \"type\": \"component\", \"version\": \"1.4.1\", \"dependencies\": [] },\n { \"name\": \"dropdown\", \"type\": \"component\", \"version\": \"1.2.2\", \"dependencies\": [] },\n { \"name\": \"dropzone\", \"type\": \"component\", \"version\": \"1.2.1\", \"dependencies\": [] },\n { \"name\": \"input\", \"type\": \"component\", \"version\": \"1.3.1\", \"dependencies\": [] },\n {\n \"name\": \"item\",\n \"type\": \"component\",\n \"version\": \"1.0.0\",\n \"dependencies\": [\"@starwind-ui/core/separator@^1.0.0\"]\n },\n { \"name\": \"kbd\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"label\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"pagination\", \"type\": \"component\", \"version\": \"3.0.2\", \"dependencies\": [] },\n { \"name\": \"progress\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n { \"name\": \"radio-group\", \"type\": \"component\", \"version\": \"1.2.3\", \"dependencies\": [] },\n { \"name\": \"select\", \"type\": \"component\", \"version\": \"1.8.0\", \"dependencies\": [] },\n { \"name\": \"separator\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n {\n \"name\": \"sheet\",\n \"type\": \"component\",\n \"version\": \"1.1.1\",\n \"dependencies\": [\"@starwind-ui/core/dialog@^1.3.0\"]\n },\n { \"name\": \"skeleton\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"spinner\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"switch\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"table\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n { \"name\": \"tabs\", \"type\": \"component\", \"version\": \"1.4.0\", \"dependencies\": [] },\n { \"name\": \"textarea\", \"type\": \"component\", \"version\": \"1.3.1\", \"dependencies\": [] },\n { \"name\": \"toggle\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"tooltip\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] }\n ]\n}\n"],"mappings":"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","names":[]}
|
|
@@ -19,6 +19,7 @@ export const checkbox = tv({
|
|
|
19
19
|
"transition-all focus-visible:ring-3",
|
|
20
20
|
"outline-0 focus:ring-0 focus:ring-offset-0",
|
|
21
21
|
"not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
|
|
22
|
+
"aria-invalid:border-error aria-invalid:focus-visible:ring-error/40",
|
|
22
23
|
],
|
|
23
24
|
icon: [
|
|
24
25
|
"pointer-events-none absolute stroke-3 p-0.5 opacity-0 transition-opacity peer-checked:opacity-100",
|
|
@@ -23,6 +23,7 @@ export const dropzone = tv({
|
|
|
23
23
|
"bg-background dark:bg-input/30 text-muted-foreground border-input cursor-pointer border border-dashed text-center text-sm",
|
|
24
24
|
"data-[is-uploading=false]:hover:bg-muted/50 data-[drag-active=true]:bg-muted/50 transition",
|
|
25
25
|
"focus-visible:border-outline focus-visible:ring-outline/50 outline-none focus-visible:ring-3",
|
|
26
|
+
"aria-invalid:border-error aria-invalid:focus-visible:ring-error/40",
|
|
26
27
|
],
|
|
27
28
|
});
|
|
28
29
|
---
|
|
@@ -10,6 +10,7 @@ export const input = tv({
|
|
|
10
10
|
"focus-visible:border-outline focus-visible:ring-outline/50 transition-[color,box-shadow] focus-visible:ring-3",
|
|
11
11
|
"file:text-foreground file:my-auto file:mr-4 file:h-full file:border-0 file:bg-transparent file:text-sm file:font-medium",
|
|
12
12
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
|
+
"aria-invalid:border-error aria-invalid:focus-visible:ring-error/40",
|
|
13
14
|
"peer placeholder:text-muted-foreground",
|
|
14
15
|
],
|
|
15
16
|
variants: {
|
|
@@ -54,11 +54,11 @@ export const radioControl = tv({
|
|
|
54
54
|
base: [
|
|
55
55
|
"starwind-radio-control",
|
|
56
56
|
"border-input bg-background dark:bg-input/30",
|
|
57
|
-
"peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:transition-none",
|
|
58
57
|
"outline-none peer-focus-visible:ring-3",
|
|
59
58
|
"absolute inset-0 rounded-full border shadow-xs",
|
|
60
|
-
"transition-
|
|
59
|
+
"transition-[color,box-shadow] peer-checked:[&>svg]:opacity-100",
|
|
61
60
|
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
61
|
+
"peer-aria-invalid:border-error peer-focus-visible:peer-aria-invalid:ring-error/40",
|
|
62
62
|
"flex items-center justify-center",
|
|
63
63
|
],
|
|
64
64
|
variants: {
|
|
@@ -10,17 +10,22 @@ type Props = HTMLAttributes<"div"> & {
|
|
|
10
10
|
* The value of the item that should be selected by default
|
|
11
11
|
*/
|
|
12
12
|
defaultValue?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the select field is required in a form context
|
|
15
|
+
*/
|
|
16
|
+
required?: boolean;
|
|
13
17
|
|
|
14
18
|
children: any;
|
|
15
19
|
};
|
|
16
20
|
|
|
17
|
-
const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
21
|
+
const { class: className, name, defaultValue, required, ...rest } = Astro.props;
|
|
18
22
|
---
|
|
19
23
|
|
|
20
24
|
<div
|
|
21
25
|
class:list={["starwind-select", "relative", className]}
|
|
22
26
|
data-name={name}
|
|
23
27
|
data-value={defaultValue}
|
|
28
|
+
data-required={required}
|
|
24
29
|
data-slot="select"
|
|
25
30
|
{...rest}
|
|
26
31
|
>
|
|
@@ -43,6 +48,7 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
43
48
|
private typeaheadTimerRef: number | null = null;
|
|
44
49
|
private typeaheadSearch = "";
|
|
45
50
|
private returnFocusOnClose: boolean = true;
|
|
51
|
+
private required: boolean = false;
|
|
46
52
|
|
|
47
53
|
constructor(select: HTMLElement, selectIdx: number) {
|
|
48
54
|
this.select = select;
|
|
@@ -53,6 +59,8 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
53
59
|
|
|
54
60
|
if (!this.trigger || !this.content) return;
|
|
55
61
|
|
|
62
|
+
this.required = this.select.getAttribute("data-required") === "true";
|
|
63
|
+
|
|
56
64
|
// animationDuration is set with inline styles through passed prop to SelectContent
|
|
57
65
|
const animationDurationString = this.content.style.animationDuration;
|
|
58
66
|
if (animationDurationString.endsWith("ms")) {
|
|
@@ -80,6 +88,11 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
80
88
|
selectField.setAttribute("aria-hidden", "true");
|
|
81
89
|
selectField.setAttribute("placeholder", "select");
|
|
82
90
|
const selectName = this.select.getAttribute("data-name");
|
|
91
|
+
|
|
92
|
+
if (this.required) {
|
|
93
|
+
selectField.required = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
if (selectName) {
|
|
84
97
|
selectField.name = selectName;
|
|
85
98
|
}
|
|
@@ -109,6 +122,14 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
109
122
|
// add this select field right after the trigger
|
|
110
123
|
this.trigger.parentElement?.insertBefore(selectField, this.trigger.nextSibling);
|
|
111
124
|
|
|
125
|
+
// Intercept invalid event to show error on trigger instead of hidden select
|
|
126
|
+
if (this.required) {
|
|
127
|
+
selectField.addEventListener("invalid", (e) => {
|
|
128
|
+
this.showValidationError();
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
this.setSize();
|
|
113
134
|
this.content.style.width = "var(--starwind-select-trigger-width)";
|
|
114
135
|
}
|
|
@@ -122,6 +143,7 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
122
143
|
|
|
123
144
|
// Set up additional ARIA attributes
|
|
124
145
|
this.trigger.setAttribute("aria-controls", this.content.id);
|
|
146
|
+
this.trigger.setAttribute("aria-required", this.required ? "true" : "false");
|
|
125
147
|
this.content.setAttribute("aria-labelledby", this.trigger.id);
|
|
126
148
|
|
|
127
149
|
// If search input exists, add IDs to all items for aria-activedescendant
|
|
@@ -592,6 +614,9 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
592
614
|
private handleSelection(item: HTMLElement) {
|
|
593
615
|
if (!this.trigger) return;
|
|
594
616
|
|
|
617
|
+
// Clear any validation error when a selection is made
|
|
618
|
+
this.clearValidationError();
|
|
619
|
+
|
|
595
620
|
// update the hidden select field
|
|
596
621
|
const selectField = this.select.querySelector("select");
|
|
597
622
|
if (selectField) {
|
|
@@ -663,6 +688,31 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
|
663
688
|
this.handleSelection(item);
|
|
664
689
|
}
|
|
665
690
|
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Shows validation error on the trigger
|
|
694
|
+
*/
|
|
695
|
+
private showValidationError(): void {
|
|
696
|
+
if (!this.trigger) return;
|
|
697
|
+
|
|
698
|
+
// Add error state to trigger
|
|
699
|
+
this.trigger.setAttribute("aria-invalid", "true");
|
|
700
|
+
|
|
701
|
+
// scroll trigger into view
|
|
702
|
+
this.trigger.scrollIntoView({ behavior: "auto", block: "center" });
|
|
703
|
+
|
|
704
|
+
// Focus the trigger so user knows where the error is
|
|
705
|
+
this.trigger.focus();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Clears validation error from the trigger
|
|
710
|
+
*/
|
|
711
|
+
private clearValidationError(): void {
|
|
712
|
+
if (!this.trigger) return;
|
|
713
|
+
|
|
714
|
+
this.trigger.setAttribute("aria-invalid", "false");
|
|
715
|
+
}
|
|
666
716
|
}
|
|
667
717
|
|
|
668
718
|
// Store instances in a WeakMap to avoid memory leaks
|
|
@@ -9,10 +9,6 @@ type Props = Omit<HTMLAttributes<"button">, "role" | "type"> &
|
|
|
9
9
|
* The content to be rendered inside the select trigger
|
|
10
10
|
*/
|
|
11
11
|
children: any;
|
|
12
|
-
/**
|
|
13
|
-
* Whether the select field is required in a form context
|
|
14
|
-
*/
|
|
15
|
-
required?: boolean;
|
|
16
12
|
};
|
|
17
13
|
|
|
18
14
|
export const selectTrigger = tv({
|
|
@@ -21,6 +17,7 @@ export const selectTrigger = tv({
|
|
|
21
17
|
"border-input dark:bg-input/30 text-foreground ring-offset-background flex items-center justify-between gap-2 rounded-md border bg-transparent shadow-xs",
|
|
22
18
|
"focus-visible:border-outline focus-visible:ring-outline/50 transition-[color,box-shadow] outline-none focus-visible:ring-3",
|
|
23
19
|
"disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
|
20
|
+
"aria-invalid:border-error aria-invalid:focus:ring-error/40 aria-invalid:focus:ring-3",
|
|
24
21
|
],
|
|
25
22
|
variants: {
|
|
26
23
|
size: {
|
|
@@ -34,7 +31,7 @@ export const selectTrigger = tv({
|
|
|
34
31
|
},
|
|
35
32
|
});
|
|
36
33
|
|
|
37
|
-
const { class: className, size,
|
|
34
|
+
const { class: className, size, ...rest } = Astro.props;
|
|
38
35
|
---
|
|
39
36
|
|
|
40
37
|
<button
|
|
@@ -46,7 +43,6 @@ const { class: className, size, required = false, ...rest } = Astro.props;
|
|
|
46
43
|
aria-expanded="false"
|
|
47
44
|
aria-haspopup="listbox"
|
|
48
45
|
aria-autocomplete="none"
|
|
49
|
-
aria-required={required ? "true" : "false"}
|
|
50
46
|
data-state="closed"
|
|
51
47
|
data-slot="select-trigger"
|
|
52
48
|
{...rest}
|
|
@@ -10,6 +10,7 @@ export const textarea = tv({
|
|
|
10
10
|
"focus-visible:border-outline focus-visible:ring-outline/50 transition-[color,box-shadow] focus-visible:ring-3",
|
|
11
11
|
"file:text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium",
|
|
12
12
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
|
+
"aria-invalid:border-error aria-invalid:focus-visible:ring-error/40",
|
|
13
14
|
"peer placeholder:text-muted-foreground",
|
|
14
15
|
],
|
|
15
16
|
variants: {
|