Skip to content

Commit 5846c1e

Browse files
committedFeb 14, 2025·
feat(ContextMenu/DropdownMenu/NavigationMenu): add external-icon prop
Resolves #2996
1 parent ba3ed86 commit 5846c1e

14 files changed

+607
-8
lines changed
 

‎src/runtime/components/ContextMenu.vue

+8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
5151
* @defaultValue appConfig.ui.icons.loading
5252
*/
5353
loadingIcon?: string
54+
/**
55+
* The icon displayed when the item is an external link.
56+
* Set to `false` to hide the external icon.
57+
* @defaultValue appConfig.ui.icons.external
58+
*/
59+
externalIcon?: boolean | string
5460
/** The content of the menu. */
5561
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'>
5662
/**
@@ -152,6 +158,7 @@ import UContextMenuContent from './ContextMenuContent.vue'
152158
const props = withDefaults(defineProps<ContextMenuProps<T>>(), {
153159
portal: true,
154160
modal: true,
161+
externalIcon: true,
155162
labelKey: 'label'
156163
})
157164
const emits = defineEmits<ContextMenuEmits>()
@@ -182,6 +189,7 @@ const ui = computed(() => contextMenu({
182189
:label-key="labelKey"
183190
:checked-icon="checkedIcon"
184191
:loading-icon="loadingIcon"
192+
:external-icon="externalIcon"
185193
>
186194
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
187195
<slot :name="name" v-bind="slotData" />

‎src/runtime/components/ContextMenuContent.vue

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, '
1313
labelKey: string
1414
checkedIcon?: string
1515
loadingIcon?: string
16+
externalIcon?: boolean | string
1617
class?: any
1718
ui: typeof _contextMenu
1819
uiOverride?: any
@@ -42,7 +43,7 @@ const emits = defineEmits<ContextMenuContentEmits>()
4243
const slots = defineSlots<ContextMenuSlots<T>>()
4344
4445
const appConfig = useAppConfig()
45-
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'class', 'ui', 'uiOverride'), emits)
46+
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
4647
const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
4748
4849
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>()
@@ -64,7 +65,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
6465
{{ get(item, props.labelKey as string) }}
6566
</slot>
6667

67-
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, color: item?.color, active })" />
68+
<UIcon v-if="item.target === '_blank' && externalIcon !== false" :name="typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, color: item?.color, active })" />
6869
</span>
6970

7071
<span :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
@@ -112,6 +113,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
112113
:label-key="labelKey"
113114
:checked-icon="checkedIcon"
114115
:loading-icon="loadingIcon"
116+
:external-icon="externalIcon"
115117
v-bind="item.content"
116118
>
117119
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">

‎src/runtime/components/DropdownMenu.vue

+8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
5151
* @defaultValue appConfig.ui.icons.loading
5252
*/
5353
loadingIcon?: string
54+
/**
55+
* The icon displayed when the item is an external link.
56+
* Set to `false` to hide the external icon.
57+
* @defaultValue appConfig.ui.icons.external
58+
*/
59+
externalIcon?: boolean | string
5460
/**
5561
* The content of the menu.
5662
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 }
@@ -149,6 +155,7 @@ import UDropdownMenuContent from './DropdownMenuContent.vue'
149155
const props = withDefaults(defineProps<DropdownMenuProps<T>>(), {
150156
portal: true,
151157
modal: true,
158+
externalIcon: true,
152159
labelKey: 'label'
153160
})
154161
const emits = defineEmits<DropdownMenuEmits>()
@@ -180,6 +187,7 @@ const ui = computed(() => dropdownMenu({
180187
:label-key="labelKey"
181188
:checked-icon="checkedIcon"
182189
:loading-icon="loadingIcon"
190+
:external-icon="externalIcon"
183191
>
184192
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
185193
<slot :name="name" v-bind="slotData" />

‎src/runtime/components/DropdownMenuContent.vue

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface DropdownMenuContentProps<T> extends Omit<RekaDropdownMenuContentProps,
1414
labelKey: string
1515
checkedIcon?: string
1616
loadingIcon?: string
17+
externalIcon?: boolean | string
1718
class?: any
1819
ui: typeof _dropdownMenu
1920
uiOverride?: any
@@ -48,7 +49,7 @@ const emits = defineEmits<DropdownMenuContentEmits>()
4849
const slots = defineSlots<DropdownMenuContentSlots<T>>()
4950

5051
const appConfig = useAppConfig()
51-
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'class', 'ui', 'uiOverride'), emits)
52+
const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
5253
const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuContentSlots<T>[string]>
5354

5455
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: DropdownMenuItem, active?: boolean, index: number }>()
@@ -70,7 +71,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
7071
{{ get(item, props.labelKey as string) }}
7172
</slot>
7273

73-
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, color: item?.color, active })" />
74+
<UIcon v-if="item.target === '_blank' && externalIcon !== false" :name="typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, color: item?.color, active })" />
7475
</span>
7576

7677
<span :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
@@ -121,6 +122,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
121122
:label-key="labelKey"
122123
:checked-icon="checkedIcon"
123124
:loading-icon="loadingIcon"
125+
:external-icon="externalIcon"
124126
v-bind="item.content"
125127
>
126128
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">

‎src/runtime/components/NavigationMenu.vue

+9-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
5353
* @defaultValue appConfig.ui.icons.chevronDown
5454
*/
5555
trailingIcon?: string
56+
/**
57+
* The icon displayed when the item is an external link.
58+
* Set to `false` to hide the external icon.
59+
* @defaultValue appConfig.ui.icons.external
60+
*/
61+
externalIcon?: boolean | string
5662
items?: T
5763
color?: NavigationMenuVariants['color']
5864
variant?: NavigationMenuVariants['variant']
@@ -161,6 +167,7 @@ import UCollapsible from './Collapsible.vue'
161167
const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
162168
orientation: 'horizontal',
163169
contentOrientation: 'horizontal',
170+
externalIcon: true,
164171
delayDuration: 0,
165172
unmountOnHide: true,
166173
labelKey: 'label'
@@ -222,7 +229,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
222229
{{ get(item, props.labelKey as string) }}
223230
</slot>
224231

