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

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 (95) 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/messageprovider/Message.vue.d.ts +1 -1
  26. package/lib/components/messageprovider/MessageProvider.vue.d.ts +22 -24
  27. package/lib/components/messageprovider/style/Message.css.d.ts +1 -1
  28. package/lib/components/messageprovider/types.d.ts +5 -0
  29. package/lib/components/modal/Modal.vue.d.ts +18 -6
  30. package/lib/components/radio/Radio.vue.d.ts +2 -0
  31. package/lib/components/radiobuttongroup/RadioButtonGroup.vue.d.ts +1 -0
  32. package/lib/components/select/Select.vue.d.ts +20 -11
  33. package/lib/components/switch/Switch.vue.d.ts +2 -0
  34. package/lib/components/tab/Tab.vue.d.ts +5 -2
  35. package/lib/components/tab/TabGroup.vue.d.ts +8 -3
  36. package/lib/components/textarea/Textarea.vue.d.ts +2 -1
  37. package/lib/components/tooltip/Tooltip.vue.d.ts +5 -20
  38. package/lib/components/tooltip/TooltipTrigger.vue.d.ts +1 -10
  39. package/lib/components/tree/Tree.vue.d.ts +3 -3
  40. package/lib/components/tree/TreeNode.vue.d.ts +26 -12
  41. package/lib/components/tree/types.d.ts +5 -5
  42. package/lib/index.cjs.js +5 -14
  43. package/lib/index.es.js +15444 -10914
  44. package/lib/style.css +1 -1
  45. package/package.json +12 -6
  46. package/src/components/avatar/Avatar.vue +32 -24
  47. package/src/components/button/Button.vue +24 -5
  48. package/src/components/cardcontextmenu/CardContextMenu.vue +68 -95
  49. package/src/components/cardcontextmenu/CardContextMenuItem.vue +40 -50
  50. package/src/components/cardcontextmenu/CardContextMenuSeperator.vue +9 -7
  51. package/src/components/cardcontextmenu/CardContextMenuTriggerProvider.vue +15 -0
  52. package/src/components/cardcontextmenu/style/Dropdown.css.ts +155 -2
  53. package/src/components/cardcontextmenu/symbol.ts +3 -0
  54. package/src/components/checkbox/Checkbox.vue +37 -12
  55. package/src/components/checkbox/style/Checkbox.css.ts +6 -6
  56. package/src/components/collapse/CollapseItem.vue +46 -21
  57. package/src/components/collapse/style/Collapse.css.ts +5 -1
  58. package/src/components/colorpicker/ColorPicker.vue +76 -45
  59. package/src/components/confirm/Confirm.vue +15 -8
  60. package/src/components/confirm/props.ts +4 -0
  61. package/src/components/dialog/Dialog.vue +183 -37
  62. package/src/components/dialog/style/Dialog.css.ts +15 -1
  63. package/src/components/dialogprovider/types.ts +1 -0
  64. package/src/components/dropdown/Dropdown.vue +46 -3
  65. package/src/components/dropdown/DropdownItem.vue +55 -25
  66. package/src/components/dropdown/DropdownSeperator.vue +19 -11
  67. package/src/components/dropdown/DropdownTriggerProvider.vue +15 -0
  68. package/src/components/dropdown/examples/context.vue +2 -2
  69. package/src/components/dropdown/style/Dropdown.css.ts +157 -4
  70. package/src/components/dropdown/symbol.ts +4 -0
  71. package/src/components/form/examples/basic.vue +19 -8
  72. package/src/components/messageprovider/MessageProvider.vue +136 -90
  73. package/src/components/messageprovider/examples/basic.vue +3 -1
  74. package/src/components/messageprovider/examples/index.stories.ts +37 -1
  75. package/src/components/messageprovider/types.ts +10 -0
  76. package/src/components/modal/Modal.vue +142 -67
  77. package/src/components/modal/style/Modal.css.ts +26 -2
  78. package/src/components/radio/Radio.vue +31 -17
  79. package/src/components/radiobutton/RadioButton.vue +15 -2
  80. package/src/components/radiobuttongroup/RadioButtonGroup.vue +11 -4
  81. package/src/components/select/Select.vue +301 -153
  82. package/src/components/select/examples/basic.vue +1 -1
  83. package/src/components/slider/Slider.vue +1 -1
  84. package/src/components/switch/Switch.vue +30 -10
  85. package/src/components/switch/style/Switch.css.ts +3 -5
  86. package/src/components/tab/Tab.vue +29 -20
  87. package/src/components/tab/TabGroup.vue +29 -38
  88. package/src/components/textarea/Textarea.vue +8 -4
  89. package/src/components/tooltip/Tooltip.vue +52 -141
  90. package/src/components/tooltip/TooltipTrigger.vue +14 -17
  91. package/src/components/tooltip/style/Tooltip.css.ts +148 -29
  92. package/src/components/tree/Tree.vue +138 -100
  93. package/src/components/tree/TreeNode.vue +105 -98
  94. package/src/components/tree/types.ts +4 -4
  95. package/src/styles/transitions.css.ts +36 -16
