@teammodlr/ui-kit 2.3.23 → 3.0.0-beta.10

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 (111) hide show
  1. package/lib/components/avatar/Avatar.vue.d.ts +2 -2
  2. package/lib/components/badge/style/Badge.css.d.ts +2 -2
  3. package/lib/components/button/Button.vue.d.ts +7 -4
  4. package/lib/components/button/style/Button.css.d.ts +3 -3
  5. package/lib/components/cardcontextmenu/CardContextMenu.vue.d.ts +5 -8
  6. package/lib/components/cardcontextmenu/CardContextMenuItem.vue.d.ts +11 -2
  7. package/lib/components/cardcontextmenu/CardContextMenuTriggerProvider.vue.d.ts +2 -0
  8. package/lib/components/cardcontextmenu/style/Dropdown.css.d.ts +2 -1
  9. package/lib/components/cardcontextmenu/symbol.d.ts +1 -0
  10. package/lib/components/checkbox/Checkbox.vue.d.ts +15 -4
  11. package/lib/components/collapse/CollapseItem.vue.d.ts +3 -2
  12. package/lib/components/colorpicker/ColorPicker.vue.d.ts +8 -9
  13. package/lib/components/confirm/Confirm.vue.d.ts +9 -0
  14. package/lib/components/confirm/props.d.ts +4 -0
  15. package/lib/components/dialog/Dialog.vue.d.ts +13 -5
  16. package/lib/components/dialogprovider/DialogProvider.vue.d.ts +1 -0
  17. package/lib/components/dialogprovider/types.d.ts +1 -0
  18. package/lib/components/dropdown/Dropdown.vue.d.ts +3 -1
  19. package/lib/components/dropdown/DropdownItem.vue.d.ts +11 -1
  20. package/lib/components/dropdown/DropdownSeperator.vue.d.ts +1 -0
  21. package/lib/components/dropdown/DropdownTriggerProvider.vue.d.ts +2 -0
  22. package/lib/components/dropdown/style/Dropdown.css.d.ts +2 -1
  23. package/lib/components/dropdown/symbol.d.ts +2 -0
  24. package/lib/components/image/Image.vue.d.ts +1 -1
  25. package/lib/components/index.d.ts +1 -0
  26. package/lib/components/messageprovider/Message.vue.d.ts +1 -1
  27. package/lib/components/messageprovider/MessageProvider.vue.d.ts +24 -24
  28. package/lib/components/messageprovider/style/Message.css.d.ts +1 -1
  29. package/lib/components/messageprovider/style/Toaster.css.d.ts +1 -0
  30. package/lib/components/messageprovider/types.d.ts +5 -0
  31. package/lib/components/modal/Modal.vue.d.ts +18 -6
  32. package/lib/components/popover/Popover.vue.d.ts +113 -0
  33. package/lib/components/popover/style/Popover.css.d.ts +4 -0
  34. package/lib/components/popover/style/index.d.ts +1 -0
  35. package/lib/components/radio/Radio.vue.d.ts +2 -0
  36. package/lib/components/radiobuttongroup/RadioButtonGroup.vue.d.ts +1 -0
  37. package/lib/components/select/Select.vue.d.ts +20 -11
  38. package/lib/components/switch/Switch.vue.d.ts +2 -0
  39. package/lib/components/tab/Tab.vue.d.ts +5 -2
  40. package/lib/components/tab/TabGroup.vue.d.ts +8 -3
  41. package/lib/components/textarea/Textarea.vue.d.ts +2 -1
  42. package/lib/components/tooltip/Tooltip.vue.d.ts +5 -20
  43. package/lib/components/tooltip/TooltipTrigger.vue.d.ts +1 -10
  44. package/lib/components/tree/Tree.vue.d.ts +3 -3
  45. package/lib/components/tree/TreeNode.vue.d.ts +26 -12
  46. package/lib/components/tree/types.d.ts +5 -5
  47. package/lib/index.cjs.js +5 -14
  48. package/lib/index.es.js +16907 -12199
  49. package/lib/style.css +1 -1
  50. package/package.json +12 -6
  51. package/src/components/avatar/Avatar.vue +32 -24
  52. package/src/components/button/Button.vue +24 -5
  53. package/src/components/cardcontextmenu/CardContextMenu.vue +68 -95
  54. package/src/components/cardcontextmenu/CardContextMenuItem.vue +40 -50
  55. package/src/components/cardcontextmenu/CardContextMenuSeperator.vue +9 -7
  56. package/src/components/cardcontextmenu/CardContextMenuTriggerProvider.vue +15 -0
  57. package/src/components/cardcontextmenu/style/Dropdown.css.ts +155 -2
  58. package/src/components/cardcontextmenu/symbol.ts +3 -0
  59. package/src/components/checkbox/Checkbox.vue +37 -12
  60. package/src/components/checkbox/style/Checkbox.css.ts +6 -6
  61. package/src/components/collapse/CollapseItem.vue +46 -21
  62. package/src/components/collapse/style/Collapse.css.ts +5 -1
  63. package/src/components/colorpicker/ColorPicker.vue +76 -45
  64. package/src/components/confirm/Confirm.vue +15 -8
  65. package/src/components/confirm/props.ts +4 -0
  66. package/src/components/dialog/Dialog.vue +183 -37
  67. package/src/components/dialog/style/Dialog.css.ts +15 -1
  68. package/src/components/dialogprovider/types.ts +1 -0
  69. package/src/components/dropdown/Dropdown.vue +46 -3
  70. package/src/components/dropdown/DropdownItem.vue +55 -25
  71. package/src/components/dropdown/DropdownSeperator.vue +19 -11
  72. package/src/components/dropdown/DropdownTriggerProvider.vue +15 -0
  73. package/src/components/dropdown/examples/context.vue +2 -2
  74. package/src/components/dropdown/style/Dropdown.css.ts +173 -4
  75. package/src/components/dropdown/symbol.ts +4 -0
  76. package/src/components/form/examples/basic.vue +19 -8
  77. package/src/components/index.ts +1 -0
  78. package/src/components/messageprovider/MessageProvider.vue +139 -90
  79. package/src/components/messageprovider/examples/basic.vue +3 -1
  80. package/src/components/messageprovider/examples/index.stories.ts +37 -1
  81. package/src/components/messageprovider/style/Toaster.css.ts +24 -0
  82. package/src/components/messageprovider/types.ts +10 -0
  83. package/src/components/modal/Modal.vue +142 -67
  84. package/src/components/modal/style/Modal.css.ts +26 -2
  85. package/src/components/popover/Popover.vue +155 -0
  86. package/src/components/popover/examples/arrow.vue +47 -0
  87. package/src/components/popover/examples/basic.vue +15 -0
  88. package/src/components/popover/examples/index.stories.ts +19 -0
  89. package/src/components/popover/examples/index.ts +4 -0
  90. package/src/components/popover/examples/placement.vue +59 -0
  91. package/src/components/popover/examples/variant.vue +30 -0
  92. package/src/components/popover/style/Popover.css.ts +203 -0
  93. package/src/components/popover/style/index.ts +1 -0
  94. package/src/components/radio/Radio.vue +31 -17
  95. package/src/components/radiobutton/RadioButton.vue +15 -2
  96. package/src/components/radiobuttongroup/RadioButtonGroup.vue +11 -4
  97. package/src/components/select/Select.vue +301 -153
  98. package/src/components/select/examples/basic.vue +1 -1
  99. package/src/components/slider/Slider.vue +1 -1
  100. package/src/components/switch/Switch.vue +30 -10
  101. package/src/components/switch/style/Switch.css.ts +3 -5
  102. package/src/components/tab/Tab.vue +29 -20
  103. package/src/components/tab/TabGroup.vue +29 -38
  104. package/src/components/textarea/Textarea.vue +8 -4
  105. package/src/components/tooltip/Tooltip.vue +52 -141
  106. package/src/components/tooltip/TooltipTrigger.vue +14 -17
  107. package/src/components/tooltip/style/Tooltip.css.ts +148 -29
  108. package/src/components/tree/Tree.vue +138 -100
  109. package/src/components/tree/TreeNode.vue +105 -98
  110. package/src/components/tree/types.ts +4 -4
  111. package/src/styles/transitions.css.ts +36 -16
