@teammodlr/ui-kit 3.0.0-beta.33 → 3.0.0-beta.36

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 (39) hide show
  1. package/lib/components/clearbutton/ClearButton.vue.d.ts +7 -0
  2. package/lib/components/clearbutton/style/ClearButton.css.d.ts +2 -0
  3. package/lib/components/clearbutton/style/index.d.ts +1 -0
  4. package/lib/components/hovercard/HoverCard.vue.d.ts +122 -0
  5. package/lib/components/hovercard/style/HoverCard.css.d.ts +3 -0
  6. package/lib/components/hovercard/style/index.d.ts +1 -0
  7. package/lib/components/index.d.ts +1 -0
  8. package/lib/components/input/Input.vue.d.ts +15 -0
  9. package/lib/components/input/style/Input.css.d.ts +4 -0
  10. package/lib/components/select/Select.vue.d.ts +12 -0
  11. package/lib/components/select/style/Select.css.d.ts +2 -1
  12. package/lib/components/textarea/Textarea.vue.d.ts +11 -0
  13. package/lib/components/textarea/style/Textarea.css.d.ts +2 -0
  14. package/lib/index.cjs.js +3 -3
  15. package/lib/index.es.js +4853 -4079
  16. package/lib/style.css +1 -1
  17. package/package.json +1 -1
  18. package/src/components/clearbutton/ClearButton.vue +39 -0
  19. package/src/components/clearbutton/style/ClearButton.css.ts +37 -0
  20. package/src/components/clearbutton/style/index.ts +1 -0
  21. package/src/components/hovercard/HoverCard.vue +163 -0
  22. package/src/components/hovercard/style/HoverCard.css.ts +175 -0
  23. package/src/components/hovercard/style/index.ts +1 -0
  24. package/src/components/index.ts +1 -1
  25. package/src/components/input/Input.vue +51 -3
  26. package/src/components/input/examples/clearable.vue +18 -0
  27. package/src/components/input/examples/index.stories.ts +3 -1
  28. package/src/components/input/examples/index.ts +1 -0
  29. package/src/components/input/style/Input.css.ts +20 -0
  30. package/src/components/select/Select.vue +31 -1
  31. package/src/components/select/examples/clearable.vue +18 -0
  32. package/src/components/select/examples/index.stories.ts +2 -1
  33. package/src/components/select/examples/index.ts +1 -0
  34. package/src/components/select/style/Select.css.ts +9 -6
  35. package/src/components/textarea/Textarea.vue +39 -3
  36. package/src/components/textarea/examples/clearable.vue +16 -0
  37. package/src/components/textarea/examples/index.stories.ts +2 -1
  38. package/src/components/textarea/examples/index.ts +1 -0
  39. package/src/components/textarea/style/Textarea.css.ts +11 -0
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <HoverCardRoot
3
+ v-model:open="isOpen"
4
+ :open-delay="openDelay"
5
+ :close-delay="closeDelay"
6
+ >
7
+ <HoverCardTrigger as-child>
8
+ <slot />
9
+ </HoverCardTrigger>
10
+ <HoverCardPortal :disabled="!transfer">
11
+ <HoverCardContent
12
+ :class="contentClasses"
13
+ :side="resolvedSide"
14
+ :align="resolvedAlign"
15
+ :side-offset="sideOffset"
16
+ :collision-padding="collisionPadding"
17
+ :style="{ zIndex: zIndex }"
18
+ >
19
+ <slot name="content" />
20
+ <HoverCardArrow v-if="showArrow" :class="style.arrow" :width="12" :height="6" />
21
+ </HoverCardContent>
22
+ </HoverCardPortal>
23
+ </HoverCardRoot>
24
+ </template>
25
+
26
+ <script lang="ts">
27
+ import {
28
+ HoverCardArrow,
29
+ HoverCardContent,
30
+ HoverCardPortal,
31
+ HoverCardRoot,
32
+ HoverCardTrigger,
33
+ type HoverCardContentProps
34
+ } from 'reka-ui';
35
+ import {
36
+ computed,
37
+ defineComponent,
38
+ ref,
39
+ useAttrs,
40
+ watch,
41
+ type PropType
42
+ } from 'vue';
43
+ import {
44
+ type Placement,
45
+ placementWhileList
46
+ } from '../../composables/usePopper';
47
+ import { useZIndex } from '../../composables/useZIndex';
48
+ import * as style from './style';
49
+
50
+ export default defineComponent({
51
+ name: 'MHoverCard',
52
+ inheritAttrs: false,
53
+ components: {
54
+ HoverCardArrow,
55
+ HoverCardContent,
56
+ HoverCardPortal,
57
+ HoverCardRoot,
58
+ HoverCardTrigger
59
+ },
60
+ props: {
61
+ open: {
62
+ type: Boolean,
63
+ default: false
64
+ },
65
+ placement: {
66
+ type: String as PropType<Placement>,
67
+ default: 'bottom',
68
+ validator: (value: Placement) => placementWhileList.includes(value)
69
+ },
70
+ transfer: {
71
+ type: Boolean,
72
+ default: true
73
+ },
74
+ sideOffset: {
75
+ type: Number,
76
+ default: 5
77
+ },
78
+ collisionPadding: {
79
+ type: [Number, Object] as PropType<number | { top?: number; right?: number; bottom?: number; left?: number }>,
80
+ default: 10
81
+ },
82
+ arrow: {
83
+ type: Boolean,
84
+ default: false
85
+ },
86
+ variant: {
87
+ type: String as PropType<'light' | 'dark'>,
88
+ default: 'light'
89
+ },
90
+ openDelay: {
91
+ type: Number,
92
+ default: 700
93
+ },
94
+ closeDelay: {
95
+ type: Number,
96
+ default: 300
97
+ },
98
+ zIndex: {
99
+ type: Number
100
+ }
101
+ },
102
+ emits: ['update:open'],
103
+ setup(props, { emit }) {
104
+ const attrs = useAttrs();
105
+ const { nextZIndex } = useZIndex();
106
+ const isOpen = ref(props.open);
107
+ const zIndex = ref(props.zIndex ?? nextZIndex());
108
+
109
+ const resolvedPlacement = computed(() => {
110
+ const [side, align] = props.placement.split('-');
111
+
112
+ return {
113
+ side: side === 'top' || side === 'right' || side === 'bottom' || side === 'left'
114
+ ? side
115
+ : undefined,
116
+ align: align === 'start' || align === 'center' || align === 'end'
117
+ ? align
118
+ : undefined
119
+ } as {
120
+ side?: HoverCardContentProps['side'];
121
+ align?: HoverCardContentProps['align'];
122
+ };
123
+ });
124
+
125
+ const showArrow = computed(() => {
126
+ const { align } = resolvedPlacement.value;
127
+ return props.arrow && (!align || align === 'center');
128
+ });
129
+
130
+ const contentClasses = computed(() => {
131
+ return [
132
+ 'm-hover-card-content',
133
+ style.content,
134
+ {
135
+ [style.dark]: props.variant === 'dark'
136
+ },
137
+ attrs.class
138
+ ];
139
+ });
140
+
141
+ watch(
142
+ () => props.open,
143
+ value => {
144
+ isOpen.value = value;
145
+ }
146
+ );
147
+
148
+ watch(isOpen, value => {
149
+ emit('update:open', value);
150
+ });
151
+
152
+ return {
153
+ isOpen,
154
+ zIndex,
155
+ showArrow,
156
+ contentClasses,
157
+ resolvedSide: computed(() => resolvedPlacement.value.side),
158
+ resolvedAlign: computed(() => resolvedPlacement.value.align),
159
+ style
160
+ };
161
+ }
162
+ });
163
+ </script>
@@ -0,0 +1,175 @@
1
+ import { globalKeyframes, globalStyle, style } from '@vanilla-extract/css';
2
+ import { themeVars } from '../../../styles/theme.css';
3
+
4
+ const slideFadeOutTopAnimation = 'hover-card-slide-fade-out-top';
5
+ globalKeyframes(slideFadeOutTopAnimation, {
6
+ from: {
7
+ opacity: 1,
8
+ transform: 'translateY(0) scale(1)'
9
+ },
10
+ to: {
11
+ opacity: 0,
12
+ transform: 'translateY(6px) scale(0.98)'
13
+ }
14
+ });
15
+
16
+ const slideFadeOutBottomAnimation = 'hover-card-slide-fade-out-bottom';
17
+ globalKeyframes(slideFadeOutBottomAnimation, {
18
+ from: {
19
+ opacity: 1,
20
+ transform: 'translateY(0) scale(1)'
21
+ },
22
+ to: {
23
+ opacity: 0,
24
+ transform: 'translateY(-6px) scale(0.98)'
25
+ }
26
+ });
27
+
28
+ const slideFadeOutLeftAnimation = 'hover-card-slide-fade-out-left';
29
+ globalKeyframes(slideFadeOutLeftAnimation, {
30
+ from: {
31
+ opacity: 1,
32
+ transform: 'translateX(0) scale(1)'
33
+ },
34
+ to: {
35
+ opacity: 0,
36
+ transform: 'translateX(6px) scale(0.98)'
37
+ }
38
+ });
39
+
40
+ const slideFadeOutRightAnimation = 'hover-card-slide-fade-out-right';
41
+ globalKeyframes(slideFadeOutRightAnimation, {
42
+ from: {
43
+ opacity: 1,
44
+ transform: 'translateX(0) scale(1)'
45
+ },
46
+ to: {
47
+ opacity: 0,
48
+ transform: 'translateX(-6px) scale(0.98)'
49
+ }
50
+ });
51
+
52
+ const slideFadeTopAnimation = 'hover-card-slide-fade-top';
53
+ globalKeyframes(slideFadeTopAnimation, {
54
+ from: {
55
+ opacity: 0,
56
+ transform: 'translateY(6px) scale(0.98)'
57
+ },
58
+ to: {
59
+ opacity: 1,
60
+ transform: 'translateY(0) scale(1)'
61
+ }
62
+ });
63
+
64
+ const slideFadeBottomAnimation = 'hover-card-slide-fade-bottom';
65
+ globalKeyframes(slideFadeBottomAnimation, {
66
+ from: {
67
+ opacity: 0,
68
+ transform: 'translateY(-6px) scale(0.98)'
69
+ },
70
+ to: {
71
+ opacity: 1,
72
+ transform: 'translateY(0) scale(1)'
73
+ }
74
+ });
75
+
76
+ const slideFadeLeftAnimation = 'hover-card-slide-fade-left';
77
+ globalKeyframes(slideFadeLeftAnimation, {
78
+ from: {
79
+ opacity: 0,
80
+ transform: 'translateX(6px) scale(0.98)'
81
+ },
82
+ to: {
83
+ opacity: 1,
84
+ transform: 'translateX(0) scale(1)'
85
+ }
86
+ });
87
+
88
+ const slideFadeRightAnimation = 'hover-card-slide-fade-right';
89
+ globalKeyframes(slideFadeRightAnimation, {
90
+ from: {
91
+ opacity: 0,
92
+ transform: 'translateX(-6px) scale(0.98)'
93
+ },
94
+ to: {
95
+ opacity: 1,
96
+ transform: 'translateX(0) scale(1)'
97
+ }
98
+ });
99
+
100
+ export const content = style({
101
+ backgroundColor: '#ffffff',
102
+ borderRadius: '5px',
103
+ padding: '5px',
104
+ zIndex: 4000,
105
+ boxSizing: 'border-box',
106
+ position: 'relative',
107
+ minWidth: '130px',
108
+ maxWidth: '350px',
109
+ border: `1px solid ${themeVars.color.gray[300]}`,
110
+ boxShadow: themeVars.shadow.md,
111
+ transformOrigin: 'var(--reka-hover-card-content-transform-origin)',
112
+ willChange: 'transform, opacity',
113
+ outline: 'none',
114
+ selectors: {
115
+ '&[data-state="open"]': {
116
+ animationDuration: '0.16s',
117
+ animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
118
+ animationFillMode: 'both'
119
+ },
120
+ '&[data-state="closed"]': {
121
+ animationDuration: '0.12s',
122
+ animationTimingFunction: 'ease-out',
123
+ animationFillMode: 'both'
124
+ },
125
+ '&[data-state="closed"][data-side="top"]': {
126
+ animationName: slideFadeOutTopAnimation
127
+ },
128
+ '&[data-state="closed"][data-side="bottom"]': {
129
+ animationName: slideFadeOutBottomAnimation
130
+ },
131
+ '&[data-state="closed"][data-side="left"]': {
132
+ animationName: slideFadeOutLeftAnimation
133
+ },
134
+ '&[data-state="closed"][data-side="right"]': {
135
+ animationName: slideFadeOutRightAnimation
136
+ },
137
+ '&[data-state="open"][data-side="top"]': {
138
+ animationName: slideFadeTopAnimation
139
+ },
140
+ '&[data-state="open"][data-side="bottom"]': {
141
+ animationName: slideFadeBottomAnimation
142
+ },
143
+ '&[data-state="open"][data-side="left"]': {
144
+ animationName: slideFadeLeftAnimation
145
+ },
146
+ '&[data-state="open"][data-side="right"]': {
147
+ animationName: slideFadeRightAnimation
148
+ }
149
+ }
150
+ });
151
+
152
+ export const arrow = style({
153
+ fill: '#ffffff'
154
+ });
155
+
156
+ globalStyle(`${arrow} path`, {
157
+ fill: '#ffffff',
158
+ stroke: themeVars.color.gray[300],
159
+ strokeWidth: '1px'
160
+ });
161
+
162
+ export const dark = style({
163
+ backgroundColor: themeVars.color.gray[900],
164
+ border: `1px solid ${themeVars.color.gray[800]}`,
165
+ color: themeVars.color.gray[100]
166
+ });
167
+
168
+ globalStyle(`${dark} ${arrow}`, {
169
+ fill: themeVars.color.gray[900]
170
+ });
171
+
172
+ globalStyle(`${dark} ${arrow} path`, {
173
+ fill: themeVars.color.gray[900],
174
+ stroke: themeVars.color.gray[800]
175
+ });
@@ -0,0 +1 @@
1
+ export * from './HoverCard.css';
@@ -59,4 +59,4 @@ export { default as MDialogProvider } from './dialogprovider/DialogProvider.vue'
59
59
  export { default as MDrawer } from './drawer/Drawer.vue';