@@ -1,25 +1,41 @@
1
1
  <template>
2
- <Teleport to="body">
3
- <Transition :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.root" v-if="open"
6
- :style="{ zIndex: zIndex, position: 'fixed', inset: '0', pointerEvents: modal ? 'all' : 'none' }">
7
- <div :class="backdropClasses" v-if="modal"></div>
8
- <div ref="wrapper" :class="wrapperClasses">
9
-
10
- <div ref="dialog" :style="computedStyle">
11
- <div tabindex="0" aria-hidden="true"
12
- style="width: 0px; height: 0px; overflow: hidden; outline: none;"></div>
2
+ <DialogRoot :open="isOpen" :modal="modal" @update:open="handleOpenChange">
3
+ <DialogPortal to="body">
4
+ <Transition 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 && modal" force-mount :class="backdropClasses" style="z-index: 10" />
7
+ </Transition>
8
+ <Transition type="animation" :enter-active-class="modalScale.enterActive" :leave-active-class="modalScale.leaveActive"
9
+ @after-leave="handleAfterLeave"
10
+ :enter-from-class="modalScale.enterFrom" :leave-to-class="modalScale.leaveTo">
11
+
12
+ <DialogContent
13
+ v-if="isOpen"
14
+ ref="dialog"
15
+ force-mount
16
+ :style="computedStyle"
17
+ :disable-outside-pointer-events="modal"
18
+ @open-auto-focus="handleOpenAutoFocus"
19
+ @escape-key-down="handleEscapeKeyDown"
20
+ @pointer-down-outside.prevent
21
+ @focus-outside="handleFocusOutside"
22
+ @interact-outside.prevent
23
+ >
24
+ <FocusScope :trapped="focusTrap" :loop="focusTrap">
13
25
  <slot v-if="$slots.container" name="container" :close="close"></slot>
14
26
  <template v-else>
15
27
  <div :class="style.content">
16
- <button role="button" :class="style.close" @click="close"></button>
28
+ <DialogClose as-child>
29
+ <button ref="closeButton" role="button" :class="style.close"></button>
30
+ </DialogClose>
17
31
  <div :class="headerClasses" ref="handle">
18
- <div :class="style.title">
19
- <slot name="title">
20
- {{ resolvedTitle }}
21
- </slot>
22
- </div>
32
+ <DialogTitle as-child>
33
+ <div :class="style.title">
34
+ <slot name="title">
35
+ {{ resolvedTitle }}
36
+ </slot>
37
+ </div>
38
+ </DialogTitle>
23
39
  </div>
24
40
  <div>
25
41
  <slot></slot>
@@ -29,35 +45,53 @@
29
45
  </div>
30
46
  </div>
31
47
  </template>
