@fiscozen/card-list 1.0.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +59 -0
  4. package/dist/card-list.js +427 -0
  5. package/dist/card-list.umd.cjs +1 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/src/FzCardList.vue.d.ts +18 -0
  8. package/dist/src/FzCardListItem.vue.d.ts +18 -0
  9. package/dist/src/components/FzCardActionLink.vue.d.ts +17 -0
  10. package/dist/src/components/FzCardFooter.vue.d.ts +13 -0
  11. package/dist/src/components/FzCardHeader.vue.d.ts +13 -0
  12. package/dist/src/components/FzCardMultiActions.vue.d.ts +18 -0
  13. package/dist/src/components/FzCardNoAction.vue.d.ts +13 -0
  14. package/dist/src/components/FzCardTitle.vue.d.ts +13 -0
  15. package/dist/src/components/types.d.ts +34 -0
  16. package/dist/src/index.d.ts +3 -0
  17. package/dist/src/types.d.ts +64 -0
  18. package/package.json +49 -0
  19. package/src/FzCardList.vue +54 -0
  20. package/src/FzCardListItem.vue +71 -0
  21. package/src/__tests__/FzCardList.spec.ts +322 -0
  22. package/src/__tests__/FzCardListItem.spec.ts +458 -0
  23. package/src/__tests__/__snapshots__/FzCardList.spec.ts.snap +52 -0
  24. package/src/__tests__/__snapshots__/FzCardListItem.spec.ts.snap +90 -0
  25. package/src/components/FzCardActionLink.vue +87 -0
  26. package/src/components/FzCardFooter.vue +13 -0
  27. package/src/components/FzCardHeader.vue +33 -0
  28. package/src/components/FzCardMultiActions.vue +75 -0
  29. package/src/components/FzCardNoAction.vue +29 -0
  30. package/src/components/FzCardTitle.vue +29 -0
  31. package/src/components/types.ts +45 -0
  32. package/src/index.ts +3 -0
  33. package/src/types.ts +74 -0
  34. package/tsconfig.json +4 -0
  35. package/tsconfig.tsbuildinfo +1 -0
  36. package/vite.config.ts +42 -0
  37. package/vitest.config.ts +26 -0
