vueless 1.1.1-beta.2 → 1.1.1-beta.20

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 (55) hide show
  1. package/adapter.locale/vue-i18n.ts +3 -2
  2. package/adapter.locale/vueless.ts +2 -23
  3. package/composables/tests/useUI.test.ts +28 -13
  4. package/composables/useComponentLocaleMassages.ts +6 -3
  5. package/composables/useLocale.ts +2 -2
  6. package/constants.js +2 -5
  7. package/directives/tooltip/storybook/docs.mdx +37 -0
  8. package/directives/tooltip/storybook/stories.ts +11 -8
  9. package/directives/tooltip/vTooltip.ts +1 -2
  10. package/icons/storybook/contact_mail.svg +1 -0
  11. package/icons/storybook/vpn_key.svg +1 -0
  12. package/icons/storybook/web_traffic.svg +1 -0
  13. package/index.d.ts +2 -1
  14. package/index.ts +2 -1
  15. package/package.json +2 -3
  16. package/plugin-vite.js +1 -8
  17. package/tailwind.css +25 -0
  18. package/types.ts +21 -1
  19. package/ui.boilerplate/tests/UBoilerplate.test.ts +2 -24
  20. package/ui.container-accordion/UAccordion.vue +15 -2
  21. package/ui.container-accordion/config.ts +1 -0
  22. package/ui.container-accordion/storybook/stories.ts +28 -1
  23. package/ui.container-accordion/tests/UAccordion.test.ts +46 -0
  24. package/ui.data-list/UDataList.vue +2 -0
  25. package/ui.data-table/UTable.vue +19 -13
  26. package/ui.data-table/storybook/stories.ts +11 -0
  27. package/ui.data-table/tests/UTable.test.ts +18 -0
  28. package/ui.dropdown-button/UDropdownButton.vue +1 -0
  29. package/ui.dropdown-button/config.ts +9 -1
  30. package/ui.dropdown-button/storybook/stories.ts +1 -1
  31. package/ui.dropdown-button/tests/UDropdownButton.test.ts +17 -1
  32. package/ui.dropdown-button/types.ts +5 -0
  33. package/ui.form-calendar/UCalendar.vue +9 -7
  34. package/ui.form-input/tests/UInput.test.ts +1 -1
  35. package/ui.form-input-counter/tests/UInputCounter.test.ts +1 -1
  36. package/ui.form-input-file/tests/UInputFile.test.ts +1 -1
  37. package/ui.form-input-password/tests/UInputPassword.test.ts +3 -3
  38. package/ui.form-input-rating/tests/UInputRating.test.ts +2 -2
  39. package/ui.form-input-search/tests/UInputSearch.test.ts +2 -2
  40. package/ui.form-listbox/UListbox.vue +1 -1
  41. package/ui.form-listbox/storybook/stories.ts +5 -5
  42. package/ui.form-select/config.ts +1 -1
  43. package/ui.form-select/storybook/stories.ts +5 -5
  44. package/ui.image-icon/tests/UIcon.test.ts +2 -2
  45. package/ui.navigation-pagination/UPagination.vue +2 -7
  46. package/ui.navigation-pagination/config.ts +1 -0
  47. package/ui.navigation-pagination/tests/UPagination.test.ts +1 -1
  48. package/ui.navigation-pagination/types.ts +1 -1
  49. package/ui.text-notify/tests/UNotify.test.ts +1 -1
  50. package/utils/node/dynamicProps.js +17 -8
  51. package/utils/node/helper.js +11 -1
  52. package/utils/node/storybook.js +52 -0
  53. package/utils/node/vuelessConfig.js +18 -8
  54. package/utils/theme.ts +13 -11
  55. package/utils/node/dynamicStories.js +0 -62
