vueless 0.0.680 → 0.0.682

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,94 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { cwd } from "node:process";
6
+ import { cp, readFile, writeFile, rename } from "node:fs/promises";
7
+ import { styleText } from "node:util";
8
+
9
+ import { getDirFiles } from "../../utils/node/helper.js";
10
+ import { replaceRelativeImports } from "../utils/formatUtil.js";
11
+ import { getStorybookId, getStoryMetaKeyIndex } from "../utils/dataUtils.js";
12
+
13
+ import { SRC_COMPONENTS_PATH, COMPONENTS_PATH } from "../constants.js";
14
+ import { COMPONENTS, VUELESS_DIR, VUELESS_LOCAL_DIR } from "../../constants.js";
15
+
16
+ function getSourcePath(componentName) {
17
+ return path.join(cwd(), VUELESS_DIR, COMPONENTS[componentName]);
18
+ }
19
+
20
+ export async function copyVuelessComponent(options) {
21
+ const [componentName, newComponentName] = options;
22
+
23
+ if (!componentName) {
24
+ throw new Error("Component name is required.");
25
+ }
26
+
27
+ if (!(componentName in COMPONENTS)) {
28
+ throw new Error("There is no such component.");
29
+ }
30
+
31
+ const sourceComponentPath = getSourcePath(componentName);
32
+
33
+ const isSrcDir = existsSync(path.join(cwd(), VUELESS_LOCAL_DIR));
34
+ const destPath = isSrcDir
35
+ ? path.join(cwd(), SRC_COMPONENTS_PATH, newComponentName)
36
+ : path.join(cwd(), COMPONENTS_PATH, newComponentName);
37
+
38
+ const isComponentExists = newComponentName in COMPONENTS || existsSync(destPath);
39
+
40
+ if (isComponentExists) {
41
+ throw new Error(`Component with name ${newComponentName} alrady exists.`);
42
+ }
43
+
44
+ await cp(sourceComponentPath, destPath, { recursive: true });
45
+ await modifyCreatedComponent(destPath, componentName, newComponentName);
46
+
47
+ const successMessage = styleText(
48
+ "green",
49
+ `Success: ${componentName} was copied into ${destPath} directory.`,
50
+ );
51
+
52
+ console.log(successMessage);
53
+ }
54
+
55
+ async function modifyCreatedComponent(destPath, componentName, newComponentName) {
56
+ const destFiles = await getDirFiles(destPath, "");
57
+ const storybookId = await getStorybookId();
58
+
59
+ for await (const filePath of destFiles) {
60
+ const fileContent = await readFile(filePath, "utf-8");
61
+
62
+ let updatedContent = replaceRelativeImports(newComponentName, filePath, fileContent);
63
+ let targetPath = filePath;
64
+
65
+ if (filePath.endsWith("constants.ts")) {
66
+ updatedContent = updatedContent.replace(componentName, newComponentName);
67
+ }
68
+
69
+ if (filePath.endsWith("stories.ts")) {
70
+ const storyLines = updatedContent.split("\n");
71
+
72
+ const storyComponentImportIndex = storyLines.findIndex(
73
+ (line) => line.includes(componentName) && line.includes("import"),
74
+ );
75
+ const storyIdIndex = getStoryMetaKeyIndex(fileContent, "id");
76
+ const storyTitleIndex = getStoryMetaKeyIndex(fileContent, "title");
77
+
78
+ storyLines[storyIdIndex] = ` id: "${storybookId}",`;
79
+ storyLines[storyTitleIndex] = ` title: "Custom / ${newComponentName}",`;
80
+ storyLines[storyComponentImportIndex] =
81
+ `import ${newComponentName} from "../${newComponentName}.vue"`;
82
+
83
+ updatedContent = storyLines.join("\n").replaceAll(componentName, newComponentName);
84
+ }
85
+
86
+ if (targetPath.endsWith(`${componentName}.vue`)) {
87
+ targetPath = targetPath.replace(componentName, newComponentName);
88
+
89
+ await rename(filePath, targetPath);
90
+ }
91
+
92
+ await writeFile(targetPath, updatedContent);
93
+ }
94
+ }
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable no-console */
2
+
2
3
  import { existsSync } from "node:fs";
3
4
  import path from "node:path";
4
5
  import { cwd } from "node:process";
