vueless 1.3.7-beta.1 → 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.
- package/components.d.ts +1 -0
- package/components.ts +1 -0
- package/constants.d.ts +2 -0
- package/constants.js +2 -0
- package/package.json +2 -2
- package/ui.container-col/UCol.vue +0 -1
- package/ui.container-collapsible/UCollapsible.vue +190 -0
- package/ui.container-collapsible/config.ts +45 -0
- package/ui.container-collapsible/constants.ts +1 -0
- package/ui.container-collapsible/storybook/docs.mdx +17 -0
- package/ui.container-collapsible/storybook/stories.ts +261 -0
- package/ui.container-collapsible/tests/UCollapsible.test.ts +571 -0
- package/ui.container-collapsible/types.ts +57 -0
- package/ui.dropdown/UDropdown.vue +324 -0
- package/ui.dropdown/config.ts +27 -0
- package/ui.dropdown/constants.ts +1 -0
- package/ui.dropdown/storybook/docs.mdx +17 -0
- package/ui.dropdown/storybook/stories.ts +286 -0
- package/ui.dropdown/tests/UDropdown.test.ts +631 -0
- package/ui.dropdown/types.ts +127 -0
- package/ui.dropdown-badge/UDropdownBadge.vue +119 -227
- package/ui.dropdown-badge/config.ts +18 -15
- package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +201 -67
- package/ui.dropdown-button/UDropdownButton.vue +121 -226
- package/ui.dropdown-button/config.ts +32 -28
- package/ui.dropdown-button/tests/UDropdownButton.test.ts +189 -73
- package/ui.dropdown-link/UDropdownLink.vue +123 -233
- package/ui.dropdown-link/config.ts +15 -18
- package/ui.dropdown-link/tests/UDropdownLink.test.ts +190 -71
- package/ui.form-listbox/UListbox.vue +2 -3
- package/ui.form-listbox/config.ts +2 -2
- package/ui.form-select/config.ts +1 -1
- package/utils/node/helper.js +6 -5
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { nextTick, computed, ref, useTemplateRef } from "vue";
|
|
3
|
+
import { isEqual } from "lodash-es";
|
|
4
|
+
|
|
5
|
+
import { useUI } from "../composables/useUI";
|
|
6
|
+
import { getDefaults, cx } from "../utils/ui";
|
|
7
|
+
|
|
8
|
+
import UListbox from "../ui.form-listbox/UListbox.vue";
|
|
9
|
+
import UCollapsible from "../ui.container-collapsible/UCollapsible.vue";
|
|
10
|
+
|
|
11
|
+
import defaultConfig from "./config";
|
|
12
|
+
import { COMPONENT_NAME } from "./constants";
|
|
13
|
+
|
|
14
|
+
import type { Props, Config } from "./types";
|
|
15
|
+
import type { Option, SelectedValue } from "../ui.form-listbox/types";
|
|
16
|
+
|
|
17
|
+
defineOptions({ inheritAttrs: false });
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
20
|
+
...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
|
|
21
|
+
options: () => [],
|
|
22
|
+
modelValue: "",
|
|
23
|
+
label: "",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits([
|
|
27
|
+
/**
|
|
28
|
+
* Triggers on a dropdown option click.
|
|
29
|
+
* @property {string} value
|
|
30
|
+
*/
|
|
31
|
+
"clickOption",
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Triggers when an option is selected.
|
|
35
|
+
* @property {string} value
|
|
36
|
+
* @property {number} value
|
|
37
|
+
*/
|
|
38
|
+
"update:modelValue",
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Triggers when a dropdown list is opened.
|
|
42
|
+
*/
|
|
43
|
+
"open",
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Triggers when a dropdown list is closed.
|
|
47
|
+
*/
|
|
48
|
+
"close",
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Triggers when the search value is changed.
|
|
52
|
+
* @property {string} query
|
|
53
|
+
*/
|
|
54
|
+
"searchChange",
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Triggers when the search v-model updates.
|
|
58
|
+
* @property {string} query
|
|
59
|
+
*/
|
|
60
|
+
"update:search",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
type UListboxRef = InstanceType<typeof UListbox>;
|
|
64
|
+
type UCollapsibleRef = InstanceType<typeof UCollapsible>;
|
|
65
|
+
|
|
66
|
+
const isClickingOption = ref(false);
|
|
67
|
+
const listboxRef = useTemplateRef<UListboxRef>("dropdown-list");
|
|
68
|
+
const collapsibleRef = useTemplateRef<UCollapsibleRef>("collapsible");
|
|
69
|
+
|
|
70
|
+
const dropdownValue = computed({
|
|
71
|
+
get: () => {
|
|
72
|
+
if (props.multiple && !Array.isArray(props.modelValue)) {
|
|
73
|
+
return props.modelValue ? [props.modelValue] : [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return props.modelValue;
|
|
77
|
+
},
|
|
78
|
+
set: (value) => emit("update:modelValue", value),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const dropdownSearch = computed({
|
|
82
|
+
get: () => props.search ?? "",
|
|
83
|
+
set: (value: string) => emit("update:search", value),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const selectedOptions = computed(() => {
|
|
87
|
+
if (props.multiple) {
|
|
88
|
+
return props.options.filter((option) => {
|
|
89
|
+
return (
|
|
90
|
+
option[props.valueKey] &&
|
|
91
|
+
(dropdownValue.value as SelectedValue[]).find((selected) =>
|
|
92
|
+
isEqual(selected, option[props.valueKey]),
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
props.options.find(
|
|
100
|
+
(option) => option[props.valueKey] && isEqual(option[props.valueKey], dropdownValue.value),
|
|
101
|
+
),
|
|
102
|
+
].filter((option) => !!option);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const displayLabel = computed(() => {
|
|
106
|
+
if (!props.labelDisplayCount || !selectedOptions.value.length) {
|
|
107
|
+
return props.label;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const selectedLabels = selectedOptions.value
|
|
111
|
+
.slice(0, props.labelDisplayCount)
|
|
112
|
+
.map((option) => option[props.labelKey]);
|
|
113
|
+
const restLabelCount = selectedOptions.value.length - props.labelDisplayCount;
|
|
114
|
+
|
|
115
|
+
if (restLabelCount > 0) {
|
|
116
|
+
selectedLabels.push(`+${restLabelCount}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return selectedLabels.join(", ");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const fullLabel = computed(() => {
|
|
123
|
+
if (!selectedOptions.value.length) {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return selectedOptions.value.map((option) => option[props.labelKey]).join(", ");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function onSearchChange(query: string) {
|
|
131
|
+
emit("searchChange", query);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function onClickOption(option: Option) {
|
|
135
|
+
isClickingOption.value = true;
|
|
136
|
+
|
|
137
|
+
emit("clickOption", option);
|
|
138
|
+
|
|
139
|
+
if (props.closeOnSelect) {
|
|
140
|
+
hide();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
nextTick(() => {
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
isClickingOption.value = false;
|
|
146
|
+
}, 10);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function onOpen() {
|
|
151
|
+
nextTick(() => listboxRef.value?.wrapperRef?.focus());
|
|
152
|
+
|
|
153
|
+
emit("open");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function onClose() {
|
|
157
|
+
dropdownSearch.value = "";
|
|
158
|
+
|
|
159
|
+
emit("close");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toggle() {
|
|
163
|
+
collapsibleRef.value?.toggle();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function hide() {
|
|
167
|
+
collapsibleRef.value?.hide();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
defineExpose({
|
|
171
|
+
/**
|
|
172
|
+
* A reference to the component's wrapper element for direct DOM manipulation.
|
|
173
|
+
* @property {HTMLDivElement}
|
|
174
|
+
*/
|
|
175
|
+
wrapperRef: computed(() => collapsibleRef.value?.wrapperRef),
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Hides the dropdown.
|
|
179
|
+
* @property {function}
|
|
180
|
+
*/
|
|
181
|
+
hide,
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Toggles the dropdown visibility.
|
|
185
|
+
* @property {function}
|
|
186
|
+
*/
|
|
187
|
+
toggle,
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Indicates whether the dropdown is opened.
|
|
191
|
+
* @property {boolean}
|
|
192
|
+
*/
|
|
193
|
+
isOpened: computed(() => collapsibleRef.value?.isOpened ?? false),
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The currently selected options.
|
|
197
|
+
* @property {Option[]}
|
|
198
|
+
*/
|
|
199
|
+
selectedOptions,
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The display label for the dropdown.
|
|
203
|
+
* @property {string}
|
|
204
|
+
*/
|
|
205
|
+
displayLabel,
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Returns full option labels joined by comma.
|
|
209
|
+
* @property {string}
|
|
210
|
+
*/
|
|
211
|
+
fullLabel,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* Vueless: Get element / nested component attributes for each config token ✨
|
|
216
|
+
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
217
|
+
*/
|
|
218
|
+
const mutatedProps = computed(() => ({
|
|
219
|
+
/* component state, not a props */
|
|
220
|
+
opened: collapsibleRef.value?.isOpened ?? false,
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
const { getDataTest, dropdownAttrs, listboxAttrs } = useUI<Config>(
|
|
224
|
+
defaultConfig,
|
|
225
|
+
mutatedProps,
|
|
226
|
+
"dropdown",
|
|
227
|
+
);
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<template>
|
|
231
|
+
<UCollapsible
|
|
232
|
+
:id="id"
|
|
233
|
+
ref="collapsible"
|
|
234
|
+
:y-position="yPosition"
|
|
235
|
+
:x-position="xPosition"
|
|
236
|
+
:close-on-click-outside="!isClickingOption"
|
|
237
|
+
:close-on-content-click="false"
|
|
238
|
+
:disabled="disabled"
|
|
239
|
+
v-bind="dropdownAttrs"
|
|
240
|
+
:data-test="dataTest"
|
|
241
|
+
@open="onOpen"
|
|
242
|
+
@close="onClose"
|
|
243
|
+
>
|
|
244
|
+
<template #default="{ opened }">
|
|
245
|
+
<!--
|
|
246
|
+
@slot Use it to add custom trigger element for the dropdown.
|
|
247
|
+
@binding {boolean} opened
|
|
248
|
+
@binding {string} displayLabel
|
|
249
|
+
@binding {string} fullLabel
|
|
250
|
+
@binding {Option[]} selectedOptions
|
|
251
|
+
-->
|
|
252
|
+
<slot
|
|
253
|
+
:opened="opened"
|
|
254
|
+
:display-label="displayLabel"
|
|
255
|
+
:full-label="fullLabel"
|
|
256
|
+
:selected-options="selectedOptions"
|
|
257
|
+
/>
|
|
258
|
+
</template>
|
|
259
|
+
|
|
260
|
+
<template #content="{ opened, contentClasses }">
|
|
261
|
+
<!--
|
|
262
|
+
@slot Use it to replace the UListbox with custom dropdown content.
|
|
263
|
+
@binding {boolean} opened
|
|
264
|
+
@binding {string} contentClasses
|
|
265
|
+
-->
|
|
266
|
+
<slot name="dropdown" :opened="opened" :content-classes="contentClasses">
|
|
267
|
+
<UListbox
|
|
268
|
+
ref="dropdown-list"
|
|
269
|
+
v-model="dropdownValue"
|
|
270
|
+
v-model:search="dropdownSearch"
|
|
271
|
+
:size="size"
|
|
272
|
+
:searchable="searchable"
|
|
273
|
+
:multiple="multiple"
|
|
274
|
+
:color="color"
|
|
275
|
+
:options="options"
|
|
276
|
+
:options-limit="optionsLimit"
|
|
277
|
+
:visible-options="visibleOptions"
|
|
278
|
+
:label-key="labelKey"
|
|
279
|
+
:value-key="valueKey"
|
|
280
|
+
:group-label-key="groupLabelKey"
|
|
281
|
+
:group-value-key="groupValueKey"
|
|
282
|
+
v-bind="listboxAttrs"
|
|
283
|
+
:class="cx([contentClasses, listboxAttrs.class])"
|
|
284
|
+
:data-test="getDataTest('list')"
|
|
285
|
+
@click-option="onClickOption"
|
|
286
|
+
@search-change="onSearchChange"
|
|
287
|
+
@update:search="(value) => emit('update:search', value)"
|
|
288
|
+
>
|
|
289
|
+
<template #before-option="{ option, index }">
|
|
290
|
+
<!--
|
|
291
|
+
@slot Use it to add something before option.
|
|
292
|
+
@binding {object} option
|
|
293
|
+
@binding {number} index
|
|
294
|
+
-->
|
|
295
|
+
<slot name="before-option" :option="option" :index="index" />
|
|
296
|
+
</template>
|
|
297
|
+
|
|
298
|
+
<template #option="{ option, index }">
|
|
299
|
+
<!--
|
|
300
|
+
@slot Use it to customize the option.
|
|
301
|
+
@binding {object} option
|
|
302
|
+
@binding {number} index
|
|
303
|
+
-->
|
|
304
|
+
<slot name="option" :option="option" :index="index" />
|
|
305
|
+
</template>
|
|
306
|
+
|
|
307
|
+
<template #after-option="{ option, index }">
|
|
308
|
+
<!--
|
|
309
|
+
@slot Use it to add something after option.
|
|
310
|
+
@binding {object} option
|
|
311
|
+
@binding {number} index
|
|
312
|
+
-->
|
|
313
|
+
<slot name="after-option" :option="option" :index="index" />
|
|
314
|
+
</template>
|
|
315
|
+
|
|
316
|
+
<template #empty>
|
|
317
|
+
<!-- @slot Use it to add something instead of empty state. -->
|
|
318
|
+
<slot name="empty" />
|
|
319
|
+
</template>
|
|
320
|
+
</UListbox>
|
|
321
|
+
</slot>
|
|
322
|
+
</template>
|
|
323
|
+
</UCollapsible>
|
|
324
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default /*tw*/ {
|
|
2
|
+
dropdown: "{UCollapsible}",
|
|
3
|
+
listbox: {
|
|
4
|
+
base: "{UListbox}",
|
|
5
|
+
defaults: {
|
|
6
|
+
size: {
|
|
7
|
+
sm: "sm",
|
|
8
|
+
md: "md",
|
|
9
|
+
lg: "lg",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaults: {
|
|
14
|
+
color: "primary",
|
|
15
|
+
size: "md",
|
|
16
|
+
labelKey: "label",
|
|
17
|
+
valueKey: "value",
|
|
18
|
+
groupLabelKey: "label",
|
|
19
|
+
yPosition: "bottom",
|
|
20
|
+
xPosition: "left",
|
|
21
|
+
optionsLimit: 0,
|
|
22
|
+
visibleOptions: 8,
|
|
23
|
+
searchable: false,
|
|
24
|
+
multiple: false,
|
|
25
|
+
closeOnSelect: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const COMPONENT_NAME = "UDropdown";
|
|
@@ -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,286 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getArgs,
|
|
3
|
+
getArgTypes,
|
|
4
|
+
getSlotNames,
|
|
5
|
+
getSlotsFragment,
|
|
6
|
+
getDocsDescription,
|
|
7
|
+
} from "../../utils/storybook";
|
|
8
|
+
|
|
9
|
+
import UDropdown from "../../ui.dropdown/UDropdown.vue";
|
|
10
|
+
import URow from "../../ui.container-row/URow.vue";
|
|
11
|
+
import UCol from "../../ui.container-col/UCol.vue";
|
|
12
|
+
import UButton from "../../ui.button/UButton.vue";
|
|
13
|
+
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
14
|
+
import ULink from "../../ui.button-link/ULink.vue";
|
|
15
|
+
import UBadge from "../../ui.text-badge/UBadge.vue";
|
|
16
|
+
import UText from "../../ui.text-block/UText.vue";
|
|
17
|
+
import ULoader from "../../ui.loader/ULoader.vue";
|
|
18
|
+
import UAvatar from "../../ui.image-avatar/UAvatar.vue";
|
|
19
|
+
|
|
20
|
+
import type { Meta, StoryFn } from "@storybook/vue3-vite";
|
|
21
|
+
import type { Props } from "../types";
|
|
22
|
+
|
|
23
|
+
interface DefaultUDropdownArgs extends Props {
|
|
24
|
+
slotTemplate?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface EnumUDropdownArgs extends DefaultUDropdownArgs {
|
|
28
|
+
enum: keyof Pick<Props, "size" | "xPosition" | "yPosition" | "color">;
|
|
29
|
+
class?: string;
|
|
30
|
+
buttonClass?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
id: "2000",
|
|
35
|
+
title: "Dropdowns / Dropdown",
|
|
36
|
+
component: UDropdown,
|
|
37
|
+
args: {
|
|
38
|
+
label: "Select option",
|
|
39
|
+
options: [
|
|
40
|
+
{ label: "Edit", value: "edit" },
|
|
41
|
+
{ label: "Copy", value: "copy" },
|
|
42
|
+
{ label: "Remove", value: "delete" },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
argTypes: {
|
|
46
|
+
...getArgTypes(UDropdown.__name),
|
|
47
|
+
},
|
|
48
|
+
parameters: {
|
|
49
|
+
docs: {
|
|
50
|
+
...getDocsDescription(UDropdown.__name),
|
|
51
|
+
story: {
|
|
52
|
+
height: "230px",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
} as Meta;
|
|
57
|
+
|
|
58
|
+
const defaultTemplate = `
|
|
59
|
+
<UAvatar label="JD" interactive />
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const DefaultTemplate: StoryFn<DefaultUDropdownArgs> = (args: DefaultUDropdownArgs) => ({
|
|
63
|
+
components: { UDropdown, UButton, UIcon, ULink, UBadge, ULoader, URow, UCol, UText, UAvatar },
|
|
64
|
+
setup: () => ({ args, slots: getSlotNames(UDropdown.__name) }),
|
|
65
|
+
template: `
|
|
66
|
+
<UDropdown v-bind="args">
|
|
67
|
+
${args.slotTemplate || getSlotsFragment(defaultTemplate)}
|
|
68
|
+
</UDropdown>
|
|
69
|
+
`,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const SelectableTemplate: StoryFn<DefaultUDropdownArgs> = (args: DefaultUDropdownArgs) => ({
|
|
73
|
+
components: { UDropdown, UButton, UIcon, UAvatar },
|
|
74
|
+
setup: () => ({ args, slots: getSlotNames(UDropdown.__name) }),
|
|
75
|
+
template: `
|
|
76
|
+
<UDropdown v-bind="args" v-model="args.modelValue">
|
|
77
|
+
${args.slotTemplate || getSlotsFragment(defaultTemplate)}
|
|
78
|
+
</UDropdown>
|
|
79
|
+
`,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const EnumTemplate: StoryFn<EnumUDropdownArgs> = (args: EnumUDropdownArgs, { argTypes }) => ({
|
|
83
|
+
components: { UDropdown, UButton, UIcon, URow, UAvatar },
|
|
84
|
+
setup: () => ({ args, argTypes, getArgs }),
|
|
85
|
+
template: `
|
|
86
|
+
<URow>
|
|
87
|
+
<UDropdown
|
|
88
|
+
v-for="option in argTypes?.[args.enum]?.options"
|
|
89
|
+
v-bind="getArgs(args, option)"
|
|
90
|
+
:key="option"
|
|
91
|
+
#default="{ opened }"
|
|
92
|
+
>
|
|
93
|
+
<UButton :[args.enum]="option" :label="option" :class="args.buttonClass" interactive />
|
|
94
|
+
</UDropdown>
|
|
95
|
+
</URow>
|
|
96
|
+
`,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const Default = DefaultTemplate.bind({});
|
|
100
|
+
Default.args = {};
|
|
101
|
+
|
|
102
|
+
export const Disabled = DefaultTemplate.bind({});
|
|
103
|
+
Disabled.args = { disabled: true };
|
|
104
|
+
Disabled.parameters = {
|
|
105
|
+
docs: {
|
|
106
|
+
story: {
|
|
107
|
+
height: "120px",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const Searchable = DefaultTemplate.bind({});
|
|
113
|
+
Searchable.args = { searchable: true };
|
|
114
|
+
Searchable.parameters = {
|
|
115
|
+
docs: {
|
|
116
|
+
story: {
|
|
117
|
+
height: "270px",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const SearchModelValue = DefaultTemplate.bind({});
|
|
123
|
+
SearchModelValue.args = { searchable: true, search: "Copy" };
|
|
124
|
+
SearchModelValue.parameters = {
|
|
125
|
+
docs: {
|
|
126
|
+
story: {
|
|
127
|
+
height: "270px",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const NoCloseOnSelect = SelectableTemplate.bind({});
|
|
133
|
+
NoCloseOnSelect.args = {
|
|
134
|
+
modelValue: "pending",
|
|
135
|
+
closeOnSelect: false,
|
|
136
|
+
options: [
|
|
137
|
+
{ label: "Active", value: "active" },
|
|
138
|
+
{ label: "Pending", value: "pending" },
|
|
139
|
+
{ label: "Archived", value: "archived" },
|
|
140
|
+
],
|
|
141
|
+
slotTemplate: `
|
|
142
|
+
<template #default>
|
|
143
|
+
<UButton label="Select status" />
|
|
144
|
+
</template>
|
|
145
|
+
`,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const OptionSelection = SelectableTemplate.bind({});
|
|
149
|
+
OptionSelection.args = {
|
|
150
|
+
modelValue: "active",
|
|
151
|
+
options: [
|
|
152
|
+
{ label: "Active", value: "active" },
|
|
153
|
+
{ label: "Pending", value: "pending" },
|
|
154
|
+
{ label: "Archived", value: "archived" },
|
|
155
|
+
],
|
|
156
|
+
slotTemplate: `
|
|
157
|
+
<template #default>
|
|
158
|
+
<UButton label="Select status" />
|
|
159
|
+
</template>
|
|
160
|
+
`,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const MultipleOptionSelection = SelectableTemplate.bind({});
|
|
164
|
+
MultipleOptionSelection.args = {
|
|
165
|
+
modelValue: ["active", "pending", "archived"],
|
|
166
|
+
multiple: true,
|
|
167
|
+
options: [
|
|
168
|
+
{ label: "Active", value: "active" },
|
|
169
|
+
{ label: "Pending", value: "pending" },
|
|
170
|
+
{ label: "Archived", value: "archived" },
|
|
171
|
+
],
|
|
172
|
+
slotTemplate: `
|
|
173
|
+
<template #default>
|
|
174
|
+
<UButton label="Select status" />
|
|
175
|
+
</template>
|
|
176
|
+
`,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const Size = EnumTemplate.bind({});
|
|
180
|
+
Size.args = { enum: "size" };
|
|
181
|
+
|
|
182
|
+
export const Color = EnumTemplate.bind({});
|
|
183
|
+
Color.args = { enum: "color" };
|
|
184
|
+
|
|
185
|
+
export const ListboxXPosition = EnumTemplate.bind({});
|
|
186
|
+
ListboxXPosition.args = { enum: "xPosition", buttonClass: "min-w-32" };
|
|
187
|
+
|
|
188
|
+
export const ListboxYPosition = EnumTemplate.bind({});
|
|
189
|
+
ListboxYPosition.args = { enum: "yPosition" };
|
|
190
|
+
ListboxYPosition.parameters = {
|
|
191
|
+
storyClasses: "h-[350px] flex items-center px-6 pt-8 pb-12",
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const OptionsLimit = DefaultTemplate.bind({});
|
|
195
|
+
OptionsLimit.args = { optionsLimit: 2 };
|
|
196
|
+
OptionsLimit.parameters = {
|
|
197
|
+
docs: {
|
|
198
|
+
description: {
|
|
199
|
+
story: "`optionsLimit` prop controls the number of options displayed in the dropdown.",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const VisibleOptions = DefaultTemplate.bind({});
|
|
205
|
+
VisibleOptions.args = { visibleOptions: 2 };
|
|
206
|
+
VisibleOptions.parameters = {
|
|
207
|
+
docs: {
|
|
208
|
+
description: {
|
|
209
|
+
story: "`visibleOptions` prop controls the number of options you can see without a scroll.",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const EmptySlot = DefaultTemplate.bind({});
|
|
215
|
+
EmptySlot.args = {
|
|
216
|
+
options: [],
|
|
217
|
+
slotTemplate: `
|
|
218
|
+
${defaultTemplate}
|
|
219
|
+
<template #empty>
|
|
220
|
+
<UCol align="center" gap="xs" class="w-32 p-2">
|
|
221
|
+
<ULoader loading size="sm" />
|
|
222
|
+
<UText align="center" label="Loading, this may take a while..." />
|
|
223
|
+
</UCol>
|
|
224
|
+
</template>
|
|
225
|
+
`,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const OptionSlots: StoryFn<DefaultUDropdownArgs> = (args) => ({
|
|
229
|
+
components: { UDropdown, UAvatar, UIcon, URow, UCol, UBadge, UText },
|
|
230
|
+
setup: () => ({ args }),
|
|
231
|
+
template: `
|
|
232
|
+
<URow>
|
|
233
|
+
<UDropdown
|
|
234
|
+
:options="[
|
|
235
|
+
{ label: 'John Doe', value: '1', role: 'Developer' },
|
|
236
|
+
{ label: 'Jane Smith', value: '2', role: 'Designer' },
|
|
237
|
+
{ label: 'Mike Johnson', value: '3', role: 'Product Manager' },
|
|
238
|
+
]"
|
|
239
|
+
>
|
|
240
|
+
<UAvatar label="JD" interactive />
|
|
241
|
+
<template #before-option="{ option }">
|
|
242
|
+
<UIcon name="person" size="xs" color="neutral" />
|
|
243
|
+
</template>
|
|
244
|
+
</UDropdown>
|
|
245
|
+
|
|
246
|
+
<UDropdown
|
|
247
|
+
:options="[
|
|
248
|
+
{ label: 'John Doe', value: '1', role: 'Developer', status: 'online', statusColor: 'success' },
|
|
249
|
+
{ label: 'Jane Smith', value: '2', role: 'Designer', status: 'away', statusColor: 'warning' },
|
|
250
|
+
{ label: 'Mike Johnson', value: '3', role: 'Product Manager', status: 'offline', statusColor: 'grayscale' },
|
|
251
|
+
]"
|
|
252
|
+
>
|
|
253
|
+
<UAvatar label="JD" interactive />
|
|
254
|
+
<template #option="{ option }">
|
|
255
|
+
<URow align="center" gap="xs" justify="between" class="w-40">
|
|
256
|
+
<UCol gap="none">
|
|
257
|
+
<UText size="sm">{{ option.label }}</UText>
|
|
258
|
+
<UText variant="lifted" size="xs">{{ option.role }}</UText>
|
|
259
|
+
</UCol>
|
|
260
|
+
<UBadge :label="option.status" :color="option.statusColor" size="sm" variant="subtle" />
|
|
261
|
+
</URow>
|
|
262
|
+
</template>
|
|
263
|
+
</UDropdown>
|
|
264
|
+
|
|
265
|
+
<UDropdown
|
|
266
|
+
:options="[
|
|
267
|
+
{ label: 'John Doe', value: '1', verified: true },
|
|
268
|
+
{ label: 'Jane Smith', value: '2', verified: true },
|
|
269
|
+
{ label: 'Mike Johnson', value: '3', verified: false },
|
|
270
|
+
]"
|
|
271
|
+
>
|
|
272
|
+
<UAvatar label="JD" interactive />
|
|
273
|
+
<template #after-option="{ option }">
|
|
274
|
+
<UIcon v-if="option.verified" name="verified" size="xs" color="success" />
|
|
275
|
+
</template>
|
|
276
|
+
</UDropdown>
|
|
277
|
+
</URow>
|
|
278
|
+
`,
|
|
279
|
+
});
|
|
280
|
+
OptionSlots.parameters = {
|
|
281
|
+
docs: {
|
|
282
|
+
story: {
|
|
283
|
+
height: "300px",
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|