@teammodlr/ui-kit 3.0.0-beta.2 → 3.0.0-beta.21

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 (54) hide show
  1. package/lib/components/colorpicker/ColorPicker.vue.d.ts +0 -1
  2. package/lib/components/colorpicker/style/ColorPicker.css.d.ts +1 -0
  3. package/lib/components/dialog/Dialog.vue.d.ts +6 -2
  4. package/lib/components/dialogprovider/types.d.ts +2 -0
  5. package/lib/components/drawer/Drawer.vue.d.ts +142 -0
  6. package/lib/components/drawer/style/Drawer.css.d.ts +33 -0
  7. package/lib/components/drawer/style/index.d.ts +1 -0
  8. package/lib/components/index.d.ts +2 -0
  9. package/lib/components/messageprovider/MessageProvider.vue.d.ts +2 -0
  10. package/lib/components/messageprovider/style/Toaster.css.d.ts +1 -0
  11. package/lib/components/popover/Popover.vue.d.ts +113 -0
  12. package/lib/components/popover/style/Popover.css.d.ts +4 -0
  13. package/lib/components/popover/style/index.d.ts +1 -0
  14. package/lib/index.cjs.js +3 -3
  15. package/lib/index.es.js +5825 -5297
  16. package/lib/style.css +1 -1
  17. package/package.json +1 -1
  18. package/src/components/cardcontextmenu/style/Dropdown.css.ts +7 -0
  19. package/src/components/colorpicker/ColorPicker.vue +8 -11
  20. package/src/components/colorpicker/examples/basic.vue +8 -17
  21. package/src/components/colorpicker/style/ColorPicker.css.ts +6 -1
  22. package/src/components/dialog/Dialog.vue +44 -13
  23. package/src/components/dialog/style/Dialog.css.ts +15 -1
  24. package/src/components/dialogprovider/DialogProvider.vue +7 -1
  25. package/src/components/dialogprovider/types.ts +2 -0
  26. package/src/components/drawer/Drawer.vue +276 -0
  27. package/src/components/drawer/examples/basic.vue +15 -0
  28. package/src/components/drawer/examples/custom-title.vue +21 -0
  29. package/src/components/drawer/examples/index.stories.ts +24 -0
  30. package/src/components/drawer/examples/index.ts +7 -0
  31. package/src/components/drawer/examples/no-mask.vue +15 -0
  32. package/src/components/drawer/examples/no-outside-close.vue +15 -0
  33. package/src/components/drawer/examples/placements.vue +28 -0
  34. package/src/components/drawer/examples/with-footer.vue +21 -0
  35. package/src/components/drawer/examples/with-trigger.vue +13 -0
  36. package/src/components/drawer/style/Drawer.css.ts +208 -0
  37. package/src/components/drawer/style/index.ts +1 -0
  38. package/src/components/dropdown/Dropdown.vue +1 -1
  39. package/src/components/dropdown/style/Dropdown.css.ts +16 -0
  40. package/src/components/index.ts +2 -0
  41. package/src/components/messageprovider/MessageProvider.vue +36 -7
  42. package/src/components/messageprovider/style/Toaster.css.ts +24 -0
  43. package/src/components/modal/style/Modal.css.ts +26 -2
  44. package/src/components/popover/Popover.vue +155 -0
  45. package/src/components/popover/examples/arrow.vue +47 -0
  46. package/src/components/popover/examples/basic.vue +15 -0
  47. package/src/components/popover/examples/index.stories.ts +19 -0
  48. package/src/components/popover/examples/index.ts +4 -0
  49. package/src/components/popover/examples/placement.vue +59 -0
  50. package/src/components/popover/examples/variant.vue +30 -0
  51. package/src/components/popover/style/Popover.css.ts +203 -0
  52. package/src/components/popover/style/index.ts +1 -0
  53. package/src/components/tab/Tab.vue +1 -1
  54. package/src/components/tab/style/Tab.css.ts +6 -0
