fix: prune unused shadcn menu components

This commit is contained in:
Benjamin Lu
2026-03-23 17:56:15 -07:00
parent 47f9b0a20e
commit f00c5b96b6
21 changed files with 114 additions and 508 deletions

View File

@@ -0,0 +1,57 @@
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, it } from 'vitest'
import { defineComponent, nextTick, ref } from 'vue'
import ContextMenu from './ContextMenu.vue'
import ContextMenuContent from './ContextMenuContent.vue'
import ContextMenuItem from './ContextMenuItem.vue'
import ContextMenuSeparator from './ContextMenuSeparator.vue'
import ContextMenuTrigger from './ContextMenuTrigger.vue'
const TestContextMenu = defineComponent({
components: {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator
},
setup() {
const open = ref(false)
return { open }
},
template: `
<ContextMenu v-model:open="open" :modal="false">
<ContextMenuTrigger as-child>
<button type="button">Trigger</button>
</ContextMenuTrigger>
<ContextMenuContent close-on-scroll>
<ContextMenuItem text-value="First item">First item</ContextMenuItem>
<ContextMenuSeparator />
</ContextMenuContent>
</ContextMenu>
`
})
afterEach(() => {
document.body.innerHTML = ''
})
describe('ContextMenu', () => {
it('closes the content on scroll when close-on-scroll is enabled', async () => {
const wrapper = mount(TestContextMenu, {
attachTo: document.body
})
await wrapper.find('button').trigger('contextmenu')
await nextTick()
expect(wrapper.vm.open).toBe(true)
window.dispatchEvent(new Event('scroll'))
await nextTick()
expect(wrapper.vm.open).toBe(false)
})
})

View File

@@ -1,44 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type {
ContextMenuCheckboxItemEmits,
ContextMenuCheckboxItemProps
} from 'reka-ui'
import {
ContextMenuCheckboxItem,
ContextMenuItemIndicator,
useForwardPropsEmits
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
ContextMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }
>()
const emits = defineEmits<ContextMenuCheckboxItemEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ContextMenuCheckboxItem
v-bind="forwarded"
:class="
cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50',
props.class
)
"
>
<span class="absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<i class="icon-[lucide--check] size-4" aria-hidden="true" />
</ContextMenuItemIndicator>
</span>
<slot />
</ContextMenuCheckboxItem>
</template>

View File

@@ -1,12 +0,0 @@
<script setup lang="ts">
import type { ContextMenuGroupProps } from 'reka-ui'
import { ContextMenuGroup } from 'reka-ui'
const props = defineProps<ContextMenuGroupProps>()
</script>
<template>
<ContextMenuGroup v-bind="props">
<slot />
</ContextMenuGroup>
</template>

View File

@@ -1,29 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type { ContextMenuLabelProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { ContextMenuLabel } from 'reka-ui'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
ContextMenuLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }
>()
const delegatedProps = reactiveOmit(props, 'class')
</script>
<template>
<ContextMenuLabel
v-bind="delegatedProps"
:class="
cn(
'text-foreground px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
props.class
)
"
>
<slot />
</ContextMenuLabel>
</template>

View File

@@ -1,12 +0,0 @@
<script setup lang="ts">
import type { ContextMenuPortalProps } from 'reka-ui'
import { ContextMenuPortal } from 'reka-ui'
const props = defineProps<ContextMenuPortalProps>()
</script>
<template>
<ContextMenuPortal v-bind="props">
<slot />
</ContextMenuPortal>
</template>

View File