@@ -17,6 +17,12 @@ export type MessagePlacement =
17
17
  | 'bottom-left'
18
18
  | 'bottom-right';
19
19
 
20
+ export type MessageCloseButtonPosition =
21
+ | 'top-left'
22
+ | 'top-right'
23
+ | 'bottom-left'
24
+ | 'bottom-right';
25
+
20
26
  export type MessageRenderMessage = (props: any) => VNodeChild;
21
27
 
22
28
  export interface MessageOptions {
@@ -24,6 +30,8 @@ export interface MessageOptions {
24
30
  render?: MessageRenderMessage;
25
31
  timeout?: number;
26
32
  closable?: boolean;
33
+ closeButton?: boolean;
34
+ richColors?: boolean;
27
35
  onClose?: () => void;
28
36
  }
29
37
 
@@ -31,6 +39,8 @@ export interface MessageReactive {
31
39
  content: ContentType;
32
40
  timeout?: number;
33
41
  closable?: boolean;
42
+ closeButton?: boolean;
43
+ richColors?: boolean;
34
44
  type: MessageType;
35
45
  onClose?: () => void;
36
46
  destroy: () => void;
@@ -1,66 +1,98 @@
1
1
  <template>
2
- <Teleport :to="'body'">
3
- <TransitionGroup type="animation" :enter-active-class="fade.enterActive" :leave-active-class="fade.leaveActive"
4
- :enter-from-class="fade.enterFrom" :leave-to-class="fade.leaveTo">
5
- <div :class="style.mask" :style="{ zIndex: zIndex }" v-if="isOpen"></div>
6
- <div tabIndex="-1" v-if="isOpen" @keydown="onWrapperKeyDown" ref="wrapperRef" role="dialog"
7
- :class="computedWrapperClasses" :style="computedWrapperStyles" @click="onOutsideClick">
8
- <!-- <Transition-->
9
- <!-- type="animation"-->
10
- <!-- :enter-active-class="modalScale.enterActive"-->
11
- <!-- :leave-active-class="modalScale.leaveActive"-->
12
- <!-- :enter-from-class="modalScale.enterFrom"-->
13
- <!-- :leave-to-class="modalScale.leaveTo">-->
14
- <div :class="computedModalClasses" role="document" v-bind="$attrs" :style="contentStyleRef">
15
- <div :class="computedContainerClasses" @mousedown="onContentMouseDown" @mouseup="onContentMouseUp"
16
- ref="target">
17
- <template v-if="loading">
18
- <slot name="loading"> Loading...</slot>
19
- </template>
20
- <template v-else>
21
- <div :class="style.sidebar" v-if="hasSidebar">
22
- <slot name="sidebar"></slot>
23
- </div>
24
- <div :class="style.content">
25
- <div v-if="title || showClose" :class="style.header">
26
- <div :class="style.title" v-if="title">
27
- <div>{{ title }}</div>
2
+ <DialogRoot :open="isOpen" modal @update:open="handleOpenChange">
3
+ <DialogPortal to="body">
4
+ <Transition appear type="animation" :enter-active-class="fade.enterActive" :leave-active-class="fade.leaveActive"
5
+ :enter-from-class="fade.enterFrom" :leave-to-class="fade.leaveTo">
6
+ <DialogOverlay v-if="isOpen" force-mount :class="style.mask" :style="{ zIndex }" />
7
+ </Transition>
8
+ <Transition appear type="animation" :enter-active-class="modalScale.enterActive" :leave-active-class="modalScale.leaveActive"
9
+ :enter-from-class="modalScale.enterFrom" :leave-to-class="modalScale.leaveTo">
10
+ <div tabIndex="-1" v-if="isOpen" ref="wrapperRef"
11
+ :class="computedWrapperClasses" :style="computedWrapperStyles">
12
+ <DialogContent
13
+ force-mount
14
+ :class="computedModalClasses"
15
+ v-bind="$attrs"
16
+ :style="contentStyleRef"
17
+ :disable-outside-pointer-events="true"
18
+ @open-auto-focus="handleOpenAutoFocus"
19
+ @escape-key-down="onEscapeKeyDown"
20
+ @pointer-down-outside="onPointerDownOutside"
21
+ @interact-outside="onInteractOutside"
22
+ >
23
+ <FocusScope :trapped="focusTrap" :loop="focusTrap">
24
+ <div :class="computedContainerClasses" ref="target">
25
+ <template v-if="loading">
26
+ <slot name="loading"> Loading...</slot>
27
+ </template>
28
+ <template v-else>
29
+ <div :class="style.sidebar" v-if="hasSidebar">
30
+ <slot name="sidebar"></slot>
31
+ </div>
32
+ <div :class="style.content">
33
+ <div v-if="title || showClose" :class="style.header">
34
+ <DialogTitle as-child v-if="title">
35
+ <div :class="style.title">
36
+ <div>{{ title }}</div>
37
+ </div>
38
+ </DialogTitle>
39
+ </div>
40
+ <DialogClose as-child v-if="showClose">
41
+ <button :class="style.close"></button>
42
+ </DialogClose>
43
+ <div :class="style.body" ref="bodyRef">
44
+ <slot name="body"></slot>
45
+ <slot></slot>
46
+ </div>
47
+ <div :class="style.footer" v-if="hasFooter">
48
+ <slot name="footer"></slot>
49
+ </div>
28
50
  </div>
29
- </div>
30
- <button v-if="showClose" :class="style.close" @click="close"></button>
31
- <div :class="style.body">
32
- <slot name="body"></slot>
33
- <slot></slot>
34
- </div>
35
- <div :class="style.footer" v-if="hasFooter">
36
- <slot name="footer"></slot>
37
- </div>
51
+ </template>
38
52
  </div>
39
- </template>
40
- </div>
53
+ </FocusScope>
54
+ </DialogContent>
41
55
  </div>
42
- <!-- </Transition>-->
43
- </div>
44
- </TransitionGroup>
45
- </Teleport>
56
+ </Transition>
57
+ </DialogPortal>
58
+ </DialogRoot>
46
59
  </template>
47
60
 
48
61
  <script lang="ts">
49
62
  import type { CSSProperties, PropType } from 'vue';
63
+ import {
64
+ DialogClose,
65
+ DialogContent,
66
+ DialogOverlay,
67
+ DialogPortal,
68
+ DialogRoot,
69
+ DialogTitle,
70
+ FocusScope,
71
+ } from 'reka-ui';
50
72
  import {
51
73
  computed,
52
74
  defineComponent,
53
75
  nextTick,
76
+ provide,
54
77
  ref,
55
78
  shallowRef,
56
79
  watch
57
80
  } from 'vue';
58
81
  import * as style from './style';
59
82
  import { fade, modalScale } from '../../styles/transitions.css';
60
- import { useZIndex } from '../../composables/useZIndex';
83
+ import { useZIndex, ZINDEX } from '../../composables/useZIndex';
61
84
 
62
85
  export default defineComponent({
63
86
  name: 'MModal',
87
+ components: {
88
+ DialogClose,
89
+ DialogContent,
90
+ DialogOverlay,
91
+ DialogPortal,
92
+ DialogRoot,
93
+ DialogTitle,
94
+ FocusScope,
95
+ },
64
96
  props: {
65
97
  open: {
66
98
  type: Boolean,
@@ -106,15 +138,21 @@ export default defineComponent({
106
138
  centered: {
107
139
  type: Boolean,
108
140
  default: true
141
+ },
142
+ focusTrap: {
143
+ type: Boolean,
144
+ default: true
109
145
  }
110
146
  },
111
- emits: ['open', 'update:open'],
147
+ emits: ['open', 'close', 'update:open'],
112
148
  setup(props, { emit, slots }) {
113
149
  const { nextZIndex } = useZIndex();
114
150
 
115
151
  const target = ref();
152
+ const bodyRef = ref<HTMLElement | null>(null);
116
153
  const wrapperRef = shallowRef<HTMLDivElement>();
117
154
  const zIndex = ref(props.zIndex ?? nextZIndex());
155
+ provide(ZINDEX, zIndex);
118
156
  const hasSidebar = computed(() => !!slots.sidebar);
119
157
  const hasFooter = computed(() => !!slots.footer);
120
158
  const transformOrigin = ref<string>();
@@ -194,43 +232,78 @@ export default defineComponent({
194
232
  };
195
233
  });
196
234
 
197
- const contentClickRef = shallowRef(false);
198
- const contentTimeoutRef = shallowRef<any>();
235
+ const canCloseOutside = computed(() => props.canClickOutside || props.outsideClose);
236
+
237
+ const focusableBodySelector = [
238
+ '[autofocus]',
239
+ 'input:not([type="hidden"])',
240
+ 'textarea',
241
+ 'select',
242
+ '[contenteditable="true"]',
243
+ 'button',
244
+ 'a[href]',
245
+ '[tabindex]:not([tabindex="-1"])'
246
+ ].join(', ');
247
+
248
+ function getFirstFocusableBodyElement() {
249
+ if (!bodyRef.value) {
250
+ return null;
251
+ }
252
+
253
+ return Array.from(
254
+ bodyRef.value.querySelectorAll<HTMLElement>(focusableBodySelector)
255
+ ).find(element => {
256
+ return !element.matches(':disabled')
257
+ && element.getAttribute('aria-disabled') !== 'true'
258
+ && element.getAttribute('aria-hidden') !== 'true'
259
+ && element.getClientRects().length > 0;
260
+ }) ?? null;
261
+ }
262
+
263
+ const handleOpenAutoFocus = (event: Event) => {
264
+ const firstFocusableBodyElement = getFirstFocusableBodyElement();
265
+
266
+ if (!firstFocusableBodyElement) {
267
+ return;
268
+ }
199
269
 
200
- const onContentMouseDown = () => {
201
- clearTimeout(contentTimeoutRef.value);
202
- contentClickRef.value = true;
270
+ event.preventDefault();
271
+ firstFocusableBodyElement.focus();
203
272
  };
204
273
 
205
- const onContentMouseUp = () => {
206
- contentTimeoutRef.value = setTimeout(() => {
207
- contentClickRef.value = false;
208
- }, 0);
274
+ const onEscapeKeyDown = (e: KeyboardEvent) => {
275
+ e.stopPropagation();
276
+ close(e);
209
277
  };
210
278
 
211
- const onOutsideClick = (e: MouseEvent) => {
212
- if (!(props.canClickOutside || props.outsideClose)) {
213
- return null;
279
+ const onPointerDownOutside = (e: Event) => {
280
+ if (!canCloseOutside.value) {
281
+ e.preventDefault();
214
282
  }
283
+ };
215
284
 
216
- if (contentClickRef.value) {
217
- contentClickRef.value = false;
218
- } else if (wrapperRef.value === e.target) {
219
- close(e);
285
+ const onInteractOutside = (e: Event) => {
286
+ if (!canCloseOutside.value) {
287
+ e.preventDefault();
288
+ return;
220
289
  }
290
+
291
+ close(e as MouseEvent | KeyboardEvent);
221
292
  };
222
293
 
223
- const onWrapperKeyDown = (e: KeyboardEvent) => {
224
- if (e.key === 'Escape') {
225
- e.stopPropagation();
226
- close(e);
294
+ const handleOpenChange = (value: boolean) => {
295
+ if (!value && isOpen.value) {
296
+ close(undefined);
227
297
  return;
228
298
  }
299
+
300
+ isOpen.value = value;
229
301
  };
230
302
 
231
303
  const close = (e: MouseEvent | KeyboardEvent | undefined) => {
232
304
  props.onClose?.(e);
233
305
  ownerScrollbarClose();
306
+ emit('close', e);
234
307
  isOpen.value = false;
235
308
  };
236
309
 
@@ -283,12 +356,14 @@ export default defineComponent({
283
356
 
284
357
  return {
285
358
  close,
286
- onOutsideClick,
287
359
  open,
288
- onContentMouseDown,
289
- onContentMouseUp,
290
- onWrapperKeyDown,
360
+ onEscapeKeyDown,
361
+ onPointerDownOutside,
362
+ onInteractOutside,
363
+ handleOpenChange,
364
+ handleOpenAutoFocus,
291
365
  target,
366
+ bodyRef,
292
367
  style,
293
368
  isOpen,
294
369
  computedWrapperStyles,
@@ -79,7 +79,16 @@ export const modal = style({
79
79
  top: 100,
80
80
  paddingBottom: '24px',
81
81
  maxWidth: `calc(100vw - 32px)`,
82
- pointerEvents: 'none'
82
+ pointerEvents: 'none',
83
+ selectors: {
84
+ '&:focus': {
85
+ outline: 'none'
86
+ },
87
+ '&:focus-visible': {
88
+ outline: 'none',
89
+ boxShadow: 'none'
90
+ }
91
+ }
83
92
  });
84
93
 
85
94
  export const centeredWrapper = style({
@@ -113,7 +122,17 @@ export const container = style({
113
122
  flexDirection: 'column',
114
123
  position: 'relative',
115
124
  gap: themeVars.space[2],
116
- pointerEvents: 'auto'
125
+ pointerEvents: 'auto',
126
+ selectors: {
127
+ '&:focus': {
128
+ outline: 'none'
129
+ },
130
+ '&:focus-visible': {
131
+ outline: 'none',
132
+ boxShadow:
133
+ '0 1px 2px -2px rgba(0, 0, 0, 0.08), 0 3px 6px 0 rgba(0, 0, 0, 0.06), 0 5px 12px 4px rgba(0, 0, 0, 0.04)'
134
+ }
135
+ }
117
136
  });
118
137
 
119
138
  export const hasSidebar = style({
@@ -159,6 +178,11 @@ export const close = style([
159
178
  ':focus': {
160
179
  backgroundColor: themeVars.color.gray[150]
161
180
  },
181
+ ':focus-visible': {
182
+ outline: 'none',
183
+ boxShadow: 'none',
184
+ backgroundColor: themeVars.color.gray[150]
185
+ },
162
186
  '::before': {
163
187
  content: '""',
164
188
  position: 'absolute',
@@ -0,0 +1,155 @@
1
+ <template>
2
+ <PopoverRoot v-model:open="isOpen" :modal="modal">
3
+ <PopoverTrigger as-child>
4
+ <slot />
5
+ </PopoverTrigger>
6
+ <PopoverPortal :disabled="!transfer">
7
+ <PopoverContent
8
+ :class="contentClasses"
9
+ :side="resolvedSide"
10
+ :align="resolvedAlign"
11
+ :side-offset="sideOffset"
12
+ :collision-padding="collisionPadding"
13
+ :style="{ zIndex: zIndex }"
14
+ >
15
+ <slot name="content" />
16
+ <PopoverArrow v-if="showArrow" :class="style.arrow" :width="12" :height="6" />
17
+ </PopoverContent>
18
+ </PopoverPortal>
19
+ </PopoverRoot>
20
+ </template>
21
+
22
+ <script lang="ts">
23
+ import {
24
+ PopoverArrow,
25
+ PopoverContent,
26
+ PopoverPortal,
27
+ PopoverRoot,
28
+ PopoverTrigger,
29
+ type PopoverContentProps
30
+ } from 'reka-ui';
31
+ import {
32
+ computed,
33
+ defineComponent,
34
+ ref,
35
+ useAttrs,
36
+ watch,
37
+ type PropType
38
+ } from 'vue';
39
+ import {
40
+ type Placement,
41
+ placementWhileList
42
+ } from '../../composables/usePopper';
43
+ import { useZIndex } from '../../composables/useZIndex';
44
+ import * as style from './style';
45
+
46
+ export default defineComponent({
47
+ name: 'MPopover',
48
+ inheritAttrs: false,
49
+ components: {
50
+ PopoverArrow,
51
+ PopoverContent,
52
+ PopoverPortal,
53
+ PopoverRoot,
54
+ PopoverTrigger
55
+ },
56
+ props: {
57
+ open: {
58
+ type: Boolean,
59
+ default: false
60
+ },
61
+ modal: {
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
+ zIndex: {
91
+ type: Number
92
+ }
93
+ },
94
+ emits: ['update:open'],
95
+ setup(props, { emit }) {
96
+ const attrs = useAttrs();
97
+ const { nextZIndex } = useZIndex();
98
+ const isOpen = ref(props.open);
99
+ const zIndex = ref(props.zIndex ?? nextZIndex());
100
+
101
+ const resolvedPlacement = computed(() => {
102
+ const [side, align] = props.placement.split('-');
103
+
104
+ return {
105
+ side: side === 'top' || side === 'right' || side === 'bottom' || side === 'left'
106
+ ? side
107
+ : undefined,
108
+ align: align === 'start' || align === 'center' || align === 'end'
109
+ ? align
110
+ : undefined
111
+ } as {
112
+ side?: PopoverContentProps['side'];
113
+ align?: PopoverContentProps['align'];
114
+ };
115
+ });
116
+
117
+ const showArrow = computed(() => {
118
+ const { align } = resolvedPlacement.value;
119
+ return props.arrow && (!align || align === 'center');
120
+ });
121
+
122
+ const contentClasses = computed(() => {
123
+ return [
124
+ 'm-popover-content',
125
+ style.content,
126
+ {
127
+ [style.dark]: props.variant === 'dark'
128
+ },
129
+ attrs.class
130
+ ];
131
+ });
132
+
133
+ watch(
134
+ () => props.open,
135
+ value => {
136
+ isOpen.value = value;
137
+ }
138
+ );
139
+
140
+ watch(isOpen, value => {
141
+ emit('update:open', value);
142
+ });
143
+
144
+ return {
145
+ isOpen,
146
+ zIndex,
147
+ showArrow,
148
+ contentClasses,
149
+ resolvedSide: computed(() => resolvedPlacement.value.side),
150
+ resolvedAlign: computed(() => resolvedPlacement.value.align),
151
+ style
152
+ };
153
+ }
154
+ });
155
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <div style="padding: 100px; display: flex; gap: 16px; flex-wrap: wrap;">
3
+ <MPopover arrow placement="bottom">
4
+ <MButton>Bottom (Arrow)</MButton>
5
+ <template #content>
6
+ <div style="padding: 8px; min-width: 180px;">
7
+ <p style="margin: 0; font-size: 14px;">
8
+ Arrow points to the trigger.
9
+ </p>
10
+ </div>
11
+ </template>
12
+ </MPopover>
13
+
14
+ <MPopover arrow placement="top">
15
+ <MButton>Top (Arrow)</MButton>
16
+ <template #content>
17
+ <div style="padding: 8px; min-width: 180px;">
18
+ <p style="margin: 0; font-size: 14px;">
19
+ Arrow points to the trigger.
20
+ </p>
21
+ </div>
22
+ </template>
23
+ </MPopover>
24
+
25
+ <MPopover arrow placement="left">
26
+ <MButton>Left (Arrow)</MButton>
27
+ <template #content>
28
+ <div style="padding: 8px; min-width: 180px;">
29
+ <p style="margin: 0; font-size: 14px;">
30
+ Arrow points to the trigger.
31
+ </p>
32
+ </div>
33
+ </template>
34
+ </MPopover>
35
+
36
+ <MPopover arrow placement="right">
37
+ <MButton>Right (Arrow)</MButton>
38
+ <template #content>
39
+ <div style="padding: 8px; min-width: 180px;">
40
+ <p style="margin: 0; font-size: 14px;">
41
+ Arrow points to the trigger.
42
+ </p>
43
+ </div>
44
+ </template>
45
+ </MPopover>
46
+ </div>
47
+ </template>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <div style="padding: 100px;">
3
+ <MPopover>
4
+ <MButton>Open Popover</MButton>
5
+ <template #content>
6
+ <div style="padding: 8px; min-width: 200px;">
7
+ <h4 style="margin: 0 0 8px; font-weight: 600;">Popover Title</h4>
8
+ <p style="margin: 0; color: #666; font-size: 14px;">
9
+ This is the popover content. You can put anything here.
10
+ </p>
11
+ </div>
12
+ </template>
13
+ </MPopover>
14
+ </div>
15
+ </template>
@@ -0,0 +1,19 @@
1
+ import MPopover from '../Popover.vue';
2
+ import MButton from '../../button/Button.vue';
3
+
4
+ import { MPopoverBasic, MPopoverPlacement, MPopoverArrow, MPopoverVariant } from './index';
5
+ import { createStory, popperPositionArgType } from '../../../__storybook__';
6
+
7
+ export default {
8
+ component: MPopover,
9
+ subcomponents: { MButton },
10
+ title: 'Components/Popover',
11
+ argTypes: {
12
+ ...popperPositionArgType()
13
+ }
14
+ };
15
+
16
+ export const Basic = createStory(MPopoverBasic);
17
+ export const Placement = createStory(MPopoverPlacement);
18
+ export const Arrow = createStory(MPopoverArrow);
19
+ export const Variant = createStory(MPopoverVariant);
@@ -0,0 +1,4 @@
1
+ export { default as MPopoverBasic } from './basic.vue';
2
+ export { default as MPopoverPlacement } from './placement.vue';
3
+ export { default as MPopoverArrow } from './arrow.vue';
4
+ export { default as MPopoverVariant } from './variant.vue';
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div style="padding: 150px; display: flex; gap: 16px; flex-wrap: wrap;">
3
+ <MPopover placement="top">
4
+ <MButton>Top</MButton>
5
+ <template #content>
6
+ <div style="padding: 8px;">Top</div>
7
+ </template>
8
+ </MPopover>
9
+
10
+ <MPopover placement="top-start">
11
+ <MButton>Top Start</MButton>
12
+ <template #content>
13
+ <div style="padding: 8px;">Top Start</div>
14
+ </template>
15
+ </MPopover>
16
+
17
+ <MPopover placement="top-end">
18
+ <MButton>Top End</MButton>
19
+ <template #content>
20
+ <div style="padding: 8px;">Top End</div>
21
+ </template>
22
+ </MPopover>
23
+
24
+ <MPopover placement="bottom">
25
+ <MButton>Bottom</MButton>
26
+ <template #content>
27
+ <div style="padding: 8px;">Bottom</div>
28
+ </template>
29
+ </MPopover>
30
+
31
+ <MPopover placement="bottom-start">
32
+ <MButton>Bottom Start</MButton>
33
+ <template #content>
34
+ <div style="padding: 8px;">Bottom Start</div>
35
+ </template>
36
+ </MPopover>
37
+
38
+ <MPopover placement="bottom-end">
39
+ <MButton>Bottom End</MButton>
40
+ <template #content>
41
+ <div style="padding: 8px;">Bottom End</div>
42
+ </template>
43
+ </MPopover>
44
+
45
+ <MPopover placement="left">
46
+ <MButton>Left</MButton>
47
+ <template #content>
48
+ <div style="padding: 8px;">Left</div>
49
+ </template>
50
+ </MPopover>
51
+
52
+ <MPopover placement="right">
53
+ <MButton>Right</MButton>
54
+ <template #content>
55
+ <div style="padding: 8px;">Right</div>
56
+ </template>
57
+ </MPopover>
58
+ </div>
59
+ </template>