@@ -82,10 +82,12 @@ describe("UAccordion", () => {
82
82
  // ID prop
83
83
  it("uses provided id prop", () => {
84
84
  const id = "custom-id";
85
+ const description = "some text";
85
86
 
86
87
  const component = mount(UAccordion, {
87
88
  props: {
88
89
  id,
90
+ description,
89
91
  },
90
92
  });
91
93
 
@@ -173,6 +175,50 @@ describe("UAccordion", () => {
173
175
 
174
176
  expect(toggleElement.attributes("data-opened")).toBe("true");
175
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 () => {
216
+ const component = mount(UAccordion);
217
+
218
+ await component.trigger("click");
219
+
220
+ expect(component.find("[vl-key='content']").exists()).toBe(false);
221
+ });
176
222
  });
177
223
 
178
224
  // Events
@@ -186,6 +186,7 @@ const {
186
186
  :data-test="getDataTest('table')"
187
187
  @drag-sort="onDragEnd"
188
188
  >
189
+ <!-- @vue-ignore -->
189
190
  <template #label="slotProps: { item: DataListItem; crossed: boolean }">
190
191
  <!--
191
192
  @slot Use it to modify label.
@@ -200,6 +201,7 @@ const {
200
201
  </slot>
201
202
  </template>
202
203
 
204
+ <!-- @vue-ignore -->
203
205
  <template #actions="slotProps: { item: DataListItem }">
204
206
  <!--
205
207
  @slot Use it to add custom actions.
@@ -378,9 +378,7 @@ function onChangeLocalSelectedRows(selectedRows: Row[]) {
378
378
 
379
379
  selectAll.value = !!selectedRows.length;
380
380
 
381
- if (!isEqual(localSelectedRows.value, props.selectedRows)) {
382
- emit("update:selectedRows", localSelectedRows.value);
383
- }
381
+ emit("update:selectedRows", localSelectedRows.value);
384
382
  }
385
383
 
386
384
  function clearSelectedItems() {
@@ -533,11 +531,15 @@ const {
533
531
  :data-test="getDataTest('select-all')"
534
532
  />
535
533
 
536
- <div
537
- v-if="localSelectedRows.length"
538
- v-bind="stickyHeaderCounterAttrs"
539
- v-text="localSelectedRows.length"
540
- />
534
+ <div v-if="localSelectedRows.length" v-bind="stickyHeaderCounterAttrs">
535
+ <!--
536
+ @slot Use it to customize header counter.
537
+ @binding {number} total
538
+ -->
539
+ <slot name="header-counter" :total="localSelectedRows.length">
540
+ {{ localSelectedRows.length }}
541
+ </slot>
542
+ </div>
541
543
  </div>
542
544
 
543
545
  <!-- TODO: Remove any when key attrs are typed-->
@@ -659,11 +661,15 @@ const {
659
661
  :data-test="getDataTest('select-all')"
660
662
  />
661
663
 
662
- <div
663
- v-if="localSelectedRows.length"
664
- v-bind="headerCounterAttrs"
665
- v-text="localSelectedRows.length"
666
- />
664
+ <div v-if="localSelectedRows.length" v-bind="headerCounterAttrs">
665
+ <!--
666
+ @slot Use it to customize header counter.
667
+ @binding {number} total
668
+ -->
669
+ <slot name="header-counter" :total="localSelectedRows.length">
670
+ {{ localSelectedRows.length }}
671
+ </slot>
672
+ </div>
667
673
  </th>
668
674
 
669
675
  <th
@@ -474,6 +474,17 @@ DateDividerCustomLabel.parameters = {
474
474
  },
475
475
  };
476
476
 
477
+ export const HeaderCounterSlot = DefaultTemplate.bind({});
478
+ HeaderCounterSlot.args = {
479
+ selectable: true,
480
+ config: { headerCellCheckbox: "w-20" },
481
+ slotTemplate: `
482
+ <template #header-counter="{ total }">
483
+ Total: {{ total }}
484
+ </template>
485
+ `,
486
+ };
487
+
477
488
  export const HeaderKeySlot = DefaultTemplate.bind({});
478
489
  HeaderKeySlot.args = {
479
490
  slotTemplate: `
@@ -294,6 +294,24 @@ describe("UTable.vue", () => {
294
294
  });
295
295
 
296
296
  describe("Slots", () => {
297
+ it("Header Counter Slot – renders custom header counter content", () => {
298
+ const customHeaderCounterContent = "Custom Header Counter";
299
+
300
+ const component = mountUTable(
301
+ getDefaultProps({
302
+ selectable: true,
303
+ selectedRows: [defaultRows[0]],
304
+ }),
305
+ {
306
+ slots: {
307
+ "header-counter": customHeaderCounterContent,
308
+ },
309
+ },
310
+ );
311
+
312
+ expect(component.text()).toContain(customHeaderCounterContent);
313
+ });
314
+
297
315
  it("Header Slot – renders custom header content", () => {
298
316
  const customHeaderContent = "Custom Name Header";
299
317
 
@@ -173,6 +173,7 @@ const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs,
173
173
  :label="buttonLabel"
174
174
  :size="size"
175
175
  :color="color"
176
+ :block="block"
176
177
  :round="round"
177
178
  :square="square"
178
179
  :variant="variant"
@@ -1,5 +1,12 @@
1
1
  export default /*tw*/ {
2
- wrapper: "relative inline-block h-max",
2
+ wrapper: {
3
+ base: "relative inline-block h-max",
4
+ variants: {
5
+ block: {
6
+ true: "w-full",
7
+ },
8
+ },
9
+ },
3
10
  dropdownButton: "{UButton} justify-between",
4
11
  toggleIcon: {
5
12
  base: "{UIcon} transition duration-300 -mr-1",
@@ -48,6 +55,7 @@ export default /*tw*/ {
48
55
  xPosition: "left",
49
56
  searchable: false,
50
57
  round: false,
58
+ block: false,
51
59
  square: false,
52
60
  disabled: false,
53
61
  multiple: false,
@@ -167,7 +167,7 @@ export const ListboxXPosition = EnumTemplate.bind({});
167
167
  ListboxXPosition.args = {
168
168
  enum: "xPosition",
169
169
  label: "{enumValue}",
170
- class: "w-40",
170
+ block: true,
171
171
  };
172
172
 
173
173
  export const ListboxYPosition = EnumTemplate.bind({});
@@ -207,6 +207,22 @@ describe("UDropdownButton.vue", () => {
207
207
  expect(component.findComponent(UButton).props("disabled")).toBe(disabled);
208
208
  });
209
209
 
210
+ // Block prop
211
+ it("applies class when prop is true", () => {
212
+ const block = true;
213
+ const expectedClass = "w-full";
214
+
215
+ const component = mount(UDropdownButton, {
216
+ props: {
217
+ block,
218
+ options: defaultOptions,
219
+ },
220
+ });
221
+
222
+ expect(component.attributes("class")).toContain(expectedClass);
223
+ expect(component.findComponent(UButton).attributes("class")).toContain(expectedClass);
224
+ });
225
+
210
226
  // ToggleIcon prop (boolean: true)
211
227
  it("shows default toggle icon when toggleIcon is true", () => {
212
228
  const toggleIcon = true;
@@ -225,7 +241,7 @@ describe("UDropdownButton.vue", () => {
225
241
  });
226
242
 
227
243
  // ToggleIcon prop (boolean: false)
228
- it("shows default toggle icon when toggleIcon is true", () => {
244
+ it("shows default toggle icon when toggleIcon is false", () => {
229
245
  const toggleIcon = false;
230
246
 
231
247
  const component = mount(UDropdownButton, {
@@ -75,6 +75,11 @@ export interface Props {
75
75
  */
76
76
  multiple?: boolean;
77
77
 
78
+ /**
79
+ * Make the dropdown button expand to fill the entire width of its container.
80
+ */
81
+ block?: boolean;
82
+
78
83
  /**
79
84
  * Set button corners rounded.
80
85
  */
@@ -232,13 +232,15 @@ const localValue = computed({
232
232
  parsedDate.setSeconds(Number(secondsRef.value?.value));
233
233
  }
234
234
 
235
- const isOutOfRange = dateIsOutOfRange(
236
- parsedDate || new Date(),
237
- props.minDate,
238
- props.maxDate,
239
- locale.value,
240
- actualDateFormat.value,
241
- );
235
+ const isOutOfRange =
236
+ parsedDate !== null &&
237
+ dateIsOutOfRange(
238
+ parsedDate,
239
+ props.minDate,
240
+ props.maxDate,
241
+ locale.value,
242
+ actualDateFormat.value,
243
+ );
242
244
 
243
245
  if (isOutOfRange) {
244
246
  return;
@@ -291,7 +291,7 @@ describe("UInput.vue", () => {
291
291
  expect(component.get("input").attributes("id")).toBe(idValue);
292
292
  });
293
293
 
294
- it("DataTest – sets data-test attribute on input", () => {
294
+ it("Data Test – sets data-test attribute on input", () => {
295
295
  const dataTestValue = "test-input";
296
296
 
297
297
  const component = mount(UInput, {
@@ -261,7 +261,7 @@ describe("UInputCounter.vue", () => {
261
261
  },
262
262
  });
263
263
 
264
- component.get(`[data-test='test-${testCase}']`);
264
+ expect(component.get(`[data-test='test-${testCase}']`)).toBeTruthy();
265
265
  });
266
266
  });
267
267
  });
@@ -250,7 +250,7 @@ describe("UInputFile.vue", () => {
250
250
  },
251
251
  });
252
252
 
253
- component.get(`[data-test="${dataTestValue}-${key}"]`);
253
+ expect(component.get(`[data-test="${dataTestValue}-${key}"]`)).toBeTruthy();
254
254
  });
255
255
  });
256
256
  });
@@ -172,7 +172,7 @@ describe("UInputPassword.vue", () => {
172
172
 
173
173
  await flushPromises();
174
174
 
175
- component.get(`[data-test='${dataTest}-password-icon']`);
175
+ expect(component.get(`[data-test='${dataTest}-password-icon']`)).toBeTruthy();
176
176
  });
177
177
  });
178
178
 
@@ -215,7 +215,7 @@ describe("UInputPassword.vue", () => {
215
215
  },
216
216
  });
217
217
 
218
- component.get(`.${testClass}`);
218
+ expect(component.get(`.${testClass}`)).toBeTruthy();
219
219
  });
220
220
 
221
221
  it("Left – exposes leftIcon prop", () => {
@@ -242,7 +242,7 @@ describe("UInputPassword.vue", () => {
242
242
  },
243
243
  });
244
244
 
245
- component.get(`.${testClass}`);
245
+ expect(component.get(`.${testClass}`)).toBeTruthy();
246
246
  });
247
247
 
248
248
  it("Right – exposes password visibility state", async () => {
@@ -18,8 +18,8 @@ describe("UInputRating.vue", () => {
18
18
 
19
19
  const icons = component.findAllComponents(UIcon);
20
20
 
21
- expect(icons[2].props("name")).that.includes("star-fill");
22
- expect(icons[3].props("name")).that.includes("star");
21
+ expect(icons[2].props("name")).toContain("star-fill");
22
+ expect(icons[3].props("name")).toContain("star");
23
23
  });
24
24
 
25
25
  it("Model Value – updates value on click", async () => {
@@ -227,7 +227,7 @@ describe("UInputSearch.vue", () => {
227
227
  expect(component.get("input").attributes("disabled")).toBeDefined();
228
228
  });
229
229
 
230
- it("Data Test – applies the correct data-test attribute", async () => {
230
+ it("Data Test – applies the correct data-test attribute", () => {
231
231
  const testCases = [
232
232
  { testCase: "search-icon" },
233
233
  { testCase: "clear" },
@@ -248,7 +248,7 @@ describe("UInputSearch.vue", () => {
248
248
 
249
249
  await flushPromises();
250
250
 
251
- component.get(`[data-test='${resolvedDataTest}']`);
251
+ expect(component.get(`[data-test='${resolvedDataTest}']`)).toBeTruthy();
252
252
  });
253
253
  });
254
254
  });
@@ -471,7 +471,7 @@ const {
471
471
  <span
472
472
  :style="getMarginForSubCategory(option.level)"
473
473
  v-bind="optionContentAttrs"
474
- :title="String(option.label)"
474
+ :title="String(option[labelKey])"
475
475
  v-text="option[labelKey]"
476
476
  />
477
477
  </slot>
@@ -26,11 +26,11 @@ export default {
26
26
  component: UListbox,
27
27
  args: {
28
28
  options: [
29
- { label: "New York", id: ["1"] },
30
- { label: "Los Angeles", id: ["2"] },
31
- { label: "Chicago", id: ["3"] },
32
- { label: "Houston", id: ["4"] },
33
- { label: "San Francisco", id: ["5"] },
29
+ { label: "New York", id: 1 },
30
+ { label: "Los Angeles", id: 2 },
31
+ { label: "Chicago", id: 3 },
32
+ { label: "Houston", id: 4 },
33
+ { label: "San Francisco", id: 5 },
34
34
  ],
35
35
  },
36
36
  argTypes: {
@@ -118,7 +118,7 @@ export default /*tw*/ {
118
118
  clear: "{>toggle}",
119
119
  clearIcon: "{UIcon} {>selectIcon}",
120
120
  placeholder: {
121
- base: "flex items-center text-muted !leading-none",
121
+ base: "flex items-center text-muted !leading-none shrink-0",
122
122
  variants: {
123
123
  size: {
124
124
  sm: "text-small",
@@ -38,11 +38,11 @@ export default {
38
38
  label: "Choose a city",
39
39
  modelValue: null,
40
40
  options: [
41
- { label: "New York", id: "1" },
42
- { label: "Los Angeles", id: "2" },
43
- { label: "Chicago", id: "3" },
44
- { label: "Houston", id: "4" },
45
- { label: "San Francisco", id: "5" },
41
+ { label: "New York", id: 1 },
42
+ { label: "Los Angeles", id: 2 },
43
+ { label: "Chicago", id: 3 },
44
+ { label: "Houston", id: 4 },
45
+ { label: "San Francisco", id: 5 },
46
46
  ],
47
47
  },
48
48
  argTypes: {
@@ -20,7 +20,7 @@ describe("UIcon.vue", () => {
20
20
 
21
21
  await flushPromises();
22
22
 
23
- component.get("[vl-key='icon']");
23
+ expect(component.get("[vl-key='icon']")).toBeTruthy();
24
24
  });
25
25
 
26
26
  it("Src – renders icon based on provided src", async () => {
@@ -32,7 +32,7 @@ describe("UIcon.vue", () => {
32
32
 
33
33
  await flushPromises();
34
34
 
35
- component.get("[vl-key='icon']");
35
+ expect(component.get("[vl-key='icon']")).toBeTruthy();
36
36
  });
37
37
 
38
38
  it("Color – applies correct color classes", () => {
@@ -124,6 +124,7 @@ const {
124
124
  nextButtonAttrs,
125
125
  activeButtonAttrs,
126
126
  inactiveButtonAttrs,
127
+ ellipsisAttrs,
127
128
  lastIconAttrs,
128
129
  firstIconAttrs,
129
130
  prevIconAttrs,
@@ -181,13 +182,7 @@ const {
181
182
  </UButton>
182
183
 
183
184
  <template v-for="page in pageButtons" :key="page">
184
- <UButton
185
- v-if="!isFinite(page.number)"
186
- square
187
- disabled
188
- variant="ghost"
189
- v-bind="inactiveButtonAttrs"
190
- >
185
+ <UButton v-if="!isFinite(page.number)" square disabled variant="ghost" v-bind="ellipsisAttrs">
191
186
  <!-- @slot Use it to add something instead of the ellipsis. -->
192
187
  <slot name="ellipsis">&hellip;</slot>
193
188
  </UButton>
@@ -19,6 +19,7 @@ export default /*tw*/ {
19
19
  nextButton: "{>paginationButton}",
20
20
  inactiveButton: "{>paginationButton}",
21
21
  activeButton: "{>paginationButton}",
22
+ ellipsis: "{>paginationButton} {>inactiveButton}",
22
23
  paginationIcon: {
23
24
  base: "{UIcon}",
24
25
  defaults: {
@@ -12,7 +12,7 @@ describe("UPagination.vue", () => {
12
12
  describe("Props", () => {
13
13
  // Variant prop
14
14
  it("applies the correct variant to buttons", async () => {
15
- const variants = ["solid", "outlined", "soft", "ghost"];
15
+ const variants = ["solid", "outlined", "subtle", "soft", "ghost"];
16
16
 
17
17
  variants.forEach((variant) => {
18
18
  const component = mount(UPagination, {
@@ -27,7 +27,7 @@ export interface Props {
27
27
  /**
28
28
  * Pagination variant.
29
29
  */
30
- variant?: "solid" | "outlined" | "soft" | "ghost";
30
+ variant?: "solid" | "outlined" | "subtle" | "soft" | "ghost";
31
31
 
32
32
  /**
33
33
  * Pagination size.
@@ -6,7 +6,7 @@ import UIcon from "../../ui.image-icon/UIcon.vue";
6
6
 
7
7
  import { NotificationType } from "../constants.ts";
8
8
  import { LocaleSymbol } from "../../composables/useLocale.ts";
9
- import createVuelessAdapter from "../../adapter.locale/vueless.ts";
9
+ import { createVuelessAdapter } from "../../adapter.locale/vueless.ts";
10
10
 
11
11
  import type { Props, Notification } from "../types.ts";
12
12
 
@@ -2,9 +2,11 @@ import fs from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
4
 
5
+ import { removeFolderIfEmpty } from "./helper.js";
5
6
  import { vuelessConfig } from "./vuelessConfig.js";
6
7
 
7
8
  import {
9
+ CACHE_DIR,
8
10
  COMPONENTS,
9
11
  GRAYSCALE_COLOR,
10
12
  INHERIT_COLOR,
@@ -74,9 +76,8 @@ export async function setCustomPropTypes(srcDir) {
74
76
  }
75
77
 
76
78
  const isCustomProps = componentGlobalConfig && componentGlobalConfig.props;
77
- const isHiddenStories = componentGlobalConfig && componentGlobalConfig.storybook === false;
78
79
 
79
- if (isCustomProps && !isHiddenStories) {
80
+ if (isCustomProps) {
80
81
  await cacheComponentTypes(path.join(srcDir, componentDir));
81
82
  await modifyComponentTypes(path.join(srcDir, componentDir), componentGlobalConfig.props);
82
83
  }
@@ -91,26 +92,34 @@ export async function removeCustomPropTypes(srcDir) {
91
92
  }
92
93
 
93
94
  async function cacheComponentTypes(filePath) {
94
- const cacheDir = path.join(filePath, ".cache");
95
+ const cacheDir = path.join(filePath, CACHE_DIR);
95
96
  const sourceFile = path.join(filePath, "types.ts");
96
97
  const destFile = path.join(cacheDir, "types.ts");
97
98
 
98
- if (existsSync(cacheDir)) {
99
+ if (existsSync(destFile) || !existsSync(sourceFile)) {
99
100
  return;
100
101
  }
101
102
 
102
- if (existsSync(sourceFile)) {
103
+ if (!existsSync(cacheDir)) {
103
104
  await fs.mkdir(cacheDir);
104
- await fs.cp(sourceFile, destFile);
105
105
  }
106
+
107
+ await fs.cp(sourceFile, destFile);
106
108
  }
107
109
 
108
110
  async function clearComponentTypesCache(filePath) {
109
- await fs.rm(path.join(filePath, ".cache"), { force: true, recursive: true });
111
+ const cacheDir = path.join(filePath, CACHE_DIR);
112
+ const sourceFile = path.join(cacheDir, "types.ts");
113
+
114
+ if (existsSync(sourceFile)) {
115
+ await fs.rm(sourceFile, { force: true });
116
+ }
117
+
118
+ await removeFolderIfEmpty(cacheDir);
110
119
  }
111
120
 
112
121
  async function restoreComponentTypes(filePath) {
113
- const cacheDir = path.join(filePath, ".cache");
122
+ const cacheDir = path.join(filePath, CACHE_DIR);
114
123
  const sourceFile = path.join(cacheDir, "types.ts");
115
124
  const destFile = path.join(filePath, "types.ts");
116
125
 
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { cwd } from "node:process";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { existsSync, statSync } from "node:fs";
6
- import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
6
+ import { mkdir, readdir, rmdir, readFile, writeFile } from "node:fs/promises";
7
7
 
8
8
  import { vuelessConfig, getMergedConfig } from "./vuelessConfig.js";
9
9
 
@@ -157,6 +157,16 @@ export async function buildTSFile(entryPath, configOutFile) {
157
157
  });
158
158
  }
159
159
 
160
+ export async function removeFolderIfEmpty(dirPath) {
161
+ if (existsSync(dirPath)) {
162
+ const files = await readdir(dirPath);
163
+
164
+ if (!files.length) {
165
+ await rmdir(dirPath);
166
+ }
167
+ }
168
+ }
169
+
160
170
  export async function autoImportUserConfigs() {
161
171
  const vuelessConfigDir = path.join(cwd(), ".vueless");
162
172
 
@@ -0,0 +1,52 @@
1
+ import { getVuelessConfig } from "./vuelessConfig.js";
2
+ import {
3
+ COMPONENTS,
4
+ INTERNAL_ENV,
5
+ VUELESS_LOCAL_DIR,
6
+ VUELESS_PACKAGE_DIR,
7
+ } from "../../constants.js";
8
+
9
+ /**
10
+ * Defines the config for Storybook.
11
+ *
12
+ * @param {Object} config - The config object.
13
+ * @return {Promise<Object>} A promise that resolves to the modified config object.
14
+ */
15
+ export function defineConfigWithVueless(config) {
16
+ return (async () => ({
17
+ ...config,
18
+ stories: [...config.stories, ...(await getVuelessStoriesGlob(config?.vuelessEnv))],
19
+ }))();
20
+ }
21
+
22
+ /**
23
+ * Retrieves the glob pattern for Vueless stories based on the provided Vueless environment.
24
+ *
25
+ * @param {string} vuelessEnv - The Vueless environment.
26
+ * @return {Promise<string[]>} A promise that resolves to an array of glob patterns for Vueless stories.
27
+ */
28
+ export async function getVuelessStoriesGlob(vuelessEnv) {
29
+ const vuelessSrcDir = vuelessEnv === INTERNAL_ENV ? VUELESS_LOCAL_DIR : VUELESS_PACKAGE_DIR;
30
+
31
+ const storiesGlob = [
32
+ `../${vuelessSrcDir}/directives/**/stories.{js,jsx,ts,tsx}`,
33
+ `../${vuelessSrcDir}/directives/**/docs.mdx`,
34
+ ];
35
+
36
+ const vuelessConfig = await getVuelessConfig();
37
+
38
+ for (const [componentName, componentDir] of Object.entries(COMPONENTS)) {
39
+ const componentGlobalConfig = vuelessConfig.components?.[componentName];
40
+ const isHiddenStoriesByComponent = componentGlobalConfig === false;
41
+ const isHiddenStoriesByKey = componentGlobalConfig?.storybook === false;
42
+
43
+ if (isHiddenStoriesByComponent || isHiddenStoriesByKey) {
44
+ continue;
45
+ }
46
+
47
+ storiesGlob.push(`../${vuelessSrcDir}/${componentDir}/storybook/stories.{js,jsx,ts,tsx}`);
48
+ storiesGlob.push(`../${vuelessSrcDir}/${componentDir}/storybook/docs.mdx`);
49
+ }
50
+
51
+ return storiesGlob;
52
+ }