225-
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.linkLabelExternalIcon({ class: props.ui?.linkLabelExternalIcon, active })" />
232+
<UIcon v-if="item.target === '_blank' && externalIcon !== false" :name="typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class="ui.linkLabelExternalIcon({ class: props.ui?.linkLabelExternalIcon, active })" />
226233
</span>
227234

228235
<span v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[item.slot ? `${item.slot}-trailing` : 'item-trailing'])" :class="ui.linkTrailing({ class: props.ui?.linkTrailing })">
@@ -281,7 +288,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
281288
<p :class="ui.childLinkLabel({ class: props.ui?.childLinkLabel, active: childActive })">
282289
{{ get(childItem, props.labelKey as string) }}
283290

284-
<UIcon v-if="childItem.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ class: props.ui?.childLinkLabelExternalIcon, active: childActive })" />
291+
<UIcon v-if="childItem.target === '_blank' && externalIcon !== false" :name="typeof externalIcon === 'string' ? externalIcon : appConfig.ui.icons.external" :class="ui.childLinkLabelExternalIcon({ class: props.ui?.childLinkLabelExternalIcon, active: childActive })" />
285292
</p>
286293
<p v-if="childItem.description" :class="ui.childLinkDescription({ class: props.ui?.childLinkDescription, active: childActive })">
287294
{{ childItem.description }}

‎test/components/ContextMenu.spec.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ describe('ContextMenu', () => {
3434
label: 'Dark',
3535
icon: 'i-lucide-moon'
3636
}]
37-
}],
38-
[{
37+
}], [{
3938
label: 'Show Sidebar',
4039
color: 'primary',
4140
kbds: ['meta', 'S']
@@ -69,6 +68,11 @@ describe('ContextMenu', () => {
6968
kbds: ['option', 'meta', 'J'],
7069
slot: 'custom'
7170
}]]
71+
}], [{
72+
label: 'GitHub',
73+
icon: 'i-simple-icons-github',
74+
to: 'https://github.com/nuxt/ui',
75+
target: '_blank'
7276
}]
7377
]
7478