@@ -0,0 +1,208 @@
1
+ import {
2
+ globalKeyframes,
3
+ style,
4
+ styleVariants
5
+ } from '@vanilla-extract/css';
6
+ import { themeVars } from '../../../styles/theme.css';
7
+
8
+ export const mask = style({
9
+ position: 'fixed',
10
+ top: 0,
11
+ insetInlineEnd: 0,
12
+ bottom: 0,
13
+ insetInlineStart: 0,
14
+ height: '100%',
15
+ backgroundColor: 'hsla(0,0%,45.1%,0.7)'
16
+ });
17
+
18
+ const drawerBase = {
19
+ position: 'fixed' as const,
20
+ backgroundColor: '#fff',
21
+ display: 'flex',
22
+ flexDirection: 'column' as const,
23
+ boxShadow:
24
+ '0 6px 16px 0 rgba(0,0,0,0.08), 0 3px 6px -4px rgba(0,0,0,0.12), 0 9px 28px 8px rgba(0,0,0,0.05)',
25
+ selectors: {
26
+ '&:focus': { outline: 'none' },
27
+ '&:focus-visible': { outline: 'none' }
28
+ }
29
+ };
30
+
31
+ export const placement = styleVariants({
32
+ left: {
33
+ ...drawerBase,
34
+ top: 0,
35
+ left: 0,
36
+ bottom: 0,
37
+ height: '100%'
38
+ },
39
+ right: {
40
+ ...drawerBase,
41
+ top: 0,
42
+ right: 0,
43
+ bottom: 0,
44
+ height: '100%'
45
+ },
46
+ top: {
47
+ ...drawerBase,
48
+ top: 0,
49
+ left: 0,
50
+ right: 0,
51
+ width: '100%'
52
+ },
53
+ bottom: {
54
+ ...drawerBase,
55
+ bottom: 0,
56
+ left: 0,
57
+ right: 0,
58
+ width: '100%'
59
+ }
60
+ });
61
+
62
+ const DURATION = '0.28s';
63
+ const TIMING = 'cubic-bezier(0.16, 1, 0.3, 1)';
64
+
65
+ const SLIDE_LEFT_IN = 'drawer-slide-left-in';
66
+ const SLIDE_LEFT_OUT = 'drawer-slide-left-out';
67
+ const SLIDE_RIGHT_IN = 'drawer-slide-right-in';
68
+ const SLIDE_RIGHT_OUT = 'drawer-slide-right-out';
69
+ const SLIDE_TOP_IN = 'drawer-slide-top-in';
70
+ const SLIDE_TOP_OUT = 'drawer-slide-top-out';
71
+ const SLIDE_BOTTOM_IN = 'drawer-slide-bottom-in';
72
+ const SLIDE_BOTTOM_OUT = 'drawer-slide-bottom-out';
73
+
74
+ globalKeyframes(SLIDE_LEFT_IN, {
75
+ from: { transform: 'translateX(-100%)' },
76
+ to: { transform: 'translateX(0)' }
77
+ });
78
+ globalKeyframes(SLIDE_LEFT_OUT, {
79
+ from: { transform: 'translateX(0)' },
80
+ to: { transform: 'translateX(-100%)' }
81
+ });
82
+ globalKeyframes(SLIDE_RIGHT_IN, {
83
+ from: { transform: 'translateX(100%)' },
84
+ to: { transform: 'translateX(0)' }
85
+ });
86
+ globalKeyframes(SLIDE_RIGHT_OUT, {
87
+ from: { transform: 'translateX(0)' },
88
+ to: { transform: 'translateX(100%)' }
89
+ });
90
+ globalKeyframes(SLIDE_TOP_IN, {
91
+ from: { transform: 'translateY(-100%)' },
92
+ to: { transform: 'translateY(0)' }
93
+ });
94
+ globalKeyframes(SLIDE_TOP_OUT, {
95
+ from: { transform: 'translateY(0)' },
96
+ to: { transform: 'translateY(-100%)' }
97
+ });
98
+ globalKeyframes(SLIDE_BOTTOM_IN, {
99
+ from: { transform: 'translateY(100%)' },
100
+ to: { transform: 'translateY(0)' }
101
+ });
102
+ globalKeyframes(SLIDE_BOTTOM_OUT, {
103
+ from: { transform: 'translateY(0)' },
104
+ to: { transform: 'translateY(100%)' }
105
+ });
106
+
107
+ export const slideTransition = {
108
+ left: {
109
+ enterActive: style({ animation: `${DURATION} ${SLIDE_LEFT_IN} ${TIMING}`, willChange: 'transform' }),
110
+ leaveActive: style({ animation: `${DURATION} ${SLIDE_LEFT_OUT} ${TIMING}`, willChange: 'transform' }),
111
+ enterFrom: style({ transform: 'translateX(-100%)' }),
112
+ leaveTo: style({ transform: 'translateX(-100%)' })
113
+ },
114
+ right: {
115
+ enterActive: style({ animation: `${DURATION} ${SLIDE_RIGHT_IN} ${TIMING}`, willChange: 'transform' }),
116
+ leaveActive: style({ animation: `${DURATION} ${SLIDE_RIGHT_OUT} ${TIMING}`, willChange: 'transform' }),
117
+ enterFrom: style({ transform: 'translateX(100%)' }),
118
+ leaveTo: style({ transform: 'translateX(100%)' })
119
+ },
120
+ top: {
121
+ enterActive: style({ animation: `${DURATION} ${SLIDE_TOP_IN} ${TIMING}`, willChange: 'transform' }),
122
+ leaveActive: style({ animation: `${DURATION} ${SLIDE_TOP_OUT} ${TIMING}`, willChange: 'transform' }),
123
+ enterFrom: style({ transform: 'translateY(-100%)' }),
124
+ leaveTo: style({ transform: 'translateY(-100%)' })
125
+ },
126
+ bottom: {
127
+ enterActive: style({ animation: `${DURATION} ${SLIDE_BOTTOM_IN} ${TIMING}`, willChange: 'transform' }),
128
+ leaveActive: style({ animation: `${DURATION} ${SLIDE_BOTTOM_OUT} ${TIMING}`, willChange: 'transform' }),
129
+ enterFrom: style({ transform: 'translateY(100%)' }),
130
+ leaveTo: style({ transform: 'translateY(100%)' })
131
+ }
132
+ };
133
+
134
+ export const header = style({
135
+ display: 'flex',
136
+ justifyContent: 'space-between',
137
+ alignItems: 'center',
138
+ padding: `${themeVars.space[4]} ${themeVars.space[4]} 0 ${themeVars.space[4]}`,
139
+ flexShrink: '0'
140
+ });
141
+
142
+ export const title = style({
143
+ margin: 0,
144
+ fontSize: `${themeVars.fontSize.xlarge}`,
145
+ fontWeight: '500',
146
+ color: themeVars.color.font.default
147
+ });
148
+
149
+ export const close = style({
150
+ outline: 'none',
151
+ display: 'inline-flex',
152
+ alignItems: 'center',
153
+ justifyContent: 'center',
154
+ width: '1.5rem',
155
+ height: '1.5rem',
156
+ backgroundColor: 'transparent',
157
+ border: 'none',
158
+ padding: 0,
159
+ cursor: 'pointer',
160
+ borderRadius: themeVars.rounded.full,
161
+ position: 'relative',
162
+ transition: 'background-color 0.2s',
163
+ ':hover': {
164
+ backgroundColor: themeVars.color.gray[150]
165
+ },
166
+ ':focus-visible': {
167
+ outline: 'none',
168
+ backgroundColor: themeVars.color.gray[150]
169
+ },
170
+ '::before': {
171
+ content: '""',
172
+ position: 'absolute',
173
+ top: '50%',
174
+ left: '50%',
175
+ transform: 'translate(-50%, -50%) rotate(45deg)',
176
+ width: '0.8rem',
177
+ height: '2px',
178
+ backgroundColor: themeVars.color.gray[500],
179
+ transformOrigin: 'center'
180
+ },
181
+ '::after': {
182
+ content: '""',
183
+ position: 'absolute',
184
+ top: '50%',
185
+ left: '50%',
186
+ transform: 'translate(-50%, -50%) rotate(-45deg)',
187
+ width: '0.8rem',
188
+ height: '2px',
189
+ backgroundColor: themeVars.color.gray[500],
190
+ transformOrigin: 'center'
191
+ }
192
+ });
193
+
194
+ export const body = style({
195
+ padding: themeVars.space[4],
196
+ display: 'flex',
197
+ flexDirection: 'column',
198
+ flexGrow: '1',
199
+ overflowY: 'auto',
200
+ color: themeVars.color.font.text
201
+ });
202
+
203
+ export const footer = style({
204
+ display: 'flex',
205
+ alignItems: 'center',
206
+ padding: `0 ${themeVars.space[4]} ${themeVars.space[4]} ${themeVars.space[4]}`,
207
+ flexShrink: '0'
208
+ });
@@ -0,0 +1 @@
1
+ export * from './Drawer.css';
@@ -9,7 +9,7 @@
9
9
  </ContextMenuSubContent>
