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.
@@ -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
+ }
@@ -40,7 +40,7 @@ export default /*tw*/ {
40
40
  interactive: false,
41
41
  /* icon library */
42
42
  library: "@material-symbols",
43
- path: "", // set for `custom-icons` library only.
43
+ path: "", // set for `custom` library only.
44
44
  style: "outlined",
45
45
  weight: 500,
46
46
  },
@@ -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-icons";
9
+ export type IconLibraries = "@material-symbols" | "bootstrap-icons" | "heroicons" | "custom";
10
10
 
11
11
  export interface Props {
12
12
  /**
@@ -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 === "custom-icons") {
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