vueless 1.3.7-beta.2 → 1.3.7-beta.3

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 (32) hide show
  1. package/components.d.ts +1 -0
  2. package/components.ts +1 -0
  3. package/constants.d.ts +2 -0
  4. package/constants.js +2 -0
  5. package/package.json +2 -2
  6. package/ui.container-col/UCol.vue +0 -1
  7. package/ui.container-collapsible/UCollapsible.vue +190 -0
  8. package/ui.container-collapsible/config.ts +45 -0
  9. package/ui.container-collapsible/constants.ts +1 -0
  10. package/ui.container-collapsible/storybook/docs.mdx +17 -0
  11. package/ui.container-collapsible/storybook/stories.ts +261 -0
  12. package/ui.container-collapsible/tests/UCollapsible.test.ts +571 -0
  13. package/ui.container-collapsible/types.ts +57 -0
  14. package/ui.dropdown/UDropdown.vue +324 -0
  15. package/ui.dropdown/config.ts +27 -0
  16. package/ui.dropdown/constants.ts +1 -0
  17. package/ui.dropdown/storybook/docs.mdx +17 -0
  18. package/ui.dropdown/storybook/stories.ts +286 -0
  19. package/ui.dropdown/tests/UDropdown.test.ts +631 -0
  20. package/ui.dropdown/types.ts +127 -0
  21. package/ui.dropdown-badge/UDropdownBadge.vue +119 -227
  22. package/ui.dropdown-badge/config.ts +18 -15
  23. package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +201 -67
  24. package/ui.dropdown-button/UDropdownButton.vue +121 -226
  25. package/ui.dropdown-button/config.ts +32 -28
  26. package/ui.dropdown-button/tests/UDropdownButton.test.ts +189 -73
  27. package/ui.dropdown-link/UDropdownLink.vue +123 -233
  28. package/ui.dropdown-link/config.ts +15 -18
  29. package/ui.dropdown-link/tests/UDropdownLink.test.ts +190 -71
  30. package/ui.form-listbox/UListbox.vue +2 -3
  31. package/ui.form-listbox/config.ts +2 -2
  32. package/ui.form-select/config.ts +1 -1
package/components.d.ts CHANGED
@@ -44,6 +44,7 @@ export { default as URow } from "./ui.container-row/URow.vue";
44
44
  export { default as UGrid } from "./ui.container-grid/UGrid.vue";
45
45
  export { default as UGroup } from "./ui.container-group/UGroup.vue";
46
46
  export { default as UGroups } from "./ui.container-groups/UGroups.vue";
47
+ export { default as UCollapsible } from "./ui.container-collapsible/UCollapsible.vue";
47
48
  export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
48
49
  export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
49
50
  export { default as UEmpty } from "./ui.container-empty/UEmpty.vue";
package/components.ts CHANGED
@@ -44,6 +44,7 @@ export { default as URow } from "./ui.container-row/URow.vue";
44
44
  export { default as UGrid } from "./ui.container-grid/UGrid.vue";
45
45
  export { default as UGroup } from "./ui.container-group/UGroup.vue";
46
46
  export { default as UGroups } from "./ui.container-groups/UGroups.vue";
47
+ export { default as UCollapsible } from "./ui.container-collapsible/UCollapsible.vue";
47
48
  export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
48
49
  export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
49
50
  export { default as UEmpty } from "./ui.container-empty/UEmpty.vue";
