@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/nuxt.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { defineNuxtModule as c, addComponent as s, addTemplate as a } from "@nuxt/kit";
2
- const x = "components/AnimatedNumber/AnimatedNumber.vue", r = "components/Avatar/Avatar.vue", i = "components/Button/Button.vue", m = "components/Calendar/Calendar.vue", u = "components/Card/Card.vue", p = "components/Checkbox/Checkbox.vue", l = "components/Chip/Chip.vue", d = "components/Combobox/Combobox.vue", v = "components/ComboboxPanel/ComboboxPanel.vue", C = "components/ContactInfoCard/ContactInfoCard.vue", N = "components/CustomerJourneyTile/CustomerJourneyTile.vue", b = "components/DatePicker/DatePicker.vue", T = "components/DoughnutChart/DoughnutChart.vue", P = "components/DropdownButton/DropdownButton.vue", S = "components/Header/Header.vue", h = "components/Icon/Icon.vue", k = "components/InfoBlock/InfoBlock.vue", I = "components/InputField/InputField.vue", f = "components/Modal/Modal.vue", y = "components/Pagination/Pagination.vue", g = "components/PresenceIndicator/PresenceIndicator.vue", M = "components/ProgressBar/ProgressBar.vue", B = "components/PropertyCard/PropertyCard.vue", D = "components/PropertyListing/PropertyListing.vue", A = "components/Skeleton/Skeleton.vue", H = "components/SkeletonPropertyCard/SkeletonPropertyCard.vue", w = "components/SocialIcons/SocialIcons.vue", E = "components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue", F = "components/SocialMediaTemplate/SocialMediaTemplate.vue", J = "components/SocialMediaType/SocialMediaType.vue", L = "components/Source/Source.vue", j = "components/StepperHeader/StepperHeader.vue", $ = "components/TabbedContent/TabbedContent.vue", K = "components/Table/Table.vue", O = "components/Tabs/Tabs.vue", U = "components/TimelineEvent/TimelineEvent.vue", _ = "components/TimelinePhaseblock/TimelinePhaseblock.vue", q = "components/Tooltip/Tooltip.vue", z = {
2
+ const x = "components/AnimatedNumber/AnimatedNumber.vue", i = "components/Avatar/Avatar.vue", r = "components/Button/Button.vue", m = "components/Calendar/Calendar.vue", u = "components/Card/Card.vue", p = "components/Checkbox/Checkbox.vue", l = "components/Chip/Chip.vue", d = "components/Combobox/Combobox.vue", v = "components/ComboboxPanel/ComboboxPanel.vue", C = "components/ContactInfoCard/ContactInfoCard.vue", N = "components/CustomerJourneyTile/CustomerJourneyTile.vue", b = "components/DatePicker/DatePicker.vue", P = "components/DoughnutChart/DoughnutChart.vue", T = "components/DropdownButton/DropdownButton.vue", S = "components/FloatingPanel/FloatingPanel.vue", h = "components/Header/Header.vue", k = "components/Icon/Icon.vue", I = "components/InfoBlock/InfoBlock.vue", f = "components/InputField/InputField.vue", g = "components/Modal/Modal.vue", y = "components/Pagination/Pagination.vue", M = "components/PresenceIndicator/PresenceIndicator.vue", B = "components/ProgressBar/ProgressBar.vue", D = "components/PropertyCard/PropertyCard.vue", A = "components/PropertyListing/PropertyListing.vue", F = "components/Skeleton/Skeleton.vue", H = "components/SkeletonPropertyCard/SkeletonPropertyCard.vue", w = "components/SocialIcons/SocialIcons.vue", E = "components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue", J = "components/SocialMediaTemplate/SocialMediaTemplate.vue", L = "components/SocialMediaType/SocialMediaType.vue", j = "components/Source/Source.vue", $ = "components/StepperHeader/StepperHeader.vue", K = "components/TabbedContent/TabbedContent.vue", O = "components/Table/Table.vue", U = "components/Tabs/Tabs.vue", _ = "components/TimelineEvent/TimelineEvent.vue", q = "components/TimelinePhaseblock/TimelinePhaseblock.vue", z = "components/Tooltip/Tooltip.vue", G = {
3
3
  NexxtAnimatedNumber: x,
4
- NexxtAvatar: r,
5
- NexxtButton: i,
4
+ NexxtAvatar: i,
5
+ NexxtButton: r,
6
6
  NexxtCalendar: m,
7
7
  NexxtCard: u,
8
8
  NexxtCheckbox: p,
@@ -12,33 +12,34 @@ const x = "components/AnimatedNumber/AnimatedNumber.vue", r = "components/Avatar
12
12
  NexxtContactInfoCard: C,
13
13
  NexxtCustomerJourneyTile: N,
14
14
  NexxtDatePicker: b,
15
- NexxtDoughnutChart: T,
16
- NexxtDropdownButton: P,
17
- NexxtHeader: S,
18
- NexxtIcon: h,
19
- NexxtInfoBlock: k,
20
- NexxtInputField: I,
21
- NexxtModal: f,
15
+ NexxtDoughnutChart: P,
16
+ NexxtDropdownButton: T,
17
+ NexxtFloatingPanel: S,
18
+ NexxtHeader: h,
19
+ NexxtIcon: k,
20
+ NexxtInfoBlock: I,
21
+ NexxtInputField: f,
22
+ NexxtModal: g,
22
23
  NexxtPagination: y,
23
- NexxtPresenceIndicator: g,
24
- NexxtProgressBar: M,
25
- NexxtPropertyCard: B,
26
- NexxtPropertyListing: D,
27
- NexxtSkeleton: A,
24
+ NexxtPresenceIndicator: M,
25
+ NexxtProgressBar: B,
26
+ NexxtPropertyCard: D,
27
+ NexxtPropertyListing: A,
28
+ NexxtSkeleton: F,
28
29
  NexxtSkeletonPropertyCard: H,
29
30
  NexxtSocialIcons: w,
30
31
  NexxtSocialMediaCustomTemplate: E,
31
- NexxtSocialMediaTemplate: F,
32
- NexxtSocialMediaType: J,
33
- NexxtSource: L,
34
- NexxtStepperHeader: j,
35
- NexxtTabbedContent: $,
36
- NexxtTable: K,
37
- NexxtTabs: O,
38
- NexxtTimelineEvent: U,
39
- NexxtTimelinePhaseblock: _,
40
- NexxtTooltip: q
41
- }, Q = c({
32
+ NexxtSocialMediaTemplate: J,
33
+ NexxtSocialMediaType: L,
34
+ NexxtSource: j,
35
+ NexxtStepperHeader: $,
36
+ NexxtTabbedContent: K,
37
+ NexxtTable: O,
38
+ NexxtTabs: U,
39
+ NexxtTimelineEvent: _,
40
+ NexxtTimelinePhaseblock: q,
41
+ NexxtTooltip: z
42
+ }, R = c({
42
43
  meta: {
43
44
  name: "@nexxtmove/ui",
44
45
  configKey: "nexxtmoveUi",
@@ -50,7 +51,7 @@ const x = "components/AnimatedNumber/AnimatedNumber.vue", r = "components/Avatar
50
51
  addCSS: !0
51
52
  },
52
53
  setup(e, o) {
53
- for (const [t, n] of Object.entries(z))
54
+ for (const [t, n] of Object.entries(G))
54
55
  s({
55
56
  name: t,
56
57
  export: "default",
@@ -69,5 +70,5 @@ const x = "components/AnimatedNumber/AnimatedNumber.vue", r = "components/Avatar
69
70
  }
70
71
  });
71
72
  export {
72
- Q as default
73
+ R as default
73
74
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "0.1.58",
4
+ "version": "0.1.60",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -20,6 +20,7 @@
20
20
  "tailwind"
21
21
  ],
22
22
  "dependencies": {
23
+ "@floating-ui/vue": "^1.1.11",
23
24
  "@number-flow/vue": "^0.5.0",
24
25
  "date-fns": "^4.1.0",
25
26
  "date-fns-tz": "^3.2.0",
@@ -1,7 +1,9 @@
1
1
  <script lang="ts" setup>
2
- import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
2
+ import { ref, computed, nextTick } from 'vue'
3
+ import type { Placement } from '@floating-ui/vue'
3
4
  import NexxtButton from '../Button/Button.vue'
4
5
  import NexxtIcon from '../Icon/Icon.vue'
6
+ import NexxtFloatingPanel from '../FloatingPanel/FloatingPanel.vue'
5
7
  import ComboboxPanel from '../ComboboxPanel/ComboboxPanel.vue'
6
8
  import type { ComboboxOptions } from '../ComboboxPanel/combobox-types'
7
9
 
@@ -23,78 +25,36 @@ const {
23
25
 
24
26
  const selected = defineModel<(string | number)[]>({ default: [] })
25
27
 
26
- const isOpen = ref(false)
27
28
  const panelRef = ref<InstanceType<typeof ComboboxPanel> | null>(null)
28
- const wrapperRef = ref<HTMLElement | null>(null)
29
29
 
30
- watch(isOpen, async (open) => {
31
- if (open) {
32
- await nextTick()
33
- panelRef.value?.focus()
34
- }
35
- })
30
+ const floatingPlacement = computed(
31
+ (): Placement => (placement === 'right' ? 'bottom-end' : 'bottom-start'),
32
+ )
36
33
 
37
- const handleClickOutside = (event: MouseEvent) => {
38
- if (wrapperRef.value && !wrapperRef.value.contains(event.target as Node)) {
39
- isOpen.value = false
40
- }
34
+ const onPanelOpen = async () => {
35
+ await nextTick()
36
+ panelRef.value?.focus()
41
37
  }
42
-
43
- const handleKeydown = (event: KeyboardEvent) => {
44
- if (event.key === 'Escape' && isOpen.value) {
45
- isOpen.value = false
46
- }
47
- }
48
-
49
- onMounted(() => {
50
- document.addEventListener('mousedown', handleClickOutside)
51
- document.addEventListener('keydown', handleKeydown)
52
- })
53
- onUnmounted(() => {
54
- document.removeEventListener('mousedown', handleClickOutside)
55
- document.removeEventListener('keydown', handleKeydown)
56
- })
57
-
58
- const transitionClasses = computed(() => {
59
- const origin = placement === 'right' ? 'origin-top-right' : 'origin-top-left'
60
- return {
61
- enterActiveClass: `transition-all duration-200 ease-out ${origin}`,
62
- leaveActiveClass: `transition-all duration-150 ease-in ${origin}`,
63
- enterFromClass: 'opacity-0 scale-95 -translate-y-3',
64
- leaveToClass: 'opacity-0 scale-95 -translate-y-3',
65
- enterToClass: 'opacity-100 scale-100 translate-y-0',
66
- leaveFromClass: 'opacity-100 scale-100 translate-y-0',
67
- }
68
- })
69
38
  </script>
70
39
 
71
40
  <template>
72
- <div ref="wrapperRef" class="relative">
73
- <NexxtButton
74
- :variant
75
- :icon
76
- :aria-expanded="isOpen"
77
- aria-haspopup="listbox"
78
- @click="isOpen = !isOpen"
79
- >
80
- <span class="flex gap-2"><slot>Combobox</slot><NexxtIcon name="chevron-down" /></span>
81
- </NexxtButton>
82
- <Transition v-bind="transitionClasses">
83
- <div
84
- v-if="isOpen"
85
- class="absolute top-full z-10 mt-2.5"
86
- :class="placement === 'right' ? 'right-0' : 'left-0'"
87
- >
88
- <ComboboxPanel
89
- ref="panelRef"
90
- v-model="selected"
91
- :options="props.options"
92
- :allow-new="props.allowNew"
93
- :icon
94
- :placeholder
95
- :floating="false"
96
- />
97
- </div>
98
- </Transition>
99
- </div>
41
+ <NexxtFloatingPanel :placement="floatingPlacement" @open="onPanelOpen">
42
+ <template #trigger="{ isOpen, toggle }">
43
+ <NexxtButton :variant :icon :aria-expanded="isOpen" aria-haspopup="listbox" @click="toggle">
44
+ <span class="flex gap-2"><slot>Combobox</slot><NexxtIcon name="chevron-down" /></span>
45
+ </NexxtButton>
46
+ </template>
47
+
48
+ <template #content>
49
+ <ComboboxPanel
50
+ ref="panelRef"
51
+ v-model="selected"
52
+ :options="props.options"
53
+ :allow-new="props.allowNew"
54
+ :icon
55
+ :placeholder
56
+ :floating="false"
57
+ />
58
+ </template>
59
+ </NexxtFloatingPanel>
100
60
  </template>
@@ -1,7 +1,9 @@
1
1
  <script lang="ts" setup>
2
- import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
2
+ import { ref, computed, nextTick } from 'vue'
3
+ import type { Placement } from '@floating-ui/vue'
3
4
  import NexxtButton from '../Button/Button.vue'
4
5
  import NexxtCalendar from '../Calendar/Calendar.vue'
6
+ import NexxtFloatingPanel from '../FloatingPanel/FloatingPanel.vue'
5
7
  import type { NexxtCalendarProps } from '../Calendar/calendar.types'
6
8
 
7
9
  export type CalendarAnchor = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
@@ -36,110 +38,65 @@ const calendarProps = computed((): NexxtCalendarProps => {
36
38
 
37
39
  // ── State ─────────────────────────────────────────────────────────────────────
38
40
 
39
- const isOpen = ref(false)
40
- const wrapperRef = ref<HTMLElement | null>(null)
41
- const triggerRef = ref<HTMLElement | null>(null)
41
+ const panelRef = ref<InstanceType<typeof NexxtFloatingPanel> | null>(null)
42
42
  const popoverRef = ref<HTMLElement | null>(null)
43
43
 
44
- // ── Watchers ──────────────────────────────────────────────────────────────────
45
-
46
- watch(isOpen, async (val) => {
47
- if (!val) {
48
- await nextTick()
49
- const focusable = triggerRef.value?.querySelector<HTMLElement>(
50
- 'button, input, [tabindex]:not([tabindex="-1"])',
51
- )
52
- focusable?.focus()
53
- } else {
54
- await nextTick()
55
- popoverRef.value?.focus()
56
- }
57
- })
44
+ // ── Anchor → Floating UI placement ───────────────────────────────────────────
45
+
46
+ const anchorToPlacement: Record<CalendarAnchor, Placement> = {
47
+ 'bottom-right': 'bottom-end',
48
+ 'bottom-left': 'bottom-start',
49
+ 'top-right': 'top-end',
50
+ 'top-left': 'top-start',
51
+ }
52
+
53
+ const floatingPlacement = computed(() => anchorToPlacement[props.anchor])
58
54
 
59
55
  // ── Handlers ──────────────────────────────────────────────────────────────────
60
56
 
61
57
  const handleSelect = () => {
62
- if (props.autoClose) {
63
- isOpen.value = false
64
- }
58
+ if (props.autoClose) panelRef.value?.close()
65
59
  }
66
60
 
67
- const handleKeydown = (event: KeyboardEvent) => {
68
- if (!isOpen.value) return
69
- if (event.key === 'Escape') {
70
- isOpen.value = false
71
- }
61
+ const onPanelOpen = async () => {
62
+ await nextTick()
63
+ popoverRef.value?.focus()
72
64
  }
73
65
 
74
- const handleClickOutside = (event: MouseEvent) => {
75
- if (wrapperRef.value && !wrapperRef.value.contains(event.target as Node)) {
76
- isOpen.value = false
77
- }
66
+ const onPanelClose = async () => {
67
+ await nextTick()
68
+ const focusable = panelRef.value?.triggerRef?.querySelector<HTMLElement>(
69
+ 'button, input, [tabindex]:not([tabindex="-1"])',
70
+ )
71
+ focusable?.focus()
78
72
  }
79
-
80
- onMounted(() => document.addEventListener('mousedown', handleClickOutside))
81
- onUnmounted(() => document.removeEventListener('mousedown', handleClickOutside))
82
-
83
- // ── Popover placement ─────────────────────────────────────────────────────────
84
-
85
- const popoverClasses = computed(() => {
86
- const map: Record<CalendarAnchor, string> = {
87
- 'bottom-right': 'top-full right-0 mt-2 origin-top-right',
88
- 'bottom-left': 'top-full left-0 mt-2 origin-top-left',
89
- 'top-right': 'bottom-full right-0 mb-2 origin-bottom-right',
90
- 'top-left': 'bottom-full left-0 mb-2 origin-bottom-left',
91
- }
92
- return map[props.anchor]
93
- })
94
-
95
- const transitionClasses = computed(() => {
96
- const isBottom = props.anchor.startsWith('bottom')
97
-
98
- const origins: Record<CalendarAnchor, string> = {
99
- 'bottom-left': 'origin-top-left',
100
- 'bottom-right': 'origin-top-right',
101
- 'top-left': 'origin-bottom-left',
102
- 'top-right': 'origin-bottom-right',
103
- }
104
-
105
- const translateClass = isBottom ? '-translate-y-3' : 'translate-y-3'
106
-
107
- return {
108
- enterActiveClass: `transition-all duration-200 ease-out ${origins[props.anchor]}`,
109
- leaveActiveClass: `transition-all duration-150 ease-in ${origins[props.anchor]}`,
110
- enterFromClass: `opacity-0 scale-95 ${translateClass}`,
111
- leaveToClass: `opacity-0 scale-95 ${translateClass}`,
112
- enterToClass: 'opacity-100 scale-100 translate-y-0',
113
- leaveFromClass: 'opacity-100 scale-100 translate-y-0',
114
- }
115
- })
116
73
  </script>
117
74
 
118
75
  <template>
119
- <div ref="wrapperRef" class="relative inline-block" @keydown="handleKeydown">
120
- <span ref="triggerRef">
121
- <slot name="trigger" :is-open="isOpen" :toggle="() => (isOpen = !isOpen)">
76
+ <NexxtFloatingPanel
77
+ ref="panelRef"
78
+ :placement="floatingPlacement"
79
+ @open="onPanelOpen"
80
+ @close="onPanelClose"
81
+ >
82
+ <template #trigger="{ isOpen, toggle }">
83
+ <slot name="trigger" :is-open="isOpen" :toggle="toggle">
122
84
  <NexxtButton
123
85
  icon="calendar"
124
86
  variant="secondary"
125
87
  :aria-expanded="isOpen"
126
88
  aria-haspopup="grid"
127
- @click="isOpen = !isOpen"
89
+ @click="toggle"
128
90
  >
129
91
  {{ props.placeholder }}
130
92
  </NexxtButton>
131
93
  </slot>
132
- </span>
133
-
134
- <Transition v-bind="transitionClasses">
135
- <div
136
- v-if="isOpen"
137
- ref="popoverRef"
138
- tabindex="-1"
139
- :class="['absolute z-50 outline-none', popoverClasses]"
140
- >
94
+ </template>
95
+
96
+ <template #content>
97
+ <div ref="popoverRef" tabindex="-1" class="outline-none">
141
98
  <NexxtCalendar v-model="model" v-bind="calendarProps" @select="handleSelect" />
142
99
  </div>
143
- </Transition>
144
- </div>
100
+ </template>
101
+ </NexxtFloatingPanel>
145
102
  </template>
@@ -1,7 +1,9 @@
1
1
  <script lang="ts" setup>
2
- import { ref, onMounted, onUnmounted, computed } from 'vue'
2
+ import { ref, computed } from 'vue'
3
+ import type { Placement } from '@floating-ui/vue'
3
4
  import NexxtIcon from '../Icon/Icon.vue'
4
5
  import NexxtCheckbox from '../Checkbox/Checkbox.vue'
6
+ import NexxtFloatingPanel from '../FloatingPanel/FloatingPanel.vue'
5
7
 
6
8
  interface NexxtDropdownButton {
7
9
  label: string
@@ -44,8 +46,12 @@ const emit = defineEmits<{
44
46
  checkboxChange: [label: string, checked: boolean]
45
47
  }>()
46
48
 
47
- const isOpen = ref(false)
48
- const wrapperRef = ref<HTMLElement | null>(null)
49
+ const panelRef = ref<InstanceType<typeof NexxtFloatingPanel> | null>(null)
50
+
51
+ const floatingPlacement = computed(
52
+ (): Placement => (placement === 'right' ? 'bottom-end' : 'bottom-start'),
53
+ )
54
+
49
55
  const flatItems = computed<NexxtDropdownButton[]>(() =>
50
56
  options.length > 0 && 'items' in options[0]
51
57
  ? (options as NexxtDropdownButtonGroup[]).flatMap((g) => g.items)
@@ -75,35 +81,17 @@ const handleAction = (action: string | (() => void)) => {
75
81
  }
76
82
  }
77
83
 
78
- const toggleDropdown = () => {
79
- isOpen.value = !isOpen.value
80
- }
81
-
82
84
  const handleOptionClick = (option: NexxtDropdownButton) => {
83
85
  if (option.checkbox) {
84
86
  const newValue = !checkedItems.value[option.label]
85
87
  toggledItems.value[option.label] = newValue
86
88
  emit('checkboxChange', option.label, newValue)
87
89
  } else {
88
- isOpen.value = false
90
+ panelRef.value?.close()
89
91
  }
90
92
  handleAction(option.action)
91
93
  }
92
94
 
93
- const handleClickOutside = (event: MouseEvent) => {
94
- if (wrapperRef.value && !wrapperRef.value.contains(event.target as Node)) {
95
- isOpen.value = false
96
- }
97
- }
98
-
99
- onMounted(() => {
100
- document.addEventListener('mousedown', handleClickOutside)
101
- })
102
-
103
- onUnmounted(() => {
104
- document.removeEventListener('mousedown', handleClickOutside)
105
- })
106
-
107
95
  const isGrouped = (
108
96
  opts: NexxtDropdownButton[] | NexxtDropdownButtonGroup[],
109
97
  ): opts is NexxtDropdownButtonGroup[] => opts.length > 0 && 'items' in opts[0]
@@ -136,66 +124,49 @@ const buttonClasses = computed(() => {
136
124
 
137
125
  return ''
138
126
  })
139
-
140
- const transitionClasses = computed(() => {
141
- const origin = placement === 'right' ? 'origin-top-right' : 'origin-top-left'
142
-
143
- return {
144
- enterActiveClass: `transition-all duration-200 ease-out ${origin}`,
145
- leaveActiveClass: `transition-all duration-150 ease-in ${origin}`,
146
- enterFromClass: 'opacity-0 scale-95 -translate-y-3',
147
- leaveToClass: 'opacity-0 scale-95 -translate-y-3',
148
- enterToClass: 'opacity-100 scale-100 translate-y-0',
149
- leaveFromClass: 'opacity-100 scale-100 translate-y-0',
150
- }
151
- })
152
127
  </script>
153
128
 
154
129
  <template>
155
- <div
156
- ref="wrapperRef"
157
- class="relative inline-flex flex-col"
158
- :class="placement === 'right' ? 'items-end' : 'items-start'"
159
- >
160
- <div class="inline-flex items-center overflow-hidden rounded-lg" :class="componentClasses">
161
- <template v-if="!iconOnly">
130
+ <NexxtFloatingPanel ref="panelRef" :placement="floatingPlacement">
131
+ <template #trigger="{ isOpen, toggle }">
132
+ <div class="inline-flex items-center overflow-hidden rounded-lg" :class="componentClasses">
133
+ <template v-if="!iconOnly">
134
+ <button
135
+ class="flex cursor-pointer items-center gap-2 px-4 py-2 transition-colors duration-200"
136
+ :class="buttonClasses"
137
+ @click="action && handleAction(action)"
138
+ >
139
+ <NexxtIcon v-if="icon" :name="icon" aria-hidden="true" />
140
+ {{ label }}
141
+ </button>
142
+ <span
143
+ class="h-7 w-px rounded-sm"
144
+ :class="variant === 'primary' ? 'bg-white' : 'bg-gray-200'"
145
+ aria-hidden="true"
146
+ ></span>
147
+ </template>
162
148
  <button
163
- class="flex cursor-pointer items-center gap-2 px-4 py-2 transition-colors duration-200"
164
- :class="buttonClasses"
165
- @click="action && handleAction(action)"
149
+ class="w-10 shrink-0 cursor-pointer transition-colors duration-200"
150
+ :class="[buttonClasses, iconOnly ? 'p-2' : 'px-2 py-2']"
151
+ aria-haspopup="menu"
152
+ :aria-expanded="isOpen"
153
+ :aria-label="`Open ${label} menu`"
154
+ @click="toggle"
166
155
  >
167
- <NexxtIcon v-if="icon" :name="icon" aria-hidden="true" />
168
- {{ label }}
156
+ <NexxtIcon
157
+ :name="triggerIcon"
158
+ class="transition-transform duration-200"
159
+ :class="{ 'rotate-180': isOpen && triggerIcon === 'chevron-down' }"
160
+ aria-hidden="true"
161
+ />
169
162
  </button>
170
- <span
171
- class="h-7 w-px rounded-sm"
172
- :class="variant === 'primary' ? 'bg-white' : 'bg-gray-200'"
173
- aria-hidden="true"
174
- ></span>
175
- </template>
176
- <button
177
- class="w-10 shrink-0 cursor-pointer transition-colors duration-200"
178
- :class="[buttonClasses, iconOnly ? 'p-2' : 'px-2 py-2']"
179
- @click="iconOnly ? toggleDropdown() : toggleDropdown()"
180
- aria-haspopup="menu"
181
- :aria-expanded="isOpen"
182
- :aria-label="`Open ${label} menu`"
183
- >
184
- <NexxtIcon
185
- :name="triggerIcon"
186
- class="transition-transform duration-200"
187
- :class="{ 'rotate-180': isOpen && triggerIcon === 'chevron-down' }"
188
- aria-hidden="true"
189
- />
190
- </button>
191
- </div>
192
-
193
- <Transition v-bind="transitionClasses">
163
+ </div>
164
+ </template>
165
+
166
+ <template #content>
194
167
  <div
195
- v-if="isOpen"
196
168
  role="menu"
197
- class="absolute top-full z-10 mt-2.5 min-w-full rounded-lg border border-gray-200 bg-white whitespace-nowrap"
198
- :class="placement === 'right' ? 'right-0' : 'left-0'"
169
+ class="min-w-full rounded-lg border border-gray-200 bg-white whitespace-nowrap"
199
170
  >
200
171
  <template v-for="(group, groupIndex) in groupedOptions" :key="group.title || groupIndex">
201
172
  <div
@@ -225,6 +196,6 @@ const transitionClasses = computed(() => {
225
196
  </button>
226
197
  </template>
227
198
  </div>
228
- </Transition>
229
- </div>
199
+ </template>
200
+ </NexxtFloatingPanel>
230
201
  </template>