@nexxtmove/ui 0.1.58 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +359 -36
- package/dist/index.js +2946 -1864
- package/dist/nuxt.js +29 -28
- package/package.json +2 -1
- package/src/components/Combobox/Combobox.vue +28 -68
- package/src/components/DatePicker/DatePicker.vue +39 -82
- package/src/components/DropdownButton/DropdownButton.vue +47 -76
- package/src/components/FloatingPanel/FloatingPanel.vue +143 -0
- package/src/components/InputField/InputField.vue +1 -1
- package/src/components/Modal/Modal.vue +1 -1
- package/src/components.json +1 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
3
|
+
import { useFloating, autoUpdate, flip, shift, offset } from '@floating-ui/vue'
|
|
4
|
+
import type { Placement } from '@floating-ui/vue'
|
|
5
|
+
|
|
6
|
+
export interface NexxtFloatingPanelProps {
|
|
7
|
+
placement?: Placement
|
|
8
|
+
offsetDistance?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
defineOptions({ name: 'NexxtFloatingPanel' })
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<NexxtFloatingPanelProps>(), {
|
|
14
|
+
placement: 'bottom-start',
|
|
15
|
+
offsetDistance: 8,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
open: []
|
|
20
|
+
close: []
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const isOpen = ref(false)
|
|
24
|
+
// Keeps the positioning container alive while the leave transition plays.
|
|
25
|
+
const isLeaving = ref(false)
|
|
26
|
+
const triggerRef = ref<HTMLElement | null>(null)
|
|
27
|
+
const floatingRef = ref<HTMLElement | null>(null)
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
floatingStyles,
|
|
31
|
+
placement: computedPlacement,
|
|
32
|
+
isPositioned,
|
|
33
|
+
} = useFloating(triggerRef, floatingRef, {
|
|
34
|
+
placement: computed(() => props.placement),
|
|
35
|
+
whileElementsMounted: autoUpdate,
|
|
36
|
+
middleware: [offset(props.offsetDistance), flip(), shift({ padding: 8 })],
|
|
37
|
+
// fixed strategy escapes overflow clipping (e.g. inside a Modal) without
|
|
38
|
+
// needing a body teleport that Reka UI's dialog would block with pointer-events:none.
|
|
39
|
+
strategy: 'fixed',
|
|
40
|
+
// Use top/left positioning so `transform` is free for Tailwind transition classes.
|
|
41
|
+
transform: false,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const open = () => {
|
|
45
|
+
isOpen.value = true
|
|
46
|
+
emit('open')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const close = () => {
|
|
50
|
+
if (!isOpen.value) return
|
|
51
|
+
// Only keep the container alive if content was actually shown (isPositioned),
|
|
52
|
+
// otherwise there is no leave transition to wait for.
|
|
53
|
+
if (isPositioned.value) isLeaving.value = true
|
|
54
|
+
isOpen.value = false
|
|
55
|
+
emit('close')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const toggle = () => (isOpen.value ? close() : open())
|
|
59
|
+
|
|
60
|
+
const handleMousedown = (event: MouseEvent) => {
|
|
61
|
+
const target = event.target as Node
|
|
62
|
+
if (triggerRef.value?.contains(target) || floatingRef.value?.contains(target)) return
|
|
63
|
+
close()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
67
|
+
if (event.key === 'Escape' && isOpen.value) close()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const transitionClasses = computed(() => {
|
|
71
|
+
const placement = computedPlacement.value
|
|
72
|
+
const [side, align] = placement.split('-') as [string, string | undefined]
|
|
73
|
+
|
|
74
|
+
const originMap: Record<string, string> = {
|
|
75
|
+
'bottom-start': 'origin-top-left',
|
|
76
|
+
'bottom-end': 'origin-top-right',
|
|
77
|
+
bottom: 'origin-top',
|
|
78
|
+
'top-start': 'origin-bottom-left',
|
|
79
|
+
'top-end': 'origin-bottom-right',
|
|
80
|
+
top: 'origin-bottom',
|
|
81
|
+
'left-start': 'origin-top-right',
|
|
82
|
+
'left-end': 'origin-bottom-right',
|
|
83
|
+
left: 'origin-right',
|
|
84
|
+
'right-start': 'origin-top-left',
|
|
85
|
+
'right-end': 'origin-bottom-left',
|
|
86
|
+
right: 'origin-left',
|
|
87
|
+
}
|
|
88
|
+
const translates: Record<string, string> = {
|
|
89
|
+
bottom: '-translate-y-2',
|
|
90
|
+
top: 'translate-y-2',
|
|
91
|
+
left: 'translate-x-2',
|
|
92
|
+
right: '-translate-x-2',
|
|
93
|
+
}
|
|
94
|
+
const origin = originMap[align ? `${side}-${align}` : side] ?? 'origin-top'
|
|
95
|
+
const translate = translates[side] ?? '-translate-y-2'
|
|
96
|
+
return {
|
|
97
|
+
enterActiveClass: `transition-all duration-200 ease-out ${origin}`,
|
|
98
|
+
leaveActiveClass: `transition-all duration-150 ease-in ${origin}`,
|
|
99
|
+
enterFromClass: `opacity-0 scale-95 ${translate}`,
|
|
100
|
+
leaveToClass: `opacity-0 scale-95 ${translate}`,
|
|
101
|
+
enterToClass: 'opacity-100 scale-100 translate-x-0 translate-y-0',
|
|
102
|
+
leaveFromClass: 'opacity-100 scale-100 translate-x-0 translate-y-0',
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
defineExpose({ isOpen, open, close, toggle, triggerRef, floatingRef })
|
|
107
|
+
|
|
108
|
+
onMounted(() => {
|
|
109
|
+
document.addEventListener('mousedown', handleMousedown)
|
|
110
|
+
document.addEventListener('keydown', handleKeydown)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
onUnmounted(() => {
|
|
114
|
+
document.removeEventListener('mousedown', handleMousedown)
|
|
115
|
+
document.removeEventListener('keydown', handleKeydown)
|
|
116
|
+
})
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<template>
|
|
120
|
+
<span ref="triggerRef" v-bind="$attrs" class="inline-flex">
|
|
121
|
+
<slot name="trigger" :is-open="isOpen" :toggle="toggle" :open="open" :close="close" />
|
|
122
|
+
</span>
|
|
123
|
+
<!--
|
|
124
|
+
floatingRef is the pure positioning container. It mounts immediately when
|
|
125
|
+
open so useFloating can compute the correct top/left before anything is visible.
|
|
126
|
+
isLeaving keeps it alive while the leave transition plays so the position
|
|
127
|
+
stays anchored to the trigger during the outgoing animation.
|
|
128
|
+
strategy:'fixed' means position:fixed is used, so this escapes any overflow
|
|
129
|
+
clipping without needing a body teleport.
|
|
130
|
+
-->
|
|
131
|
+
<div v-if="isOpen || isLeaving" ref="floatingRef" class="z-50" :style="floatingStyles">
|
|
132
|
+
<!--
|
|
133
|
+
Content only renders once isPositioned is true, meaning floatingRef is
|
|
134
|
+
already at the correct position. The enter transition therefore always
|
|
135
|
+
originates from the trigger — never from the page origin.
|
|
136
|
+
-->
|
|
137
|
+
<Transition v-bind="transitionClasses" @after-leave="isLeaving = false">
|
|
138
|
+
<div v-if="isOpen && isPositioned">
|
|
139
|
+
<slot name="content" :is-open="isOpen" :close="close" :toggle="toggle" />
|
|
140
|
+
</div>
|
|
141
|
+
</Transition>
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
@@ -36,7 +36,7 @@ const modelValue = defineModel<string>()
|
|
|
36
36
|
|
|
37
37
|
<div
|
|
38
38
|
class="flex items-center gap-2 overflow-hidden rounded-lg ring transition-colors ease-out focus-within:ring-cornflower-blue-500"
|
|
39
|
-
:class="[error ? 'ring-brick-400' : 'ring-gray-200', { '
|
|
39
|
+
:class="[error ? 'ring-brick-400' : 'ring-gray-200', { 'pl-3': leftIcon, 'pr-3': rightIcon }]"
|
|
40
40
|
>
|
|
41
41
|
<span v-if="leftIcon" class="flex items-center">
|
|
42
42
|
<Icon :name="leftIcon" />
|
|
@@ -55,7 +55,7 @@ const sizeClass = {
|
|
|
55
55
|
<template>
|
|
56
56
|
<DialogRoot :open="open" @update:open="handleOpenChange">
|
|
57
57
|
<DialogPortal>
|
|
58
|
-
<div class="pointer-events-none fixed inset-0 z-
|
|
58
|
+
<div class="pointer-events-none fixed inset-0 z-40 grid place-items-center px-8">
|
|
59
59
|
<Transition
|
|
60
60
|
enter-active-class="transition-opacity duration-200 ease-out"
|
|
61
61
|
enter-from-class="opacity-0"
|
package/src/components.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"NexxtDatePicker": "components/DatePicker/DatePicker.vue",
|
|
14
14
|
"NexxtDoughnutChart": "components/DoughnutChart/DoughnutChart.vue",
|
|
15
15
|
"NexxtDropdownButton": "components/DropdownButton/DropdownButton.vue",
|
|
16
|
+
"NexxtFloatingPanel": "components/FloatingPanel/FloatingPanel.vue",
|
|
16
17
|
"NexxtHeader": "components/Header/Header.vue",
|
|
17
18
|
"NexxtIcon": "components/Icon/Icon.vue",
|
|
18
19
|
"NexxtInfoBlock": "components/InfoBlock/InfoBlock.vue",
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { default as NexxtCustomerJourneyTile } from './components/CustomerJourne
|
|
|
13
13
|
export { default as NexxtDatePicker } from './components/DatePicker/DatePicker.vue'
|
|
14
14
|
export { default as NexxtDoughnutChart } from './components/DoughnutChart/DoughnutChart.vue'
|
|
15
15
|
export { default as NexxtDropdownButton } from './components/DropdownButton/DropdownButton.vue'
|
|
16
|
+
export { default as NexxtFloatingPanel } from './components/FloatingPanel/FloatingPanel.vue'
|
|
16
17
|
export { default as NexxtHeader } from './components/Header/Header.vue'
|
|
17
18
|
export { default as NexxtIcon } from './components/Icon/Icon.vue'
|
|
18
19
|
export { default as NexxtInfoBlock } from './components/InfoBlock/InfoBlock.vue'
|