vueless 1.2.3 → 1.2.5-beta.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/constants.d.ts +1 -0
- package/constants.js +1 -0
- package/index.d.ts +1 -0
- package/index.ts +1 -0
- package/package.json +1 -1
- package/types.ts +2 -0
- package/ui.container-accordion/UAccordion.vue +69 -74
- package/ui.container-accordion/config.ts +4 -46
- package/ui.container-accordion/storybook/stories.ts +51 -90
- package/ui.container-accordion/tests/UAccordion.test.ts +44 -239
- package/ui.container-accordion/types.ts +20 -6
- package/ui.container-accordion-item/UAccordionItem.vue +158 -0
- package/ui.container-accordion-item/config.ts +52 -0
- package/ui.container-accordion-item/constants.ts +5 -0
- package/ui.container-accordion-item/storybook/docs.mdx +16 -0
- package/ui.container-accordion-item/storybook/stories.ts +113 -0
- package/ui.container-accordion-item/tests/UAccordionItem.test.ts +296 -0
- package/ui.container-accordion-item/types.ts +56 -0
- package/utils/node/loaderIcon.js +5 -1
- package/utils/ui.ts +5 -10
|
@@ -2,293 +2,98 @@ import { mount } from "@vue/test-utils";
|
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
|
|
4
4
|
import UAccordion from "../UAccordion.vue";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
import type { Props } from "../types";
|
|
5
|
+
import UAccordionItem from "../../ui.container-accordion-item/UAccordionItem.vue";
|
|
8
6
|
|
|
9
7
|
describe("UAccordion", () => {
|
|
8
|
+
const options = [
|
|
9
|
+
{ value: "a", title: "A" },
|
|
10
|
+
{ value: "b", title: "B" },
|
|
11
|
+
];
|
|
12
|
+
|
|
10
13
|
// Props
|
|
11
14
|
describe("Props", () => {
|
|
12
|
-
|
|
13
|
-
it("renders with title prop", () => {
|
|
14
|
-
const title = "Accordion Title";
|
|
15
|
-
|
|
15
|
+
it("renders items from options", () => {
|
|
16
16
|
const component = mount(UAccordion, {
|
|
17
|
-
props: {
|
|
18
|
-
title,
|
|
19
|
-
},
|
|
17
|
+
props: { options },
|
|
20
18
|
});
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Description prop
|
|
26
|
-
it("renders with description prop", () => {
|
|
27
|
-
const description = "Accordion Description";
|
|
20
|
+
const items = component.findAllComponents(UAccordionItem);
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
props: {
|
|
31
|
-
description,
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
expect(component.find("[vl-key='description']").text()).toBe(description);
|
|
22
|
+
expect(items.length).toBe(options.length);
|
|
36
23
|
});
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
it("applies correct size classes", () => {
|
|
40
|
-
const sizeClasses = {
|
|
41
|
-
sm: "text-small",
|
|
42
|
-
md: "text-medium",
|
|
43
|
-
lg: "text-large",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
Object.entries(sizeClasses).forEach(([size, classes]) => {
|
|
47
|
-
const component = mount(UAccordion, {
|
|
48
|
-
props: {
|
|
49
|
-
size: size as Props["size"],
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(component.find("[vl-key='title']").classes()).toContain(classes);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// ToggleIcon prop
|
|
58
|
-
it("applies correct toggle icon behavior", () => {
|
|
59
|
-
const toggleIconTests = [
|
|
60
|
-
{ toggleIcon: true, exists: true, iconName: "keyboard_arrow_down" },
|
|
61
|
-
{ toggleIcon: "custom_icon", exists: true, iconName: "custom_icon" },
|
|
62
|
-
{ toggleIcon: false, exists: false, iconName: undefined },
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
toggleIconTests.forEach(({ toggleIcon, exists, iconName }) => {
|
|
66
|
-
const component = mount(UAccordion, {
|
|
67
|
-
props: {
|
|
68
|
-
toggleIcon: toggleIcon as Props["toggleIcon"],
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const icon = component.findComponent(UIcon);
|
|
73
|
-
|
|
74
|
-
expect(icon.exists()).toBe(exists);
|
|
75
|
-
|
|
76
|
-
if (exists) {
|
|
77
|
-
expect(icon.props("name")).toBe(iconName);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// ID prop
|
|
83
|
-
it("uses provided id prop", () => {
|
|
84
|
-
const id = "custom-id";
|
|
85
|
-
const description = "some text";
|
|
86
|
-
|
|
25
|
+
it("passes base props down to items", () => {
|
|
87
26
|
const component = mount(UAccordion, {
|
|
88
|
-
props: {
|
|
89
|
-
id,
|
|
90
|
-
description,
|
|
91
|
-
},
|
|
27
|
+
props: { size: "md", disabled: true, options },
|
|
92
28
|
});
|
|
93
29
|
|
|
94
|
-
|
|
30
|
+
const item = component.findComponent(UAccordionItem);
|
|
31
|
+
|
|
32
|
+
expect(item.props("size")).toBe("md");
|
|
33
|
+
expect(item.props("disabled")).toBe(true);
|
|
95
34
|
});
|
|
96
35
|
|
|
97
|
-
|
|
98
|
-
it("applies data-test attribute", () => {
|
|
36
|
+
it("applies data-test attribute to wrapper", () => {
|
|
99
37
|
const dataTest = "accordion-test";
|
|
100
38
|
|
|
101
39
|
const component = mount(UAccordion, {
|
|
102
|
-
props: {
|
|
103
|
-
dataTest,
|
|
104
|
-
},
|
|
40
|
+
props: { dataTest },
|
|
105
41
|
});
|
|
106
42
|
|
|
107
43
|
expect(component.attributes("data-test")).toBe(dataTest);
|
|
108
44
|
});
|
|
109
45
|
});
|
|
110
46
|
|
|
111
|
-
//
|
|
112
|
-
describe("
|
|
113
|
-
|
|
114
|
-
it("renders default toggle icon when toggle slot is not provided", () => {
|
|
115
|
-
const toggleIcon = true;
|
|
116
|
-
|
|
117
|
-
const component = mount(UAccordion, {
|
|
118
|
-
props: {
|
|
119
|
-
toggleIcon,
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const icon = component.findComponent(UIcon);
|
|
124
|
-
|
|
125
|
-
expect(icon.exists()).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Custom toggle slot
|
|
129
|
-
it("renders custom content in toggle slot", () => {
|
|
130
|
-
const toggleIcon = true;
|
|
131
|
-
const slotClass = "custom-toggle";
|
|
132
|
-
const slotContent = "Custom Toggle";
|
|
133
|
-
|
|
134
|
-
const component = mount(UAccordion, {
|
|
135
|
-
props: {
|
|
136
|
-
toggleIcon,
|
|
137
|
-
},
|
|
138
|
-
slots: {
|
|
139
|
-
toggle: `<div class="${slotClass}">${slotContent}</div>`,
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
expect(component.find(`.${slotClass}`).exists()).toBe(true);
|
|
144
|
-
expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
|
|
145
|
-
expect(component.findComponent(UIcon).exists()).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Toggle slot bindings
|
|
149
|
-
it("provides icon-name and opened bindings to toggle slot", async () => {
|
|
150
|
-
const toggleIcon = true;
|
|
151
|
-
const toggleClass = "custom-toggle";
|
|
152
|
-
const defaultIconName = "keyboard_arrow_down";
|
|
153
|
-
|
|
154
|
-
const component = mount(UAccordion, {
|
|
155
|
-
props: {
|
|
156
|
-
toggleIcon,
|
|
157
|
-
},
|
|
158
|
-
slots: {
|
|
159
|
-
toggle: `
|
|
160
|
-
<template #default="{ iconName, opened }">
|
|
161
|
-
<div class="${toggleClass}" :data-icon="iconName" :data-opened="opened"></div>
|
|
162
|
-
</template>
|
|
163
|
-
`,
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const toggleElement = component.find(`.${toggleClass}`);
|
|
168
|
-
|
|
169
|
-
expect(toggleElement.exists()).toBe(true);
|
|
170
|
-
expect(toggleElement.attributes("data-icon")).toBe(defaultIconName);
|
|
171
|
-
expect(toggleElement.attributes("data-opened")).toBe("false");
|
|
172
|
-
|
|
173
|
-
// Click to toggle
|
|
174
|
-
await component.trigger("click");
|
|
175
|
-
|
|
176
|
-
expect(toggleElement.attributes("data-opened")).toBe("true");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Default slot
|
|
180
|
-
it("renders default slot content when accordion is opened", async () => {
|
|
181
|
-
const slotContent = "Custom accordion content";
|
|
182
|
-
const slotClass = "custom-content";
|
|
183
|
-
|
|
184
|
-
const component = mount(UAccordion, {
|
|
185
|
-
slots: {
|
|
186
|
-
default: `<div class="${slotClass}">${slotContent}</div>`,
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
expect(component.find(`.${slotClass}`).exists()).toBe(false);
|
|
191
|
-
|
|
192
|
-
await component.trigger("click");
|
|
193
|
-
|
|
194
|
-
expect(component.find(`.${slotClass}`).exists()).toBe(true);
|
|
195
|
-
expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
|
|
196
|
-
|
|
197
|
-
await component.trigger("click");
|
|
198
|
-
|
|
199
|
-
expect(component.find(`.${slotClass}`).exists()).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("does not render default slot content when accordion is closed", () => {
|
|
203
|
-
const slotContent = "Custom accordion content";
|
|
204
|
-
const slotClass = "custom-content";
|
|
205
|
-
|
|
206
|
-
const component = mount(UAccordion, {
|
|
207
|
-
slots: {
|
|
208
|
-
default: `<div class="${slotClass}">${slotContent}</div>`,
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
expect(component.find(`.${slotClass}`).exists()).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("does not render content wrapper when default slot is empty", async () => {
|
|
47
|
+
// Exposed refs
|
|
48
|
+
describe("Exposed refs", () => {
|
|
49
|
+
it("exposes accordionRef", () => {
|
|
216
50
|
const component = mount(UAccordion);
|
|
217
51
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
expect(component.find("[vl-key='content']").exists()).toBe(false);
|
|
52
|
+
expect(component.vm.accordionRef).toBeDefined();
|
|
53
|
+
expect(component.vm.accordionRef instanceof HTMLDivElement).toBe(true);
|
|
221
54
|
});
|
|
222
55
|
});
|
|
223
56
|
|
|
224
57
|
// Events
|
|
225
58
|
describe("Events", () => {
|
|
226
|
-
|
|
227
|
-
it("emits click event with id and opened state when clicked", async () => {
|
|
228
|
-
const id = "test-id";
|
|
229
|
-
|
|
59
|
+
it("emits update:modelValue when an item is toggled (single)", async () => {
|
|
230
60
|
const component = mount(UAccordion, {
|
|
231
|
-
props: {
|
|
232
|
-
id,
|
|
233
|
-
},
|
|
61
|
+
props: { options },
|
|
234
62
|
});
|
|
235
63
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const emitted = component.emitted("click");
|
|
64
|
+
const firstItem = component.findAllComponents(UAccordionItem)[0];
|
|
239
65
|
|
|
240
|
-
|
|
241
|
-
expect(emitted?.[0]).toEqual([id, true]);
|
|
66
|
+
await firstItem.trigger("click");
|
|
242
67
|
|
|
243
|
-
|
|
244
|
-
await component.trigger("click");
|
|
68
|
+
const updates = component.emitted("update:modelValue");
|
|
245
69
|
|
|
246
|
-
|
|
70
|
+
expect(updates).toBeTruthy();
|
|
71
|
+
expect(updates?.[0]).toEqual(["a"]);
|
|
247
72
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
73
|
+
await firstItem.trigger("click");
|
|
74
|
+
const updates2 = component.emitted("update:modelValue");
|
|
251
75
|
|
|
252
|
-
|
|
253
|
-
describe("Exposed refs", () => {
|
|
254
|
-
// WrapperRef
|
|
255
|
-
it("exposes wrapperRef", () => {
|
|
256
|
-
const component = mount(UAccordion);
|
|
257
|
-
|
|
258
|
-
expect(component.vm.wrapperRef).toBeDefined();
|
|
259
|
-
expect(component.vm.wrapperRef instanceof HTMLDivElement).toBe(true);
|
|
76
|
+
expect(updates2?.[1]).toEqual([null]);
|
|
260
77
|
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Component behavior
|
|
264
|
-
describe("Component behavior", () => {
|
|
265
|
-
// Toggle behavior
|
|
266
|
-
it("toggles opened state when clicked", async () => {
|
|
267
|
-
const description = "Test Description";
|
|
268
|
-
const openedClass = "opacity-100";
|
|
269
78
|
|
|
79
|
+
it("emits update:modelValue with arrays when multiple=true", async () => {
|
|
270
80
|
const component = mount(UAccordion, {
|
|
271
|
-
props: {
|
|
272
|
-
description,
|
|
273
|
-
},
|
|
81
|
+
props: { options, multiple: true },
|
|
274
82
|
});
|
|
275
83
|
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
// Initially not opened
|
|
279
|
-
expect(descriptionElement.classes()).not.toContain(openedClass);
|
|
84
|
+
const [firstItem, secondItem] = component.findAllComponents(UAccordionItem);
|
|
280
85
|
|
|
281
|
-
|
|
282
|
-
await
|
|
86
|
+
await firstItem.trigger("click");
|
|
87
|
+
await secondItem.trigger("click");
|
|
88
|
+
const updates = component.emitted("update:modelValue");
|
|
283
89
|
|
|
284
|
-
|
|
285
|
-
expect(
|
|
90
|
+
expect(updates?.[0]).toEqual([["a"]]);
|
|
91
|
+
expect(updates?.[1]).toEqual([["a", "b"]]);
|
|
286
92
|
|
|
287
|
-
|
|
288
|
-
|
|
93
|
+
await firstItem.trigger("click");
|
|
94
|
+
const updates2 = component.emitted("update:modelValue");
|
|
289
95
|
|
|
290
|
-
|
|
291
|
-
expect(descriptionElement.classes()).not.toContain(openedClass);
|
|
96
|
+
expect(updates2?.[2]).toEqual([["b"]]);
|
|
292
97
|
});
|
|
293
98
|
});
|
|
294
99
|
});
|
|
@@ -3,16 +3,25 @@ import type { ComponentConfig } from "../types";
|
|
|
3
3
|
|
|
4
4
|
export type Config = typeof defaultConfig;
|
|
5
5
|
|
|
6
|
+
export interface UAccordionOption {
|
|
7
|
+
value: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
opened?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type SetAccordionSelectedItem = (value: string, opened: boolean) => void;
|
|
14
|
+
|
|
6
15
|
export interface Props {
|
|
7
16
|
/**
|
|
8
|
-
* Accordion
|
|
17
|
+
* Accordion items state control.
|
|
9
18
|
*/
|
|
10
|
-
|
|
19
|
+
modelValue?: string | string[] | null;
|
|
11
20
|
|
|
12
21
|
/**
|
|
13
|
-
* Accordion
|
|
22
|
+
* Accordion options.
|
|
14
23
|
*/
|
|
15
|
-
|
|
24
|
+
options?: UAccordionOption[];
|
|
16
25
|
|
|
17
26
|
/**
|
|
18
27
|
* Accordion size.
|
|
@@ -20,9 +29,14 @@ export interface Props {
|
|
|
20
29
|
size?: "sm" | "md" | "lg";
|
|
21
30
|
|
|
22
31
|
/**
|
|
23
|
-
*
|
|
32
|
+
* Allow multiple items to be opened at the same time.
|
|
33
|
+
*/
|
|
34
|
+
multiple?: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Disable an accordion.
|
|
24
38
|
*/
|
|
25
|
-
|
|
39
|
+
disabled?: boolean;
|
|
26
40
|
|
|
27
41
|
/**
|
|
28
42
|
* Unique element id.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, inject, useId, useSlots, useTemplateRef, toValue, watchEffect } from "vue";
|
|
3
|
+
|
|
4
|
+
import { isEqual } from "lodash-es";
|
|
5
|
+
|
|
6
|
+
import useUI from "../composables/useUI";
|
|
7
|
+
import { getDefaults } from "../utils/ui";
|
|
8
|
+
import { hasSlotContent } from "../utils/helper";
|
|
9
|
+
|
|
10
|
+
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
11
|
+
|
|
12
|
+
import { COMPONENT_NAME } from "./constants";
|
|
13
|
+
import defaultConfig from "./config";
|
|
14
|
+
|
|
15
|
+
import type { Props, Config } from "./types";
|
|
16
|
+
import type { SetAccordionSelectedItem } from "../ui.container-accordion/types";
|
|
17
|
+
|
|
18
|
+
defineOptions({ inheritAttrs: false });
|
|
19
|
+
|
|
20
|
+
const setAccordionSelectedItem = inject<SetAccordionSelectedItem | null>(
|
|
21
|
+
"setAccordionSelectedItem",
|
|
22
|
+
null,
|
|
23
|
+
);
|
|
24
|
+
const getAccordionSize = inject("getAccordionSize", null);
|
|
25
|
+
const getAccordionDisabled = inject("getAccordionDisabled", null);
|
|
26
|
+
const getAccordionSelectedItem = inject<(() => string | string[] | null) | null>(
|
|
27
|
+
"getAccordionSelectedItem",
|
|
28
|
+
null,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
32
|
+
...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits([
|
|
36
|
+
/**
|
|
37
|
+
* Triggers when the accordion item is toggled.
|
|
38
|
+
* @property {string} elementId
|
|
39
|
+
* @property {boolean} isOpened
|
|
40
|
+
*/
|
|
41
|
+
"click",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
45
|
+
const descriptionRef = useTemplateRef<HTMLDivElement>("description");
|
|
46
|
+
const contentRef = useTemplateRef<HTMLDivElement>("content");
|
|
47
|
+
|
|
48
|
+
const accordionSize = ref(toValue(getAccordionSize) || props.size);
|
|
49
|
+
const accordionDisabled = ref(toValue(getAccordionDisabled) || props.disabled);
|
|
50
|
+
|
|
51
|
+
watchEffect(() => (accordionSize.value = toValue(getAccordionSize) || props.size));
|
|
52
|
+
watchEffect(() => (accordionDisabled.value = toValue(getAccordionDisabled) || props.disabled));
|
|
53
|
+
|
|
54
|
+
const slots = useSlots();
|
|
55
|
+
const elementId = props.id || useId();
|
|
56
|
+
|
|
57
|
+
const internalOpened = ref(false);
|
|
58
|
+
|
|
59
|
+
const isOpened = computed(() => {
|
|
60
|
+
const selectedItem = toValue(getAccordionSelectedItem);
|
|
61
|
+
|
|
62
|
+
if (selectedItem !== null) {
|
|
63
|
+
if (Array.isArray(selectedItem)) {
|
|
64
|
+
return selectedItem.includes(props.value ?? "");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return isEqual(selectedItem, props.value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return props.opened || internalOpened.value;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const toggleIconName = computed(() => {
|
|
74
|
+
if (typeof props.toggleIcon === "string") {
|
|
75
|
+
return props.toggleIcon;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return props.toggleIcon ? config.value.defaults.toggleIcon : "";
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function onClickItem(event: MouseEvent) {
|
|
82
|
+
const clickedInsideContent = contentRef.value?.contains(event.target as Node);
|
|
83
|
+
const clickedDescription = descriptionRef.value?.contains(event.target as Node);
|
|
84
|
+
|
|
85
|
+
if (props.disabled || clickedDescription || clickedInsideContent) return;
|
|
86
|
+
|
|
87
|
+
emit("click", elementId, !isOpened.value);
|
|
88
|
+
|
|
89
|
+
if (setAccordionSelectedItem) {
|
|
90
|
+
setAccordionSelectedItem(props.value ?? "", !isOpened.value);
|
|
91
|
+
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
internalOpened.value = !internalOpened.value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
defineExpose({
|
|
99
|
+
/**
|
|
100
|
+
* A reference to the component's wrapper element for direct DOM manipulation.
|
|
101
|
+
* @property {HTMLDivElement}
|
|
102
|
+
*/
|
|
103
|
+
wrapperRef,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const mutatedProps = computed(() => ({
|
|
107
|
+
/* component state, not a props */
|
|
108
|
+
opened: isOpened.value,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
const {
|
|
112
|
+
getDataTest,
|
|
113
|
+
config,
|
|
114
|
+
wrapperAttrs,
|
|
115
|
+
descriptionAttrs,
|
|
116
|
+
bodyAttrs,
|
|
117
|
+
titleAttrs,
|
|
118
|
+
contentAttrs,
|
|
119
|
+
toggleIconAttrs,
|
|
120
|
+
} = useUI<Config>(defaultConfig, mutatedProps);
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<template>
|
|
124
|
+
<div ref="wrapper" v-bind="wrapperAttrs" :data-test="getDataTest()" @click="onClickItem">
|
|
125
|
+
<div v-bind="bodyAttrs">
|
|
126
|
+
<div v-bind="titleAttrs">
|
|
127
|
+
{{ title }}
|
|
128
|
+
<!--
|
|
129
|
+
@slot Use it to add something instead of the toggle icon.
|
|
130
|
+
@binding {string} icon-name
|
|
131
|
+
@binding {boolean} opened
|
|
132
|
+
-->
|
|
133
|
+
<slot name="toggle" :icon-name="toggleIconName" :opened="isOpened">
|
|
134
|
+
<UIcon
|
|
135
|
+
v-if="toggleIconName"
|
|
136
|
+
:name="toggleIconName"
|
|
137
|
+
:size="size"
|
|
138
|
+
color="neutral"
|
|
139
|
+
v-bind="toggleIconAttrs"
|
|
140
|
+
/>
|
|
141
|
+
</slot>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div
|
|
145
|
+
v-if="description"
|
|
146
|
+
:id="`description-${elementId}`"
|
|
147
|
+
ref="description"
|
|
148
|
+
v-bind="descriptionAttrs"
|
|
149
|
+
v-text="description"
|
|
150
|
+
/>
|
|
151
|
+
|
|
152
|
+
<div v-if="isOpened && hasSlotContent(slots['default'])" ref="content" v-bind="contentAttrs">
|
|
153
|
+
<!-- @slot Use it to add accordion content. -->
|
|
154
|
+
<slot />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export default /*tw*/ {
|
|
2
|
+
wrapper: {
|
|
3
|
+
base: "group cursor-pointer",
|
|
4
|
+
variants: {
|
|
5
|
+
disabled: {
|
|
6
|
+
true: "cursor-not-allowed text-default/(--vl-disabled-opacity)",
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
body: "",
|
|
11
|
+
title: {
|
|
12
|
+
base: "flex items-center justify-between font-medium",
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
sm: "text-small",
|
|
16
|
+
md: "text-medium",
|
|
17
|
+
lg: "text-large",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
description: {
|
|
22
|
+
base: "text-accented h-0 opacity-0 transition-all cursor-default",
|
|
23
|
+
variants: {
|
|
24
|
+
size: {
|
|
25
|
+
sm: "text-tiny",
|
|
26
|
+
md: "text-small",
|
|
27
|
+
lg: "text-medium",
|
|
28
|
+
},
|
|
29
|
+
opened: {
|
|
30
|
+
true: "pt-2 h-fit opacity-100",
|
|
31
|
+
},
|
|
32
|
+
disabled: {
|
|
33
|
+
true: "text-accented/(--vl-disabled-opacity)",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
content: "pt-3 cursor-default",
|
|
38
|
+
toggleIcon: {
|
|
39
|
+
base: "{UIcon} transition duration-300",
|
|
40
|
+
variants: {
|
|
41
|
+
opened: {
|
|
42
|
+
true: "group-[*]:rotate-180",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaults: {
|
|
47
|
+
size: "md",
|
|
48
|
+
disabled: false,
|
|
49
|
+
/* icons */
|
|
50
|
+
toggleIcon: "keyboard_arrow_down",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/addon-docs/blocks";
|
|
2
|
+
import { getSource } from "../../utils/storybook";
|
|
3
|
+
|
|
4
|
+
import * as stories from "./stories";
|
|
5
|
+
import defaultConfig from "../config?raw"
|
|
6
|
+
|
|
7
|
+
<Meta of={stories} />
|
|
8
|
+
<Title of={stories} />
|
|
9
|
+
<Subtitle of={stories} />
|
|
10
|
+
<Description of={stories} />
|
|
11
|
+
<Primary of={stories} />
|
|
12
|
+
<Controls of={stories.Default} />
|
|
13
|
+
<Stories of={stories} />
|
|
14
|
+
|
|
15
|
+
## Default config
|
|
16
|
+
<Source code={getSource(defaultConfig)} language="jsx" dark />
|