package/constants.d.ts CHANGED
@@ -145,6 +145,7 @@ export namespace COMPONENTS {
145
145
  let UButton: string;
146
146
  let ULink: string;
147
147
  let UToggle: string;
148
+ let UDropdown: string;
148
149
  let UDropdownButton: string;
149
150
  let UDropdownBadge: string;
150
151
  let UDropdownLink: string;
@@ -183,6 +184,7 @@ export namespace COMPONENTS {
183
184
  let UGrid: string;
184
185
  let UGroup: string;
185
186
  let UGroups: string;
187
+ let UCollapsible: string;
186
188
  let UAccordion: string;
187
189
  let UAccordionItem: string;
188
190
  let UEmpty: string;
package/constants.js CHANGED
@@ -249,6 +249,7 @@ export const COMPONENTS = {
249
249
  UToggle: "ui.button-toggle",
250
250
 
251
251
  /* Dropdowns */
252
+ UDropdown: "ui.dropdown",
252
253
  UDropdownButton: "ui.dropdown-button",
253
254
  UDropdownBadge: "ui.dropdown-badge",
254
255
  UDropdownLink: "ui.dropdown-link",
@@ -293,6 +294,7 @@ export const COMPONENTS = {
293
294
  UGrid: "ui.container-grid",
294
295
  UGroup: "ui.container-group",
295
296
  UGroups: "ui.container-groups",
297
+ UCollapsible: "ui.container-collapsible",
296
298
  UAccordion: "ui.container-accordion",
297
299
  UAccordionItem: "ui.container-accordion-item",
298
300
  UEmpty: "ui.container-empty",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.3.7-beta.2",
3
+ "version": "1.3.7-beta.3",
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",
@@ -57,7 +57,7 @@
57
57
  "@vue/eslint-config-typescript": "^14.6.0",
58
58
  "@vue/test-utils": "^2.4.6",
59
59
  "@vue/tsconfig": "^0.7.0",
60
- "@vueless/storybook": "^1.3.16",
60
+ "@vueless/storybook": "^1.3.18",
61
61
  "eslint": "^9.32.0",
62
62
  "eslint-plugin-storybook": "^10.0.2",
63
63
  "eslint-plugin-vue": "^10.3.0",
@@ -28,7 +28,6 @@ defineExpose({
28
28
  * Get element / nested component attributes for each config token ✨
29
29
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
30
30
  */
31
-
32
31
  const { getDataTest, wrapperAttrs } = useUI<Config>(defaultConfig);
33
32
  </script>
34
33
 
@@ -0,0 +1,190 @@
1
+ <script setup lang="ts">
2
+ import { nextTick, computed, ref, useId, useTemplateRef, watch } from "vue";
3
+
4
+ import { useUI } from "../composables/useUI";
5
+ import { getDefaults } from "../utils/ui";
6
+
7
+ import vClickOutside from "../v.click-outside/vClickOutside";
8
+
9
+ import defaultConfig from "./config";
10
+ import { COMPONENT_NAME } from "./constants";
11
+
12
+ import type { Props, Config } from "./types";
13
+
14
+ defineOptions({ inheritAttrs: false });
15
+
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
18
+ });
19
+
20
+ const emit = defineEmits([
21
+ /**
22
+ * Triggers when the opened state changes.
23
+ * @property {boolean} value
24
+ */
25
+ "update:open",
26
+
27
+ /**
28
+ * Triggers when collapsible is opened.
29
+ */
30
+ "open",
31
+
32
+ /**
33
+ * Triggers when collapsible is closed.
34
+ */
35
+ "close",
36
+ ]);
37
+
38
+ const internalIsOpened = ref(false);
39
+ const isClickingContent = ref(false);
40
+
41
+ const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
42
+ const elementId = props.id || useId();
43
+
44
+ watch(
45
+ () => props.open,
46
+ (newValue) => {
47
+ if (newValue !== undefined) {
48
+ internalIsOpened.value = newValue;
49
+ }
50
+ },
51
+ { immediate: true },
52
+ );
53
+
54
+ const isOpened = computed({
55
+ get: () => internalIsOpened.value,
56
+ set: (value) => {
57
+ internalIsOpened.value = value;
58
+ emit("update:open", value);
59
+ },
60
+ });
61
+
62
+ function handleClickOutside() {
63
+ if (isClickingContent.value || !props.closeOnOutside) {
64
+ return;
65
+ }
66
+
67
+ hide();
68
+ }
69
+
70
+ function toggle() {
71
+ if (props.disabled) return;
72
+
73
+ isOpened.value = !isOpened.value;
74
+
75
+ isOpened.value ? emit("open") : emit("close");
76
+ }
77
+
78
+ function hide() {
79
+ isOpened.value = false;
80
+
81
+ emit("close");
82
+ }
83
+
84
+ function show() {
85
+ if (props.disabled) return;
86
+
87
+ isOpened.value = true;
88
+
89
+ emit("open");
90
+ }
91
+
92
+ function onContentClick() {
93
+ if (!props.closeOnContent) {
94
+ isClickingContent.value = true;
95
+
96
+ nextTick(() => {
97
+ isClickingContent.value = false;
98
+ });
99
+
100
+ return;
101
+ }
102
+
103
+ hide();
104
+ }
105
+
106
+ defineExpose({
107
+ /**
108
+ * A reference to the component's wrapper element for direct DOM manipulation.
109
+ * @property {HTMLDivElement}
110
+ */
111
+ wrapperRef,
112
+
113
+ /**
114
+ * Hides the collapsible content.
115
+ * @property {function}
116
+ */
117
+ hide,
118
+
119
+ /**
120
+ * Shows the collapsible content.
121
+ * @property {function}
122
+ */
123
+ show,
124
+
125
+ /**
126
+ * Toggles the collapsible visibility.
127
+ * @property {function}
128
+ */
129
+ toggle,
130
+
131
+ /**
132
+ * Indicates whether the collapsible is opened.
133
+ * @property {boolean}
134
+ */
135
+ isOpened,
136
+ });
137
+
138
+ /**
139
+ * Get element / nested component attributes for each config token ✨
140
+ * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
141
+ */
142
+ const mutatedProps = computed(() => ({
143
+ /* component state, not a props */
144
+ opened: isOpened.value,
145
+ }));
146
+
147
+ const { getDataTest, config, wrapperAttrs, contentAttrs } = useUI<Config>(
148
+ defaultConfig,
149
+ mutatedProps,
150
+ );
151
+ </script>
152
+
153
+ <template>
154
+ <div
155
+ :id="elementId"
156
+ ref="wrapper"
157
+ v-click-outside="handleClickOutside"
158
+ v-bind="wrapperAttrs"
159
+ tabindex="0"
160
+ :data-test="getDataTest()"
161
+ @click="toggle"
162
+ @keydown.enter="toggle"
163
+ >
164
+ <!--
165
+ @slot Use it to add custom trigger element for the collapsible.
166
+ @binding {boolean} opened
167
+ -->
168
+ <slot :opened="isOpened" />
169
+
170
+ <Transition v-bind="config.contentTransition">
171
+ <!--
172
+ @slot Use it to add collapsible content.
173
+ @binding {boolean} opened
174
+ -->
175
+ <div
176
+ v-if="isOpened"
177
+ v-bind="contentAttrs"
178
+ :data-test="getDataTest('content')"
179
+ @click.stop="onContentClick"
180
+ >
181
+ <!--
182
+ @slot Use it to add some content need to be shown.
183
+ @binding {boolean} opened
184
+ @binding {string} contentClasses
185
+ -->
186
+ <slot name="content" :opened="isOpened" :content-classes="contentAttrs.class" />
187
+ </div>
188
+ </Transition>
189
+ </div>
190
+ </template>
@@ -0,0 +1,45 @@
1
+ export default /*tw*/ {
2
+ wrapper: {
3
+ base: `
4
+ relative inline-block h-max rounded-medium transition cursor-pointer outline-transparent
5
+ focus-visible:outline-medium focus-visible:outline-offset-2 focus-visible:outline-primary`,
6
+ variants: {
7
+ disabled: {
8
+ true: "cursor-not-allowed",
9
+ },
10
+ },
11
+ },
12
+ content: {
13
+ base: "z-10 w-fit",
14
+ variants: {
15
+ absolute: {
16
+ true: "absolute",
17
+ false: "",
18
+ },
19
+ yPosition: {
20
+ top: "bottom-full mb-1",
21
+ bottom: "top-full mt-1",
22
+ },
23
+ xPosition: {
24
+ left: "left-0",
25
+ right: "right-0",
26
+ },
27
+ },
28
+ },
29
+ contentTransition: {
30
+ enterActiveClass: "ease-out duration-200",
31
+ enterFromClass: "opacity-0 scale-95",
32
+ enterToClass: "opacity-100 scale-100",
33
+ leaveActiveClass: "ease-in duration-150",
34
+ leaveFromClass: "opacity-100 scale-100",
35
+ leaveToClass: "opacity-0 scale-95",
36
+ },
37
+ defaults: {
38
+ absolute: true,
39
+ yPosition: "bottom",
40
+ xPosition: "left",
41
+ closeOnOutside: true,
42
+ closeOnContent: false,
43
+ disabled: false,
44
+ },
45
+ };
@@ -0,0 +1 @@
1
+ export const COMPONENT_NAME = "UCollapsible";
@@ -0,0 +1,17 @@
1
+ import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/addon-docs/blocks";
2
+ import { getSource } from "../../utils/storybook.ts";
3
+
4
+ import * as stories from "./stories.ts";
5
+ import defaultConfig from "../config.ts?raw"
6
+
7
+ <Meta of={stories} />
8
+ <Title of={stories} />
9
+ <Subtitle of={stories} />
10
+ <Description of={stories} />
11
+ <Primary of={stories} />
12
+ <Controls of={stories.Default} />
13
+ <Stories of={stories} />
14
+
15
+ ## Default config
16
+ <Source code={getSource(defaultConfig)} language="jsx" dark />
17
+
@@ -0,0 +1,261 @@
1
+ import { ref } from "vue";
2
+ import {
3
+ getArgs,
4
+ getArgTypes,
5
+ getSlotNames,
6
+ getSlotsFragment,
7
+ getDocsDescription,
8
+ } from "../../utils/storybook";
9
+
10
+ import UCollapsible from "../UCollapsible.vue";
11
+ import UButton from "../../ui.button/UButton.vue";
12
+ import UCard from "../../ui.container-card/UCard.vue";
13
+ import URow from "../../ui.container-row/URow.vue";
14
+ import UCol from "../../ui.container-col/UCol.vue";
15
+
16
+ import type { Meta, StoryFn } from "@storybook/vue3-vite";
17
+ import type { Props } from "../types";
18
+
19
+ interface UCollapsibleArgs extends Props {
20
+ slotTemplate?: string;
21
+ enum: "yPosition" | "xPosition";
22
+ }
23
+
24
+ const defaultTemplate = `
25
+ <template #default="{ opened }">
26
+ <UButton :label="opened ? 'Close' : 'Open'" />
27
+ </template>
28
+
29
+ <template #content>
30
+ <UCard title="Collapsible Content" description="This is the collapsible content area." />
31
+ </template>
32
+ `;
33
+
34
+ export default {
35
+ id: "5270",
36
+ title: "Containers / Collapsible",
37
+ component: UCollapsible,
38
+ args: {
39
+ slotTemplate: defaultTemplate,
40
+ },
41
+ argTypes: {
42
+ ...getArgTypes(UCollapsible.__name),
43
+ },
44
+ parameters: {
45
+ docs: {
46
+ ...getDocsDescription(UCollapsible.__name),
47
+ story: {
48
+ height: "300px",
49
+ },
50
+ },
51
+ },
52
+ } as Meta;
53
+
54
+ const DefaultTemplate: StoryFn<UCollapsibleArgs> = (args: UCollapsibleArgs) => ({
55
+ components: { UCollapsible, UButton, UCard },
56
+ setup: () => ({ args, slots: getSlotNames(UCollapsible.__name) }),
57
+ template: `
58
+ <UCollapsible v-bind="args">
59
+ ${args.slotTemplate || getSlotsFragment("")}
60
+ </UCollapsible>
61
+ `,
62
+ });
63
+
64
+ const EnumTemplate: StoryFn<UCollapsibleArgs> = (args: UCollapsibleArgs, { argTypes }) => ({
65
+ components: { UCollapsible, UButton, UCard, URow },
66
+ setup: () => ({ args, argTypes, getArgs }),
67
+ template: `
68
+ <URow>
69
+ <UCollapsible
70
+ v-for="option in argTypes?.[args.enum]?.options"
71
+ v-bind="getArgs(args, option)"
72
+ v-model:open="args.open"
73
+ :key="option"
74
+ >
75
+ ${args.slotTemplate}
76
+ </UCollapsible>
77
+ </URow>
78
+ `,
79
+ });
80
+
81
+ export const Default = DefaultTemplate.bind({});
82
+ Default.args = {};
83
+
84
+ export const YPositions = EnumTemplate.bind({});
85
+ YPositions.args = { enum: "yPosition", slotTemplate: defaultTemplate };
86
+
87
+ export const XPositions = EnumTemplate.bind({});
88
+ XPositions.args = { enum: "xPosition" };
89
+
90
+ export const NonAbsolute: StoryFn<UCollapsibleArgs> = (args) => ({
91
+ components: { UCollapsible, UButton, UCard, UCol },
92
+ setup() {
93
+ return { args };
94
+ },
95
+ template: `
96
+ <UCol gap="md">
97
+ <UCollapsible :absolute="false">
98
+ <template #default="{ opened }">
99
+ <UButton :label="opened ? 'Close' : 'Open'" />
100
+ </template>
101
+ <template #content>
102
+ <UCard
103
+ title="Non-Absolute Content"
104
+ description="This content is not positioned absolutely, so it flows with the document."
105
+ />
106
+ </template>
107
+ </UCollapsible>
108
+ <div class="p-4 border border-gray-300 rounded">
109
+ <p>This content appears below the collapsible and will be pushed down when it opens.</p>
110
+ </div>
111
+ </UCol>
112
+ `,
113
+ });
114
+
115
+ export const VModel: StoryFn<UCollapsibleArgs> = (args) => ({
116
+ components: { UCollapsible, UButton, UCard, URow },
117
+ setup() {
118
+ const isOpen = ref(false);
119
+
120
+ function toggle() {
121
+ isOpen.value = !isOpen.value;
122
+ }
123
+
124
+ return { args, isOpen, toggle };
125
+ },
126
+ template: `
127
+ <UCol gap="md">
128
+ <URow gap="md">
129
+ <UButton label="Toggle Collapsible" @click="toggle" />
130
+ <UButton :label="isOpen ? 'Close' : 'Open'" variant="outlined" @click="isOpen = !isOpen" />
131
+ </URow>
132
+
133
+ <UCollapsible v-model:open="isOpen">
134
+ <template #default="{ opened }">
135
+ <UButton :label="opened ? 'Opened' : 'Closed'" color="secondary" />
136
+ </template>
137
+ <template #content>
138
+ <UCard
139
+ title="Controlled Content"
140
+ description="This collapsible is controlled by v-model. Use the buttons above to toggle it."
141
+ />
142
+ </template>
143
+ </UCollapsible>
144
+ </UCol>
145
+ `,
146
+ });
147
+
148
+ export const CloseOnOutside: StoryFn<UCollapsibleArgs> = (args) => ({
149
+ components: { UCollapsible, UButton, UCard },
150
+ setup() {
151
+ return { args };
152
+ },
153
+ template: `
154
+ <div class="p-8">
155
+ <p class="mb-4 text-sm text-gray-600">Click outside the collapsible content to close it.</p>
156
+ <UCollapsible close-on-outside>
157
+ <template #default="{ opened }">
158
+ <UButton :label="opened ? 'Close' : 'Open'" />
159
+ </template>
160
+ <template #content>
161
+ <UCard
162
+ title="Click Outside to Close"
163
+ description="This collapsible will close when you click outside of it."
164
+ />
165
+ </template>
166
+ </UCollapsible>
167
+ </div>
168
+ `,
169
+ });
170
+
171
+ export const CloseOnContent: StoryFn<UCollapsibleArgs> = (args) => ({
172
+ components: { UCollapsible, UButton, UCard },
173
+ setup() {
174
+ return { args };
175
+ },
176
+ template: `
177
+ <div class="p-8">
178
+ <p class="mb-4 text-sm text-gray-600">Click on the content to close the collapsible.</p>
179
+ <UCollapsible close-on-content>
180
+ <template #default="{ opened }">
181
+ <UButton :label="opened ? 'Close' : 'Open'" />
182
+ </template>
183
+ <template #content>
184
+ <UCard
185
+ title="Click Content to Close"
186
+ description="Click anywhere on this card to close the collapsible."
187
+ />
188
+ </template>
189
+ </UCollapsible>
190
+ </div>
191
+ `,
192
+ });
193
+
194
+ export const Disabled: StoryFn<UCollapsibleArgs> = (args) => ({
195
+ components: { UCollapsible, UButton, UCard },
196
+ setup() {
197
+ return { args };
198
+ },
199
+ template: `
200
+ <UCollapsible disabled>
201
+ <template #default="{ opened }">
202
+ <UButton :label="opened ? 'Close' : 'Open'" disabled />
203
+ </template>
204
+ <template #content>
205
+ <UCard title="Disabled Content" description="This content cannot be shown." />
206
+ </template>
207
+ </UCollapsible>
208
+ `,
209
+ });
210
+
211
+ export const CustomContent: StoryFn<UCollapsibleArgs> = (args) => ({
212
+ components: { UCollapsible, UButton, UCard, URow },
213
+ setup() {
214
+ return { args };
215
+ },
216
+ template: `
217
+ <URow gap="xl">
218
+ <UCollapsible>
219
+ <template #default="{ opened }">
220
+ <UButton
221
+ :label="opened ? 'Hide Menu' : 'Show Menu'"
222
+ :left-icon="opened ? 'expand_less' : 'expand_more'"
223
+ />
224
+ </template>
225
+ <template #content>
226
+ <UCard class="w-64">
227
+ <div class="p-4 space-y-2">
228
+ <div class="hover:bg-gray-100 p-2 rounded cursor-pointer">Menu Item 1</div>
229
+ <div class="hover:bg-gray-100 p-2 rounded cursor-pointer">Menu Item 2</div>
230
+ <div class="hover:bg-gray-100 p-2 rounded cursor-pointer">Menu Item 3</div>
231
+ <div class="hover:bg-gray-100 p-2 rounded cursor-pointer">Menu Item 4</div>
232
+ </div>
233
+ </UCard>
234
+ </template>
235
+ </UCollapsible>
236
+
237
+ <UCollapsible y-position="top">
238
+ <template #default="{ opened }">
239
+ <UButton
240
+ :label="opened ? 'Hide Tooltip' : 'Show Tooltip'"
241
+ variant="outlined"
242
+ />
243
+ </template>
244
+ <template #content>
245
+ <UCard class="w-48">
246
+ <div class="p-3 text-sm">
247
+ This is a tooltip-like collapsible that appears above the trigger.
248
+ </div>
249
+ </UCard>
250
+ </template>
251
+ </UCollapsible>
252
+ </URow>
253
+ `,
254
+ });
255
+ CustomContent.parameters = {
256
+ docs: {
257
+ story: {
258
+ height: "300px",
259
+ },
260
+ },
261
+ };