vueless 1.2.3-beta.1 → 1.2.3-beta.11

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Vueless UI
4
4
 
5
- Vueless is an open-source UI library and design system framework for Vue.js 3 and Nuxt.js 3, built on top of Tailwind CSS v4.
5
+ Vueless is an open-source UI library and design system framework for Vue.js 3 and Nuxt.js 3 / 4, built on top of Tailwind CSS v4.
6
6
 
7
7
  It’s completely styleless, allowing you to extend or override default styles without modifying the components themselves — only your custom styles are included in the final build.
8
8
 
@@ -40,6 +40,18 @@ Vueless is simple enough for everyday use and powerful enough for advanced scena
40
40
 
41
41
  ## Quick Start (Vue)
42
42
 
43
+ ### New project
44
+
45
+ To get started with Vueless UI, simply paste the following code into your terminal:
46
+
47
+ ```bash
48
+ npm create vueless@latest
49
+ ```
50
+
51
+ This command guides you through a few setup options, then generates a new scaffolded Vue + Vueless UI project with the complete application structure.
52
+
53
+ ### Existing project
54
+
43
55
  1. Install `vueless` UI library packages.
44
56
 
45
57
  ```bash
@@ -108,6 +120,14 @@ export default defineNuxtConfig({
108
120
  @import "vueless";
109
121
  ```
110
122
 
