vueless 1.2.4 → 1.2.5-beta.1
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/ui.image-icon/config.ts +1 -1
- package/ui.image-icon/types.ts +1 -1
- package/utils/node/loaderIcon.js +5 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getArgs,
|
|
3
|
+
getArgTypes,
|
|
4
|
+
getSlotNames,
|
|
5
|
+
getSlotsFragment,
|
|
6
|
+
getDocsDescription,
|
|
7
|
+
trimText,
|
|
8
|
+
} from "../../utils/storybook";
|
|
9
|
+
|
|
10
|
+
import UAccordionItem from "../../ui.container-accordion-item/UAccordionItem.vue";
|
|
11
|
+
import UButton from "../../ui.button/UButton.vue";
|
|
12
|
+
import ULink from "../../ui.button-link/ULink.vue";
|
|
13
|
+
import UCol from "../../ui.container-col/UCol.vue";
|
|
14
|
+
import URow from "../../ui.container-row/URow.vue";
|
|
15
|
+
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
16
|
+
|
|
17
|
+
import type { Meta, StoryFn } from "@storybook/vue3-vite";
|
|
18
|
+
import type { Props } from "../types";
|
|
19
|
+
|
|
20
|
+
interface UAccordionItemArgs extends Props {
|
|
21
|
+
slotTemplate?: string;
|
|
22
|
+
enum: "size";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
id: "5050",
|
|
27
|
+
title: "Containers / Accordion Item",
|
|
28
|
+
component: UAccordionItem,
|
|
29
|
+
args: {
|
|
30
|
+
title: "Committed to Quality and Performance",
|
|
31
|
+
description: trimText(
|
|
32
|
+
`We take pride in delivering high-quality solutions tailored to your needs.
|
|
33
|
+
Our expertise ensures that your project is built with efficiency, scalability, and reliability in mind.`,
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
argTypes: {
|
|
37
|
+
...getArgTypes(UAccordionItem.__name),
|
|
38
|
+
},
|
|
39
|
+
parameters: {
|
|
40
|
+
docs: {
|
|
41
|
+
...getDocsDescription(UAccordionItem.__name),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} as Meta;
|
|
45
|
+
|
|
46
|
+
const DefaultTemplate: StoryFn<UAccordionItemArgs> = (args: UAccordionItemArgs) => ({
|
|
47
|
+
components: { UAccordionItem, ULink, UButton, UCol, URow, UIcon },
|
|
48
|
+
setup: () => ({ args, slots: getSlotNames(UAccordionItem.__name) }),
|
|
49
|
+
template: `
|
|
50
|
+
<UAccordionItem v-bind="args">
|
|
51
|
+
${args.slotTemplate || getSlotsFragment("")}
|
|
52
|
+
</UAccordionItem>
|
|
53
|
+
`,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const EnumTemplate: StoryFn<UAccordionItemArgs> = (args: UAccordionItemArgs, { argTypes }) => ({
|
|
57
|
+
components: { UAccordionItem, UCol },
|
|
58
|
+
setup: () => ({ args, argTypes, getArgs }),
|
|
59
|
+
template: `
|
|
60
|
+
<UCol gap="xl">
|
|
61
|
+
<UAccordionItem
|
|
62
|
+
v-for="option in argTypes?.[args.enum]?.options"
|
|
63
|
+
v-bind="getArgs(args, option)"
|
|
64
|
+
:key="option"
|
|
65
|
+
/>
|
|
66
|
+
</UCol>
|
|
67
|
+
`,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export const Default = DefaultTemplate.bind({});
|
|
71
|
+
Default.args = {};
|
|
72
|
+
|
|
73
|
+
export const Disabled = DefaultTemplate.bind({});
|
|
74
|
+
Disabled.args = { disabled: true };
|
|
75
|
+
|
|
76
|
+
export const Opened = DefaultTemplate.bind({});
|
|
77
|
+
Opened.args = { opened: true };
|
|
78
|
+
|
|
79
|
+
export const Sizes = EnumTemplate.bind({});
|
|
80
|
+
Sizes.args = { enum: "size", description: "{enumValue}" };
|
|
81
|
+
|
|
82
|
+
export const DefaultSlot = DefaultTemplate.bind({});
|
|
83
|
+
DefaultSlot.args = {
|
|
84
|
+
slotTemplate: `
|
|
85
|
+
<template #default>
|
|
86
|
+
<UCol gap="sm">
|
|
87
|
+
<URow gap="xs" align="end">
|
|
88
|
+
<UIcon name="contact_mail" size="xs" color="primary" />
|
|
89
|
+
<ULink label="Email services" />
|
|
90
|
+
</URow>
|
|
91
|
+
|
|
92
|
+
<URow gap="xs" align="end">
|
|
93
|
+
<UIcon name="vpn_key" size="xs" color="primary" />
|
|
94
|
+
<ULink label="VPN" />
|
|
95
|
+
</URow>
|
|
96
|
+
|
|
97
|
+
<URow gap="xs" align="end">
|
|
98
|
+
<UIcon name="web_traffic" size="xs" color="primary" />
|
|
99
|
+
<ULink label="SEO Tools" />
|
|
100
|
+
</URow>
|
|
101
|
+
</UCol>
|
|
102
|
+
</template>
|
|
103
|
+
`,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const ToggleSlot = DefaultTemplate.bind({});
|
|
107
|
+
ToggleSlot.args = {
|
|
108
|
+
slotTemplate: `
|
|
109
|
+
<template #toggle="{ opened }">
|
|
110
|
+
<ULink :label="opened ? 'Collapse' : 'Expand'" color="grayscale" />
|
|
111
|
+
</template>
|
|
112
|
+
`,
|
|
113
|
+
};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
import UAccordionItem from "../UAccordionItem.vue";
|
|
5
|
+
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
6
|
+
|
|
7
|
+
import type { Props } from "../types";
|
|
8
|
+
|
|
9
|
+
describe("UAccordionItem", () => {
|
|
10
|
+
// Props
|
|
11
|
+
describe("Props", () => {
|
|
12
|
+
// Title prop
|
|
13
|
+
it("renders with title prop", () => {
|
|
14
|
+
const title = "Accordion Title";
|
|
15
|
+
|
|
16
|
+
const component = mount(UAccordionItem, {
|
|
17
|
+
props: {
|
|
18
|
+
title,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(component.find("[vl-key='title']").text()).toContain(title);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Description prop
|
|
26
|
+
it("renders with description prop", () => {
|
|
27
|
+
const description = "Accordion Description";
|
|
28
|
+
|
|
29
|
+
const component = mount(UAccordionItem, {
|
|
30
|
+
props: {
|
|
31
|
+
description,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(component.find("[vl-key='description']").text()).toBe(description);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Size prop
|
|
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(UAccordionItem, {
|
|
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(UAccordionItem, {
|
|
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
|
+
|
|
87
|
+
const component = mount(UAccordionItem, {
|
|
88
|
+
props: {
|
|
89
|
+
id,
|
|
90
|
+
description,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(component.find(`[id="description-${id}"]`).exists()).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// DataTest prop
|
|
98
|
+
it("applies data-test attribute", () => {
|
|
99
|
+
const dataTest = "accordion-test";
|
|
100
|
+
|
|
101
|
+
const component = mount(UAccordionItem, {
|
|
102
|
+
props: {
|
|
103
|
+
dataTest,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(component.attributes("data-test")).toBe(dataTest);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Slots
|
|
112
|
+
describe("Slots", () => {
|
|
113
|
+
// Toggle slot
|
|
114
|
+
it("renders default toggle icon when toggle slot is not provided", () => {
|
|
115
|
+
const toggleIcon = true;
|
|
116
|
+
|
|
117
|
+
const component = mount(UAccordionItem, {
|
|
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(UAccordionItem, {
|
|
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(UAccordionItem, {
|
|
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(UAccordionItem, {
|
|
185
|
+
props: { name: "test" },
|
|
186
|
+
slots: {
|
|
187
|
+
default: `<div class="${slotClass}">${slotContent}</div>`,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(component.find(`.${slotClass}`).exists()).toBe(false);
|
|
192
|
+
|
|
193
|
+
await component.trigger("click");
|
|
194
|
+
|
|
195
|
+
expect(component.find(`.${slotClass}`).exists()).toBe(true);
|
|
196
|
+
expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
|
|
197
|
+
|
|
198
|
+
await component.trigger("click");
|
|
199
|
+
|
|
200
|
+
expect(component.find(`.${slotClass}`).exists()).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("does not render default slot content when accordion is closed", () => {
|
|
204
|
+
const slotContent = "Custom accordion content";
|
|
205
|
+
const slotClass = "custom-content";
|
|
206
|
+
|
|
207
|
+
const component = mount(UAccordionItem, {
|
|
208
|
+
props: { name: "test" },
|
|
209
|
+
slots: {
|
|
210
|
+
default: `<div class="${slotClass}">${slotContent}</div>`,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(component.find(`.${slotClass}`).exists()).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("does not render content wrapper when default slot is empty", async () => {
|
|
218
|
+
const component = mount(UAccordionItem, { props: { name: "test" } });
|
|
219
|
+
|
|
220
|
+
await component.trigger("click");
|
|
221
|
+
|
|
222
|
+
expect(component.find("[vl-key='content']").exists()).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Events
|
|
227
|
+
describe("Events", () => {
|
|
228
|
+
// Click event
|
|
229
|
+
it("emits click event with id and opened state when clicked", async () => {
|
|
230
|
+
const id = "test-id";
|
|
231
|
+
|
|
232
|
+
const component = mount(UAccordionItem, {
|
|
233
|
+
props: {
|
|
234
|
+
id,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await component.trigger("click");
|
|
239
|
+
|
|
240
|
+
const emitted = component.emitted("click");
|
|
241
|
+
|
|
242
|
+
expect(emitted).toBeTruthy();
|
|
243
|
+
expect(emitted?.[0]).toEqual([id, true]);
|
|
244
|
+
|
|
245
|
+
// Click again to toggle back
|
|
246
|
+
await component.trigger("click");
|
|
247
|
+
|
|
248
|
+
const emittedAgain = component.emitted("click");
|
|
249
|
+
|
|
250
|
+
expect(emittedAgain?.[1]).toEqual([id, false]);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Exposed refs
|
|
255
|
+
describe("Exposed refs", () => {
|
|
256
|
+
// WrapperRef
|
|
257
|
+
it("exposes wrapperRef", () => {
|
|
258
|
+
const component = mount(UAccordionItem);
|
|
259
|
+
|
|
260
|
+
expect(component.vm.wrapperRef).toBeDefined();
|
|
261
|
+
expect(component.vm.wrapperRef instanceof HTMLDivElement).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Component behavior
|
|
266
|
+
describe("Component behavior", () => {
|
|
267
|
+
// Toggle behavior
|
|
268
|
+
it("toggles opened state when clicked", async () => {
|
|
269
|
+
const description = "Test Description";
|
|
270
|
+
const openedClass = "opacity-100";
|
|
271
|
+
|
|
272
|
+
const component = mount(UAccordionItem, {
|
|
273
|
+
props: {
|
|
274
|
+
description,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const descriptionElement = component.find("[id^='description-']");
|
|
279
|
+
|
|
280
|
+
// Initially not opened
|
|
281
|
+
expect(descriptionElement.classes()).not.toContain(openedClass);
|
|
282
|
+
|
|
283
|
+
// Click to open
|
|
284
|
+
await component.trigger("click");
|
|
285
|
+
|
|
286
|
+
// Should be opened
|
|
287
|
+
expect(descriptionElement.classes()).toContain(openedClass);
|
|
288
|
+
|
|
289
|
+
// Click to close
|
|
290
|
+
await component.trigger("click");
|
|
291
|
+
|
|
292
|
+
// Should be closed again
|
|
293
|
+
expect(descriptionElement.classes()).not.toContain(openedClass);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import defaultConfig from "./config";
|
|
2
|
+
import type { ComponentConfig } from "../types";
|
|
3
|
+
|
|
4
|
+
export type Config = typeof defaultConfig;
|
|
5
|
+
|
|
6
|
+
export interface Props {
|
|
7
|
+
/**
|
|
8
|
+
* Accordion item value attribute.
|
|
9
|
+
*/
|
|
10
|
+
value?: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Accordion item title.
|
|
14
|
+
*/
|
|
15
|
+
title?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Accordion item description.
|
|
19
|
+
*/
|
|
20
|
+
description?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Accordion item size.
|
|
24
|
+
*/
|
|
25
|
+
size?: "sm" | "md" | "lg";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Control accordion item state (opened/closed).
|
|
29
|
+
*/
|
|
30
|
+
opened?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Disable accordion item.
|
|
34
|
+
*/
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Accordion item toggle icon.
|
|
39
|
+
*/
|
|
40
|
+
toggleIcon?: boolean | string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Unique element id.
|
|
44
|
+
*/
|
|
45
|
+
id?: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Component config object.
|
|
49
|
+
*/
|
|
50
|
+
config?: ComponentConfig<Config>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Data-test attribute for automated testing.
|
|
54
|
+
*/
|
|
55
|
+
dataTest?: string | null;
|
|
56
|
+
}
|
package/ui.image-icon/config.ts
CHANGED
package/ui.image-icon/types.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { ComponentConfig } from "../types";
|
|
|
6
6
|
|
|
7
7
|
export type Config = typeof defaultConfig;
|
|
8
8
|
|
|
9
|
-
export type IconLibraries = "@material-symbols" | "bootstrap-icons" | "heroicons" | "custom
|
|
9
|
+
export type IconLibraries = "@material-symbols" | "bootstrap-icons" | "heroicons" | "custom";
|
|
10
10
|
|
|
11
11
|
export interface Props {
|
|
12
12
|
/**
|
package/utils/node/loaderIcon.js
CHANGED
|
@@ -337,7 +337,11 @@ function getIconLibraryPaths(name, library) {
|
|
|
337
337
|
sourcePath = path.join(cwd(), NODE_MODULES_DIR, library, "24", fillVariant, `${iconName}.svg`);
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
if (library === "
|
|
340
|
+
if (library === "lucide-static") {
|
|
341
|
+
sourcePath = path.join(cwd(), NODE_MODULES_DIR, library, "icons", `${name}.svg`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (library === "custom") {
|
|
341
345
|
sourcePath = path.join(cwd(), uIconDefaults.path, `${name}.svg`);
|
|
342
346
|
}
|
|
343
347
|
|