10
10
  </ContextMenuPortal>
11
11
  </ContextMenuSub>
12
- <ContextMenuRoot v-else-if="isContextMenu" v-model:open="isOpen" v-bind="$attrs">
12
+ <ContextMenuRoot v-else-if="isContextMenu" v-model:open="isOpen" :modal="modal" v-bind="$attrs">
13
13
  <ContextMenuTrigger as-child>
14
14
  <slot />
15
15
  </ContextMenuTrigger>
@@ -197,6 +197,22 @@ export const menuItem = style({
197
197
  backgroundColor: vars.backgroundColor,
198
198
  color: vars.color,
199
199
  outline: 'none'
200
+ },
201
+ ':focus': {
202
+ vars: {
203
+ [vars.backgroundColor]: themeVars.color.gray[150],
204
+ },
205
+ backgroundColor: vars.backgroundColor,
206
+ outline: 'none'
207
+ },
208
+ selectors: {
209
+ '&[aria-expanded="true"]': {
210
+ vars: {
211
+ [vars.backgroundColor]: themeVars.color.gray[150],
212
+ },
213
+ backgroundColor: vars.backgroundColor,
214
+ outline: 'none'
215
+ }
200
216
  }
201
217
  });
202
218
 
@@ -55,4 +55,6 @@ export { default as MConfirm } from './confirm/Confirm.vue';
55
55
  export { default as MConfirmProvider } from './confirm/ConfirmProvider.vue';