32
- <div tabindex="0" aria-hidden="true"
33
- style="width: 0px; height: 0px; overflow: hidden; outline: none;"></div>
34
- </div>
35
-
36
- </div>
37
- </div>
38
- </Transition>
39
- </Teleport>
48
+ </FocusScope>
49
+ </DialogContent>
50
+ </Transition>
51
+ </DialogPortal>
52
+ </DialogRoot>
40
53
  </template>
41
54
  <script lang="ts">
42
55
  import { fade, modalScale } from '../../styles/transitions.css';
56
+ import {
57
+ DialogClose,
58
+ DialogContent,
59
+ DialogOverlay,
60
+ DialogPortal,
61
+ DialogRoot,
62
+ DialogTitle,
63
+ FocusScope,
64
+ } from 'reka-ui';
43
65
  import {
44
66
  computed,
45
67
  defineComponent,
68
+ provide,
46
69
  ref,
70
+ shallowRef,
47
71
  watch,
48
72
  nextTick,
49
73
  isRef,
50
74
  type CSSProperties,
75
+ type ComponentPublicInstance,
51
76
  type PropType,
52
77
  type Ref,
53
78
  } from 'vue';
54
79
  import type { ComputedRef } from 'vue';
55
- import { useZIndex } from '../../composables/useZIndex';
80
+ import { useZIndex, ZINDEX } from '../../composables/useZIndex';
56
81
  import { useDraggable, useEventListener } from '@vueuse/core';
57
82
  import * as style from './style';
58
83
 