@@ -80,6 +84,8 @@ describe('ContextMenu', () => {
8084
['with labelKey', { props: { ...props, labelKey: 'icon' } }],
8185
['with disabled', { props: { ...props, disabled: true } }],
8286
...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]),
87+
['with externalIcon', { props: { ...props, externalIcon: 'i-lucide-external-link' } }],
88+
['without externalIcon', { props: { ...props, externalIcon: false } }],
8389
['with class', { props: { ...props, class: 'min-w-96' } }],
8490
['with ui', { props: { ...props, ui: { itemLeadingIcon: 'size-4' } } }],
8591
// Slots

‎test/components/DropdownMenu.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ describe('DropdownMenu', () => {
9494
['with disabled', { props: { ...props, disabled: true } }],
9595
['with arrow', { props: { ...props, arrow: true } }],
9696
...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]),
97+
['with externalIcon', { props: { ...props, externalIcon: 'i-lucide-external-link' } }],
98+
['without externalIcon', { props: { ...props, externalIcon: false } }],
9799
['with class', { props: { ...props, class: 'min-w-96' } }],
98100
['with ui', { props: { ...props, ui: { itemLeadingIcon: 'size-4' } } }],
99101
// Slots

‎test/components/NavigationMenu.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ describe('NavigationMenu', () => {
9696
...variants.map((variant: string) => [`with neutral variant ${variant} highlight`, { props: { ...props, variant, color: 'neutral', highlight: true } }]),
9797
...variants.map((variant: string) => [`with neutral variant ${variant} highlight neutral`, { props: { ...props, variant, color: 'neutral', highlight: true, highlightColor: 'neutral' } }]),
9898
['with trailingIcon', { props: { ...props, trailingIcon: 'i-lucide-plus' } }],
99+
['with externalIcon', { props: { ...props, externalIcon: 'i-lucide-external-link' } }],
100+
['without externalIcon', { props: { ...props, externalIcon: false } }],
99101
['with unmountOnHide', { props: { ...props, unmountOnHide: false } }],
100102
['with as', { props: { ...props, as: 'section' } }],
101103
['with class', { props: { ...props, class: 'w-48' } }],

‎test/components/__snapshots__/ContextMenu-vue.spec.ts.snap

+113
Large diffs are not rendered by default.

‎test/components/__snapshots__/ContextMenu.spec.ts.snap

+117
Large diffs are not rendered by default.

‎test/components/__snapshots__/DropdownMenu-vue.spec.ts.snap

+76
Large diffs are not rendered by default.

‎test/components/__snapshots__/DropdownMenu.spec.ts.snap

+80
Large diffs are not rendered by default.

‎test/components/__snapshots__/NavigationMenu-vue.spec.ts.snap

+88
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,50 @@ exports[`NavigationMenu > renders with custom slot correctly 1`] = `
226226
</nav>"
227227
`;
228228

229+
exports[`NavigationMenu > renders with externalIcon correctly 1`] = `
230+
"<nav aria-label="Main" data-orientation="horizontal" dir="ltr" data-reka-navigation-menu="" data-collapsed="false" class="relative flex gap-1.5 [&>div]:min-w-0 items-center justify-between">
231+
<div style="position: relative;">
232+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
233+
<li data-menu-item="" class="min-w-0 py-2"></li>
234+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" data-reka-collection-item="" id="reka-navigation-menu-v-0-trigger-1" aria-controls="reka-navigation-menu-v-0-content-1"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Documentation<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="font-medium inline-flex items-center text-[10px]/3 px-1.5 py-1 gap-1 rounded-[calc(var(--ui-radius))] ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg) shrink-0"><!--v-if--><span class="truncate">10</span>
235+
<!--v-if--></span><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" width="1em" height="1em" viewBox="0 0 16 16"></svg></span>
236+
</button>
237+
<!---->
238+
<!--teleport start-->
239+
<!---->
240+
<!--teleport end-->
241+
</li>
242+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-primary) before:bg-(--ui-bg-elevated)" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" active="true" data-reka-collection-item="" id="reka-navigation-menu-v-0-trigger-2" aria-controls="reka-navigation-menu-v-0-content-2"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Components<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" width="1em" height="1em" viewBox="0 0 16 16"></svg></span></button>
243+
<!---->
244+
<!--teleport start-->
245+
<!---->
246+
<!--teleport end-->
247+
</li>
248+
</ul>
249+
</div>
250+
<!--v-if-->
251+
<div style="position: relative;">
252+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
253+
<li data-menu-item="" class="min-w-0 py-2"><a href="https://github.com/nuxt/ui" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-reka-collection-item=""><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">GitHub<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="inline-block size-3 align-top text-(--ui-text-dimmed)" width="1em" height="1em" viewBox="0 0 16 16"></svg></span>
254+
<!--v-if-->
255+
</a>
256+
<!--v-if-->
257+
</li>
258+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" disabled="" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) cursor-not-allowed opacity-75" data-reka-collection-item=""><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-text-dimmed)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Help<!--v-if--></span>
259+
<!--v-if-->
260+
</button>
261+
<!--v-if-->
262+
</li>
263+
</ul>
264+
</div>
265+
<!--v-if-->
266+
<div class="absolute top-full left-0 flex w-full justify-center">
267+
<!--v-if-->
268+
<!---->
269+
</div>
270+
</nav>"
271+
`;
272+
229273
exports[`NavigationMenu > renders with item slot correctly 1`] = `
230274
"<nav aria-label="Main" data-orientation="horizontal" dir="ltr" data-reka-navigation-menu="" data-collapsed="false" class="relative flex gap-1.5 [&>div]:min-w-0 items-center justify-between">
231275
<div style="position: relative;">
@@ -1274,3 +1318,47 @@ exports[`NavigationMenu > renders with unmountOnHide correctly 1`] = `
12741318
</div>
12751319
</nav>"
12761320
`;
1321+
1322+
exports[`NavigationMenu > renders without externalIcon correctly 1`] = `
1323+
"<nav aria-label="Main" data-orientation="horizontal" dir="ltr" data-reka-navigation-menu="" data-collapsed="false" class="relative flex gap-1.5 [&>div]:min-w-0 items-center justify-between">
1324+
<div style="position: relative;">
1325+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
1326+
<li data-menu-item="" class="min-w-0 py-2"></li>
1327+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" data-reka-collection-item="" id="reka-navigation-menu-v-0-trigger-1" aria-controls="reka-navigation-menu-v-0-content-1"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Documentation<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="font-medium inline-flex items-center text-[10px]/3 px-1.5 py-1 gap-1 rounded-[calc(var(--ui-radius))] ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg) shrink-0"><!--v-if--><span class="truncate">10</span>
1328+
<!--v-if--></span><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" width="1em" height="1em" viewBox="0 0 16 16"></svg></span>
1329+
</button>
1330+
<!---->
1331+
<!--teleport start-->
1332+
<!---->
1333+
<!--teleport end-->
1334+
</li>
1335+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-primary) before:bg-(--ui-bg-elevated)" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" active="true" data-reka-collection-item="" id="reka-navigation-menu-v-0-trigger-2" aria-controls="reka-navigation-menu-v-0-content-2"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Components<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" width="1em" height="1em" viewBox="0 0 16 16"></svg></span></button>
1336+
<!---->
1337+
<!--teleport start-->
1338+
<!---->
1339+
<!--teleport end-->
1340+
</li>
1341+
</ul>
1342+
</div>
1343+
<!--v-if-->
1344+
<div style="position: relative;">
1345+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
1346+
<li data-menu-item="" class="min-w-0 py-2"><a href="https://github.com/nuxt/ui" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-reka-collection-item=""><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">GitHub<!--v-if--></span>
1347+
<!--v-if-->
1348+
</a>
1349+
<!--v-if-->
1350+
</li>
1351+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" disabled="" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) cursor-not-allowed opacity-75" data-reka-collection-item=""><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5 text-(--ui-text-dimmed)" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Help<!--v-if--></span>
1352+
<!--v-if-->
1353+
</button>
1354+
<!--v-if-->
1355+
</li>
1356+
</ul>
1357+
</div>
1358+
<!--v-if-->
1359+
<div class="absolute top-full left-0 flex w-full justify-center">
1360+
<!--v-if-->
1361+
<!---->
1362+
</div>
1363+
</nav>"
1364+
`;

‎test/components/__snapshots__/NavigationMenu.spec.ts.snap

+88
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,50 @@ exports[`NavigationMenu > renders with custom slot correctly 1`] = `
226226
</nav>"
227227
`;
228228

229+
exports[`NavigationMenu > renders with externalIcon correctly 1`] = `
230+
"<nav aria-label="Main" data-orientation="horizontal" dir="ltr" data-reka-navigation-menu="" data-collapsed="false" class="relative flex gap-1.5 [&>div]:min-w-0 items-center justify-between">
231+
<div style="position: relative;">
232+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
233+
<li data-menu-item="" class="min-w-0 py-2"></li>
234+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" data-reka-collection-item="" id="reka-navigation-menu-v-0-0-0-trigger-1" aria-controls="reka-navigation-menu-v-0-0-0-content-1"><span class="iconify i-lucide:book-open shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" aria-hidden="true"></span><span class="truncate">Documentation<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="font-medium inline-flex items-center text-[10px]/3 px-1.5 py-1 gap-1 rounded-[calc(var(--ui-radius))] ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg) shrink-0"><!--v-if--><span class="truncate">10</span>
235+
<!--v-if--></span><span class="iconify i-lucide:chevron-down size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" aria-hidden="true"></span></span>
236+
</button>
237+
<!---->
238+
<!--teleport start-->
239+
<!---->
240+
<!--teleport end-->
241+
</li>
242+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-primary) before:bg-(--ui-bg-elevated)" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" active="true" data-reka-collection-item="" id="reka-navigation-menu-v-0-0-0-trigger-2" aria-controls="reka-navigation-menu-v-0-0-0-content-2"><span class="iconify i-lucide:box shrink-0 size-5 text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)" aria-hidden="true"></span><span class="truncate">Components<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><span class="iconify i-lucide:chevron-down size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" aria-hidden="true"></span></span></button>
243+
<!---->
244+
<!--teleport start-->
245+
<!---->
246+
<!--teleport end-->
247+
</li>
248+
</ul>
249+
</div>
250+
<!--v-if-->
251+
<div style="position: relative;">
252+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
253+
<li data-menu-item="" class="min-w-0 py-2"><a href="https://github.com/nuxt/ui" rel="noopener noreferrer" target="_blank" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-reka-collection-item=""><span class="iconify i-simple-icons:github shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" aria-hidden="true"></span><span class="truncate">GitHub<span class="iconify i-lucide:external-link inline-block size-3 align-top text-(--ui-text-dimmed)" aria-hidden="true"></span></span>
254+
<!--v-if-->
255+
</a>
256+
<!--v-if-->
257+
</li>
258+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" disabled="" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) cursor-not-allowed opacity-75" data-reka-collection-item=""><span class="iconify i-lucide:circle-help shrink-0 size-5 text-(--ui-text-dimmed)" aria-hidden="true"></span><span class="truncate">Help<!--v-if--></span>
259+
<!--v-if-->
260+
</button>
261+
<!--v-if-->
262+
</li>
263+
</ul>
264+
</div>
265+
<!--v-if-->
266+
<div class="absolute top-full left-0 flex w-full justify-center">
267+
<!--v-if-->
268+
<!---->
269+
</div>
270+
</nav>"
271+
`;
272+
229273
exports[`NavigationMenu > renders with item slot correctly 1`] = `
230274
"<nav aria-label="Main" data-orientation="horizontal" dir="ltr" data-reka-navigation-menu="" data-collapsed="false" class="relative flex gap-1.5 [&>div]:min-w-0 items-center justify-between">
231275
<div style="position: relative;">
@@ -1274,3 +1318,47 @@ exports[`NavigationMenu > renders with unmountOnHide correctly 1`] = `
12741318
</div>
12751319
</nav>"
12761320
`;
1321+
1322+
exports[`NavigationMenu > renders without externalIcon correctly 1`] = `
1323+
"<nav aria-label="Main" data-orientation="horizontal" dir="ltr" data-reka-navigation-menu="" data-collapsed="false" class="relative flex gap-1.5 [&>div]:min-w-0 items-center justify-between">
1324+
<div style="position: relative;">
1325+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
1326+
<li data-menu-item="" class="min-w-0 py-2"></li>
1327+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" data-reka-collection-item="" id="reka-navigation-menu-v-0-0-0-trigger-1" aria-controls="reka-navigation-menu-v-0-0-0-content-1"><span class="iconify i-lucide:book-open shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" aria-hidden="true"></span><span class="truncate">Documentation<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><span class="font-medium inline-flex items-center text-[10px]/3 px-1.5 py-1 gap-1 rounded-[calc(var(--ui-radius))] ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg) shrink-0"><!--v-if--><span class="truncate">10</span>
1328+
<!--v-if--></span><span class="iconify i-lucide:chevron-down size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" aria-hidden="true"></span></span>
1329+
</button>
1330+
<!---->
1331+
<!--teleport start-->
1332+
<!---->
1333+
<!--teleport end-->
1334+
</li>
1335+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-primary) before:bg-(--ui-bg-elevated)" data-state="closed" data-navigation-menu-trigger="" aria-expanded="false" active="true" data-reka-collection-item="" id="reka-navigation-menu-v-0-0-0-trigger-2" aria-controls="reka-navigation-menu-v-0-0-0-content-2"><span class="iconify i-lucide:box shrink-0 size-5 text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)" aria-hidden="true"></span><span class="truncate">Components<!--v-if--></span><span class="ms-auto inline-flex gap-1.5 items-center"><!--v-if--><span class="iconify i-lucide:chevron-down size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200" aria-hidden="true"></span></span></button>
1336+
<!---->
1337+
<!--teleport start-->
1338+
<!---->
1339+
<!--teleport end-->
1340+
</li>
1341+
</ul>
1342+
</div>
1343+
<!--v-if-->
1344+
<div style="position: relative;">
1345+
<ul class="isolate min-w-0 flex items-center" data-orientation="horizontal">
1346+
<li data-menu-item="" class="min-w-0 py-2"><a href="https://github.com/nuxt/ui" rel="noopener noreferrer" target="_blank" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 transition-colors before:transition-colors data-[state=open]:text-(--ui-text-highlighted) data-[state=open]:before:bg-(--ui-bg-elevated)/50" data-reka-collection-item=""><span class="iconify i-simple-icons:github shrink-0 size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition-colors group-data-[state=open]:text-(--ui-text)" aria-hidden="true"></span><span class="truncate">GitHub<!--v-if--></span>
1347+
<!--v-if-->
1348+
</a>
1349+
<!--v-if-->
1350+
</li>
1351+
<li data-menu-item="" class="min-w-0 py-2"><button type="button" disabled="" class="group relative w-full flex items-center gap-1.5 font-medium text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2 focus-visible:before:ring-(--ui-primary) px-2.5 py-1.5 before:inset-x-px before:inset-y-0 text-(--ui-text-muted) cursor-not-allowed opacity-75" data-reka-collection-item=""><span class="iconify i-lucide:circle-help shrink-0 size-5 text-(--ui-text-dimmed)" aria-hidden="true"></span><span class="truncate">Help<!--v-if--></span>
1352+
<!--v-if-->
1353+
</button>
1354+
<!--v-if-->
1355+
</li>
1356+
</ul>
1357+
</div>
1358+
<!--v-if-->
1359+
<div class="absolute top-full left-0 flex w-full justify-center">
1360+
<!--v-if-->
1361+
<!---->
1362+
</div>
1363+
</nav>"
1364+
`;

0 commit comments

Comments
 (0)
Please sign in to comment.