60
60
  export { default as MTree } from './tree/Tree.vue';
61
61
  export { default as MPopover } from './popover/Popover.vue';
62
- // export { default as MHoverCard } from './hovercard/HoverCard.vue';
62
+ export { default as MHoverCard } from './hovercard/HoverCard.vue';
@@ -20,9 +20,10 @@
20
20
  :type="inputType"
21
21
  :placeholder="placeholder"
22
22
  :name="name"
23
- :value="modelValue || ''"
23
+ :value="currentValue"
24
24
  :readonly="readonly"
25
25
  v-on="inputListeners"
26
+ @input="onNativeInput"
26
27
  :class="inputComputedClasses"
27
28
  v-bind="$attrs"
28
29
  :disabled="disabled || loading"
@@ -32,6 +33,7 @@
32
33
  <div v-if="hasSuffix" :class="style.suffix">
33
34
  <slot name="suffix" />
34
35
  </div>
36
+ <MClearButton v-if="showClear" :class="clearClasses" @clear="onClear" />
35
37
  <div v-if="hasRightIcon" :class="style.rightIcon">
36
38
  <slot name="icon-right" />
37
39
  </div>
@@ -52,14 +54,17 @@ import {
52
54
  defineComponent,
53
55
  ref,
54
56
  computed,
55
- toRef
57
+ toRef,
58
+ watch
56
59
  } from "vue";