59
84
  export default defineComponent({
60
85
  name: 'Dialog',
86
+ components: {
87
+ DialogClose,
88
+ DialogContent,
89
+ DialogOverlay,
90
+ DialogPortal,
91
+ DialogRoot,
92
+ DialogTitle,
93
+ FocusScope,
94
+ },
61
95
  props: {
62
96
  title: {
63
97
  type: [String, Object] as PropType<string | Ref<string>>,
@@ -92,17 +126,40 @@ export default defineComponent({
92
126
  },
93
127
  },
94
128
  // Eventually remove 'open', i lazy cant be assed for now
95
- emits: ['onOpen', 'onClose', 'update:open', 'open'],
129
+ emits: ['onOpen', 'onClose', 'update:open', 'open', 'close'],
96
130
  setup(props, { emit, expose }) {
97
131
  const wrapper = ref<HTMLElement | null>(null);
98
- const dialog = ref<HTMLElement | null>(null);
132
+ const dialog = shallowRef<HTMLElement | ComponentPublicInstance | null>(null);
99
133
  const handle = ref<HTMLElement | null>(null);
134
+ const closeButton = ref<HTMLElement | null>(null);
100
135
  const { nextZIndex, current: maxZIndex } = useZIndex();
101
136
  const zIndex = ref(props.zIndex ?? nextZIndex());
137
+ provide(ZINDEX, zIndex);
138
+ const hasOpened = ref(false);
102
139
 
103
140
  const isDragging = ref<boolean>(false);
104
141
 
105
- const { x, y } = useDraggable(dialog, {
142
+ function resolveElement(
143
+ value: HTMLElement | ComponentPublicInstance | null | undefined
144
+ ): HTMLElement | null {
145
+ if (!value) {
146
+ return null;
147
+ }
148
+
149
+ if (value instanceof HTMLElement) {
150
+ return value;
151
+ }
152
+
153
+ if ('$el' in value && value.$el instanceof HTMLElement) {
154
+ return value.$el;
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ const dialogElement = computed(() => resolveElement(dialog.value));
161
+
162
+ const { x, y } = useDraggable(dialogElement, {
106
163
  handle,
107
164
  preventDefault: true,
108
165
  containerElement: wrapper,
@@ -115,6 +172,17 @@ export default defineComponent({
115
172
  isRef(props.title) ? props.title.value : props.title
116
173
  );
117
174
 
175
+ const focusableSelector = [
176
+ '[autofocus]',
177
+ 'input:not([type="hidden"])',
178
+ 'textarea',
179
+ 'select',
180
+ '[contenteditable="true"]',
181
+ 'button',
182
+ 'a[href]',
183
+ '[tabindex]:not([tabindex="-1"])'
184
+ ].join(', ');
185
+
118
186
  const backdropClasses = computed(() => ({
119
187
  [style.backdrop]: true,
120
188
  [style.backdropColor]: props.modal === true,
@@ -131,6 +199,13 @@ export default defineComponent({
131
199
  [style.notModal]: !props.modal,
132
200
  }));
133
201
 
202
+ const wrapperStyle: ComputedRef<CSSProperties> = computed(() => ({
203
+ zIndex: zIndex.value,
204
+ position: 'fixed',
205
+ inset: '0',
206
+ pointerEvents: props.modal ? 'all' : 'none'
207
+ }));
208
+
134
209
  const width = computed(() =>
135
210
  typeof props.width === 'number' ? `${props.width}px` : props.width
136
211
  );
@@ -144,8 +219,17 @@ export default defineComponent({
144
219
  }));
145
220
 
146
221
  function close() {
147
- emit('update:open', false);
148
- onCloseEmit();
222
+ handleOpenChange(false);
223
+ }
224
+
225
+ function handleEscapeKeyDown(event: KeyboardEvent) {
226
+ event.preventDefault();
227
+ event.stopPropagation();
228
+ close();
229
+ }
230
+
231
+ function handleAfterLeave() {
232
+ emit('close');
149
233
  }
150
234
 
151
235
  function onCloseEmit() {
@@ -157,9 +241,54 @@ export default defineComponent({
157
241
 
158
242
  function onEnter() {
159
243
  emit('onOpen');
244
+ emit('open');
160
245
  zIndex.value = nextZIndex();
161
246
  }
162
247
 
248
+ function handleFocusOutside(event: Event) {
249
+ if (!props.focusTrap) {
250
+ return;
251
+ }
252
+
253
+ event.preventDefault();
254
+ }
255
+
256
+ function handleOpenAutoFocus(event: Event) {
257
+ const element = dialogElement.value;
258
+
259
+ if (!element) {
260
+ return;
261
+ }
262
+
263
+ const firstFocusableElement = Array.from(
264
+ element.querySelectorAll<HTMLElement>(focusableSelector)
265
+ ).find(candidate => {
266
+ return candidate !== closeButton.value
267
+ && !candidate.matches(':disabled')
268
+ && candidate.getAttribute('aria-disabled') !== 'true'
269
+ && candidate.getAttribute('aria-hidden') !== 'true'
270
+ && candidate.getClientRects().length > 0;
271
+ });
272
+
273
+ if (!firstFocusableElement) {
274
+ return;
275
+ }
276
+
277
+ event.preventDefault();
278
+ firstFocusableElement.focus();
279
+ }
280
+
281
+ function handleOpenChange(value: boolean) {
282
+ if (!value && props.open) {
283
+ emit('update:open', false);
284
+ onCloseEmit();
285
+ hasOpened.value = false;
286
+ return;
287
+ }
288
+
289
+ emit('update:open', value);
290
+ }
291
+
163
292
  function onDragStart() {
164
293
  if (zIndex.value < maxZIndex.value) {
165
294
  zIndex.value = nextZIndex();
@@ -172,16 +301,20 @@ export default defineComponent({
172
301
  }
173
302
 
174
303
  function centerDialog() {
175
- if (dialog.value) {
176
- const boundingRect = dialog.value.getBoundingClientRect();
304
+ const element = dialogElement.value;
305
+
306
+ if (element) {
307
+ const boundingRect = element.getBoundingClientRect();
177
308
  x.value = (window.innerWidth - boundingRect.width) / 2;
178
309
  y.value = (window.innerHeight - boundingRect.height) / 2;
179
310
  }
180
311
  }
181
312
 
182
313
  function keepWithInBounds() {
183
- if (dialog.value) {
184
- const boundingRect = dialog.value.getBoundingClientRect();
314
+ const element = dialogElement.value;
315
+
316
+ if (element) {
317
+ const boundingRect = element.getBoundingClientRect();
185
318
  const maxX = window.innerWidth - boundingRect.width;
186
319
  const maxY = window.innerHeight - boundingRect.height;
187
320
 
@@ -206,7 +339,13 @@ export default defineComponent({
206
339
  if (newVal) {
207
340
  await nextTick();
208
341
  centerDialog();
209
- emit('onOpen');
342
+ zIndex.value = props.zIndex ?? nextZIndex();
343
+ if (!hasOpened.value) {
344
+ onEnter();
345
+ hasOpened.value = true;
346
+ }
347
+ } else {
348
+ hasOpened.value = false;
210
349
  }
211
350
  },
212
351
  { immediate: true }
@@ -220,6 +359,7 @@ export default defineComponent({
220
359
  wrapper,
221
360
  dialog,
222
361
  handle,
362
+ closeButton,
223
363
  zIndex,
224
364
  fade,
225
365
  modalScale,
@@ -229,9 +369,15 @@ export default defineComponent({
229
369
  backdropClasses,
230
370
  resolvedTitle,
231
371
  wrapperClasses,
372
+ wrapperStyle,
232
373
  close,
374
+ handleAfterLeave,
375
+ handleEscapeKeyDown,
233
376
  onEnter,
234
- open: computed(() => props.open)
377
+ handleOpenChange,
378
+ handleOpenAutoFocus,
379
+ handleFocusOutside,
380
+ isOpen: computed(() => props.open)
235
381
  };
236
382
  },
237
383
  });
@@ -60,7 +60,16 @@ export const content = style({
60
60
  border: `1px solid ${themeVars.color.gray[300]}`,
61
61
  borderRadius: themeVars.rounded.medium,
62
62
  pointerEvents: 'auto',
63
- boxShadow: `2px 2px 8px rgba(128, 128, 128, 0.1), 0 9px 28px 8px rgba(0,0,0,.05)`
63
+ boxShadow: `2px 2px 8px rgba(128, 128, 128, 0.1), 0 9px 28px 8px rgba(0,0,0,.05)`,
64
+ selectors: {
65
+ '&:focus': {
66
+ outline: 'none'
67
+ },
68
+ '&:focus-visible': {
69
+ outline: 'none',
70
+ boxShadow: `2px 2px 8px rgba(128, 128, 128, 0.1), 0 9px 28px 8px rgba(0,0,0,.05)`
71
+ }
72
+ }
64
73
  });
65
74
 
66
75
  export const header = style({
@@ -119,6 +128,11 @@ export const close = style([
119
128
  ':focus': {
120
129
  backgroundColor: themeVars.color.gray[150]
121
130
  },
131
+ ':focus-visible': {
132
+ outline: 'none',
133
+ boxShadow: 'none',
134
+ backgroundColor: themeVars.color.gray[150]
135
+ },
122
136
  '::before': {
123
137
  content: '""',
124
138
  position: 'absolute',
@@ -11,6 +11,7 @@ export interface DialogOptions {
11
11
  open?: boolean;
12
12
  draggable?: boolean;
13
13
  modal?: boolean;
14
+ focusTrap?: boolean;
14
15
  }
15
16
 
16
17
  export interface DialogReactive {
@@ -1,5 +1,15 @@
1
1
  <template>
2
- <ContextMenuRoot v-model:open="isOpen" v-if="contextMenu" v-bind="$attrs">
2
+ <ContextMenuSub v-model:open="isOpen" v-if="isContextMenu && isSubmenu">
3
+ <DropdownTriggerProvider>
4
+ <slot />
5
+ </DropdownTriggerProvider>
6
+ <ContextMenuPortal>
7
+ <ContextMenuSubContent :class="menuClasses" align="start" side="right" :side-offset="2" :align-offset="-6">
8
+ <slot name="menu" />
9
+ </ContextMenuSubContent>
10
+ </ContextMenuPortal>
11
+ </ContextMenuSub>
12
+ <ContextMenuRoot v-else-if="isContextMenu" v-model:open="isOpen" v-bind="$attrs">
3
13
  <ContextMenuTrigger as-child>
4
14
  <slot />
5
15
  </ContextMenuTrigger>
@@ -9,7 +19,17 @@
9
19
  </ContextMenuContent>
10
20
  </ContextMenuPortal>
11
21
  </ContextMenuRoot>
12
- <DropdownMenuRoot v-model:open="isOpen" v-else :modal="modal" v-bind="$attrs">
22
+ <DropdownMenuSub v-else-if="isSubmenu" v-model:open="isOpen">
23
+ <DropdownTriggerProvider>
24
+ <slot />
25
+ </DropdownTriggerProvider>
26
+ <DropdownMenuPortal>
27
+ <DropdownMenuSubContent :class="menuClasses" align="start" side="right" :side-offset="2" :align-offset="-6">
28
+ <slot name="menu" />
29
+ </DropdownMenuSubContent>
30
+ </DropdownMenuPortal>
31
+ </DropdownMenuSub>
32
+ <DropdownMenuRoot v-else v-model:open="isOpen" :modal="modal" v-bind="$attrs">
13
33
  <DropdownMenuTrigger as-child>
14
34
  <slot />
15
35
  </DropdownMenuTrigger>
@@ -53,10 +73,12 @@ import {
53
73
  ContextMenuSubContent,
54
74
  ContextMenuSubTrigger,
55
75
  ContextMenuTrigger,
56
- } from 'radix-vue';
76
+ } from 'reka-ui';
57
77
  import {
58
78
  computed,
59
79
  defineComponent,
80
+ inject,
81
+ provide,
60
82
  ref,
61
83
  watch,
62
84
  type PropType
@@ -65,6 +87,7 @@ import { useZIndex } from '../../composables/useZIndex';
65
87
  import { scale } from '../../styles/transitions.css';
66
88
  import * as style from './style';
67
89
  import { DROPDOWN_STATE } from './symbol';
90
+ import DropdownTriggerProvider from './DropdownTriggerProvider.vue';
68
91
 
69
92
  export default defineComponent({
70
93
  name: 'Dropdown',
@@ -99,6 +122,7 @@ export default defineComponent({
99
122
  ContextMenuSubContent,
100
123
  ContextMenuSubTrigger,
101
124
  ContextMenuTrigger,
125
+ DropdownTriggerProvider,
102
126
  },
103
127
  props: {
104
128
  open: {
@@ -151,6 +175,9 @@ export default defineComponent({
151
175
  },
152
176
  emits: ['update:open'],
153
177
  setup(props, { emit }) {
178
+ const parentState = inject(DROPDOWN_STATE, null);
179
+ const isSubmenu = computed(() => !!parentState);
180
+ const isContextMenu = computed(() => props.contextMenu || parentState?.contextMenu || false);
154
181
  const { nextZIndex } = useZIndex();
155
182
  const isOpen = ref(props.open);
156
183
  const placement = ref(props.placement);
@@ -158,6 +185,16 @@ export default defineComponent({
158
185
  const referenceRef = ref<HTMLElement>();
159
186
  const zIndex = ref(props.zIndex ?? nextZIndex());
160
187
 
188
+ provide(DROPDOWN_STATE, {
189
+ contextMenu: isContextMenu.value,
190
+ isOpen: isOpen.value,
191
+ handleSelect(labels: string[]) {
192
+ isOpen.value = false;
193
+ props.onSelect(labels);
194
+ parentState?.handleSelect(labels);
195
+ }
196
+ });
197
+
161
198
  watch(
162
199
  () => props.open,
163
200
  value => {
@@ -165,6 +202,10 @@ export default defineComponent({
165
202
  }
166
203
  );
167
204
 
205
+ watch(isOpen, value => {
206
+ emit('update:open', value);
207
+ });
208
+
168
209
  function toggleDropdown() {
169
210
  isOpen.value = !isOpen.value;
170
211
  }
@@ -199,6 +240,8 @@ export default defineComponent({
199
240
  computedReferenceClasses,
200
241
  transitionName,
201
242
  toggleDropdown,
243
+ isContextMenu,
244
+ isSubmenu,
202
245
  zIndex,
203
246
  scale
204
247
  };
@@ -1,54 +1,50 @@
1
1
  <template>
2
- <DropdownMenuItem :class="computedWrapperClasses" :as="computedType" :href="href">
3
- <div :class="style.menuItemIcon">
4
- <slot name="icon-left" />
5
- </div>
6
- <div :class="style.menuItemTextContainer">
7
- <span :class="style.menuItemText"><slot /></span>
8
- </div>
9
- <slot name="keyboard" />
10
- </DropdownMenuItem>
11
- <!-- <component
12
- :is="computedType"
13
- :href="href"
2
+ <component
3
+ :is="itemComponent"
14
4
  ref="wrapper"
15
5
  :class="computedWrapperClasses"
16
- role="menuitem"
17
- tabindex="0"
18
- @click="handleSelect"
19
- v-bind="$attrs">
6
+ :disabled="disabled"
7
+ v-bind="componentProps"
8
+ @select="!isSubmenuTrigger ? handleSelect : undefined"
9
+ >
20
10
  <div v-if="hasIconLeft" :class="style.menuItemIcon">
21
11
  <slot name="icon-left" />
22
12
  </div>
23
13
  <div :class="style.menuItemTextContainer">
24
14
  <span :class="style.menuItemText"><slot /></span>
25
15
  </div>
26
- <!-- <div v-if="hotkey" class="m-dropdown__hotkeys">
27
- <template v-for="key in hotkey.split('+')">
28
- <kbd>{{ key }}</kbd>
29
- </template>
30
- </div> -->
31
- <!-- <div v-if="hasIconRight" :class="style.menuItemIcon">
16
+ <slot name="keyboard" />
17
+ <div v-if="hasIconRight" :class="style.menuItemIconRight">
32
18
  <slot name="icon-right" />
33
19
  </div>
34
- </component> -->
20
+ </component>
35
21
  </template>
36
22
 
37
23
  <script lang="ts">
38
24
  import {
39
25
  computed,
40
26
  defineComponent,
27
+ inject,
41
28
  type PropType,
42
29
  ref
43
30
  } from 'vue';
44
- import { DropdownMenuItem } from "radix-vue";
31
+ import {
32
+ ContextMenuItem,
33
+ ContextMenuSubTrigger,
34
+ DropdownMenuItem,
35
+ DropdownMenuSubTrigger
36
+ } from 'reka-ui';
45
37
  import * as style from './style';
46
38
  import type { Variant } from '../../types/props';
39
+ import { DROPDOWN_STATE, DROPDOWN_TRIGGER_STATE } from './symbol';
47
40
 
48
41
  export default defineComponent({
49
42
  name: 'DropdownItem',
50
43
  components: {
44
+ ContextMenuItem,
45
+ ContextMenuSubTrigger,
51
46
  DropdownMenuItem
47
+ ,DropdownMenuSubTrigger
52
48
  },
53
49
  props: {
54
50
  label: {
@@ -86,15 +82,25 @@ export default defineComponent({
86
82
  },
87
83
  emits: [],
88
84
  setup(props, { slots }) {
85
+ const dropdownState = inject(DROPDOWN_STATE, null);
86
+ const isSubmenuTrigger = inject(DROPDOWN_TRIGGER_STATE, false);
89
87
 
90
88
  const hasIconLeft = computed(() => !!slots['icon-left']);
91
89
  const hasIconRight = computed(() => !!slots['icon-right']);
92
90
 
93
- const wrapper = ref(null);
91
+ const wrapper = ref<HTMLElement | null>(null);
94
92
  const computedWrapperClasses = computed(() => {
95
93
  return [style.menuItem, style.variant[props.variant]];
96
94
  });
97
95
 
96
+ const itemComponent = computed(() => {
97
+ if (isSubmenuTrigger) {
98
+ return dropdownState?.contextMenu ? ContextMenuSubTrigger : DropdownMenuSubTrigger;
99
+ }
100
+
101
+ return dropdownState?.contextMenu ? ContextMenuItem : DropdownMenuItem;
102
+ });
103
+
98
104
  const computedType = computed(() => {
99
105
  if (props.href) {
100
106
  return 'a';
@@ -103,11 +109,35 @@ export default defineComponent({
103
109
  return 'div';
104
110
  });
105
111
 
112
+ const componentProps = computed(() => {
113
+ if (isSubmenuTrigger) {
114
+ return {};
115
+ }
116
+
117
+ return {
118
+ as: computedType.value,
119
+ href: props.href
120
+ };
121
+ });
122
+
123
+ function handleSelect() {
124
+ if (props.disabled || isSubmenuTrigger) {
125
+ return;
126
+ }
127
+
128
+ dropdownState?.handleSelect([String(props.label ?? wrapper.value?.textContent?.trim() ?? '')]);
129
+ props.onSelect(props.label ?? wrapper.value?.textContent?.trim() ?? '');
130
+ }
131
+
106
132
  return {
107
133
  wrapper,
134
+ itemComponent,
135
+ isSubmenuTrigger,
108
136
  hasIconLeft,
109
137
  hasIconRight,
110
138
  computedWrapperClasses,
139
+ componentProps,
140
+ handleSelect,
111
141
  style,
112
142
  computedType
113
143
  };
@@ -1,26 +1,34 @@
1
1
  <template>
2
- <DropdownMenuSeparator :class="computedClasses" />
2
+ <component :is="separatorComponent" :class="computedClasses" />
3
3
  </template>
4
4
 
5
5
  <script lang="ts">
6
- import {computed, defineComponent} from "vue";
7
- import * as style from "./style";
8
- import { DropdownMenuSeparator } from 'radix-vue';
6
+ import { computed, defineComponent, inject } from 'vue';
7
+ import { ContextMenuSeparator, DropdownMenuSeparator } from 'reka-ui';
8
+ import * as style from './style';
9
+ import { DROPDOWN_STATE } from './symbol';
10
+
9
11
  export default defineComponent({
10
- name: "DropdownSeperator",
12
+ name: 'DropdownSeperator',
11
13
  components: {
14
+ ContextMenuSeparator,
12
15
  DropdownMenuSeparator
13
16
  },
14
17
  setup() {
18
+ const dropdownState = inject(DROPDOWN_STATE, null);
19
+
15
20
  const computedClasses = computed(() => {
16
- return [
17
- style.menuSeperator
18
- ]
21
+ return [style.menuSeperator];
22
+ });
23
+
24
+ const separatorComponent = computed(() => {
25
+ return dropdownState?.contextMenu ? ContextMenuSeparator : DropdownMenuSeparator;
19
26
  });
20
27
 
21
28
  return {
22
- computedClasses
23
- }
29
+ computedClasses,
30
+ separatorComponent
31
+ };
24
32
  }
25
- })
33
+ });
26
34
  </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <slot />
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { defineComponent, provide } from 'vue';
7
+ import { DROPDOWN_TRIGGER_STATE } from './symbol';
8
+
9
+ export default defineComponent({
10
+ name: 'DropdownTriggerProvider',
11
+ setup() {
12
+ provide(DROPDOWN_TRIGGER_STATE, true);
13
+ }
14
+ });
15
+ </script>