@@ -1,19 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type {
ContextMenuRadioGroupEmits,
ContextMenuRadioGroupProps
} from 'reka-ui'
import { ContextMenuRadioGroup, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<ContextMenuRadioGroupProps>()
const emits = defineEmits<ContextMenuRadioGroupEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<ContextMenuRadioGroup v-bind="forwarded">
<slot />
</ContextMenuRadioGroup>
</template>

View File

@@ -1,44 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type {
ContextMenuRadioItemEmits,
ContextMenuRadioItemProps
} from 'reka-ui'
import {
ContextMenuItemIndicator,
ContextMenuRadioItem,
useForwardPropsEmits
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
ContextMenuRadioItemProps & { class?: HTMLAttributes['class'] }
>()
const emits = defineEmits<ContextMenuRadioItemEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ContextMenuRadioItem
v-bind="forwarded"
:class="
cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50',
props.class
)
"
>
<span class="absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<span class="size-2 rounded-full bg-current" aria-hidden="true" />
</ContextMenuItemIndicator>
</span>
<slot />
</ContextMenuRadioItem>
</template>

View File

@@ -1,18 +0,0 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<span
:class="
cn('ml-auto text-xs tracking-widest text-muted-foreground', props.class)
"
>
<slot />
</span>
</template>

View File

@@ -1,16 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type { ContextMenuSubEmits, ContextMenuSubProps } from 'reka-ui'
import { ContextMenuSub, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<ContextMenuSubProps>()
const emits = defineEmits<ContextMenuSubEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<ContextMenuSub v-bind="forwarded">
<slot />
</ContextMenuSub>
</template>

View File

@@ -1,35 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type {
ContextMenuSubContentEmits,
ContextMenuSubContentProps
} from 'reka-ui'
import { ContextMenuSubContent, useForwardPropsEmits } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
ContextMenuSubContentProps & { class?: HTMLAttributes['class'] }
>()
const emits = defineEmits<ContextMenuSubContentEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ContextMenuSubContent
v-bind="forwarded"
:class="
cn(
'bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
props.class
)
"
>
<slot />
</ContextMenuSubContent>
</template>

View File

@@ -1,36 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type { ContextMenuSubTriggerProps } from 'reka-ui'
import { ContextMenuSubTrigger, useForwardProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
ContextMenuSubTriggerProps & {
class?: HTMLAttributes['class']
inset?: boolean
}
>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<ContextMenuSubTrigger
v-bind="forwardedProps"
:class="
cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none',
inset && 'pl-8',
props.class
)
"
>
<slot />
<i class="ml-auto icon-[lucide--chevron-right] size-4" aria-hidden="true" />
</ContextMenuSubTrigger>
</template>

View File

@@ -0,0 +1,57 @@
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, it } from 'vitest'
import { defineComponent, nextTick, ref } from 'vue'
import DropdownMenu from './DropdownMenu.vue'
import DropdownMenuContent from './DropdownMenuContent.vue'
import DropdownMenuItem from './DropdownMenuItem.vue'
import DropdownMenuSeparator from './DropdownMenuSeparator.vue'
import DropdownMenuTrigger from './DropdownMenuTrigger.vue'
const TestDropdownMenu = defineComponent({
components: {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator
},
setup() {
const open = ref(false)
return { open }
},
template: `
<DropdownMenu v-model:open="open">
<DropdownMenuTrigger as-child>
<button type="button">Trigger</button>
</DropdownMenuTrigger>
<DropdownMenuContent close-on-scroll>
<DropdownMenuItem text-value="First item">First item</DropdownMenuItem>
<DropdownMenuSeparator />
</DropdownMenuContent>
</DropdownMenu>
`
})
afterEach(() => {
document.body.innerHTML = ''
})
describe('DropdownMenu', () => {
it('closes the content on scroll when close-on-scroll is enabled', async () => {
const wrapper = mount(TestDropdownMenu, {
attachTo: document.body
})
await wrapper.find('button').trigger('click')
await nextTick()
expect(wrapper.vm.open).toBe(true)
window.dispatchEvent(new Event('scroll'))
await nextTick()
expect(wrapper.vm.open).toBe(false)
})
})

View File

@@ -1,44 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type {
DropdownMenuCheckboxItemEmits,
DropdownMenuCheckboxItemProps
} from 'reka-ui'
import {
DropdownMenuCheckboxItem,
DropdownMenuItemIndicator,
useForwardPropsEmits
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }
>()
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<DropdownMenuCheckboxItem
v-bind="forwarded"
:class="
cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50',
props.class
)
"
>
<span class="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<i class="icon-[lucide--check] size-4" aria-hidden="true" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuCheckboxItem>
</template>

View File

@@ -1,12 +0,0 @@
<script setup lang="ts">
import type { DropdownMenuGroupProps } from 'reka-ui'
import { DropdownMenuGroup } from 'reka-ui'
const props = defineProps<DropdownMenuGroupProps>()
</script>
<template>
<DropdownMenuGroup v-bind="props">
<slot />
</DropdownMenuGroup>
</template>

View File

@@ -1,27 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type { DropdownMenuLabelProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { DropdownMenuLabel, useForwardProps } from 'reka-ui'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
DropdownMenuLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }
>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<DropdownMenuLabel
v-bind="forwardedProps"
:class="
cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)
"
>
<slot />
</DropdownMenuLabel>
</template>

View File

@@ -1,19 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type {
DropdownMenuRadioGroupEmits,
DropdownMenuRadioGroupProps
} from 'reka-ui'
import { DropdownMenuRadioGroup, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<DropdownMenuRadioGroupProps>()
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DropdownMenuRadioGroup v-bind="forwarded">
<slot />
</DropdownMenuRadioGroup>
</template>

View File

@@ -1,45 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type {
DropdownMenuRadioItemEmits,
DropdownMenuRadioItemProps
} from 'reka-ui'
import {
DropdownMenuItemIndicator,
DropdownMenuRadioItem,
useForwardPropsEmits
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }
>()
const emits = defineEmits<DropdownMenuRadioItemEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<DropdownMenuRadioItem
v-bind="forwarded"
:class="
cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50',
props.class
)
"
>
<span class="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<span class="size-2 rounded-full bg-current" aria-hidden="true" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuRadioItem>
</template>

View File

@@ -1,14 +0,0 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<span :class="cn('ml-auto text-xs tracking-widest opacity-60', props.class)">
<slot />
</span>
</template>

View File

@@ -1,16 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type { DropdownMenuSubEmits, DropdownMenuSubProps } from 'reka-ui'
import { DropdownMenuSub, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<DropdownMenuSubProps>()
const emits = defineEmits<DropdownMenuSubEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DropdownMenuSub v-bind="forwarded">
<slot />
</DropdownMenuSub>
</template>

View File

@@ -1,34 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import type {
DropdownMenuSubContentEmits,
DropdownMenuSubContentProps
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { DropdownMenuSubContent, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }
>()
const emits = defineEmits<DropdownMenuSubContentEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<DropdownMenuSubContent
v-bind="forwarded"
:class="
cn(
'bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
props.class
)
"
>
<slot />
</DropdownMenuSubContent>
</template>

View File

@@ -1,32 +0,0 @@
<script setup lang="ts">
/* eslint-disable vue/no-unused-properties */
import { reactiveOmit } from '@vueuse/core'
import type { DropdownMenuSubTriggerProps } from 'reka-ui'
import { DropdownMenuSubTrigger, useForwardProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<
DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class'] }
>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<DropdownMenuSubTrigger
v-bind="forwardedProps"
:class="
cn(
'focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none',
props.class
)
"
>
<slot />
<i class="ml-auto icon-[lucide--chevron-right] size-4" aria-hidden="true" />
</DropdownMenuSubTrigger>
</template>