@@ -0,0 +1,458 @@
1
+ import { mount, config } from "@vue/test-utils";
2
+ import { describe, it, expect, vi, beforeEach } from "vitest";
3
+ import type { FzActionProps } from "@fiscozen/action";
4
+ import FzCardListItem from "../FzCardListItem.vue";
5
+
6
+ const actionA: FzActionProps = {
7
+ type: "action",
8
+ variant: "textLeft",
9
+ label: "Action A",
10
+ };
11
+
12
+ const actionB: FzActionProps = {
13
+ type: "action",
14
+ variant: "textLeft",
15
+ label: "Action B",
16
+ };
17
+
18
+ const linkAction = { type: "link" as const, to: "/" };
19
+
20
+ /** Status indicator icons (circle-small in both title-only and title+value rows) */
21
+ function indicatorIcons(wrapper: ReturnType<typeof mount>) {
22
+ return wrapper.findAllComponents({ name: "FzIcon" }).filter((w) => {
23
+ const name = w.props("name");
24
+ return name === "circle-small";
25
+ });
26
+ }
27
+
28
+ function descriptionParagraphs(wrapper: ReturnType<typeof mount>) {
29
+ const block = wrapper.find(".gap-section-content-none");
30
+ if (!block.exists()) return [];
31
+ return block.findAll("p");
32
+ }
33
+
34
+ const badgeDraft = { text: "Bozza", tone: "dark" as const };
35
+
36
+ beforeEach(() => {
37
+ // Mock IntersectionObserver for FzFloating / dropdown (not in jsdom)
38
+ global.IntersectionObserver = class IntersectionObserver {
39
+ constructor() {}
40
+ observe() {}
41
+ unobserve() {}
42
+ disconnect() {}
43
+ } as any;
44
+
45
+ config.global.directives = {
46
+ bold: () => {},
47
+ color: () => {},
48
+ small: () => {},
49
+ };
50
+
51
+ Object.defineProperty(window, "matchMedia", {
52
+ writable: true,
53
+ configurable: true,
54
+ value: vi.fn().mockImplementation((query) => ({
55
+ matches: false,
56
+ media: query,
57
+ onchange: null,
58
+ addListener: vi.fn(),
59
+ removeListener: vi.fn(),
60
+ addEventListener: vi.fn(),
61
+ removeEventListener: vi.fn(),
62
+ dispatchEvent: vi.fn(),
63
+ })),
64
+ });
65
+ });
66
+
67
+ describe("FzCardListItem", () => {
68
+ describe("Rendering", () => {
69
+ it("should render with only a title", async () => {
70
+ const wrapper = mount(FzCardListItem, {
71
+ props: { title: "My Title" },
72
+ });
73
+ await wrapper.vm.$nextTick();
74
+ expect(wrapper.exists()).toBe(true);
75
+ expect(wrapper.text()).toContain("My Title");
76
+ });
77
+
78
+ it("should render the title in a truncated paragraph", async () => {
79
+ const wrapper = mount(FzCardListItem, {
80
+ props: { title: "Bold Title" },
81
+ });
82
+ await wrapper.vm.$nextTick();
83
+ const titleEl = wrapper.find("p.truncate");
84
+ expect(titleEl.exists()).toBe(true);
85
+ expect(titleEl.text()).toBe("Bold Title");
86
+ });
87
+
88
+ it("should apply min-w-0 and flex-1 on the title paragraph so truncate works in flex rows", async () => {
89
+ const longTitle = "A".repeat(200);
90
+ const wrapper = mount(FzCardListItem, {
91
+ props: { title: longTitle },
92
+ });
93
+ await wrapper.vm.$nextTick();
94
+ const titles = wrapper.findAll("p.truncate");
95
+ expect(titles).toHaveLength(1);
96
+ for (const el of titles) {
97
+ expect(el.classes()).toEqual(
98
+ expect.arrayContaining(["min-w-0", "flex-1", "truncate"]),
99
+ );
100
+ }
101
+ });
102
+
103
+ it("should render a single title row with value (title lives in the second row when value is set)", async () => {
104
+ const longTitle = "A".repeat(200);
105
+ const wrapper = mount(FzCardListItem, {
106
+ props: { title: longTitle, value: "10,00 €" },
107
+ });
108
+ await wrapper.vm.$nextTick();
109
+ const titles = wrapper.findAll("p.truncate");
110
+ expect(titles).toHaveLength(1);
111
+ for (const el of titles) {
112
+ expect(el.classes()).toEqual(
113
+ expect.arrayContaining(["min-w-0", "flex-1", "truncate"]),
114
+ );
115
+ }
116
+ });
117
+
118
+ it("should render value when provided", async () => {
119
+ const wrapper = mount(FzCardListItem, {
120
+ props: { title: "Item", value: "1.200,00 €" },
121
+ });
122
+ await wrapper.vm.$nextTick();
123
+ expect(wrapper.text()).toContain("1.200,00 €");
124
+ });
125
+
126
+ it("should not render value when not provided", async () => {
127
+ const wrapper = mount(FzCardListItem, {
128
+ props: { title: "Item" },
129
+ });
130
+ await wrapper.vm.$nextTick();
131
+ const valueEl = wrapper.find("p.text-base.whitespace-nowrap");
132
+ expect(valueEl.exists()).toBe(false);
133
+ });
134
+
135
+ it("should render badge when provided", async () => {
136
+ const wrapper = mount(FzCardListItem, {
137
+ props: { title: "Item", badge: badgeDraft },
138
+ });
139
+ await wrapper.vm.$nextTick();
140
+ expect(wrapper.text()).toContain("Bozza");
141
+ });
142
+
143
+ it("should not render a trailing control when actions is omitted", async () => {
144
+ const wrapper = mount(FzCardListItem, {
145
+ props: { title: "Item" },
146
+ });
147
+ await wrapper.vm.$nextTick();
148
+ expect(wrapper.findComponent({ name: "FzIconButton" }).exists()).toBe(
149
+ false,
150
+ );
151
+ expect(wrapper.findComponent({ name: "FzIconDropdown" }).exists()).toBe(
152
+ false,
153
+ );
154
+ });
155
+
156
+ it("should not render a trailing control when actions is an empty array", async () => {
157
+ const wrapper = mount(FzCardListItem, {
158
+ props: { title: "Item", actions: [] },
159
+ });
160
+ await wrapper.vm.$nextTick();
161
+ expect(wrapper.findComponent({ name: "FzIconButton" }).exists()).toBe(
162
+ false,
163
+ );
164
+ expect(wrapper.findComponent({ name: "FzIconDropdown" }).exists()).toBe(
165
+ false,
166
+ );
167
+ });
168
+
169
+ it("should render arrow icon when exactly one link action is passed", async () => {
170
+ const wrapper = mount(FzCardListItem, {
171
+ props: { title: "Item", actions: [linkAction] },
172
+ });
173
+ await wrapper.vm.$nextTick();
174
+ const arrows = wrapper
175
+ .findAllComponents({ name: "FzIcon" })
176
+ .filter((w) => w.props("name") === "chevron-right");
177
+ expect(arrows).toHaveLength(1);
178
+ });
179
+
180
+ it("should apply cursor-pointer on the row only when exactly one link action is passed", async () => {
181
+ const none = mount(FzCardListItem, { props: { title: "Item" } });
182
+ const many = mount(FzCardListItem, {
183
+ props: { title: "Item", actions: [actionA, actionB] },
184
+ });
185
+ const one = mount(FzCardListItem, {
186
+ props: { title: "Item", actions: [linkAction] },
187
+ });
188
+ await Promise.all([
189
+ none.vm.$nextTick(),
190
+ many.vm.$nextTick(),
191
+ one.vm.$nextTick(),
192
+ ]);
193
+ expect(none.find(".cursor-pointer").exists()).toBe(false);
194
+ expect(many.find(".cursor-pointer").exists()).toBe(false);
195
+ expect(one.find(".cursor-pointer").exists()).toBe(true);
196
+ });
197
+
198
+ it("should render dropdown when more than one action is passed", async () => {
199
+ const wrapper = mount(FzCardListItem, {
200
+ props: { title: "Item", actions: [actionA, actionB] },
201
+ });
202
+ await wrapper.vm.$nextTick();
203
+ const dropdown = wrapper.findComponent({ name: "FzIconDropdown" });
204
+ expect(dropdown.exists()).toBe(true);
205
+ expect(dropdown.props("actions")).toEqual([actionA, actionB]);
206
+ });
207
+
208
+ it("should render badge in the top row when badge is provided", async () => {
209
+ const wrapper = mount(FzCardListItem, {
210
+ props: { title: "Item", badge: { text: "Tag", tone: "info" } },
211
+ });
212
+ await wrapper.vm.$nextTick();
213
+ expect(wrapper.findComponent({ name: "FzBadge" }).exists()).toBe(true);
214
+ });
215
+
216
+ it("should render description lines when provided", async () => {
217
+ const wrapper = mount(FzCardListItem, {
218
+ props: { title: "Item", descriptions: ["Line 1", "Line 2", "Line 3"] },
219
+ });
220
+ await wrapper.vm.$nextTick();
221
+ const paragraphs = descriptionParagraphs(wrapper);
222
+ expect(paragraphs).toHaveLength(3);
223
+ expect(paragraphs[0].text()).toBe("Line 1");
224
+ expect(paragraphs[1].text()).toBe("Line 2");
225
+ expect(paragraphs[2].text()).toBe("Line 3");
226
+ });
227
+
228
+ it("should not render description paragraphs when descriptions is not provided", async () => {
229
+ const wrapper = mount(FzCardListItem, {
230
+ props: { title: "Item" },
231
+ });
232
+ await wrapper.vm.$nextTick();
233
+ expect(descriptionParagraphs(wrapper)).toHaveLength(0);
234
+ });
235
+
236
+ it("should not render description paragraphs when descriptions is an empty array", async () => {
237
+ const wrapper = mount(FzCardListItem, {
238
+ props: { title: "Item", descriptions: [] },
239
+ });
240
+ await wrapper.vm.$nextTick();
241
+ expect(descriptionParagraphs(wrapper)).toHaveLength(0);
242
+ });
243
+ });
244
+
245
+ describe("Props", () => {
246
+ describe("showIndicator prop", () => {
247
+ it("should not render indicator icon when showIndicator is not true", async () => {
248
+ const wrapper = mount(FzCardListItem, {
249
+ props: { title: "Item" },
250
+ });
251
+ await wrapper.vm.$nextTick();
252
+ expect(indicatorIcons(wrapper)).toHaveLength(0);
253
+ });
254
+
255
+ it("should render circle-small icon when showIndicator is true (title-only layout)", async () => {
256
+ const wrapper = mount(FzCardListItem, {
257
+ props: { title: "Item", showIndicator: true },
258
+ });
259
+ await wrapper.vm.$nextTick();
260
+ const icons = indicatorIcons(wrapper);
261
+ expect(icons).toHaveLength(1);
262
+ expect(icons[0].props("name")).toBe("circle-small");
263
+ });
264
+
265
+ it("should render circle-small icon when showIndicator is true (with badge and amount)", async () => {
266
+ const wrapper = mount(FzCardListItem, {
267
+ props: {
268
+ title: "Item",
269
+ badge: { text: "X", tone: "dark" },
270
+ value: "10 €",
271
+ showIndicator: true,
272
+ },
273
+ });
274
+ await wrapper.vm.$nextTick();
275
+ const circleSmall = wrapper
276
+ .findAllComponents({ name: "FzIcon" })
277
+ .filter((w) => w.props("name") === "circle-small");
278
+ expect(circleSmall).toHaveLength(1);
279
+ });
280
+ });
281
+
282
+ describe("badge prop", () => {
283
+ it("should pass tone and variant text to FzBadge", async () => {
284
+ const wrapper = mount(FzCardListItem, {
285
+ props: {
286
+ title: "Item",
287
+ badge: { text: "Tag", tone: "success" },
288
+ },
289
+ });
290
+ await wrapper.vm.$nextTick();
291
+ const badge = wrapper.findComponent({ name: "FzBadge" });
292
+ expect(badge.exists()).toBe(true);
293
+ expect(badge.props("tone")).toBe("success");
294
+ expect(badge.props("variant")).toBe("text");
295
+ });
296
+ });
297
+ });
298
+
299
+ describe("Events", () => {
300
+ it("should emit fzaction:click when single link-action row is clicked", async () => {
301
+ const wrapper = mount(FzCardListItem, {
302
+ props: { title: "Item", actions: [linkAction] },
303
+ });
304
+ await wrapper.vm.$nextTick();
305
+
306
+ await wrapper.get('[role="button"]').trigger("click");
307
+
308
+ expect(wrapper.emitted("fzaction:click")).toBeTruthy();
309
+ expect(wrapper.emitted("fzaction:click")).toHaveLength(1);
310
+ expect(wrapper.emitted("fzaction:click")![0]).toEqual([0, linkAction]);
311
+ });
312
+
313
+ it("should emit fzaction:click when single link-action row is activated via Enter", async () => {
314
+ const wrapper = mount(FzCardListItem, {
315
+ props: { title: "Item", actions: [linkAction] },
316
+ });
317
+ await wrapper.vm.$nextTick();
318
+
319
+ const row = wrapper.get('[role="button"]');
320
+ await row.trigger("keydown", { key: "Enter" });
321
+
322
+ expect(wrapper.emitted("fzaction:click")).toBeTruthy();
323
+ expect(wrapper.emitted("fzaction:click")).toHaveLength(1);
324
+ expect(wrapper.emitted("fzaction:click")![0]).toEqual([0, linkAction]);
325
+ });
326
+
327
+ it("should emit fzaction:click when single link-action row is activated via Space", async () => {
328
+ const wrapper = mount(FzCardListItem, {
329
+ props: { title: "Item", actions: [linkAction] },
330
+ });
331
+ await wrapper.vm.$nextTick();
332
+
333
+ const row = wrapper.get('[role="button"]');
334
+ await row.trigger("keydown", { key: " " });
335
+
336
+ expect(wrapper.emitted("fzaction:click")).toHaveLength(1);
337
+ expect(wrapper.emitted("fzaction:click")![0]).toEqual([0, linkAction]);
338
+ });
339
+
340
+ it("should emit fzaction:click multiple times on repeated link-action row activations", async () => {
341
+ const wrapper = mount(FzCardListItem, {
342
+ props: { title: "Item", actions: [linkAction] },
343
+ });
344
+ await wrapper.vm.$nextTick();
345
+
346
+ const row = wrapper.get('[role="button"]');
347
+ await row.trigger("keydown", { key: "Enter" });
348
+ await row.trigger("keydown", { key: "Enter" });
349
+ await row.trigger("keydown", { key: "Enter" });
350
+
351
+ expect(wrapper.emitted("fzaction:click")).toHaveLength(3);
352
+ for (const payload of wrapper.emitted("fzaction:click")!) {
353
+ expect(payload).toEqual([0, linkAction]);
354
+ }
355
+ });
356
+
357
+ it("should forward fzaction:click from dropdown", async () => {
358
+ const wrapper = mount(FzCardListItem, {
359
+ props: { title: "Item", actions: [actionA, actionB] },
360
+ });
361
+ await wrapper.vm.$nextTick();
362
+
363
+ const dropdown = wrapper.findComponent({ name: "FzIconDropdown" });
364
+ await dropdown.vm.$emit("fzaction:click", 1, actionB);
365
+
366
+ expect(wrapper.emitted("fzaction:click")).toBeTruthy();
367
+ expect(wrapper.emitted("fzaction:click")![0]).toEqual([1, actionB]);
368
+ });
369
+ });
370
+
371
+ describe("Accessibility", () => {
372
+ it("should use the card row as a block root", async () => {
373
+ const wrapper = mount(FzCardListItem, {
374
+ props: { title: "Item" },
375
+ });
376
+ await wrapper.vm.$nextTick();
377
+ expect(wrapper.find(".p-8").exists()).toBe(true);
378
+ });
379
+
380
+ it("should expose link-action row as focusable button with labelled title", async () => {
381
+ const wrapper = mount(FzCardListItem, {
382
+ props: { title: "Pay invoice", actions: [linkAction] },
383
+ });
384
+ await wrapper.vm.$nextTick();
385
+ const row = wrapper.get('[role="button"]');
386
+ expect(row.attributes("tabindex")).toBe("0");
387
+ expect(row.attributes("aria-labelledby")).toBeTruthy();
388
+ expect(wrapper.get("p.truncate").text()).toBe("Pay invoice");
389
+ });
390
+ });
391
+
392
+ describe("Edge Cases", () => {
393
+ it("should render with only a title and no other props", async () => {
394
+ const wrapper = mount(FzCardListItem, {
395
+ props: { title: "Minimal" },
396
+ });
397
+ await wrapper.vm.$nextTick();
398
+ expect(wrapper.find("p.truncate").text()).toBe("Minimal");
399
+ expect(indicatorIcons(wrapper)).toHaveLength(0);
400
+ expect(descriptionParagraphs(wrapper)).toHaveLength(0);
401
+ expect(wrapper.findComponent({ name: "FzIconButton" }).exists()).toBe(
402
+ false,
403
+ );
404
+ });
405
+
406
+ it("should render badge without a trailing control when actions are omitted", async () => {
407
+ const wrapper = mount(FzCardListItem, {
408
+ props: { title: "Item", badge: { text: "Label", tone: "dark" } },
409
+ });
410
+ await wrapper.vm.$nextTick();
411
+ expect(wrapper.findComponent({ name: "FzBadge" }).exists()).toBe(true);
412
+ expect(wrapper.text()).toContain("Label");
413
+ expect(wrapper.findComponent({ name: "FzIconButton" }).exists()).toBe(
414
+ false,
415
+ );
416
+ });
417
+
418
+ it("should handle a single description line", async () => {
419
+ const wrapper = mount(FzCardListItem, {
420
+ props: { title: "Item", descriptions: ["Single description"] },
421
+ });
422
+ await wrapper.vm.$nextTick();
423
+ const paragraphs = descriptionParagraphs(wrapper);
424
+ expect(paragraphs).toHaveLength(1);
425
+ expect(paragraphs[0].text()).toBe("Single description");
426
+ });
427
+ });
428
+
429
+ describe("Snapshots", () => {
430
+ it("should match snapshot - minimal (title only)", () => {
431
+ const wrapper = mount(FzCardListItem, {
432
+ props: { title: "Item Title" },
433
+ });
434
+ expect(wrapper.html()).toMatchSnapshot();
435
+ });
436
+
437
+ it("should match snapshot - full featured item", () => {
438
+ const wrapper = mount(FzCardListItem, {
439
+ props: {
440
+ title: "Fattura #001",
441
+ value: "1.200,00 €",
442
+ badge: badgeDraft,
443
+ showIndicator: true,
444
+ descriptions: ["Cliente: Rossi S.r.l.", "Scadenza: 31/03/2024"],
445
+ actions: [actionA, actionB],
446
+ },
447
+ });
448
+ expect(wrapper.html()).toMatchSnapshot();
449
+ });
450
+
451
+ it("should match snapshot - with showIndicator", () => {
452
+ const wrapper = mount(FzCardListItem, {
453
+ props: { title: "Item", showIndicator: true },
454
+ });
455
+ expect(wrapper.html()).toMatchSnapshot();
456
+ });
457
+ });
458
+ });
@@ -0,0 +1,52 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`FzCardList > Snapshots > should match snapshot - default state 1`] = `"<div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch"></div>"`;
4
+
5
+ exports[`FzCardList > Snapshots > should match snapshot - with items 1`] = `
6
+ "<div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch">
7
+ <div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch p-8">
8
+ <!--v-if-->
9
+ <!-- Title + value row (when badge or value is present) -->
10
+ <!-- Title Only -->
11
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-sm align-items-center layout-space-between">
12
+ <!-- Title and indicator -->
13
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-xs align-items-center layout-default min-w-0">
14
+ <!-- Indicator --><span role="presentation" class="flex items-center justify-center size-[12.5px]"><svg class="svg-inline--fa fa-circle-small fa-xs h-[10px]" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="circle-small" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path class="" fill="currentColor" d="M0 256a160 160 0 1 1 320 0A160 160 0 1 1 0 256z"></path></svg></span><!-- Title -->
15
+ <p class="min-w-0 flex-1 truncate">Item 1</p>
16
+ </div><!-- Value -->
17
+ <p class="text-base whitespace-nowrap">1.200,00 €</p>
18
+ </div>
19
+ <!--v-if-->
20
+ </div>
21
+ <div class="w-full my-0">
22
+ <div class="border-solid border-t-1 border-grey-200"></div>
23
+ </div>
24
+ <div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch p-8">
25
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-base align-items-center layout-default">
26
+ <!-- Badge -->
27
+ <div class="flex items-center justify-center truncate max-w-full gap-4 py-4 px-12 h-24 w-fit rounded-2xl bg-grey-500 text-core-white" title="New">
28
+ <!--v-if-->
29
+ <p class="text-[inherit] text-sm">New</p>
30
+ <!--v-if-->
31
+ </div>
32
+ </div><!-- Title + value row (when badge or value is present) -->
33
+ <!-- Title Only -->
34
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-sm align-items-center layout-space-between">
35
+ <!-- Title and indicator -->
36
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-xs align-items-center layout-default min-w-0">
37
+ <!-- Indicator -->
38
+ <!--v-if-->
39
+ <!-- Title -->
40
+ <p class="min-w-0 flex-1 truncate">Item 2</p>
41
+ </div><!-- Value -->
42
+ <!--v-if-->
43
+ </div>
44
+ <div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-none align-items-stretch">
45
+ <p>Due date: 2024-03-01</p>
46
+ </div>
47
+ </div>
48
+ <div class="w-full my-0">
49
+ <div class="border-solid border-t-1 border-grey-200"></div>
50
+ </div>
51
+ </div>"
52
+ `;
@@ -0,0 +1,90 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`FzCardListItem > Snapshots > should match snapshot - full featured item 1`] = `
4
+ "<div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch p-8 hover:bg-semantic-info-50 hover:rounded">
5
+ <!--
6
+ Header row layout:
7
+ - badge present → badge + actions
8
+ - hasTitleOnly (no badge, no value) → title + actions (inline)
9
+ - value present, no badge → actions only; title + value are in the second row
10
+ -->
11
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-base align-items-center layout-default">
12
+ <!-- Badge -->
13
+ <div class="flex items-center justify-center truncate max-w-full gap-4 py-4 px-12 h-24 w-fit rounded-2xl bg-grey-500 text-core-white" title="Bozza">
14
+ <!--v-if-->
15
+ <p class="text-[inherit] text-sm">Bozza</p>
16
+ <!--v-if-->
17
+ </div><!-- Multiple actions dropdown -->
18
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-xs align-items-center layout-expand-last shrink-0 ml-auto">
19
+ <div iconname="ellipsis-vertical" hasnotification="false" label="Open dropdown" variant="invisible" aria-label="Mostra azioni">
20
+ <div class="inline-flex w-full sm:w-auto"><span data-v-8090b1b6="" class="fz-icon-button-wrapper relative fz-icon-button-wrapper--frontoffice"><button data-v-8090b1b6="" type="button" aria-disabled="false" class="relative rounded flex flex-shrink items-center justify-center font-normal !text-[16px] !leading-[20px] border-1 border-transparent gap-8 h-44 px-12 min-w-96 bg-core-white text-grey-500 !border-grey-200 hover:bg-grey-100 hover:!border-grey-200 focus:bg-core-white focus:!border-blue-600 disabled:bg-grey-50 disabled:text-grey-200 disabled:!border-grey-200" aria-label="Open dropdown"><div class="flex items-center justify-items-center"><span role="presentation" class="flex items-center justify-center w-[20px] h-[20px]"><svg class="svg-inline--fa fa-ellipsis-vertical h-[16px]" aria-hidden="true" focusable="false" data-prefix="far" data-icon="ellipsis-vertical" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 512"><path class="" fill="currentColor" d="M64 368a48 48 0 1 0 0 96 48 48 0 1 0 0-96zm0-160a48 48 0 1 0 0 96 48 48 0 1 0 0-96zM112 96A48 48 0 1 0 16 96a48 48 0 1 0 96 0z"></path></svg></span></div>
21
+ <!--v-if-->
22
+ <!--v-if--></button>
23
+ <!--v-if--></span>
24
+ </div>
25
+ <!--v-if-->
26
+ <!--teleport start-->
27
+ <!--teleport end-->
28
+ </div>
29
+ </div>
30
+ </div><!-- Title + value row (when badge or value is present) -->
31
+ <!-- Title Only -->
32
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-sm align-items-center layout-space-between">
33
+ <!-- Title and indicator -->
34
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-xs align-items-center layout-default min-w-0">
35
+ <!-- Indicator --><span role="presentation" class="flex items-center justify-center size-[12.5px]"><svg class="svg-inline--fa fa-circle-small fa-xs h-[10px]" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="circle-small" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path class="" fill="currentColor" d="M0 256a160 160 0 1 1 320 0A160 160 0 1 1 0 256z"></path></svg></span><!-- Title -->
36
+ <p class="min-w-0 flex-1 truncate">Fattura #001</p>
37
+ </div><!-- Value -->
38
+ <p class="text-base whitespace-nowrap">1.200,00 €</p>
39
+ </div>
40
+ <div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-none align-items-stretch">
41
+ <p>Cliente: Rossi S.r.l.</p>
42
+ <p>Scadenza: 31/03/2024</p>
43
+ </div>
44
+ </div>
45
+ <div class="w-full my-0">
46
+ <div class="border-solid border-t-1 border-grey-200"></div>
47
+ </div>"
48
+ `;
49
+
50
+ exports[`FzCardListItem > Snapshots > should match snapshot - minimal (title only) 1`] = `
51
+ "<div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch p-8">
52
+ <!--v-if-->
53
+ <!-- Title + value row (when badge or value is present) -->
54
+ <!-- Title Only -->
55
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-sm align-items-center layout-space-between">
56
+ <!-- Title and indicator -->
57
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-xs align-items-center layout-default min-w-0">
58
+ <!-- Indicator -->
59
+ <!--v-if-->
60
+ <!-- Title -->
61
+ <p class="min-w-0 flex-1 truncate">Item Title</p>
62
+ </div><!-- Value -->
63
+ <!--v-if-->
64
+ </div>
65
+ <!--v-if-->
66
+ </div>
67
+ <div class="w-full my-0">
68
+ <div class="border-solid border-t-1 border-grey-200"></div>
69
+ </div>"
70
+ `;
71
+
72
+ exports[`FzCardListItem > Snapshots > should match snapshot - with showIndicator 1`] = `
73
+ "<div data-v-da7f5873="" class="fz-container fz-container--vertical gap-section-content-xs align-items-stretch p-8">
74
+ <!--v-if-->
75
+ <!-- Title + value row (when badge or value is present) -->
76
+ <!-- Title Only -->
77
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-sm align-items-center layout-space-between">
78
+ <!-- Title and indicator -->
79
+ <div data-v-da7f5873="" class="fz-container fz-container--horizontal gap-section-content-xs align-items-center layout-default min-w-0">
80
+ <!-- Indicator --><span role="presentation" class="flex items-center justify-center size-[12.5px]"><svg class="svg-inline--fa fa-circle-small fa-xs h-[10px]" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="circle-small" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path class="" fill="currentColor" d="M0 256a160 160 0 1 1 320 0A160 160 0 1 1 0 256z"></path></svg></span><!-- Title -->
81
+ <p class="min-w-0 flex-1 truncate">Item</p>
82
+ </div><!-- Value -->
83
+ <!--v-if-->
84
+ </div>
85
+ <!--v-if-->
86
+ </div>
87
+ <div class="w-full my-0">
88
+ <div class="border-solid border-t-1 border-grey-200"></div>
89
+ </div>"
90
+ `;