57
60
  import { useInput } from "../../composables/useInput";
58
61
  import { useInteractive } from "../../composables/useInteractive";
62
+ import MClearButton from '../clearbutton/ClearButton.vue';
59
63
  import * as style from './style';
60
64
 
61
65
  export default defineComponent({
62
66
  name: 'MInput',
67
+ components: { MClearButton },
63
68
  props: {
64
69
  ...useInput.props(),
65
70
  ...useInteractive.props(),
@@ -103,8 +108,12 @@ export default defineComponent({
103
108
  type: String,
104
109
  default: ''
105
110
  },
111
+ clearable: {
112
+ type: Boolean,
113
+ default: false
114
+ },
106
115
  },
107
- emits: useInput.emits(),
116
+ emits: [...useInput.emits(), 'clear'],
108
117
  setup(props, { slots, emit, expose }) {
109
118
  const element = ref<HTMLInputElement|null>(null);
110
119
  const { focus, blur } = useInteractive(element);
@@ -170,6 +179,8 @@ export default defineComponent({
170
179
  [style.disabled]: disabled.value || loading.value,
171
180
  [style.hasLeftIcon]: hasLeftIcon.value,
172
181
  [style.hasRightIcon]: hasRightIcon.value,
182
+ [style.hasClear]: props.clearable,
183
+ [style.hasClearAndRightIcon]: props.clearable && hasRightIcon.value,
173
184
  }
174
185
  ];
175
186
 
@@ -193,8 +204,41 @@ export default defineComponent({
193
204
  return formError.value || props.error;
194
205
  });
195
206
 
207
+ // Tracks typed text so clearable works without v-model
208
+ const internalValue = ref<any>(props.modelValue ?? '');
209
+ watch(() => props.modelValue, value => {
210
+ internalValue.value = value ?? '';
211
+ });
212
+
213
+ const currentValue = computed(() => {
214
+ const value = props.modelValue !== undefined ? props.modelValue : internalValue.value;
215
+ return value || '';
216
+ });
217
+
218
+ const onNativeInput = (event: Event) => {
219
+ internalValue.value = (event.target as HTMLInputElement).value;
220
+ };
221
+
222
+ const showClear = computed(() => {
223
+ return props.clearable
224
+ && !disabled.value
225
+ && !loading.value
226
+ && !props.readonly
227
+ && currentValue.value !== '';
228
+ });
229
+
230
+ const clearClasses = computed(() => {
231
+ return [
232
+ style.clear,
233
+ { [style.clearWithRightIcon]: hasRightIcon.value }
234
+ ];
235
+ });
236
+
196
237
  const onClear = () => {
238
+ internalValue.value = '';
197
239
  emit('update:modelValue', '');
240
+ emit('clear');
241
+ focus();
198
242
  };
199
243
 
200
244
  expose({
@@ -220,6 +264,10 @@ export default defineComponent({
220
264
  hasHelpMessage,
221
265
  inputError,
222
266
  style,
267
+ showClear,
268
+ clearClasses,
269
+ currentValue,
270
+ onNativeInput,
223
271
 
224
272
  onClear,
225
273
  validate,
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <div style="display: flex; flex-direction: column; gap: 12px; max-width: 320px;">
3
+ <MInput v-model="value" placeholder="Type something..." clearable />
4
+ <MInput v-model="value" placeholder="With a right icon" clearable>
5
+ <template #icon-right>
6
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" style="width: 20px; height: 20px;">
7
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
8
+ </svg>
9
+ </template>
10
+ </MInput>
11
+ </div>
12
+ </template>
13
+
14
+ <script lang="ts" setup>
15
+ import { ref } from 'vue';
16
+
17
+ const value = ref('Clear me');
18
+ </script>
@@ -5,7 +5,8 @@ import {
5
5
  MInputPrefix,
6
6
  MInputLeftIcon,
7
7
  MInputRightIcon,
8
- MInputSizes
8
+ MInputSizes,
9
+ MInputClearable
9
10
  } from './index';
10
11
 
11
12
  import { createStory, sizeArgType } from '../../../__storybook__';
@@ -24,3 +25,4 @@ export const Prefix = createStory(MInputPrefix);
24
25
  export const Sizes = createStory(MInputSizes);
25
26
  export const LeftIcon = createStory(MInputLeftIcon);
26
27
  export const RightIcon = createStory(MInputRightIcon);
28
+ export const Clearable = createStory(MInputClearable);
@@ -4,3 +4,4 @@ export { default as MInputPrefix } from './prefix.vue';
4
4
  export { default as MInputSizes } from './sizes.vue';
5
5
  export { default as MInputLeftIcon } from './left-icon.vue';
6
6
  export { default as MInputRightIcon } from './right-icon.vue';
7
+ export { default as MInputClearable } from './clearable.vue';
@@ -251,6 +251,26 @@ export const hasRightIcon = style({
251
251
  paddingRight: '2.25rem'
252
252
  });
253
253
 
254
+ export const hasClear = style({
255
+ paddingRight: '1.75rem'
256
+ });
257
+
258
+ export const hasClearAndRightIcon = style({
259
+ paddingRight: '4rem'
260
+ });
261
+
262
+ export const clear = style({
263
+ position: 'absolute',
264
+ zIndex: 2,
265
+ top: '50%',
266
+ right: themeVars.space[1],
267
+ transform: 'translateY(-50%)'
268
+ });
269
+
270
+ export const clearWithRightIcon = style({
271
+ right: '2.25rem'
272
+ });
273
+
254
274
  export const prefix = style({
255
275
  pointerEvents: 'none',
256
276
  marginRight: themeVars.space[3],
@@ -35,6 +35,7 @@
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
 
@@ -121,6 +122,7 @@ import {
121
122
  type PropType
122
123
  } from 'vue';
123
124
  import MTag from '../tag/Tag.vue';
125
+ import MClearButton from '../clearbutton/ClearButton.vue';
124
126
  import { VList } from 'virtua/vue';
125
127
  import * as style from './style';
126
128
 
@@ -140,7 +142,8 @@ export default defineComponent({
140
142
  emits: {
141
143
  ...useInput.emits(),
142
144
  'on-open': () => true,
143
- 'on-close': () => true
145
+ 'on-close': () => true,
146
+ clear: () => true
144
147
  },
145
148
  props: {
146
149
  ...useInput.props(),
@@ -226,6 +229,10 @@ export default defineComponent({
226
229
  zIndex: {
227
230
  type: Number
228
231
  },
232
+ clearable: {
233
+ type: Boolean,
234
+ default: false
235
+ },
229
236
  showDataOptions: {
230
237
  type: Boolean,
231
238
  default: true
@@ -235,6 +242,7 @@ export default defineComponent({
235
242
  },
236
243
  components: {
237
244
  MTag,
245
+ MClearButton,
238
246
  VList,
239
247
  PopoverContent,
240
248
  PopoverPortal,
@@ -615,6 +623,25 @@ export default defineComponent({
615
623
  }
616
624
  }
617
625
 
626
+ const hasValue = computed(() => {
627
+ return props.multiple
628
+ ? Array.isArray(selected.value) && selected.value.length > 0
629
+ : !!selected.value;
630
+ });
631
+
632
+ const showClear = computed(() => {
633
+ return props.clearable
634
+ && hasValue.value
635
+ && !disabled.value
636
+ && !loading.value;
637
+ });
638
+
639
+ function clearSelected() {
640
+ selected.value = props.multiple ? [] : '';
641
+ emit('clear');
642
+ container.value?.focus();
643
+ }
644
+
618
645
  function removeSelected(value: string | number) {
619
646
  if (!props.multiple) {
620
647
  return;
@@ -783,6 +810,7 @@ export default defineComponent({
783
810
  style.base,
784
811
  style.sizes[props.size],
785
812
  {
813
+ [style.hasClear]: showClear.value,
786
814
  [style.selectedTags]:
787
815
  props.multiple &&
788
816
  Array.isArray(selected.value) &&
@@ -954,6 +982,8 @@ export default defineComponent({
954
982
  getSelectedLabel,
955
983
  openSelect,
956
984
  removeSelected,
985
+ showClear,
986
+ clearSelected,
957
987
  inputError,
958
988
  transitionName,
959
989
  inputListeners,
@@ -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>
@@ -1,5 +1,5 @@
1
1
  import MSelect from '../Select.vue';
2
- import { MSelectBasic, MSelectSearchable, MSelectMultiple, MSelectFill } from './index';
2
+ import { MSelectBasic, MSelectSearchable, MSelectMultiple, MSelectFill, MSelectClearable } from './index';
3
3
 
4
4
  import { createStory, sizeArgType } from '../../../__storybook__';
5
5
 
@@ -15,3 +15,4 @@ export const Basic = createStory(MSelectBasic);
15
15
  export const Searchable = createStory(MSelectSearchable);
16
16
  export const Multiple = createStory(MSelectMultiple);
17
17
  export const Fill = createStory(MSelectFill);
18
+ export const Clearable = createStory(MSelectClearable);
@@ -2,3 +2,4 @@ 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
4
  export { default as MSelectFill } from './fill.vue';
5
+ export { default as MSelectClearable } from './clearable.vue';
@@ -379,13 +379,16 @@ export const noResults = style({
379
379
  fontSize: themeVars.fontSize.small
380
380
  });
381
381
 
382
- export const clearable = style({
383
- width: themeVars.space[4],
384
- height: themeVars.space[4],
382
+ export const clear = style({
385
383
  position: 'absolute',
386
- right: themeVars.space[2],
387
- top: '0',
388
- display: 'flex'
384
+ zIndex: 1,
385
+ top: '50%',
386
+ right: '2.25rem',
387
+ transform: 'translateY(-50%)'
388
+ });
389
+
390
+ export const hasClear = style({
391
+ paddingRight: '3.75rem'
389
392
  });
390
393
 
391
394
  export const arrow = style({