56
56
  export { default as MDialog } from './dialog/Dialog.vue';
57
57
  export { default as MDialogProvider } from './dialogprovider/DialogProvider.vue';
58
+ export { default as MDrawer } from './drawer/Drawer.vue';
58
59
  export { default as MTree } from './tree/Tree.vue';
60
+ export { default as MPopover } from './popover/Popover.vue';
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <slot />
3
3
  <Toaster
4
+ :class="style.toaster"
4
5
  :position="sonnerPosition"
5
6
  :duration="timeout"
6
7
  :visible-toasts="max"
@@ -24,6 +25,7 @@ import type {
24
25
  MessageType,
25
26
  } from './types';
26
27
  import type { PropType } from 'vue';
28
+ import * as style from './style/Toaster.css';
27
29
 
28
30
  interface PrivateMessageReactive extends MessageReactive {
29
31
  id: string;
@@ -62,7 +64,7 @@ export default defineComponent({
62
64
  },
63
65
  },
64
66
  setup(props) {
65
- const activeMessages = new Map<string, { stop: () => void; closed: boolean; onClose?: () => void }>();
67
+ const activeMessages = new Map<string, { stop: () => void; closed: boolean; currentId: string; onClose?: () => void }>();
66
68
 
67
69
  const sonnerPosition = computed(() => placementMap[props.placement]);
68
70
 
@@ -98,9 +100,9 @@ export default defineComponent({
98
100
  return timeout ?? props.timeout;
99
101
  }
100
102
 
101
- function syncToast(message: PrivateMessageReactive) {
103
+ function createToast(message: PrivateMessageReactive, sonnerId: string) {
102
104
  const options: ExternalToast = {
103
- id: message.id,
105
+ id: sonnerId,
104
106
  duration: normalizeDuration(message.timeout),
105
107
  closeButton: message.closeButton ?? message.closable,
106
108
  richColors: message.richColors,
@@ -131,6 +133,23 @@ export default defineComponent({
131
133
  }
132
134
  }
133
135
 
136
+ function syncToast(message: PrivateMessageReactive) {
137
+ const active = activeMessages.get(message.id);
138
+ if (!active) {
139
+ return;
140
+ }
141
+
142
+ const oldId = active.currentId;
143
+ const newId = Math.random().toString(16).slice(2, 14);
144
+ active.currentId = newId;
145
+
146
+ toast.dismiss(oldId);
147
+
148
+ requestAnimationFrame(() => {
149
+ createToast(message, newId);
150
+ });
151
+ }
152
+
134
153
  function closeMessage(id: string, invokeOnClose: boolean) {
135
154
  const active = activeMessages.get(id);
136
155
  if (!active || active.closed) {
@@ -162,20 +181,29 @@ export default defineComponent({
162
181
  type: options.type,
163
182
  onClose: options.onClose,
164
183
  destroy: () => {
184
+ const active = activeMessages.get(id);
185
+ const sonnerId = active?.currentId ?? id;
165
186
  closeMessage(id, false);
166
- toast.dismiss(id);
187
+ toast.dismiss(sonnerId);
167
188
  },
168
189
  }) as PrivateMessageReactive;
169
190
 
191
+ const sonnerId = Math.random().toString(16).slice(2, 14);
192
+ activeMessages.set(id, { stop: () => {}, closed: false, currentId: sonnerId, onClose: options.onClose });
193
+
170
194
  const stop = watch(
171
195
  () => [message.content, message.type, message.timeout, message.closable, message.closeButton, message.richColors, sonnerPosition.value],
172
- () => {
173
- syncToast(message);
196
+ (_, oldVal) => {
197
+ if (!oldVal) {
198
+ createToast(message, activeMessages.get(id)!.currentId);
199
+ } else {
200
+ syncToast(message);
201
+ }
174
202
  },
175
203
  { immediate: true }
176
204
  );
177
205
 
178
- activeMessages.set(id, { stop, closed: false, onClose: options.onClose });
206
+ activeMessages.get(id)!.stop = stop;
179
207
 
180
208
  return message;
181
209
  }
@@ -192,6 +220,7 @@ export default defineComponent({
192
220
 
193
221
  return {
194
222
  sonnerPosition,
223
+ style,
195
224
  };
196
225
  },
197
226
  });
@@ -0,0 +1,24 @@
1
+ import { globalStyle, style } from '@vanilla-extract/css';
2
+ import { themeVars } from '../../../styles/theme.css';
3
+
4
+ export const toaster = style({});
5
+
6
+ globalStyle(`${toaster} [data-sonner-toast][data-type='success'] [data-icon]`, {
7
+ color: themeVars.color.green[500]
8
+ });
9
+
10
+ globalStyle(`${toaster} [data-sonner-toast][data-type='error'] [data-icon]`, {
11
+ color: themeVars.color.red[500]
12
+ });
13
+
14
+ globalStyle(`${toaster} [data-sonner-toast][data-type='warning'] [data-icon]`, {
15
+ color: themeVars.color.amber[500]
16
+ });
17
+
18
+ globalStyle(`${toaster} [data-sonner-toast][data-type='info'] [data-icon]`, {
19
+ color: themeVars.color.blue[500]
20
+ });
21
+
22
+ globalStyle(`${toaster} [data-sonner-toast][data-type='loading'] [data-icon]`, {
23
+ color: themeVars.color.gray[500]
24
+ });
@@ -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';