@teammodlr/ui-kit 3.0.0-beta.9 → 3.0.0
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/lib/components/checkbox/Checkbox.vue.d.ts +4 -4
- package/lib/components/clearbutton/ClearButton.vue.d.ts +7 -0
- package/lib/components/clearbutton/style/ClearButton.css.d.ts +2 -0
- package/lib/components/clearbutton/style/index.d.ts +1 -0
- package/lib/components/colorpicker/ColorPicker.vue.d.ts +0 -1
- package/lib/components/colorpicker/style/ColorPicker.css.d.ts +1 -0
- package/lib/components/dialog/style/Dialog.css.d.ts +2 -0
- package/lib/components/drawer/Drawer.vue.d.ts +142 -0
- package/lib/components/drawer/style/Drawer.css.d.ts +33 -0
- package/lib/components/drawer/style/index.d.ts +1 -0
- package/lib/components/hovercard/HoverCard.vue.d.ts +122 -0
- package/lib/components/hovercard/style/HoverCard.css.d.ts +3 -0
- package/lib/components/hovercard/style/index.d.ts +1 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/input/Input.vue.d.ts +20 -5
- package/lib/components/input/style/Input.css.d.ts +4 -0
- package/lib/components/paneldialog/PanelDialog.vue.d.ts +167 -0
- package/lib/components/paneldialog/style/PanelDialog.css.d.ts +8 -0
- package/lib/components/paneldialog/style/index.d.ts +1 -0
- package/lib/components/popover/Popover.vue.d.ts +24 -0
- package/lib/components/radio/Radio.vue.d.ts +5 -5
- package/lib/components/radiobutton/RadioButton.vue.d.ts +4 -4
- package/lib/components/select/Select.vue.d.ts +72 -91
- package/lib/components/select/style/Select.css.d.ts +9 -1
- package/lib/components/switch/Switch.vue.d.ts +4 -4
- package/lib/components/textarea/Textarea.vue.d.ts +16 -5
- package/lib/components/textarea/style/Textarea.css.d.ts +2 -0
- package/lib/composables/useInput.d.ts +9 -2
- package/lib/index.cjs.js +3 -3
- package/lib/index.es.js +8491 -6431
- package/lib/style.css +1 -1
- package/lib/tsconfig.app.tsbuildinfo +1 -0
- package/package.json +7 -4
- package/src/components/cardcontextmenu/style/Dropdown.css.ts +7 -0
- package/src/components/clearbutton/ClearButton.vue +39 -0
- package/src/components/clearbutton/style/ClearButton.css.ts +37 -0
- package/src/components/clearbutton/style/index.ts +1 -0
- package/src/components/colorpicker/ColorPicker.vue +8 -11
- package/src/components/colorpicker/examples/basic.vue +8 -17
- package/src/components/colorpicker/style/ColorPicker.css.ts +6 -1
- package/src/components/dialog/Dialog.vue +2 -5
- package/src/components/dialog/style/Dialog.css.ts +30 -0
- package/src/components/drawer/Drawer.vue +276 -0
- package/src/components/drawer/examples/basic.vue +15 -0
- package/src/components/drawer/examples/custom-title.vue +21 -0
- package/src/components/drawer/examples/index.stories.ts +24 -0
- package/src/components/drawer/examples/index.ts +7 -0
- package/src/components/drawer/examples/no-mask.vue +15 -0
- package/src/components/drawer/examples/no-outside-close.vue +15 -0
- package/src/components/drawer/examples/placements.vue +28 -0
- package/src/components/drawer/examples/with-footer.vue +21 -0
- package/src/components/drawer/examples/with-trigger.vue +13 -0
- package/src/components/drawer/style/Drawer.css.ts +208 -0
- package/src/components/drawer/style/index.ts +1 -0
- package/src/components/dropdown/Dropdown.vue +1 -1
- package/src/components/hovercard/HoverCard.vue +163 -0
- package/src/components/hovercard/style/HoverCard.css.ts +175 -0
- package/src/components/hovercard/style/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/components/input/Input.vue +52 -4
- package/src/components/input/examples/clearable.vue +18 -0
- package/src/components/input/examples/index.stories.ts +3 -1
- package/src/components/input/examples/index.ts +1 -0
- package/src/components/input/style/Input.css.ts +19 -0
- package/src/components/messageprovider/MessageProvider.vue +33 -7
- package/src/components/paneldialog/PanelDialog.vue +500 -0
- package/src/components/paneldialog/examples/basic.vue +36 -0
- package/src/components/paneldialog/examples/index.stories.ts +22 -0
- package/src/components/paneldialog/examples/index.ts +2 -0
- package/src/components/paneldialog/examples/modal.vue +31 -0
- package/src/components/paneldialog/style/PanelDialog.css.ts +90 -0
- package/src/components/paneldialog/style/index.ts +1 -0
- package/src/components/popover/Popover.vue +6 -1
- package/src/components/select/Select.vue +122 -42
- package/src/components/select/examples/clearable.vue +18 -0
- package/src/components/select/examples/fill.vue +52 -0
- package/src/components/select/examples/index.stories.ts +3 -1
- package/src/components/select/examples/index.ts +2 -0
- package/src/components/select/style/Select.css.ts +140 -24
- package/src/components/tab/Tab.vue +1 -1
- package/src/components/tab/style/Tab.css.ts +6 -0
- package/src/components/textarea/Textarea.vue +40 -4
- package/src/components/textarea/examples/clearable.vue +16 -0
- package/src/components/textarea/examples/index.stories.ts +2 -1
- package/src/components/textarea/examples/index.ts +1 -0
- package/src/components/textarea/style/Textarea.css.ts +10 -0
- package/src/composables/useInput.ts +10 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import MPanelDialog from '../PanelDialog.vue';
|
|
2
|
+
import { MPanelDialogBasic, MPanelDialogModal } from './index';
|
|
3
|
+
import { createStory } from '../../../__storybook__';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
component: MPanelDialog,
|
|
7
|
+
title: 'Components/PanelDialog',
|
|
8
|
+
argTypes: {
|
|
9
|
+
open: { control: 'boolean' },
|
|
10
|
+
modal: { control: 'boolean' },
|
|
11
|
+
draggable: { control: 'boolean' },
|
|
12
|
+
width: { control: 'text' },
|
|
13
|
+
height: { control: 'text' },
|
|
14
|
+
maxHeight: { control: 'text' },
|
|
15
|
+
handleSelector: { control: 'text' },
|
|
16
|
+
backdrop: { control: 'boolean' },
|
|
17
|
+
focusTrap: { control: 'boolean' }
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Basic = createStory(MPanelDialogBasic);
|
|
22
|
+
export const Modal = createStory(MPanelDialogModal);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<button @click="open = true">Open modal panel</button>
|
|
4
|
+
<MPanelDialog
|
|
5
|
+
v-model:open="open"
|
|
6
|
+
:modal="true"
|
|
7
|
+
width="520px"
|
|
8
|
+
:draggable="false"
|
|
9
|
+
:backdrop="true"
|
|
10
|
+
>
|
|
11
|
+
<div data-panel-handle style="padding: 12px 16px; border-bottom: 1px solid #e4e2ea; font-weight: 600;">
|
|
12
|
+
Modal panel (drag disabled)
|
|
13
|
+
</div>
|
|
14
|
+
<div style="padding: 16px; display: flex; flex-direction: column; gap: 12px;">
|
|
15
|
+
<p style="margin: 0;">Modal variant blocks background interaction and shows an overlay.</p>
|
|
16
|
+
<MInput placeholder="Focus trap stays inside" />
|
|
17
|
+
<div style="display: flex; gap: 8px; justify-content: flex-end;">
|
|
18
|
+
<button @click="open = false">Close</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</MPanelDialog>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script lang="ts" setup>
|
|
26
|
+
import { ref } from 'vue';
|
|
27
|
+
import MPanelDialog from '../PanelDialog.vue';
|
|
28
|
+
import MInput from '../../input/Input.vue';
|
|
29
|
+
|
|
30
|
+
const open = ref(false);
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { globalStyle, style } from '@vanilla-extract/css';
|
|
2
|
+
import { themeVars } from '../../../styles/theme.css';
|
|
3
|
+
|
|
4
|
+
export const backdrop = style({
|
|
5
|
+
position: 'fixed',
|
|
6
|
+
inset: 0,
|
|
7
|
+
backgroundColor: 'rgba(0,0,0,.45)',
|
|
8
|
+
pointerEvents: 'none'
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const dialogContent = style({
|
|
12
|
+
position: 'fixed',
|
|
13
|
+
inset: 0,
|
|
14
|
+
display: 'flex',
|
|
15
|
+
alignItems: 'center',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
outline: 'none',
|
|
18
|
+
selectors: {
|
|
19
|
+
'&:focus': {
|
|
20
|
+
outline: 'none'
|
|
21
|
+
},
|
|
22
|
+
'&:focus-visible': {
|
|
23
|
+
outline: 'none'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const dialogContentModal = style({
|
|
29
|
+
pointerEvents: 'auto'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const dialogContentNonModal = style({
|
|
33
|
+
pointerEvents: 'none'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const focusScope = style({
|
|
37
|
+
display: 'flex',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
width: '100%',
|
|
41
|
+
height: '100%',
|
|
42
|
+
outline: 'none',
|
|
43
|
+
selectors: {
|
|
44
|
+
'&:focus': {
|
|
45
|
+
outline: 'none'
|
|
46
|
+
},
|
|
47
|
+
'&:focus-visible': {
|
|
48
|
+
outline: 'none'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const focusScopeNonModal = style({
|
|
54
|
+
pointerEvents: 'none'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const focusScopeModal = style({
|
|
58
|
+
pointerEvents: 'auto'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const panel = style({
|
|
62
|
+
position: 'relative',
|
|
63
|
+
display: 'flex',
|
|
64
|
+
flexDirection: 'column',
|
|
65
|
+
backgroundColor: '#FFF',
|
|
66
|
+
backgroundClip: 'padding-box',
|
|
67
|
+
border: `1px solid ${themeVars.color.gray[300]}`,
|
|
68
|
+
borderRadius: themeVars.rounded.medium,
|
|
69
|
+
boxShadow: `2px 2px 8px rgba(128, 128, 128, 0.1), 0 9px 28px 8px rgba(0,0,0,.05)`,
|
|
70
|
+
overflow: 'hidden',
|
|
71
|
+
outline: 'none',
|
|
72
|
+
pointerEvents: 'auto',
|
|
73
|
+
maxWidth: 'calc(100vw - 32px)',
|
|
74
|
+
selectors: {
|
|
75
|
+
'&:focus': {
|
|
76
|
+
outline: 'none'
|
|
77
|
+
},
|
|
78
|
+
'&:focus-visible': {
|
|
79
|
+
outline: 'none'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
globalStyle(`${panel} [data-panel-handle]`, {
|
|
85
|
+
cursor: 'grab'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
globalStyle(`${panel} [data-panel-handle]:active`, {
|
|
89
|
+
cursor: 'grabbing'
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PanelDialog.css';
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
:side="resolvedSide"
|
|
10
10
|
:align="resolvedAlign"
|
|
11
11
|
:side-offset="sideOffset"
|
|
12
|
+
:collision-padding="collisionPadding"
|
|
12
13
|
:style="{ zIndex: zIndex }"
|
|
13
14
|
>
|
|
14
15
|
<slot name="content" />
|
|
@@ -68,12 +69,16 @@ export default defineComponent({
|
|
|
68
69
|
},
|
|
69
70
|
transfer: {
|
|
70
71
|
type: Boolean,
|
|
71
|
-
default:
|
|
72
|
+
default: true
|
|
72
73
|
},
|
|
73
74
|
sideOffset: {
|
|
74
75
|
type: Number,
|
|
75
76
|
default: 5
|
|
76
77
|
},
|
|
78
|
+
collisionPadding: {
|
|
79
|
+
type: [Number, Object] as PropType<number | { top?: number; right?: number; bottom?: number; left?: number }>,
|
|
80
|
+
default: 10
|
|
81
|
+
},
|
|
77
82
|
arrow: {
|
|
78
83
|
type: Boolean,
|
|
79
84
|
default: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<PopoverRoot :open="isOpen" @update:open="handleOpenChange">
|
|
3
|
-
<div ref="selectRef" tabindex="-1">
|
|
3
|
+
<div ref="selectRef" tabindex="-1" :class="fillRootClass">
|
|
4
4
|
<label v-if="label" :for="id" :class="style.label" :id="`${id}-label`">
|
|
5
5
|
{{ label }}
|
|
6
6
|
</label>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<div :class="selectComputedClasses">
|
|
17
17
|
<template v-if="multiple && Array.isArray(selected) && selected.length > 0">
|
|
18
18
|
<MTag v-for="value in selectedMax" :key="value" :closeable="true"
|
|
19
|
-
@close="
|
|
19
|
+
@close="() => removeSelected(value)" :size="size">
|
|
20
20
|
{{ getSelectedLabel(value) }}
|
|
21
21
|
</MTag>
|
|
22
22
|
<template v-if="selectedCountNotShown > 0">
|
|
@@ -26,15 +26,16 @@
|
|
|
26
26
|
</template>
|
|
27
27
|
</template>
|
|
28
28
|
<template v-else-if="!multiple && selected">
|
|
29
|
-
{{ getSelectedLabel(selected) }}
|
|
29
|
+
<span :class="fillLabelClass">{{ getSelectedLabel(selected) }}</span>
|
|
30
30
|
</template>
|
|
31
31
|
<template v-else>
|
|
32
|
-
<div :class="style.placeholder">{{ placeholder }}</div>
|
|
32
|
+
<div :class="[style.placeholder, fillLabelClass]">{{ placeholder }}</div>
|
|
33
33
|
</template>
|
|
34
34
|
<span :class="selectComputedArrowClasses">
|
|
35
35
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
|
|
36
36
|
</span>
|
|
37
37
|
</div>
|
|
38
|
+
<MClearButton v-if="showClear" :class="style.clear" @clear="clearSelected" />
|
|
38
39
|
</div>
|
|
39
40
|
</PopoverTrigger>
|
|
40
41
|
|
|
@@ -71,24 +72,30 @@
|
|
|
71
72
|
<input v-if="searchable" type="text" ref="searchRef" v-model="search" placeholder="Search"
|
|
72
73
|
:class="style.searchInput" :data-input="`select-menu-search-${id}`" />
|
|
73
74
|
</div>
|
|
74
|
-
<div :class="style.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
<div v-if="computedOptions.length === 0" :class="style.noResults">No results</div>
|
|
76
|
+
<VList
|
|
77
|
+
v-else
|
|
78
|
+
ref="virtualListRef"
|
|
79
|
+
:data="computedOptions"
|
|
80
|
+
:item-size="optionItemHeight"
|
|
81
|
+
:style="{ height: optionsHeight + 'px', padding: '4px' }"
|
|
82
|
+
>
|
|
83
|
+
<template #default="{ item, index }">
|
|
84
|
+
<div :class="[optionSizeClass, {
|
|
85
|
+
[style.option]: true,
|
|
86
|
+
[style.optionSelected]: item.active,
|
|
87
|
+
[style.optionFocused]: selectedIndex === index
|
|
88
|
+
}]" @click.prevent="item.onClick" @mouseenter="selectedIndex = index"
|
|
89
|
+
:data-element="`select-menu-option-${id}-${item.value}`">
|
|
90
|
+
<span :class="style.optionIcon">
|
|
91
|
+
<svg v-if="item.active" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
92
|
+
<path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd"></path>
|
|
93
|
+
</svg>
|
|
94
|
+
</span>
|
|
95
|
+
<span :class="style.optionLabel">{{ item.label }}</span>
|
|
96
|
+
</div>
|
|
90
97
|
</template>
|
|
91
|
-
</
|
|
98
|
+
</VList>
|
|
92
99
|
</PopoverContent>
|
|
93
100
|
</Transition>
|
|
94
101
|
</PopoverPortal>
|
|
@@ -101,7 +108,7 @@
|
|
|
101
108
|
</template>
|
|
102
109
|
|
|
103
110
|
<script lang="ts">
|
|
104
|
-
import { useInput } from '../../composables/useInput';
|
|
111
|
+
import { useInput, type InputEmitsMap } from '../../composables/useInput';
|
|
105
112
|
import type { MaybeArray, Size } from '../../types/props';
|
|
106
113
|
import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui';
|
|
107
114
|
import { useEventListener } from '@vueuse/core';
|
|
@@ -115,7 +122,13 @@ import {
|
|
|
115
122
|
type PropType
|
|
116
123
|
} from 'vue';
|
|
117
124
|
import MTag from '../tag/Tag.vue';
|
|
125
|
+
import MClearButton from '../clearbutton/ClearButton.vue';
|
|
126
|
+
import { VList } from 'virtua/vue';
|
|
118
127
|
import * as style from './style';
|
|
128
|
+
|
|
129
|
+
interface VListHandle {
|
|
130
|
+
scrollToIndex(index: number, opts?: { align?: 'start' | 'center' | 'end' | 'nearest' }): void;
|
|
131
|
+
}
|
|
119
132
|
import { fade } from '../../styles/transitions.css';
|
|
120
133
|
import { useInteractive } from '../../composables/useInteractive';
|
|
121
134
|
import type { OnUpdateValue, OnUpdateValueImpl } from './types';
|
|
@@ -127,7 +140,10 @@ let shouldOpenNextFocusedSelect = false;
|
|
|
127
140
|
export default defineComponent({
|
|
128
141
|
name: 'MSelect',
|
|
129
142
|
emits: {
|
|
130
|
-
...useInput.emits()
|
|
143
|
+
...(useInput.emits() as unknown as InputEmitsMap),
|
|
144
|
+
'on-open': () => true,
|
|
145
|
+
'on-close': () => true,
|
|
146
|
+
clear: () => true
|
|
131
147
|
},
|
|
132
148
|
props: {
|
|
133
149
|
...useInput.props(),
|
|
@@ -184,7 +200,7 @@ export default defineComponent({
|
|
|
184
200
|
default: false
|
|
185
201
|
},
|
|
186
202
|
error: {
|
|
187
|
-
type: String
|
|
203
|
+
type: String as PropType<string | null>
|
|
188
204
|
},
|
|
189
205
|
allowBlank: {
|
|
190
206
|
type: Boolean,
|
|
@@ -202,9 +218,26 @@ export default defineComponent({
|
|
|
202
218
|
type: Boolean,
|
|
203
219
|
default: false
|
|
204
220
|
},
|
|
221
|
+
fill: {
|
|
222
|
+
type: Boolean,
|
|
223
|
+
default: false
|
|
224
|
+
},
|
|
225
|
+
noBorder: {
|
|
226
|
+
type: Boolean,
|
|
227
|
+
default: false
|
|
228
|
+
},
|
|
205
229
|
zIndex: {
|
|
206
230
|
type: Number
|
|
207
231
|
},
|
|
232
|
+
clearable: {
|
|
233
|
+
type: Boolean,
|
|
234
|
+
default: false
|
|
235
|
+
},
|
|
236
|
+
// Value a single select resets to when cleared (e.g. a "Don't restrict" option)
|
|
237
|
+
clearValue: {
|
|
238
|
+
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | object | null>,
|
|
239
|
+
default: undefined
|
|
240
|
+
},
|
|
208
241
|
showDataOptions: {
|
|
209
242
|
type: Boolean,
|
|
210
243
|
default: true
|
|
@@ -214,6 +247,8 @@ export default defineComponent({
|
|
|
214
247
|
},
|
|
215
248
|
components: {
|
|
216
249
|
MTag,
|
|
250
|
+
MClearButton,
|
|
251
|
+
VList,
|
|
217
252
|
PopoverContent,
|
|
218
253
|
PopoverPortal,
|
|
219
254
|
PopoverRoot,
|
|
@@ -221,7 +256,7 @@ export default defineComponent({
|
|
|
221
256
|
},
|
|
222
257
|
setup(props, { emit, expose }) {
|
|
223
258
|
const container = ref<HTMLElement | null>(null);
|
|
224
|
-
const
|
|
259
|
+
const virtualListRef = ref<VListHandle | null>(null);
|
|
225
260
|
const searchRef = ref<HTMLInputElement>();
|
|
226
261
|
const selectRef = ref<HTMLElement>();
|
|
227
262
|
const popper = ref<HTMLElement>();
|
|
@@ -593,9 +628,34 @@ export default defineComponent({
|
|
|
593
628
|
}
|
|
594
629
|
}
|
|
595
630
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
631
|
+
const hasValue = computed(() => {
|
|
632
|
+
if (props.multiple) {
|
|
633
|
+
return Array.isArray(selected.value) && selected.value.length > 0;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (props.clearValue !== undefined && selected.value === props.clearValue) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return !!selected.value;
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
const showClear = computed(() => {
|
|
644
|
+
return props.clearable
|
|
645
|
+
&& hasValue.value
|
|
646
|
+
&& !disabled.value
|
|
647
|
+
&& !loading.value;
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
function clearSelected() {
|
|
651
|
+
selected.value = props.multiple
|
|
652
|
+
? []
|
|
653
|
+
: (props.clearValue !== undefined ? props.clearValue : '');
|
|
654
|
+
emit('clear');
|
|
655
|
+
container.value?.focus();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function removeSelected(value: string | number) {
|
|
599
659
|
if (!props.multiple) {
|
|
600
660
|
return;
|
|
601
661
|
}
|
|
@@ -741,20 +801,36 @@ export default defineComponent({
|
|
|
741
801
|
]
|
|
742
802
|
})
|
|
743
803
|
|
|
804
|
+
const optionItemHeightMap: Record<string, number> = { xs: 30, sm: 32, md: 38, lg: 42, xl: 46 };
|
|
805
|
+
const optionItemHeight = computed(() => optionItemHeightMap[props.size] ?? 38);
|
|
806
|
+
const optionsHeight = computed(() => {
|
|
807
|
+
const total = computedOptions.value.length * optionItemHeight.value + 8; // 8px = paddingTop+paddingBottom
|
|
808
|
+
return Math.min(total, 222);
|
|
809
|
+
});
|
|
810
|
+
const optionSizeClass = computed(() => style.optionSizes[props.size]);
|
|
811
|
+
const fillRootClass = computed(() => props.fill ? style.fillRoot : undefined);
|
|
812
|
+
const fillLabelClass = computed(() => props.fill ? style.fillLabel : undefined);
|
|
813
|
+
|
|
744
814
|
const computedWrapperClasses = computed(() => {
|
|
745
|
-
return [
|
|
815
|
+
return [
|
|
816
|
+
style.wrapper,
|
|
817
|
+
{ [style.fill]: props.fill }
|
|
818
|
+
];
|
|
746
819
|
});
|
|
747
820
|
|
|
748
821
|
const selectComputedClasses = computed(() => {
|
|
749
822
|
return [
|
|
750
823
|
style.base,
|
|
824
|
+
style.sizes[props.size],
|
|
751
825
|
{
|
|
826
|
+
[style.hasClear]: showClear.value,
|
|
752
827
|
[style.selectedTags]:
|
|
753
828
|
props.multiple &&
|
|
754
829
|
Array.isArray(selected.value) &&
|
|
755
830
|
selected.value.length > 0,
|
|
756
|
-
[style.focused]: open.value || focused.value,
|
|
757
|
-
|
|
831
|
+
[style.focused]: !props.noBorder && (open.value || focused.value),
|
|
832
|
+
[style.fillBase]: props.fill,
|
|
833
|
+
[style.noBorder]: props.noBorder,
|
|
758
834
|
'm-select--disabled': disabled.value || loading.value,
|
|
759
835
|
'm-select--focused': open.value || focused.value,
|
|
760
836
|
'm-select--multiple': props.multiple,
|
|
@@ -784,21 +860,20 @@ export default defineComponent({
|
|
|
784
860
|
if (!keyboardNavigation.value) {
|
|
785
861
|
return;
|
|
786
862
|
}
|
|
787
|
-
if (typeof index !== 'undefined'
|
|
788
|
-
|
|
789
|
-
block: 'nearest',
|
|
790
|
-
inline: 'nearest'
|
|
791
|
-
});
|
|
863
|
+
if (typeof index !== 'undefined') {
|
|
864
|
+
virtualListRef.value?.scrollToIndex(index, { align: 'nearest' });
|
|
792
865
|
}
|
|
793
866
|
});
|
|
794
867
|
|
|
795
868
|
watch(open, (value: boolean) => {
|
|
796
869
|
nextTick(() => {
|
|
797
870
|
if (!value) {
|
|
871
|
+
emit('on-close');
|
|
798
872
|
search.value = '';
|
|
799
873
|
}
|
|
800
874
|
|
|
801
875
|
if (value) {
|
|
876
|
+
emit('on-open');
|
|
802
877
|
zIndex.value = nextZIndex();
|
|
803
878
|
}
|
|
804
879
|
|
|
@@ -816,7 +891,7 @@ export default defineComponent({
|
|
|
816
891
|
});
|
|
817
892
|
}
|
|
818
893
|
|
|
819
|
-
if (value
|
|
894
|
+
if (value) {
|
|
820
895
|
const selectedValue = Array.isArray(selected.value)
|
|
821
896
|
? selected.value[0]
|
|
822
897
|
: selected.value;
|
|
@@ -824,9 +899,7 @@ export default defineComponent({
|
|
|
824
899
|
i => i.value === selectedValue
|
|
825
900
|
);
|
|
826
901
|
if (index !== -1) {
|
|
827
|
-
|
|
828
|
-
block: 'nearest'
|
|
829
|
-
});
|
|
902
|
+
nextTick(() => virtualListRef.value?.scrollToIndex(index, { align: 'nearest' }));
|
|
830
903
|
selectedIndex.value = index;
|
|
831
904
|
} else if (computedOptions.value.length > 0) {
|
|
832
905
|
selectedIndex.value = 0;
|
|
@@ -894,7 +967,7 @@ export default defineComponent({
|
|
|
894
967
|
|
|
895
968
|
return {
|
|
896
969
|
container,
|
|
897
|
-
|
|
970
|
+
virtualListRef,
|
|
898
971
|
searchRef,
|
|
899
972
|
selectRef,
|
|
900
973
|
popper,
|
|
@@ -922,13 +995,20 @@ export default defineComponent({
|
|
|
922
995
|
getSelectedLabel,
|
|
923
996
|
openSelect,
|
|
924
997
|
removeSelected,
|
|
998
|
+
showClear,
|
|
999
|
+
clearSelected,
|
|
925
1000
|
inputError,
|
|
926
1001
|
transitionName,
|
|
927
1002
|
inputListeners,
|
|
928
1003
|
style,
|
|
929
1004
|
focused,
|
|
930
1005
|
fade,
|
|
931
|
-
toggleAllItems
|
|
1006
|
+
toggleAllItems,
|
|
1007
|
+
optionItemHeight,
|
|
1008
|
+
optionsHeight,
|
|
1009
|
+
optionSizeClass,
|
|
1010
|
+
fillRootClass,
|
|
1011
|
+
fillLabelClass
|
|
932
1012
|
};
|
|
933
1013
|
}
|
|
934
1014
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 320px;">
|
|
3
|
+
<MSelect v-model="single" :options="options" clearable />
|
|
4
|
+
<MSelect v-model="multiple" :options="options" multiple clearable />
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script lang="ts" setup>
|
|
9
|
+
import { ref } from 'vue';
|
|
10
|
+
|
|
11
|
+
const options = Array.from({ length: 10 }, (_, i) => ({
|
|
12
|
+
value: i + 1,
|
|
13
|
+
label: `Option ${i + 1}`
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const single = ref(2);
|
|
17
|
+
const multiple = ref([1, 2]);
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div style="display: grid; grid-template-columns: 120px 1fr 1fr; border: 1px solid #e5e7eb;">
|
|
3
|
+
<div style="padding: 6px 8px; font-size: 13px; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb; background: #f9fafb; font-weight: 500;">Row 1</div>
|
|
4
|
+
<div style="height: 36px; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;">
|
|
5
|
+
<MSelect size="xs" v-model="row1Status" :options="statusOptions" fill no-border />
|
|
6
|
+
</div>
|
|
7
|
+
<div style="height: 36px; border-bottom: 1px solid #e5e7eb;">
|
|
8
|
+
<MSelect size="xs" v-model="row1Priority" :options="priorityOptions" fill no-border />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div style="padding: 6px 8px; font-size: 13px; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb; background: #f9fafb; font-weight: 500;">Row 2</div>
|
|
12
|
+
<div style="height: 36px; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;">
|
|
13
|
+
<MSelect size="xs" v-model="row2Status" :options="statusOptions" fill no-border />
|
|
14
|
+
</div>
|
|
15
|
+
<div style="height: 36px; border-bottom: 1px solid #e5e7eb;">
|
|
16
|
+
<MSelect size="xs" v-model="row2Priority" :options="priorityOptions" fill no-border />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div style="padding: 6px 8px; font-size: 13px; border-right: 1px solid #e5e7eb; background: #f9fafb; font-weight: 500;">Row 3</div>
|
|
20
|
+
<div style="height: 36px; border-right: 1px solid #e5e7eb;">
|
|
21
|
+
<MSelect size="xs" v-model="row3Status" :options="statusOptions" fill no-border />
|
|
22
|
+
</div>
|
|
23
|
+
<div style="height: 36px;">
|
|
24
|
+
<MSelect size="xs" v-model="row3Priority" :options="priorityOptions" fill no-border />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script lang="ts" setup>
|
|
30
|
+
import { ref } from 'vue';
|
|
31
|
+
|
|
32
|
+
const statusOptions = [
|
|
33
|
+
{ value: 'todo', label: 'To Do' },
|
|
34
|
+
{ value: 'in_progress', label: 'In Progress' },
|
|
35
|
+
{ value: 'in_review', label: 'In Review' },
|
|
36
|
+
{ value: 'done', label: 'Done' }
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const priorityOptions = [
|
|
40
|
+
{ value: 'low', label: 'Low' },
|
|
41
|
+
{ value: 'medium', label: 'Medium' },
|
|
42
|
+
{ value: 'high', label: 'High' },
|
|
43
|
+
{ value: 'critical', label: 'Critical' }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const row1Status = ref('in_progress');
|
|
47
|
+
const row1Priority = ref('high');
|
|
48
|
+
const row2Status = ref('todo');
|
|
49
|
+
const row2Priority = ref('medium');
|
|
50
|
+
const row3Status = ref('done');
|
|
51
|
+
const row3Priority = ref('low');
|
|
52
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MSelect from '../Select.vue';
|
|
2
|
-
import { MSelectBasic, MSelectSearchable, MSelectMultiple } from './index';
|
|
2
|
+
import { MSelectBasic, MSelectSearchable, MSelectMultiple, MSelectFill, MSelectClearable } from './index';
|
|
3
3
|
|
|
4
4
|
import { createStory, sizeArgType } from '../../../__storybook__';
|
|
5
5
|
|
|
@@ -14,3 +14,5 @@ export default {
|
|
|
14
14
|
export const Basic = createStory(MSelectBasic);
|
|
15
15
|
export const Searchable = createStory(MSelectSearchable);
|
|
16
16
|
export const Multiple = createStory(MSelectMultiple);
|
|
17
|
+
export const Fill = createStory(MSelectFill);
|
|
18
|
+
export const Clearable = createStory(MSelectClearable);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { default as MSelectBasic } from './basic.vue';
|
|
2
2
|
export { default as MSelectSearchable } from './searchable.vue';
|
|
3
3
|
export { default as MSelectMultiple } from './multiple.vue';
|
|
4
|
+
export { default as MSelectFill } from './fill.vue';
|
|
5
|
+
export { default as MSelectClearable } from './clearable.vue';
|