@vueless/storybook 0.0.75-beta.7 → 0.0.75-beta.8

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.
@@ -1,4 +1,4 @@
1
- import { addons, useArgs, makeDecorator } from "@storybook/preview-api";
1
+ import { addons, makeDecorator, useArgs } from "@storybook/preview-api";
2
2
  import { h, onMounted, watch } from "vue";
3
3
 
4
4
  const params = new URLSearchParams(window.location.search);
@@ -27,7 +27,7 @@ export const vue3SourceDecorator = makeDecorator({
27
27
  previousStoryId = context.id;
28
28
 
29
29
  // this returns a new component that computes the source code when mounted
30
- // and emits an events that is handled by addons-docs
30
+ // and emits an event that is handled by addons-docs
31
31
  // watch args and re-emit on change
32
32
  return {
33
33
  components: { story },
@@ -38,16 +38,20 @@ export const vue3SourceDecorator = makeDecorator({
38
38
  await setSourceCode();
39
39
  });
40
40
 
41
- watch(context.args, async () => {
42
- // it allows changing args dynamically
43
- updateArgs({ ...context.args });
44
- await setSourceCode();
45
- });
41
+ watch(
42
+ context.args,
43
+ async () => {
44
+ updateArgs({ ...context.args });
45
+ await setSourceCode();
46
+ },
47
+ { deep: true },
48
+ );
46
49
 
47
50
  async function setSourceCode() {
48
51
  try {
49
52
  const src = context.originalStoryFn(context.args, context.argTypes).template;
50
- const code = templateSourceCode(src, context.args, context.argTypes);
53
+ const code = preFormat(src, context.args, context.argTypes);
54
+
51
55
  const channel = addons.getChannel();
52
56
 
53
57
  const emitFormattedTemplate = async () => {
@@ -64,7 +68,7 @@ export const vue3SourceDecorator = makeDecorator({
64
68
  channel.emit("storybook/docs/snippet-rendered", {
65
69
  id: context.id,
66
70
  args: context.args,
67
- source: formattedCode,
71
+ source: postFormat(formattedCode),
68
72
  });
69
73
  };
70
74
 
@@ -83,22 +87,32 @@ export const vue3SourceDecorator = makeDecorator({
83
87
  },
84
88
  });
85
89
 
86
- function templateSourceCode(templateSource, args, argTypes) {
87
- const MODEL_VALUE_KEY = "modelValue";
88
- const componentArgs = {};
90
+ function preFormat(templateSource, args, argTypes) {
91
+ templateSource = expandVueLoopFromTemplate(templateSource, args, argTypes);
92
+
93
+ if (args?.outerEnum) {
94
+ templateSource = expandOuterVueLoopFromTemplate(templateSource, args, argTypes);
95
+ }
96
+
97
+ const componentArgs = {
98
+ ...(args.class && { class: args.class }),
99
+ };
100
+
101
+ const enumKeys = Object.entries(args)
102
+ .filter(([, value]) => JSON.stringify(value)?.includes("{enumValue}"))
103
+ .map(([key]) => key);
89
104
 
90
105
  for (const [key, val] of Object.entries(argTypes)) {
91
- if (key === MODEL_VALUE_KEY) continue;
106
+ if (key === "modelValue") continue;
92
107
 
93
- const value = args[key];
108
+ const isUndefined = typeof val !== "undefined";
109
+ const isProps = val?.table?.category === "props";
110
+ const isValueDefault = args[key] === val.defaultValue;
94
111
 
95
- if (
96
- typeof val !== "undefined" &&
97
- val.table &&
98
- val.table.category === "props" &&
99
- value !== val.defaultValue
100
- ) {
101
- componentArgs[key] = val;
112
+ if (isUndefined && isProps && !isValueDefault) {
113
+ if (!(args.enum && enumKeys.includes(key)) && key !== args.outerEnum) {
114
+ componentArgs[key] = val;
115
+ }
102
116
  }
103
117
  }
104
118
 
@@ -110,29 +124,158 @@ function templateSourceCode(templateSource, args, argTypes) {
110
124
  // eslint-disable-next-line vue/max-len
111
125
  `</template><template v-else-if="slot === 'default' && args['defaultSlot']">{{ args['defaultSlot'] }}</template><template v-else-if="args[slot + 'Slot']">{{ args[slot + 'Slot'] }}</template></template>`;
112
126
 
113
- return templateSource
127
+ const modelValue = JSON.stringify(args["modelValue"])?.replaceAll('"', "'");
128
+
129
+ templateSource = templateSource
114
130
  .replace(/>[\s]+</g, "><")
115
131
  .trim()
116
132
  .replace(slotTemplateCodeBefore, "")
117
133
  .replace(slotTemplateCodeAfter, "")
118
134
  .replace(
119
- `v-model="args.${MODEL_VALUE_KEY}"`,
120
- args[MODEL_VALUE_KEY] ? `v-model="${args[MODEL_VALUE_KEY]}"` : "",
135
+ new RegExp(`v-model="args\\.modelValue"`, "g"),
136
+ args["modelValue"] ? `v-model="${modelValue}"` : "",
121
137
  )
122
138
  .replace(
123
- 'v-bind="args"',
139
+ /v-bind="args"/g,
124
140
  Object.keys(componentArgs)
125
- .map((key) => " " + propToSource(kebabCase(key), args[key]))
141
+ .map((key) => " " + propToSource(kebabCase(key), args[key], argTypes[key]))
126
142
  .join(""),
127
143
  );
144
+
145
+ return templateSource;
146
+ }
147
+
148
+ function postFormat(code) {
149
+ return (
150
+ code
151
+ /* Add self-closing tag if there is no content inside */
152
+ .replace(/<(\w+)([^>]*)><\/\1>/g, (_, tag, attrs) => {
153
+ const hasText = attrs.trim().includes("\n") ? false : attrs.trim().length > 0;
154
+ const space = hasText ? " " : "";
155
+
156
+ return `<${tag}${attrs}${space}/>`;
157
+ })
158
+ /* Format objects in props */
159
+ .replace(/^(\s*):([\w-]+)="\[(\{.*?\})\]"/gm, (_, indent, propName, content) => {
160
+ const formatted = content
161
+ .split(/\},\s*\{/)
162
+ .map((obj, i, arr) => {
163
+ if (i === 0) obj += "}";
164
+ else if (i === arr.length - 1) obj = "{" + obj;
165
+ else obj = "{" + obj + "}";
166
+
167
+ return `${indent} ${obj}`;
168
+ })
169
+ .join(",\n");
170
+
171
+ return `${indent}:${propName}="[\n${formatted}\n${indent}]"`;
172
+ })
173
+ /* Added a new line between nested elements with closing tags */
174
+ .replace(/(<\/[\w-]+>)\n(\s*)(<[\w-][^>]*?>)/g, (match, closeTag, indent, openTag) => {
175
+ return `${closeTag}\n\n${indent}${openTag}`;
176
+ })
177
+ /* Added a new line between nested elements with self-closing tags */
178
+ .replace(/(\/>\n)(?=\s*<\w[\s\S]*?\n\s*\/>)/g, "$1\n")
179
+ );
180
+ }
181
+
182
+ function expandOuterVueLoopFromTemplate(template, args, argTypes) {
183
+ const loopRegex = /<(\w+)[^>]*v-for[^>]*>([\s\S]*?)<\/\1>/;
184
+ const loopMatch = template.match(loopRegex);
185
+
186
+ if (!loopMatch) return;
187
+
188
+ const [, containerTag, innerContent] = loopMatch;
189
+
190
+ const elementRegex = /<(\w+)([^>]*)>(.*?)<\/\1>/g;
191
+ const elements = [...innerContent.matchAll(elementRegex)];
192
+
193
+ if (!elements.length) return;
194
+
195
+ const newRows = argTypes[args.outerEnum]?.options
196
+ .map((variant) => {
197
+ const rows = elements
198
+ .map(([, tag, props, children]) => {
199
+ return `<${tag}${props} ${args.outerEnum}="${variant}">${children}</${tag}>`;
200
+ })
201
+ .join("\n ");
202
+
203
+ return ` <${containerTag}>\n ${rows}\n </${containerTag}>`;
204
+ })
205
+ .join("\n");
206
+
207
+ return template.replace(loopRegex, newRows);
208
+ }
209
+
210
+ function expandVueLoopFromTemplate(template, args, argTypes) {
211
+ return template.replace(
212
+ // eslint-disable-next-line vue/max-len
213
+ /<(\w+)([^>]*)\s+v-for="option\s+in\s+argTypes\?\.\[args\.enum]\?\.options"([^>]*?)>([\s\S]*?)<\/\1\s*>|<(\w+)([^>]*)\s+v-for="option\s+in\s+argTypes\?\.\[args\.enum]\?\.options"([^>]*)\/?>/g,
214
+ (
215
+ match,
216
+ name1,
217
+ pre1,
218
+ post1,
219
+ content, // For full component
220
+ name2,
221
+ pre2,
222
+ post2, // For self-closing component
223
+ ) => {
224
+ const componentName = name1 || name2;
225
+ const beforeAttrs = pre1 || pre2 || "";
226
+ const afterAttrs = post1 || post2 || "";
227
+ const slotContent = content || "";
228
+
229
+ const restProps = (beforeAttrs + " " + afterAttrs)
230
+ .replace(/\n/g, " ") // remove newlines
231
+ .replace(/\//g, "") // remove forward slashes
232
+ .replace(/\sv-bind="[^"]*"/g, ' v-bind="args"') // replace v-bind with args
233
+ .replace(/\s:key="[^"]*"/g, "") // remove :key
234
+ .replace(/\sv-model="[^"]*"/g, "") // remove v-model
235
+ .replace(/\s+/g, " ") // collapse multiple spaces
236
+ .trim();
237
+
238
+ return (
239
+ argTypes?.[args.enum]?.options
240
+ ?.map(
241
+ (option) =>
242
+ // eslint-disable-next-line vue/max-len
243
+ `<${componentName} ${generateEnumAttributes(args, option)} ${restProps}>${slotContent}</${componentName}>`,
244
+ )
245
+ ?.join("\n") || ""
246
+ );
247
+ },
248
+ );
249
+ }
250
+
251
+ function generateEnumAttributes(args, option) {
252
+ const enumKeys = Object.entries(args)
253
+ .filter(([, value]) => JSON.stringify(value)?.includes("{enumValue}"))
254
+ .map(([key]) => key);
255
+
256
+ if (args.enum) {
257
+ enumKeys.unshift(args.enum);
258
+ }
259
+
260
+ return enumKeys
261
+ .map((key) => {
262
+ const isNotPrimitive =
263
+ Object.keys(args[key] || {}).length || (Array.isArray(args[key]) && args[key].length);
264
+
265
+ return key in args && isNotPrimitive
266
+ ? `${key}="${JSON.stringify(args[key]).replaceAll('"', "'").replaceAll("{enumValue}", option)}"`
267
+ : `${key}="${option}"`;
268
+ })
269
+ .join(" ");
128
270
  }
129
271
 
130
- function propToSource(key, val) {
272
+ function propToSource(key, val, argType) {
273
+ const defaultValue = argType.table?.defaultValue?.summary;
131
274
  const type = typeof val;
132
275
 
133
276
  switch (type) {
134
277
  case "boolean":
135
- return val ? key : "";
278
+ return val || defaultValue === "false" ? key : `:${key}="${val}"`;
136
279
  case "string":
137
280
  return `${key}="${val}"`;
138
281
  case "object":
@@ -104,7 +104,7 @@ body {
104
104
  .dark .sbdocs .sbdocs-content > h3,
105
105
  .dark .sbdocs .sbdocs-content > .sb-anchor > h3,
106
106
  .dark .sbdocs .sbdocs-content > .sb-anchor > p,
107
- .dark .sbdocs .sbdocs-content > :not(.sb-anchor) ul > li,
107
+ .dark .sbdocs .sbdocs-content > ul > li,
108
108
  .dark .sbdocs .sbdocs-content > p,
109
109
  .dark .sbdocs .sbdocs-content > table th,
110
110
  .dark .sbdocs .sbdocs-content > table td {
@@ -579,6 +579,20 @@ body {
579
579
  border-top-left-radius: 0; /* rounded-tl-none */
580
580
  }
581
581
 
582
+ .dark a.sbdocs.sbdocs-a {
583
+ color: #6b7280; /* text-gray-500 */
584
+ }
585
+
586
+ .light a.sbdocs.sbdocs-a {
587
+ color: #6b7280; /* text-gray-500 */
588
+ }
589
+
590
+ .light a.sbdocs.sbdocs-a:hover,
591
+ .dark a.sbdocs.sbdocs-a:hover{
592
+ text-decoration: underline dashed #6b7280; /* text-gray-500 */
593
+ text-underline-offset: 3px;
594
+ }
595
+
582
596
  /* -------------------- Storybook story preview -------------------- */
583
597
 
584
598
  .vl-dark {
@@ -1,12 +1,11 @@
1
1
  /** @type { import('@storybook/vue3-vite').StorybookConfig } */
2
2
  export default {
3
3
  stories: [
4
- "../node_modules/vueless/**/stories.@(js|jsx|ts|tsx)",
5
- "../node_modules/vueless/**/docs.@(mdx)",
4
+ "../node_modules/vueless/**/stories.{js,jsx,ts,tsx}",
5
+ "../node_modules/vueless/**/docs.mdx",
6
6
  /* Define a path to your own component stories. */
7
- // "../.vueless/**/stories.@(js|jsx|ts|tsx)",
8
- // "../.vueless/**/docs.@(mdx)",
9
- ],
7
+ // "../src/**/stories.{js,jsx,ts,tsx}",
8
+ // "../src/**/docs.mdx"],
10
9
  addons: [
11
10
  "@storybook/addon-links",
12
11
  "@storybook/addon-essentials",
@@ -121,8 +121,6 @@ Custom dark mode related vueless loader.
121
121
  background: #1f2937 !important;
122
122
  }
123
123
 
124
-
125
-
126
124
  .search-field,
127
125
  .search-result-item {
128
126
  border-radius: 8px !important;
@@ -137,12 +135,14 @@ Custom dark mode related vueless loader.
137
135
  .light .search-result-item svg[type="component"],
138
136
  .dark .search-result-item mark {
139
137
  color: #10b981;
138
+ font-weight: 600;
140
139
  }
141
140
 
142
141
  .dark .sidebar-item svg[type="component"],
143
142
  .dark .search-result-item svg[type="component"],
144
143
  .light .search-result-item mark {
145
144
  color: #059669;
145
+ font-weight: 600;
146
146
  }
147
147
 
148
148
  .light .sidebar-item svg[type="document"],
@@ -159,6 +159,14 @@ Custom dark mode related vueless loader.
159
159
  color: #4b5563;
160
160
  }
161
161
 
162
+ .light .search-result-item--label {
163
+ color: #6b7280; /* text-gray-600 */
164
+ }
165
+
166
+ .dark .search-result-item--label {
167
+ color: #a8abb0; /* text-gray-400 */
168
+ }
169
+
162
170
  #sidebar-bottom-wrapper {
163
171
  display: none;
164
172
  }
@@ -5,6 +5,7 @@ import { getRandomId } from "vueless";
5
5
  import themeLight from "./themes/themeLight.js";
6
6
  import themeDark from "./themes/themeDark.js";
7
7
  import themeLightDocs from "./themes/themeLightDocs.js";
8
+
8
9
  import { storyDarkModeDecorator } from "./decorators/storyDarkModeDecorator.js";
9
10
  import { vue3SourceDecorator } from "./decorators/vue3SourceDecorator.js";
10
11
 
@@ -58,7 +59,7 @@ export const parameters = {
58
59
  },
59
60
  };
60
61
 
61
- /* Reload the page on error "Failed to fetch dynamically imported module..." */
62
+ /* Reload the page on the error "Failed to fetch dynamically imported module..." */
62
63
  window.addEventListener("error", (ev) => onFailedToFetchModule(ev.message));
63
64
  window.addEventListener("unhandledrejection", (ev) => onFailedToFetchModule(ev?.reason?.message));
64
65
 
@@ -3,9 +3,10 @@ import { defineConfig } from "vite";
3
3
  // Plugins
4
4
  import Vue from "@vitejs/plugin-vue";
5
5
  import { Vueless, TailwindCSS } from "vueless/plugin-vite";
6
+ import { STORYBOOK_ENV } from "vueless/constants.js";
6
7
 
7
8
  export default defineConfig({
8
- plugins: [Vue(), TailwindCSS(), Vueless({ mode: "storybook", debug: false })],
9
+ plugins: [Vue(), TailwindCSS(), Vueless({ env: STORYBOOK_ENV, debug: false })],
9
10
  optimizeDeps: {
10
11
  include: [
11
12
  "cva",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vueless/storybook",
3
- "version": "0.0.75-beta.7",
3
+ "version": "0.0.75-beta.8",
4
4
  "description": "Simplifies Storybook configuration for Vueless UI library.",
5
5
  "homepage": "https://vueless.com",
6
6
  "author": "Johnny Grid",