@@ -7,7 +8,7 @@ import { styleText } from "node:util";
7
8
 
8
9
  import { getDirFiles } from "../../utils/node/helper.js";
9
10
  import { replaceRelativeImports } from "../utils/formatUtil.js";
10
- import { getLastStorybookId } from "../utils/dataUtils.js";
11
+ import { getStorybookId } from "../utils/dataUtils.js";
11
12
 
12
13
  import { SRC_COMPONENTS_PATH, COMPONENTS_PATH } from "../constants.js";
13
14
 
@@ -20,7 +21,7 @@ export async function createVuelessComponent(options) {
20
21
  const [componentName] = options;
21
22
 
22
23
  if (!componentName) {
23
- throw new Error("Component name is required");
24
+ throw new Error("Component name is required.");
24
25
  }
25
26
 
26
27
  const isSrcDir = existsSync(path.join(cwd(), VUELESS_LOCAL_DIR));
@@ -31,7 +32,7 @@ export async function createVuelessComponent(options) {
31
32
  const isComponentExists = componentName in COMPONENTS || existsSync(destPath);
32
33
 
33
34
  if (isComponentExists) {
34
- throw new Error(`Component with name ${componentName} alrady exists`);
35
+ throw new Error(`Component with name ${componentName} alrady exists.`);
35
36
  }
36
37
 
37
38
  await cp(boilerplatePath, destPath, { recursive: true });
@@ -40,7 +41,7 @@ export async function createVuelessComponent(options) {
40
41
 
41
42
  const successMessage = styleText(
42
43
  "green",
43
- `Success: ${componentName} was created in ${destPath} directory`,
44
+ `Success: ${componentName} was created in ${destPath} directory.`,
44
45
  );
45
46
 
46
47
  console.log(successMessage);
@@ -48,7 +49,7 @@ export async function createVuelessComponent(options) {
48
49
 
49
50
  async function modifyCreatedComponent(destPath, componentName) {
50
51
  const destFiles = await getDirFiles(destPath, "");
51
- const lastStorybookId = await getLastStorybookId();
52
+ const storybookId = await getStorybookId();
52
53
 
53
54
  for await (const filePath of destFiles) {
54
55
  const fileContent = await readFile(filePath, "utf-8");
@@ -63,7 +64,7 @@ async function modifyCreatedComponent(destPath, componentName) {
63
64
  if (filePath.endsWith("stories.ts")) {
64
65
  updatedContent = updatedContent
65
66
  .replaceAll(boilerplateName, componentName)
66
- .replace("{{component_id}}", String(lastStorybookId + 10));
67
+ .replace("{{component_id}}", String(storybookId));
67
68
  }
68
69
 
69
70
  if (targetPath.endsWith(`${boilerplateName}.vue`)) {
@@ -1,7 +1,9 @@
1
1
  import { vuelssInit } from "./init.js";
2
2
  import { createVuelessComponent } from "./create.js";
3
+ import { copyVuelessComponent } from "./copy.js";
3
4
 
4
5
  export const commands = {
5
6
  init: vuelssInit,
6
7
  create: createVuelessComponent,
8
+ copy: copyVuelessComponent,
7
9
  };
@@ -16,7 +16,7 @@ export async function vuelssInit(options) {
16
16
  const isValidOptions = options.every((option) => vuelessInitOptions.includes(option));
17
17
 
18
18
  if (options.length && !isValidOptions) {
19
- throw new Error("Ivalid options were provided");
19
+ throw new Error("Ivalid options were provided.");
20
20
  }
21
21
 
22
22
  const fileExt = options.includes("--ts") ? TYPESCRIPT_EXT : JAVASCRIPT_EXT;
@@ -28,7 +28,7 @@ export async function vuelssInit(options) {
28
28
 
29
29
  const successMessage = styleText(
30
30
  "green",
31
- `Success: ${formattedDestPath.split(path.sep).at(-1)} was created in ${cwd()} directory`,
31
+ `Success: ${formattedDestPath.split(path.sep).at(-1)} was created in ${cwd()} directory.`,
32
32
  );
33
33
 
34
34
  console.log(successMessage);
@@ -10,7 +10,7 @@ import { VUELESS_DIR } from "../../constants.js";
10
10
 
11
11
  const storiesName = "stories.ts";
12
12
 
13
- export async function getLastStorybookId() {
13
+ export async function getStorybookId() {
14
14
  const srcComponentsDir = path.join(cwd(), SRC_COMPONENTS_PATH);
15
15
  const componentsDir = path.join(cwd(), COMPONENTS_PATH);
16
16
  const vuelessPackagePath = path.join(cwd(), VUELESS_DIR);
@@ -36,9 +36,8 @@ export async function getLastStorybookId() {
36
36
  for await (const storyPath of stories) {
37
37
  const storyContent = await readFile(storyPath, "utf-8");
38
38
 
39
- const storyIdLine = storyContent.split("\n").find((line, idx, array) => {
40
- return line.includes("id:") && idx && array[idx - 1].includes("export default");
41
- });
39
+ const storyIdLineIndex = getStoryMetaKeyIndex(storyContent, "id");
40
+ const storyIdLine = storyContent.split("\n").at(storyIdLineIndex);
42
41
 
43
42
  if (!storyIdLine) continue;
44
43
 
@@ -49,5 +48,36 @@ export async function getLastStorybookId() {
49
48
  }
50
49
  }
51
50
 
52
- return id;
51
+ return id + 10;
52
+ }
53
+
54
+ export function getStoryMetaKeyIndex(fileContent, key) {
55
+ const lines = fileContent.split("\n");
56
+ let insideExportBlock = false;
57
+ let bracketDepth = 0;
58
+
59
+ for (let i = 0; i < lines.length; i++) {
60
+ const line = lines[i].trim();
61
+
62
+ if (line.startsWith("export default {")) {
63
+ insideExportBlock = true;
64
+ bracketDepth = 1;
65
+ continue;
66
+ }
67
+
68
+ if (insideExportBlock) {
69
+ bracketDepth += (line.match(/{/g) || []).length;
70
+ bracketDepth -= (line.match(/}/g) || []).length;
71
+
72
+ if (bracketDepth === 1 && line.startsWith(`${key}:`)) {
73
+ return i;
74
+ }
75
+
76
+ if (bracketDepth === 0) {
77
+ break;
78
+ }
79
+ }
80
+ }
81
+
82
+ return -1; // Return -1 if no top-level `id` is found
53
83
  }
@@ -1,5 +1,7 @@
1
1
  import path from "node:path";
2
2
 
3
+ import { VUELESS_LIBRARY } from "../../constants.js";
4
+
3
5
  export function replaceRelativeImports(componentName, filePath, fileContent) {
4
6
  const isTopLevelFile = path.dirname(filePath).endsWith(componentName);
5
7
  const contentLines = fileContent.split("\n");
@@ -9,6 +11,7 @@ export function replaceRelativeImports(componentName, filePath, fileContent) {
9
11
 
10
12
  function replaceRelativeLineImports(line, isTopLevelFile) {
11
13
  const importRegex = /import\s+(?:[\w\s{},*]+)\s+from\s+(['"])(\.\.?\/.*?)(\.[tj]s)?\1(?!\?)/g;
14
+ const multiLineImportRegExp = /from\s+(['"])(\.\.?\/.*?)(\.[tj]s)?\1(?!\?)/g; // Matches import's "from" part
12
15
 
13
16
  const isTopLevelLocalImport = isTopLevelFile && !line.includes("../");
14
17
  const isInnerLocalImport =
@@ -18,6 +21,12 @@ function replaceRelativeLineImports(line, isTopLevelFile) {
18
21
  return line;
19
22
  }
20
23
 
24
+ if (line.startsWith("}")) {
25
+ return line.replace(multiLineImportRegExp, (match, quote, oldPath, ext) => {
26
+ return match.replace(oldPath + (ext || ""), VUELESS_LIBRARY);
27
+ });
28
+ }
29
+
21
30
  return line.replace(importRegex, (match, quote, oldPath, ext) => {
22
31
  const isDefaultImport = match.includes("{");
23
32
 
@@ -25,7 +34,7 @@ function replaceRelativeLineImports(line, isTopLevelFile) {
25
34
  match = defaultToNamedImport(match);
26
35
  }
27
36
 
28
- return match.replace(oldPath + (ext || ""), "vueless");
37
+ return match.replace(oldPath + (ext || ""), VUELESS_LIBRARY);
29
38
  });
30
39
  }
31
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "0.0.680",
3
+ "version": "0.0.682",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
6
6
  "keywords": [
@@ -43,9 +43,11 @@ const DefaultTemplate: StoryFn<ULinkArgs> = (args: ULinkArgs) => ({
43
43
  return { args, slots };
44
44
  },
45
45
  template: `
46
- <ULink v-if="args.block" v-bind="args" :config="{ wrapper: 'border-2 border-dashed border-green-500 p-2' }">
47
- ${args.slotTemplate || getSlotsFragment("")}
48
- </ULink>
46
+ <div v-if="args.block" class="border-2 border-dashed border-green-500 p-2 rounded-dynamic">
47
+ <ULink v-bind="args">
48
+ ${args.slotTemplate || getSlotsFragment("")}
49
+ </ULink>
50
+ </div>
49
51
  <ULink v-else v-bind="args">
50
52
  ${args.slotTemplate || getSlotsFragment("")}
51
53
  </ULink>
@@ -151,8 +153,9 @@ export const UnderlineVariants: StoryFn<ULinkArgs> = (args: ULinkArgs, { argType
151
153
  setup() {
152
154
  const variants = [
153
155
  { name: "Default", props: {} },
154
- { name: "Underlined", props: { underlined: true } },
155
156
  { name: "Dashed", props: { dashed: true } },
157
+ { name: "Underlined", props: { underlined: true } },
158
+ { name: "No underline", props: { underlined: false } },
156
159
  ];
157
160
 
158
161
  const colors = argTypes?.color?.options;
@@ -179,7 +179,7 @@ DefaultSlot.args = {
179
179
  <UBadge label="Download" color="green" right-icon="download" />
180
180
  </UToggleItem>
181
181
  <UToggleItem value="2">
182
- <UBadge label="Edit" color="amber" right-icon="edit" />
182
+ <UBadge label="Edit" color="amber" right-icon="edit_note" />
183
183
  </UToggleItem>
184
184
  <UToggleItem value="3">
185
185
  <UBadge label="Delete" color="red" right-icon="delete" />
@@ -89,7 +89,7 @@ const { config, wrapperAttrs, dropdownBadgeAttrs, dropdownListAttrs, dropdownIco
89
89
  <template #left>
90
90
  <!--
91
91
  @slot Use it to add something before the label.
92
- @binding {string} label
92
+ @binding {boolean} opened
93
93
  -->
94
94
  <slot name="left" :opened="isShownOptions" />
95
95
  </template>
@@ -105,10 +105,10 @@ const { config, wrapperAttrs, dropdownBadgeAttrs, dropdownListAttrs, dropdownIco
105
105
 
106
106
  <template #right>
107
107
  <!--
108
- @slot Use it to add something after the label.
108
+ @slot Use it to add something instead of the toggle icon.
109
109
  @binding {boolean} opened
110
110
  -->
111
- <slot v-if="!noIcon" name="right" :opened="isShownOptions">
111
+ <slot v-if="!noIcon" name="toggle" :opened="isShownOptions">
112
112
  <UIcon
113
113
  internal
114
114
  color="inherit"
@@ -7,7 +7,9 @@ import {
7
7
 
8
8
  import UDropdownBadge from "../../ui.dropdown-badge/UDropdownBadge.vue";
9
9
  import URow from "../../ui.container-row/URow.vue";
10
+ import UCol from "../../ui.container-col/UCol.vue";
10
11
  import UIcon from "../../ui.image-icon/UIcon.vue";
12
+ import ULink from "../../ui.button-link/ULink.vue";
11
13
 
12
14
  import type { Meta, StoryFn } from "@storybook/vue3";
13
15
  import type { Props } from "../types.ts";
@@ -17,7 +19,7 @@ interface DefaultUDropdownBadgeArgs extends Props {
17
19
  }
18
20
 
19
21
  interface EnumUDropdownBadgeArgs extends DefaultUDropdownBadgeArgs {
20
- enum: keyof Pick<Props, "color" | "size">;
22
+ enum: keyof Pick<Props, "color" | "size" | "variant" | "xPosition" | "yPosition">;
21
23
  }
22
24
 
23
25
  export default {
@@ -42,7 +44,7 @@ export default {
42
44
  } as Meta;
43
45
 
44
46
  const DefaultTemplate: StoryFn<DefaultUDropdownBadgeArgs> = (args: DefaultUDropdownBadgeArgs) => ({
45
- components: { UDropdownBadge, UIcon },
47
+ components: { UDropdownBadge, UIcon, ULink },
46
48
  setup() {
47
49
  const slots = getSlotNames(UDropdownBadge.__name);
48
50
 
@@ -81,48 +83,106 @@ const EnumVariantTemplate: StoryFn<EnumUDropdownBadgeArgs> = (
81
83
  `,
82
84
  });
83
85
 
86
+ const VariantColorsTemplate: StoryFn<EnumUDropdownBadgeArgs> = (
87
+ args: EnumUDropdownBadgeArgs,
88
+ { argTypes },
89
+ ) => ({
90
+ components: { UDropdownBadge, URow, UCol },
91
+ setup() {
92
+ return {
93
+ args,
94
+ variants: argTypes.variant?.options,
95
+ colors: argTypes.color?.options,
96
+ };
97
+ },
98
+ template: `
99
+ <UCol>
100
+ <URow v-for="(variant, index) in variants" :key="index">
101
+ <UDropdownBadge
102
+ v-for="(color, index) in colors"
103
+ :key="index"
104
+ v-bind="args"
105
+ :color="color"
106
+ :variant="variant"
107
+ :label="color"
108
+ />
109
+ </URow>
110
+ </UCol>
111
+ `,
112
+ });
113
+
84
114
  export const Default = DefaultTemplate.bind({});
85
115
  Default.args = {};
86
116
 
117
+ export const Variants = EnumVariantTemplate.bind({});
118
+ Variants.args = { enum: "variant" };
119
+
87
120
  export const Sizes = EnumVariantTemplate.bind({});
88
121
  Sizes.args = { enum: "size" };
89
122
 
90
- export const Colors = EnumVariantTemplate.bind({});
91
- Colors.args = { enum: "color" };
123
+ export const DropdownListXPosition = EnumVariantTemplate.bind({});
124
+ DropdownListXPosition.args = { enum: "xPosition" };
125
+
126
+ export const DropdownListYPosition = EnumVariantTemplate.bind({});
127
+ DropdownListYPosition.args = { enum: "yPosition" };
128
+
129
+ export const VariantColors = VariantColorsTemplate.bind({});
130
+ VariantColors.args = {};
92
131
 
93
132
  export const WithoutDropdownIcon = DefaultTemplate.bind({});
94
133
  WithoutDropdownIcon.args = { noIcon: true };
95
134
 
96
- export const DefaultSlot = DefaultTemplate.bind({});
97
- DefaultSlot.args = {
98
- slotTemplate: `
99
- <template #default>
100
- Custom label
101
- </template>
102
- `,
135
+ export const CustomDropdownIcon = DefaultTemplate.bind({});
136
+ CustomDropdownIcon.args = {
137
+ config: {
138
+ dropdownIcon: {
139
+ defaults: {
140
+ size: "sm",
141
+ },
142
+ },
143
+ defaults: {
144
+ dropdownIcon: "expand_circle_down",
145
+ },
146
+ },
103
147
  };
104
148
 
105
- export const LeftSlot = DefaultTemplate.bind({});
106
- LeftSlot.args = {
107
- slotTemplate: `
108
- <template #left>
109
- <UIcon
110
- name="archive"
111
- color="red"
112
- size="sm"
113
- />
114
- </template>
149
+ export const Slots: StoryFn<DefaultUDropdownBadgeArgs> = (args) => ({
150
+ components: { UDropdownBadge, UIcon, URow },
151
+ setup() {
152
+ return { args };
153
+ },
154
+ template: `
155
+ <URow no-mobile>
156
+ <UDropdownBadge v-bind="args" label="Add to favorite">
157
+ <template #left>
158
+ <UIcon
159
+ name="heart_plus"
160
+ size="xs"
161
+ color="green"
162
+ class="mx-1"
163
+ />
164
+ </template>
165
+ </UDropdownBadge>
166
+
167
+ <UDropdownBadge v-bind="args" no-icon>
168
+ <template #default>
169
+ <UIcon name="unfold_more" color="white" />
170
+ </template>
171
+ </UDropdownBadge>
172
+ </URow>
115
173
  `,
116
- };
174
+ });
117
175
 
118
- export const RightSlot = DefaultTemplate.bind({});
119
- RightSlot.args = {
176
+ export const SlotToggle = DefaultTemplate.bind({});
177
+ SlotToggle.args = {
120
178
  slotTemplate: `
121
- <template #right>
122
- <UIcon
123
- name="archive"
124
- color="red"
179
+ <template #toggle="{ opened }">
180
+ <ULink
181
+ :label="opened ? 'collapse' : 'expand'"
182
+ color="green"
125
183
  size="sm"
184
+ :ring=false
185
+ class="mx-1"
126
186
  />
127
187
  </template>
128
188
  `,
@@ -107,7 +107,7 @@ const { config, dropdownButtonAttrs, dropdownListAttrs, dropdownIconAttrs, wrapp
107
107
 
108
108
  <template #right>
109
109
  <!--
110
- @slot Use it to add something after the label.
110
+ @slot Use it to add something instead of the toggle icon.
111
111
  @binding {boolean} opened
112
112
  -->
113
113
  <slot v-if="!noIcon" name="toggle" :opened="isShownOptions">
@@ -152,6 +152,7 @@ DefaultSlot.args = {
152
152
  </template>
153
153
  `,
154
154
  noIcon: true,
155
+ square: true,
155
156
  };
156
157
 
157
158
  export const LeftSlot = DefaultTemplate.bind({});
@@ -106,10 +106,10 @@ const { config, getDataTest, wrapperAttrs, dropdownLinkAttrs, dropdownListAttrs,
106
106
  </ULink>
107
107
 
108
108
  <!--
109
- @slot Use it to add something after the label.
109
+ @slot Use it to add something instead of the toggle icon.
110
110
  @binding {boolean} opened
111
111
  -->
112
- <slot name="right" :opened="isShownOptions">
112
+ <slot name="toggle" :opened="isShownOptions">
113
113
  <UIcon
114
114
  v-if="!noIcon"
115
115
  internal
@@ -1,15 +1,23 @@
1
1
  export default /*tw*/ {
2
2
  wrapper: {
3
- base: "relative inline-flex",
3
+ base: "relative inline-flex items-center rounded",
4
4
  variants: {
5
5
  opened: {
6
6
  true: "group",
7
7
  },
8
+ ring: {
9
+ true: "focus-within:ring-dynamic focus-within:ring-offset-4 focus-within:ring-{color}-700/15",
10
+ },
8
11
  },
9
12
  },
10
- dropdownLink: "{ULink}",
13
+ dropdownLink: "{ULink} focus:ring-0 focus:ring-offset-0",
11
14
  toggleIcon: {
12
15
  base: "{UIcon} block transition duration-300 group-[]:rotate-180",
16
+ variants: {
17
+ disabled: {
18
+ true: "text-gray-400 pointer-events-none",
19
+ },
20
+ },
13
21
  defaults: {
14
22
  size: {
15
23
  sm: "2xs",
@@ -8,6 +8,8 @@ import {
8
8
  import UDropdownLink from "../../ui.dropdown-link/UDropdownLink.vue";
9
9
  import URow from "../../ui.container-row/URow.vue";
10
10
  import UIcon from "../../ui.image-icon/UIcon.vue";
11
+ import UBadge from "../../ui.text-badge/UBadge.vue";
12
+ import ULink from "../../ui.button-link/ULink.vue";
11
13
 
12
14
  import type { Meta, StoryFn } from "@storybook/vue3";
13
15
  import type { Props } from "../types.ts";
@@ -17,7 +19,7 @@ interface DefaultUDropdownLinkArgs extends Props {
17
19
  }
18
20
 
19
21
  interface EnumUDropdownLinkArgs extends DefaultUDropdownLinkArgs {
20
- enum: keyof Pick<Props, "size" | "color">;
22
+ enum: keyof Pick<Props, "size" | "color" | "xPosition" | "yPosition">;
21
23
  }
22
24
 
23
25
  export default {
@@ -46,7 +48,7 @@ export default {
46
48
  } as Meta;
47
49
 
48
50
  const DefaultTemplate: StoryFn<DefaultUDropdownLinkArgs> = (args: DefaultUDropdownLinkArgs) => ({
49
- components: { UDropdownLink, UIcon },
51
+ components: { UDropdownLink, UIcon, ULink },
50
52
  setup() {
51
53
  const slots = getSlotNames(UDropdownLink.__name);
52
54
 
@@ -96,42 +98,97 @@ Default.args = {};
96
98
  export const Sizes = EnumVariantTemplate.bind({});
97
99
  Sizes.args = { enum: "size" };
98
100
 
101
+ export const DropdownListXPosition = EnumVariantTemplate.bind({});
102
+ DropdownListXPosition.args = { enum: "xPosition" };
103
+
104
+ export const DropdownListYPosition = EnumVariantTemplate.bind({});
105
+ DropdownListYPosition.args = { enum: "yPosition" };
106
+
107
+ export const Disabled = DefaultTemplate.bind({});
108
+ Disabled.args = { disabled: true };
109
+
110
+ export const Ring = DefaultTemplate.bind({});
111
+ Ring.args = { ring: true };
112
+
99
113
  export const Colors = EnumVariantTemplate.bind({});
100
114
  Colors.args = { enum: "color" };
101
115
 
116
+ export const UnderlineVariants: StoryFn<EnumUDropdownLinkArgs> = (
117
+ args: EnumUDropdownLinkArgs,
118
+ { argTypes },
119
+ ) => ({
120
+ components: { UDropdownLink, URow },
121
+ setup() {
122
+ const variants = [
123
+ { name: "Default", props: {} },
124
+ { name: "Underlined (on hover)", props: { underlined: undefined, dashed: false } },
125
+ { name: "Underlined", props: { underlined: true, dashed: false } },
126
+ { name: "No underline", props: { underlined: false } },
127
+ ];
128
+
129
+ const colors = argTypes?.color?.options;
130
+
131
+ return {
132
+ args,
133
+ variants,
134
+ colors,
135
+ };
136
+ },
137
+ template: `
138
+ <div v-for="variant in variants" :key="variant.name" class="mb-8">
139
+ <div class="text-sm font-medium mb-2">{{ variant.name }}</div>
140
+ <URow no-mobile>
141
+ <UDropdownLink
142
+ v-for="color in colors"
143
+ :key="color"
144
+ v-bind="variant.props"
145
+ :color="color"
146
+ :label="color"
147
+ />
148
+ </URow>
149
+ </div>
150
+ `,
151
+ });
152
+
102
153
  export const WithoutDropdownIcon = DefaultTemplate.bind({});
103
154
  WithoutDropdownIcon.args = { noIcon: true };
104
155
 
105
- export const DefaultSlot = DefaultTemplate.bind({});
106
- DefaultSlot.args = {
107
- slotTemplate: `
108
- <template #default>
109
- Custom label
110
- </template>
111
- `,
112
- };
113
-
114
- export const LeftSlot = DefaultTemplate.bind({});
115
- LeftSlot.args = {
116
- slotTemplate: `
117
- <template #left>
118
- <UIcon
119
- name="archive"
120
- color="red"
121
- size="sm"
122
- />
123
- </template>
156
+ export const Slots: StoryFn<DefaultUDropdownLinkArgs> = (args) => ({
157
+ components: { UDropdownLink, UIcon, URow, UBadge },
158
+ setup() {
159
+ return { args };
160
+ },
161
+ template: `
162
+ <URow no-mobile>
163
+ <UDropdownLink v-bind="args" label="Add to favorite">
164
+ <template #left>
165
+ <UIcon
166
+ name="heart_plus"
167
+ size="sm"
168
+ color="green"
169
+ class="mx-1"
170
+ />
171
+ </template>
172
+ </UDropdownLink>
173
+
174
+ <UDropdownLink v-bind="args">
175
+ <template #default>
176
+ <UBadge label="Dropdown" color="green" variant="thirdary" />
177
+ </template>
178
+ </UDropdownLink>
179
+ </URow>
124
180
  `,
125
- };
181
+ });
126
182
 
127
- export const RightSlot = DefaultTemplate.bind({});
128
- RightSlot.args = {
183
+ export const SlotToggle = DefaultTemplate.bind({});
184
+ SlotToggle.args = {
129
185
  slotTemplate: `
130
- <template #right>
186
+ <template #toggle="{ opened }">
131
187
  <UIcon
132
- name="archive"
133
- color="red"
134
- size="sm"
188
+ name="expand_circle_down"
189
+ color="green"
190
+ class="mx-1"
191
+ :class="{ 'rotate-180' : opened }"
135
192
  />
136
193
  </template>
137
194
  `,