123
+ ## Ecosystem
124
+
125
+ * [@vueless/storybook](https://github.com/vuelessjs/vueless-storybook) - Storybook preset for Vueless UI component library [(docs)](https://docs.vueless.com/installation/storybook).
126
+ * [@vueless/nuxt](https://github.com/vuelessjs/vueless-module-nuxt) - Vueless UI module for Nuxt.js [(docs)](https://docs.vueless.com/installation/nuxt).
127
+ * [create-vueless](https://github.com/vuelessjs/vueless-create) - CLI tool to quickly start a Vueless UI project from a template [(docs)](https://docs.vueless.com/installation/vue).
128
+ * [vueless-quickstart](https://github.com/vuelessjs/vueless-quickstart) - Vue + Vueless UI + JavaScript project template.
129
+ * [vueless-quickstart-ts](https://github.com/vuelessjs/vueless-quickstart-ts) - Vue + Vueless UI + TypeScript project template.
130
+
111
131
  ## Contributing
112
132
 
113
133
  * We encourage you to contribute to Vueless! Please check out the
@@ -120,5 +140,8 @@ check out our [security policy](SECURITY.md) for guidelines.
120
140
 
121
141
  Vueless is released under the [MIT License](https://opensource.org/licenses/MIT).
122
142
 
143
+ ---
144
+ From Ukrainians to a Peaceful World 🇺🇦
145
+
123
146
 
124
147
 
@@ -1,3 +1,3 @@
1
1
  export const SRC_COMPONENTS_PATH: "/src/components";
2
2
  export const COMPONENTS_PATH: "/components";
3
- export const DEFAULT_VUELESS_CONFIG_CONTENT: "import { componentConfigs } from \"./.vueless\";\n\nexport default {\n /**\n * Global settings.\n */\n primary: \"grayscale\",\n neutral: \"gray\",\n text: 14,\n outline: 2,\n rounding: 8,\n disabledOpacity: 50,\n colorMode: \"auto\",\n\n /**\n * Component settings.\n */\n components: /*tw*/ {\n ...componentConfigs,\n },\n\n /**\n * Directive settings.\n */\n directives: {},\n\n /**\n * Light theme CSS variable settings.\n */\n lightTheme: {\n /* Primary colors */\n \"--vl-primary\": \"--vl-primary-600\",\n \"--vl-primary-lifted\": \"--vl-primary-700\",\n \"--vl-primary-accented\": \"--vl-primary-800\",\n\n /* Secondary colors */\n \"--vl-secondary\": \"--vl-neutral-500\",\n \"--vl-secondary-lifted\": \"--vl-neutral-600\",\n \"--vl-secondary-accented\": \"--vl-neutral-700\",\n\n /* Success colors */\n \"--vl-success\": \"--color-green-600\",\n \"--vl-success-lifted\": \"--color-green-700\",\n \"--vl-success-accented\": \"--color-green-800\",\n\n /* Info colors */\n \"--vl-info\": \"--color-blue-600\",\n \"--vl-info-lifted\": \"--color-blue-700\",\n \"--vl-info-accented\": \"--color-blue-800\",\n\n /* Notice colors */\n \"--vl-notice\": \"--color-violet-600\",\n \"--vl-notice-lifted\": \"--color-violet-700\",\n \"--vl-notice-accented\": \"--color-violet-800\",\n\n /* Warning colors */\n \"--vl-warning\": \"--color-orange-600\",\n \"--vl-warning-lifted\": \"--color-orange-700\",\n \"--vl-warning-accented\": \"--color-orange-800\",\n\n /* Error colors */\n \"--vl-error\": \"--color-red-600\",\n \"--vl-error-lifted\": \"--color-red-700\",\n \"--vl-error-accented\": \"--color-red-800\",\n\n /* Grayscale colors */\n \"--vl-grayscale\": \"--vl-neutral-900\",\n \"--vl-grayscale-lifted\": \"--vl-neutral-800\",\n \"--vl-grayscale-accented\": \"--vl-neutral-700\",\n\n /* Neutral colors */\n \"--vl-neutral\": \"--vl-neutral-500\",\n \"--vl-neutral-lifted\": \"--vl-neutral-600\",\n \"--vl-neutral-accented\": \"--vl-neutral-700\",\n\n /* Text neutral colors */\n \"--vl-text-inverted\": \"--color-white\",\n \"--vl-text-muted\": \"--vl-neutral-400\",\n \"--vl-text-lifted\": \"--vl-neutral-500\",\n \"--vl-text-accented\": \"--vl-neutral-600\",\n \"--vl-text\": \"--vl-neutral-900\",\n\n /* Border neutral colors */\n \"--vl-border-muted\": \"--vl-neutral-200\",\n \"--vl-border\": \"--vl-neutral-300\",\n \"--vl-border-lifted\": \"--vl-neutral-400\",\n \"--vl-border-accented\": \"--vl-neutral-600\",\n\n /* Background neutral colors */\n \"--vl-bg\": \"--color-white\",\n \"--vl-bg-muted\": \"--vl-neutral-50\",\n \"--vl-bg-lifted\": \"--vl-neutral-100\",\n \"--vl-bg-accented\": \"--vl-neutral-200\",\n \"--vl-bg-inverted\": \"--vl-neutral-900\",\n },\n\n /**\n * Dark theme CSS variable settings.\n */\n darkTheme: {\n /* Primary colors */\n \"--vl-primary\": \"--vl-primary-400\",\n \"--vl-primary-lifted\": \"--vl-primary-500\",\n \"--vl-primary-accented\": \"--vl-primary-600\",\n\n /* Secondary colors */\n \"--vl-secondary\": \"--vl-neutral-300\",\n \"--vl-secondary-lifted\": \"--vl-neutral-400\",\n \"--vl-secondary-accented\": \"--vl-neutral-500\",\n\n /* Success colors */\n \"--vl-success\": \"--color-green-400\",\n \"--vl-success-lifted\": \"--color-green-500\",\n \"--vl-success-accented\": \"--color-green-600\",\n\n /* Info colors */\n \"--vl-info\": \"--color-blue-400\",\n \"--vl-info-lifted\": \"--color-blue-500\",\n \"--vl-info-accented\": \"--color-blue-600\",\n\n /* Notice colors */\n \"--vl-notice\": \"--color-violet-400\",\n \"--vl-notice-lifted\": \"--color-violet-500\",\n \"--vl-notice-accented\": \"--color-violet-600\",\n\n /* Warning colors */\n \"--vl-warning\": \"--color-orange-400\",\n \"--vl-warning-lifted\": \"--color-orange-500\",\n \"--vl-warning-accented\": \"--color-orange-600\",\n\n /* Error colors */\n \"--vl-error\": \"--color-red-400\",\n \"--vl-error-lifted\": \"--color-red-500\",\n \"--vl-error-accented\": \"--color-red-600\",\n\n /* Grayscale colors */\n \"--vl-grayscale\": \"--vl-neutral-100\",\n \"--vl-grayscale-lifted\": \"--vl-neutral-200\",\n \"--vl-grayscale-accented\": \"--vl-neutral-300\",\n\n /* Neutral colors */\n \"--vl-neutral\": \"--vl-neutral-300\",\n \"--vl-neutral-lifted\": \"--vl-neutral-400\",\n \"--vl-neutral-accented\": \"--vl-neutral-500\",\n\n /* Text neutral colors */\n \"--vl-text-inverted\": \"--vl-neutral-900\",\n \"--vl-text-muted\": \"--vl-neutral-600\",\n \"--vl-text-lifted\": \"--vl-neutral-400\",\n \"--vl-text-accented\": \"--vl-neutral-300\",\n \"--vl-text\": \"--vl-neutral-100\",\n\n /* Border neutral colors */\n \"--vl-border-muted\": \"--vl-neutral-800\",\n \"--vl-border\": \"--vl-neutral-700\",\n \"--vl-border-lifted\": \"--vl-neutral-600\",\n \"--vl-border-accented\": \"--vl-neutral-400\",\n\n /* Background neutral colors */\n \"--vl-bg\": \"--vl-neutral-900\",\n \"--vl-bg-muted\": \"--vl-neutral-800\",\n \"--vl-bg-lifted\": \"--vl-neutral-800\",\n \"--vl-bg-accented\": \"--vl-neutral-700\",\n \"--vl-bg-inverted\": \"--vl-neutral-100\",\n },\n};\n";
3
+ export const DEFAULT_VUELESS_CONFIG_CONTENT: "import { componentConfigs } from \"./.vueless\";\n\nexport default {\n /**\n * Global settings.\n */\n primary: \"grayscale\",\n neutral: \"gray\",\n text: 14,\n outline: 2,\n rounding: 8,\n letterSpacing: 0,\n disabledOpacity: 50,\n colorMode: \"auto\",\n\n /**\n * Component settings.\n */\n components: /*tw*/ {\n ...componentConfigs,\n },\n\n /**\n * Directive settings.\n */\n directives: {},\n\n /**\n * Light theme CSS variable settings.\n */\n lightTheme: {\n /* Primary colors */\n \"--vl-primary\": \"--vl-primary-600\",\n \"--vl-primary-lifted\": \"--vl-primary-700\",\n \"--vl-primary-accented\": \"--vl-primary-800\",\n\n /* Secondary colors */\n \"--vl-secondary\": \"--vl-neutral-500\",\n \"--vl-secondary-lifted\": \"--vl-neutral-600\",\n \"--vl-secondary-accented\": \"--vl-neutral-700\",\n\n /* Success colors */\n \"--vl-success\": \"--color-green-600\",\n \"--vl-success-lifted\": \"--color-green-700\",\n \"--vl-success-accented\": \"--color-green-800\",\n\n /* Info colors */\n \"--vl-info\": \"--color-blue-600\",\n \"--vl-info-lifted\": \"--color-blue-700\",\n \"--vl-info-accented\": \"--color-blue-800\",\n\n /* Notice colors */\n \"--vl-notice\": \"--color-violet-600\",\n \"--vl-notice-lifted\": \"--color-violet-700\",\n \"--vl-notice-accented\": \"--color-violet-800\",\n\n /* Warning colors */\n \"--vl-warning\": \"--color-orange-600\",\n \"--vl-warning-lifted\": \"--color-orange-700\",\n \"--vl-warning-accented\": \"--color-orange-800\",\n\n /* Error colors */\n \"--vl-error\": \"--color-red-600\",\n \"--vl-error-lifted\": \"--color-red-700\",\n \"--vl-error-accented\": \"--color-red-800\",\n\n /* Grayscale colors */\n \"--vl-grayscale\": \"--vl-neutral-900\",\n \"--vl-grayscale-lifted\": \"--vl-neutral-800\",\n \"--vl-grayscale-accented\": \"--vl-neutral-700\",\n\n /* Neutral colors */\n \"--vl-neutral\": \"--vl-neutral-500\",\n \"--vl-neutral-lifted\": \"--vl-neutral-600\",\n \"--vl-neutral-accented\": \"--vl-neutral-700\",\n\n /* Text neutral colors */\n \"--vl-text-inverted\": \"--color-white\",\n \"--vl-text-muted\": \"--vl-neutral-400\",\n \"--vl-text-lifted\": \"--vl-neutral-500\",\n \"--vl-text-accented\": \"--vl-neutral-600\",\n \"--vl-text\": \"--vl-neutral-900\",\n\n /* Border neutral colors */\n \"--vl-border-muted\": \"--vl-neutral-200\",\n \"--vl-border\": \"--vl-neutral-300\",\n \"--vl-border-lifted\": \"--vl-neutral-400\",\n \"--vl-border-accented\": \"--vl-neutral-600\",\n\n /* Background neutral colors */\n \"--vl-bg\": \"--color-white\",\n \"--vl-bg-muted\": \"--vl-neutral-50\",\n \"--vl-bg-lifted\": \"--vl-neutral-100\",\n \"--vl-bg-accented\": \"--vl-neutral-200\",\n \"--vl-bg-inverted\": \"--vl-neutral-900\",\n },\n\n /**\n * Dark theme CSS variable settings.\n */\n darkTheme: {\n /* Primary colors */\n \"--vl-primary\": \"--vl-primary-400\",\n \"--vl-primary-lifted\": \"--vl-primary-500\",\n \"--vl-primary-accented\": \"--vl-primary-600\",\n\n /* Secondary colors */\n \"--vl-secondary\": \"--vl-neutral-300\",\n \"--vl-secondary-lifted\": \"--vl-neutral-400\",\n \"--vl-secondary-accented\": \"--vl-neutral-500\",\n\n /* Success colors */\n \"--vl-success\": \"--color-green-400\",\n \"--vl-success-lifted\": \"--color-green-500\",\n \"--vl-success-accented\": \"--color-green-600\",\n\n /* Info colors */\n \"--vl-info\": \"--color-blue-400\",\n \"--vl-info-lifted\": \"--color-blue-500\",\n \"--vl-info-accented\": \"--color-blue-600\",\n\n /* Notice colors */\n \"--vl-notice\": \"--color-violet-400\",\n \"--vl-notice-lifted\": \"--color-violet-500\",\n \"--vl-notice-accented\": \"--color-violet-600\",\n\n /* Warning colors */\n \"--vl-warning\": \"--color-orange-400\",\n \"--vl-warning-lifted\": \"--color-orange-500\",\n \"--vl-warning-accented\": \"--color-orange-600\",\n\n /* Error colors */\n \"--vl-error\": \"--color-red-400\",\n \"--vl-error-lifted\": \"--color-red-500\",\n \"--vl-error-accented\": \"--color-red-600\",\n\n /* Grayscale colors */\n \"--vl-grayscale\": \"--vl-neutral-100\",\n \"--vl-grayscale-lifted\": \"--vl-neutral-200\",\n \"--vl-grayscale-accented\": \"--vl-neutral-300\",\n\n /* Neutral colors */\n \"--vl-neutral\": \"--vl-neutral-300\",\n \"--vl-neutral-lifted\": \"--vl-neutral-400\",\n \"--vl-neutral-accented\": \"--vl-neutral-500\",\n\n /* Text neutral colors */\n \"--vl-text-inverted\": \"--vl-neutral-900\",\n \"--vl-text-muted\": \"--vl-neutral-600\",\n \"--vl-text-lifted\": \"--vl-neutral-400\",\n \"--vl-text-accented\": \"--vl-neutral-300\",\n \"--vl-text\": \"--vl-neutral-100\",\n\n /* Border neutral colors */\n \"--vl-border-muted\": \"--vl-neutral-800\",\n \"--vl-border\": \"--vl-neutral-700\",\n \"--vl-border-lifted\": \"--vl-neutral-600\",\n \"--vl-border-accented\": \"--vl-neutral-400\",\n\n /* Background neutral colors */\n \"--vl-bg\": \"--vl-neutral-900\",\n \"--vl-bg-muted\": \"--vl-neutral-800\",\n \"--vl-bg-lifted\": \"--vl-neutral-800\",\n \"--vl-bg-accented\": \"--vl-neutral-700\",\n \"--vl-bg-inverted\": \"--vl-neutral-100\",\n },\n};\n";
package/bin/constants.js CHANGED
@@ -12,6 +12,7 @@ export default {
12
12
  text: 14,
13
13
  outline: 2,
14
14
  rounding: 8,
15
+ letterSpacing: 0,
15
16
  disabledOpacity: 50,
16
17
  colorMode: "auto",
17
18
 
package/constants.d.ts CHANGED
@@ -8,6 +8,7 @@ export const TEXT: "text";
8
8
  export const OUTLINE: "outline";
9
9
  export const ROUNDING: "rounding";
10
10
  export const DISABLED_OPACITY: "disabled-opacity";
11
+ export const LETTER_SPACING: "letter-spacing";
11
12
  export const COLOR_MODE_KEY: "vl-color-mode";
12
13
  export const AUTO_MODE_KEY: "vl-auto-mode";
13
14
  export const DARK_MODE_CLASS: "vl-dark";
@@ -24,6 +25,7 @@ export const DEFAULT_ROUNDING: 8;
24
25
  export const ROUNDING_DECREMENT: 4;
25
26
  export const ROUNDING_INCREMENT: 6;
26
27
  export const DEFAULT_DISABLED_OPACITY: 50;
28
+ export const DEFAULT_LETTER_SPACING: 0;
27
29
  export const PRIMARY_COLORS: string[];
28
30
  export const STATE_COLORS: string[];
29
31
  export const NEUTRAL_COLORS: string[];
package/constants.js CHANGED
@@ -16,6 +16,7 @@ export const TEXT = "text";
16
16
  export const OUTLINE = "outline";
17
17
  export const ROUNDING = "rounding";
18
18
  export const DISABLED_OPACITY = "disabled-opacity";
19
+ export const LETTER_SPACING = "letter-spacing";
19
20
 
20
21
  /* Vueless color mode keys */
21
22
  export const COLOR_MODE_KEY = "vl-color-mode";
@@ -36,6 +37,7 @@ export const DEFAULT_ROUNDING = 8; /* pixels */
36
37
  export const ROUNDING_DECREMENT = 4; /* pixels */
37
38
  export const ROUNDING_INCREMENT = 6; /* pixels */
38
39
  export const DEFAULT_DISABLED_OPACITY = 50; /* presents */
40
+ export const DEFAULT_LETTER_SPACING = 0; /* em */
39
41
 
40
42
  /* Vueless supported color shades */
41
43
  export const PRIMARY_COLORS = [
package/modules.d.ts CHANGED
@@ -20,12 +20,6 @@ declare module "*.svg?skipsvgo" {
20
20
  export default component;
21
21
  }
22
22
 
23
- declare module "vueless/storybook" {
24
- import type { Config, UnknownObject } from "./types";
25
- export function defineConfigWithVueless(config: Config): Promise<UnknownObject>;
26
- export function getVuelessStoriesGlob(vuelessEnv: string): Promise<string[]>;
27
- }
28
-
29
23
  declare module "virtual:vueless/icons" {
30
24
  import type { UnknownArray } from "./types";
31
25
  export const cachedIcons: UnknownArray;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.2.3-beta.1",
3
+ "version": "1.2.3-beta.11",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
@@ -9,9 +9,10 @@
9
9
  "access": "public"
10
10
  },
11
11
  "scripts": {
12
- "dev:docs": "storybook dev -p 6006 --docs --no-open",
13
- "dev": "storybook dev -p 6006 --no-open",
14
- "build": "storybook build --docs",
12
+ "link:package": "rm -rf node_modules/vueless && ln -s ../src ./node_modules/vueless",
13
+ "dev:docs": "npm run link:package && storybook dev -p 6006 --docs --no-open",
14
+ "dev": "npm run link:package && storybook dev -p 6006 --no-open",
15
+ "build": "npm run link:package && storybook build --docs",
15
16
  "preview": "vite preview --host --outDir=storybook-static",
16
17
  "ts:check": "vue-tsc --build --force",
17
18
  "release:icons": "npx node .scripts/icons",
@@ -21,8 +22,8 @@
21
22
  "release:patch": "release-it patch --ci --npm.publish --git.tag --github.release",
22
23
  "release:minor": "release-it minor --ci --npm.publish --git.tag --github.release",
23
24
  "release:major": "release-it major --ci --npm.publish --git.tag --github.release",
24
- "lint": "eslint --no-fix src/ test/ .storybook/ .vueless/",
25
- "lint:fix": "eslint --fix src/ test/ .storybook/ .vueless/",
25
+ "lint": "eslint --no-fix src/ test/ .storybook/ .vueless/ .scripts/",
26
+ "lint:fix": "eslint --fix src/ test/ .storybook/ .vueless/ .scripts/",
26
27
  "lint:ci": "eslint --no-fix --max-warnings=0",
27
28
  "test": "vitest",
28
29
  "test:ci": "vitest --run"
@@ -55,7 +56,7 @@
55
56
  "@vue/eslint-config-typescript": "^14.6.0",
56
57
  "@vue/test-utils": "^2.4.6",
57
58
  "@vue/tsconfig": "^0.7.0",
58
- "@vueless/storybook": "^1.1.3-beta.10",
59
+ "@vueless/storybook": "^1.2.1",
59
60
  "eslint": "^9.32.0",
60
61
  "eslint-plugin-storybook": "^9.0.18",
61
62
  "eslint-plugin-vue": "^10.3.0",
@@ -68,8 +69,15 @@
68
69
  "vitest": "^3.2.4",
69
70
  "vue": "^3.5.18",
70
71
  "vue-router": "^4.5.1",
71
- "vue-tsc": "^3.0.4",
72
- "vueless": "^1.2.1-beta.2"
72
+ "vue-tsc": "^3.0.4"
73
+ },
74
+ "peerDependencies": {
75
+ "vue-router": "^4.5.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "vue-router": {
79
+ "optional": true
80
+ }
73
81
  },
74
82
  "sideEffects": false,
75
83
  "style": "tailwind.css",
package/tailwind.css CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  @layer base {
24
24
  body {
25
- @apply antialiased text-(--vl-text) bg-(--vl-bg) scheme-light dark:scheme-dark;
25
+ @apply antialiased text-(--vl-text) bg-(--vl-bg) tracking-(--vl-letter-spacing) scheme-light dark:scheme-dark;
26
26
  }
27
27
 
28
28
  [type='checkbox']:checked {
package/types.ts CHANGED
@@ -113,6 +113,11 @@ export interface ThemeConfig {
113
113
  */
114
114
  outline?: number | Partial<ThemeConfigOutline>;
115
115
 
116
+ /**
117
+ * Default components letter spacing.
118
+ */
119
+ letterSpacing?: number;
120
+
116
121
  /**
117
122
  * Default components opacity for disabled state (in percents).
118
123
  */
@@ -1,7 +1,7 @@
1
1
  import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/addon-docs/blocks";
2
2
  import { getSource } from "../../utils/storybook";
3
3
 
4
- import * as stories from "./stories.js";
4
+ import * as stories from "./stories";
5
5
  import defaultConfig from "../config?raw"
6
6
 
7
7
  <Meta of={stories} />
@@ -31,7 +31,10 @@ export default {
31
31
  },
32
32
  parameters: {
33
33
  docs: {
34
- ...getDocsDescription(ULink.__name),
34
+ ...getDocsDescription(
35
+ ULink.__name,
36
+ "For Vue projects <a href='https://router.vuejs.org' target='_blank'>VueRouter</a> needs to be installed.",
37
+ ),
35
38
  },
36
39
  },
37
40
  } as Meta;
@@ -39,6 +39,22 @@ const emit = defineEmits([
39
39
  * @property {number} value
40
40
  */
41
41
  "update:modelValue",
42
+
43
+ /**
44
+ * Triggers when a dropdown list is opened.
45
+ */
46
+ "open",
47
+
48
+ /**
49
+ * Triggers when a dropdown list is closed.
50
+ */
51
+ "close",
52
+
53
+ /**
54
+ * Triggers when the search value is changed.
55
+ * @property {string} query
56
+ */
57
+ "searchChange",
42
58
  ]);
43
59
 
44
60
  type UListboxRef = InstanceType<typeof UListbox>;
@@ -80,7 +96,7 @@ const selectedOptions = computed(() => {
80
96
  });
81
97
 
82
98
  const badgeLabel = computed(() => {
83
- if (!selectedOptions.value.length) {
99
+ if (!props.labelDisplayCount || !selectedOptions.value.length) {
84
100
  return props.label;
85
101
  }
86
102
 
@@ -114,22 +130,30 @@ function getFullOptionLabels(value: Option | Option[]) {
114
130
  return "";
115
131
  }
116
132
 
133
+ function onSearchChange(query: string) {
134
+ emit("searchChange", query);
135
+ }
136
+
117
137
  function onClickBadge() {
118
138
  isShownOptions.value = !isShownOptions.value;
119
139
 
120
140
  if (isShownOptions.value) {
121
141
  nextTick(() => listboxRef.value?.wrapperRef?.focus());
142
+
143
+ emit("open");
122
144
  }
123
145
  }
124
146
 
125
147
  function hideOptions() {
126
148
  isShownOptions.value = false;
149
+
150
+ emit("close");
127
151
  }
128
152
 
129
153
  function onClickOption(option: Option) {
130
154
  emit("clickOption", option);
131
155
 
132
- hideOptions();
156
+ if (!props.multiple) hideOptions();
133
157
  }
134
158
 
135
159
  defineExpose({
@@ -229,6 +253,7 @@ const { getDataTest, config, wrapperAttrs, dropdownBadgeAttrs, listboxAttrs, tog
229
253
  v-bind="listboxAttrs"
230
254
  :data-test="getDataTest('list')"
231
255
  @click-option="onClickOption"
256
+ @search-change="onSearchChange"
232
257
  />
233
258
  </div>
234
259
  </template>
@@ -39,6 +39,22 @@ const emit = defineEmits([
39
39
  * @property {number} value
40
40
  */
41
41
  "update:modelValue",
42
+
43
+ /**
44
+ * Triggers when a dropdown list is opened.
45
+ */
46
+ "open",
47
+
48
+ /**
49
+ * Triggers when a dropdown list is closed.
50
+ */
51
+ "close",
52
+
53
+ /**
54
+ * Triggers when the search value is changed.
55
+ * @property {string} query
56
+ */
57
+ "searchChange",
42
58
  ]);
43
59
 
44
60
  provide("hideDropdownOptions", hideOptions);
@@ -82,7 +98,7 @@ const selectedOptions = computed(() => {
82
98
  });
83
99
 
84
100
  const buttonLabel = computed(() => {
85
- if (!selectedOptions.value.length) {
101
+ if (!props.labelDisplayCount || !selectedOptions.value.length) {
86
102
  return props.label;
87
103
  }
88
104
 
@@ -106,6 +122,10 @@ const toggleIconName = computed(() => {
106
122
  return props.toggleIcon ? config.value.defaults.toggleIcon : "";
107
123
  });
108
124
 
125
+ function onSearchChange(query: string) {
126
+ emit("searchChange", query);
127
+ }
128
+
109
129
  function getFullOptionLabels(value: Option | Option[]) {
110
130
  const labelKey = props.labelKey;
111
131
 
@@ -119,7 +139,7 @@ function getFullOptionLabels(value: Option | Option[]) {
119
139
  function onClickOption(option: Option) {
120
140
  emit("clickOption", option);
121
141
 
122
- hideOptions();
142
+ if (!props.multiple) hideOptions();
123
143
  }
124
144
 
125
145
  function onClickButton() {
@@ -127,11 +147,15 @@ function onClickButton() {
127
147
 
128
148
  if (isShownOptions.value) {
129
149
  nextTick(() => listboxRef.value?.wrapperRef?.focus());
150
+
151
+ emit("open");
130
152
  }
131
153
  }
132
154
 
133
155
  function hideOptions() {
134
156
  isShownOptions.value = false;
157
+
158
+ emit("close");
135
159
  }
136
160
 
137
161
  defineExpose({
@@ -230,6 +254,7 @@ const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs,
230
254
  v-bind="listboxAttrs"
231
255
  :data-test="getDataTest('list')"
232
256
  @click-option="onClickOption"
257
+ @search-change="onSearchChange"
233
258
  />
234
259
  </div>
235
260
  </template>
@@ -39,6 +39,22 @@ const emit = defineEmits([
39
39
  * @property {number} value
40
40
  */
41
41
  "update:modelValue",
42
+
43
+ /**
44
+ * Triggers when a dropdown list is opened.
45
+ */
46
+ "open",
47
+
48
+ /**
49
+ * Triggers when a dropdown list is closed.
50
+ */
51
+ "close",
52
+
53
+ /**
54
+ * Triggers when the search value is changed.
55
+ * @property {string} query
56
+ */
57
+ "searchChange",
42
58
  ]);
43
59
 
44
60
  provide("hideDropdownOptions", hideOptions);
@@ -82,7 +98,7 @@ const selectedOptions = computed(() => {
82
98
  });
83
99
 
84
100
  const linkLabel = computed(() => {
85
- if (!selectedOptions.value.length) {
101
+ if (!props.labelDisplayCount || !selectedOptions.value.length) {
86
102
  return props.label;
87
103
  }
88
104
 
@@ -116,6 +132,10 @@ function getFullOptionLabels(value: Option | Option[]) {
116
132
  return "";
117
133
  }
118
134
 
135
+ function onSearchChange(query: string) {
136
+ emit("searchChange", query);
137
+ }
138
+
119
139
  function onClickLink() {
120
140
  if (props.disabled) return;
121
141
 
@@ -123,17 +143,21 @@ function onClickLink() {
123
143
 
124
144
  if (isShownOptions.value) {
125
145
  nextTick(() => listboxRef.value?.wrapperRef?.focus());
146
+
147
+ emit("open");
126
148
  }
127
149
  }
128
150
 
129
151
  function hideOptions() {
130
152
  isShownOptions.value = false;
153
+
154
+ emit("close");
131
155
  }
132
156
 
133
157
  function onClickOption(option: Option) {
134
158
  emit("clickOption", option);
135
159
 
136
- hideOptions();
160
+ if (!props.multiple) hideOptions();
137
161
  }
138
162
 
139
163
  defineExpose({
@@ -235,6 +259,7 @@ const { config, getDataTest, wrapperAttrs, dropdownLinkAttrs, listboxAttrs, togg
235
259
  v-bind="listboxAttrs"
236
260
  :data-test="getDataTest('list')"
237
261
  @click-option="onClickOption"
262
+ @search-change="onSearchChange"
238
263
  />
239
264
  </div>
240
265
  </template>
@@ -295,7 +295,17 @@ async function copyIcon(name, library) {
295
295
  const require = createRequire(import.meta.url);
296
296
 
297
297
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
298
- await cp(require.resolve(sourcePath), destinationPath);
298
+
299
+ try {
300
+ await fs.promises.copyFile(require.resolve(sourcePath), destinationPath);
301
+ } catch (err) {
302
+ if (err.code === "EEXIST" || err.code === "EBUSY") {
303
+ // Another process/thread already copied or Windows locked temporarily
304
+ return;
305
+ }
306
+
307
+ throw err;
308
+ }
299
309
  }
300
310
  }
301
311
 
@@ -3,7 +3,7 @@ import { getVuelessConfig } from "./vuelessConfig.js";
3
3
  export async function buildWebTypes({ vuelessSrcDir, basePath } = {}) {
4
4
  try {
5
5
  const vuelessConfig = await getVuelessConfig(basePath);
6
- const { default: build } = await import("@vueless/storybook/webTypes/index.js");
6
+ const { buildWebTypes: build } = await import("@vueless/storybook");
7
7
 
8
8
  await build(vuelessConfig, vuelessSrcDir);
9
9
  } catch (error) {
@@ -334,7 +334,7 @@ export function getSlotsFragment(defaultTemplate: string) {
334
334
  /**
335
335
  * Create story param config to show component description with a link on GitHub.
336
336
  */
337
- export function getDocsDescription(componentName: string | undefined) {
337
+ export function getDocsDescription(componentName: string | undefined, afterText = "") {
338
338
  if (!componentName) {
339
339
  return {};
340
340
  }
@@ -342,7 +342,7 @@ export function getDocsDescription(componentName: string | undefined) {
342
342
  let viewOnGitHub = "";
343
343
 
344
344
  if (COMPONENTS[componentName as ComponentNames]) {
345
- viewOnGitHub = `| <a href="https://github.com/vuelessjs/vueless/tree/main/src/${COMPONENTS[componentName as ComponentNames]}" target="_blank">View on GitHub</a>`;
345
+ viewOnGitHub = `| <a href="https://github.com/vuelessjs/vueless/tree/main/src/${COMPONENTS[componentName as ComponentNames]}" target="_blank">View on GitHub</a><br/>${afterText}`;
346
346
  }
347
347
 
348
348
  return {
package/utils/theme.ts CHANGED
@@ -33,6 +33,8 @@ import {
33
33
  TEXT_DECREMENT,
34
34
  DISABLED_OPACITY,
35
35
  DEFAULT_DISABLED_OPACITY,
36
+ LETTER_SPACING,
37
+ DEFAULT_LETTER_SPACING,
36
38
  } from "../constants";
37
39
 
38
40
  import type {
@@ -52,6 +54,7 @@ declare interface RootCSSVariableOptions {
52
54
  text: ThemeConfigText;
53
55
  rounding: ThemeConfigRounding;
54
56
  outline: ThemeConfigOutline;
57
+ letterSpacing: number;
55
58
  disabledOpacity: number;
56
59
  lightTheme: Partial<VuelessCssVariables>;
57
60
  darkTheme: Partial<VuelessCssVariables>;
@@ -150,6 +153,7 @@ export function setTheme(config: ThemeConfig = {}, isCachedAutoMode?: boolean) {
150
153
  const text = getText(config.text);
151
154
  const outline = getOutlines(config.outline);
152
155
  const rounding = getRoundings(config.rounding);
156
+ const letterSpacing = getLetterSpacing(config.letterSpacing);
153
157
  const disabledOpacity = getDisabledOpacity(config.disabledOpacity);
154
158
 
155
159
  let primary = getPrimaryColor(config.primary);
@@ -215,6 +219,7 @@ export function setTheme(config: ThemeConfig = {}, isCachedAutoMode?: boolean) {
215
219
  text,
216
220
  outline,
217
221
  rounding,
222
+ letterSpacing,
218
223
  disabledOpacity,
219
224
  lightTheme,
220
225
  darkTheme,
@@ -443,6 +448,24 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
443
448
  return mergedRounding;
444
449
  }
445
450
 
451
+ /**
452
+ * Retrieve letter spacing value and save them to cookie and localStorage.
453
+ * @return number - letter spacing value.
454
+ */
455
+ function getLetterSpacing(letterSpacing?: ThemeConfig["letterSpacing"]) {
456
+ const storageKey = `vl-${LETTER_SPACING}`;
457
+
458
+ const spacing = letterSpacing ?? getStored(storageKey) ?? vuelessConfig.letterSpacing;
459
+ const mergedSpacing = Number(spacing ?? DEFAULT_LETTER_SPACING);
460
+
461
+ if (isCSR && letterSpacing) {
462
+ setCookie(storageKey, String(mergedSpacing));
463
+ localStorage.setItem(storageKey, String(mergedSpacing));
464
+ }
465
+
466
+ return mergedSpacing;
467
+ }
468
+
446
469
  /**
447
470
  * Retrieve disabled opacity value and save them to cookie and localStorage.
448
471
  * @return number - opacity value.
@@ -487,6 +510,7 @@ function setRootCSSVariables(vars: RootCSSVariableOptions) {
487
510
  "--vl-rounding-sm": `${vars.rounding.sm / PX_IN_REM}rem`,
488
511
  "--vl-rounding-md": `${vars.rounding.md / PX_IN_REM}rem`,
489
512
  "--vl-rounding-lg": `${vars.rounding.lg / PX_IN_REM}rem`,
513
+ "--vl-letter-spacing": `${vars.letterSpacing}em`,
490
514
  "--vl-disabled-opacity": `${vars.disabledOpacity}%`,
491
515
  };
492
516
 
package/utils/ui.ts CHANGED
@@ -6,11 +6,10 @@ import { isCSR, isSSR } from "./helper";
6
6
  import { createGetMergedConfig } from "./node/mergeConfigs";
7
7
  import { COMPONENT_NAME as U_ICON } from "../ui.image-icon/constants";
8
8
  import {
9
- JAVASCRIPT_EXT,
10
- TYPESCRIPT_EXT,
9
+ VUELESS_CACHE_DIR,
10
+ ICON_NON_PROPS_DEFAULTS,
11
11
  VUELESS_CONFIG_FILE_NAME,
12
12
  TAILWIND_MERGE_EXTENSION,
13
- ICON_NON_PROPS_DEFAULTS,
14
13
  } from "../constants";
15
14
 
16
15
  import type { Config, ComponentDefaults, UnknownObject, ComponentNames } from "../types";
@@ -51,14 +50,10 @@ if (isSSR) {
51
50
  (async () => {
52
51
  try {
53
52
  vuelessConfig = (
54
- await import(/* @vite-ignore */ `/${VUELESS_CONFIG_FILE_NAME}${JAVASCRIPT_EXT}`)
53
+ await import(
54
+ /* @vite-ignore */ `${process.cwd()}/${VUELESS_CACHE_DIR}/${VUELESS_CONFIG_FILE_NAME}.mjs`
55
+ )
55
56
  ).default;
56
-
57
- if (!vuelessConfig) {
58
- vuelessConfig = (
59
- await import(/* @vite-ignore */ `/${VUELESS_CONFIG_FILE_NAME}${TYPESCRIPT_EXT}`)
60
- ).default;
61
- }
62
57
  } catch {
63
58
  vuelessConfig = {} as Config;
64
59
  }
@@ -1 +0,0 @@
1
- export function defineConfigWithVueless(config: Object): Promise<Object>;
@@ -1,86 +0,0 @@
1
- import { getVuelessConfig } from "./vuelessConfig.js";
2
- import {
3
- COMPONENTS,
4
- DIRECTIVES,
5
- INTERNAL_ENV,
6
- VUELESS_LOCAL_DIR,
7
- VUELESS_PACKAGE_DIR,
8
- } from "../../constants.js";
9
-
10
- /**
11
- * Defines the config for Storybook.
12
- *
13
- * @param {Object} config - The config object.
14
- * @return {Promise<Object>} A promise that resolves to the modified config object.
15
- */
16
- export function defineConfigWithVueless(config) {
17
- return (async () => ({
18
- ...config,
19
- stories: [...config.stories, ...(await getVuelessStoriesGlob(config?.vuelessEnv))],
20
- addons: [
21
- ...new Set([
22
- ...(config.addons || []),
23
- "@storybook/addon-docs",
24
- "@storybook/addon-links",
25
- "@vueless/storybook-dark-mode",
26
- "@storybook/addon-themes",
27
- ]),
28
- ],
29
- staticDirs: ["public"],
30
- framework: {
31
- ...config.framework,
32
- name: "@storybook/vue3-vite",
33
- options: {
34
- ...config.framework?.options,
35
- builder: {
36
- ...config.framework?.options?.builder,
37
- viteConfigPath: ".storybook/vite.config.js",
38
- },
39
- },
40
- },
41
- env: (envConfig) => ({
42
- ...envConfig,
43
- BASE_URL: "/",
44
- }),
45
- }))();
46
- }
47
-
48
- /**
49
- * Retrieves the glob pattern for Vueless stories based on the provided Vueless environment.
50
- *
51
- * @param {string} vuelessEnv - The Vueless environment.
52
- * @return {Promise<string[]>} A promise that resolves to an array of glob patterns for Vueless stories.
53
- */
54
- async function getVuelessStoriesGlob(vuelessEnv) {
55
- const vuelessSrcDir = vuelessEnv === INTERNAL_ENV ? VUELESS_LOCAL_DIR : VUELESS_PACKAGE_DIR;
56
- const vuelessConfig = await getVuelessConfig();
57
- const storiesGlob = [];
58
-
59
- for (const [directiveName, directiveDir] of Object.entries(DIRECTIVES)) {
60
- const directiveGlobalConfig = vuelessConfig.directives?.[directiveName];
61
- const isHiddenStoriesByDirective = directiveGlobalConfig === false;
62
- const isHiddenStoriesByKey = directiveGlobalConfig?.storybook === false;
63
-
64
- if (isHiddenStoriesByDirective || isHiddenStoriesByKey) {
65
- continue;
66
- }
67
-
68
- storiesGlob.push(`../${vuelessSrcDir}/${directiveDir}/storybook/stories.{js,ts}`);
69
- storiesGlob.push(`../${vuelessSrcDir}/${directiveDir}/storybook/docs.mdx`);
70
- }
71
-
72
- for (const [componentName, componentDir] of Object.entries(COMPONENTS)) {
73
- const componentGlobalConfig = vuelessConfig.components?.[componentName];
74
- const isHiddenStoriesByComponent = componentGlobalConfig === false;
75
- const isHiddenStoriesByKey = componentGlobalConfig?.storybook === false;
76
-
77
- if (isHiddenStoriesByComponent || isHiddenStoriesByKey) {
78
- continue;
79
- }
80
-
81
- storiesGlob.push(`../${vuelessSrcDir}/${componentDir}/storybook/stories.{js,ts}`);
82
- storiesGlob.push(`../${vuelessSrcDir}/${componentDir}/storybook/docs.mdx`);
83
- }
84
-
85
- return storiesGlob;
86
- }