mirror of
https://github.com/keven1024/015.git
synced 2026-05-26 07:08:02 +00:00
feat(front): add Menubar component and its subcomponents for improved UI navigation and organization
This commit is contained in:
24
front/components/ui/menubar/Menubar.vue
Normal file
24
front/components/ui/menubar/Menubar.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarRootEmits, MenubarRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarRootEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarRoot
|
||||
data-slot="menubar"
|
||||
v-bind="forwarded"
|
||||
:class="cn('bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</MenubarRoot>
|
||||
</template>
|
||||
35
front/components/ui/menubar/MenubarCheckboxItem.vue
Normal file
35
front/components/ui/menubar/MenubarCheckboxItem.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarCheckboxItemEmits, MenubarCheckboxItemProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { MenubarCheckboxItem, MenubarItemIndicator, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarCheckboxItemEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarCheckboxItem
|
||||
data-slot="menubar-checkbox-item"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<MenubarItemIndicator>
|
||||
<Check class="size-4" />
|
||||
</MenubarItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</MenubarCheckboxItem>
|
||||
</template>
|
||||
34
front/components/ui/menubar/MenubarContent.vue
Normal file
34
front/components/ui/menubar/MenubarContent.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarContent, MenubarPortal, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<MenubarContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
align: 'start',
|
||||
alignOffset: -4,
|
||||
sideOffset: 8,
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarPortal>
|
||||
<MenubarContent
|
||||
data-slot="menubar-content"
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 min-w-[12rem] origin-(--reka-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarContent>
|
||||
</MenubarPortal>
|
||||
</template>
|
||||
12
front/components/ui/menubar/MenubarGroup.vue
Normal file
12
front/components/ui/menubar/MenubarGroup.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarGroupProps } from 'reka-ui'
|
||||
import { MenubarGroup } from 'reka-ui'
|
||||
|
||||
const props = defineProps<MenubarGroupProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarGroup data-slot="menubar-group" v-bind="props">
|
||||
<slot />
|
||||
</MenubarGroup>
|
||||
</template>
|
||||
37
front/components/ui/menubar/MenubarItem.vue
Normal file
37
front/components/ui/menubar/MenubarItem.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarItemEmits, MenubarItemProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarItem, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<
|
||||
MenubarItemProps & {
|
||||
class?: HTMLAttributes['class']
|
||||
inset?: boolean
|
||||
variant?: 'default' | 'destructive'
|
||||
}
|
||||
>()
|
||||
|
||||
const emits = defineEmits<MenubarItemEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'inset', 'variant')
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarItem
|
||||
data-slot="menubar-item"
|
||||
:data-inset="inset ? '' : undefined"
|
||||
:data-variant="variant"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarItem>
|
||||
</template>
|
||||
20
front/components/ui/menubar/MenubarLabel.vue
Normal file
20
front/components/ui/menubar/MenubarLabel.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarLabelProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarLabel } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }>()
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'inset')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarLabel
|
||||
:data-inset="inset ? '' : undefined"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</MenubarLabel>
|
||||
</template>
|
||||
12
front/components/ui/menubar/MenubarMenu.vue
Normal file
12
front/components/ui/menubar/MenubarMenu.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarMenuProps } from 'reka-ui'
|
||||
import { MenubarMenu } from 'reka-ui'
|
||||
|
||||
const props = defineProps<MenubarMenuProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarMenu data-slot="menubar-menu" v-bind="props">
|
||||
<slot />
|
||||
</MenubarMenu>
|
||||
</template>
|
||||
15
front/components/ui/menubar/MenubarRadioGroup.vue
Normal file
15
front/components/ui/menubar/MenubarRadioGroup.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarRadioGroupEmits, MenubarRadioGroupProps } from 'reka-ui'
|
||||
import { MenubarRadioGroup, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<MenubarRadioGroupProps>()
|
||||
const emits = defineEmits<MenubarRadioGroupEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarRadioGroup data-slot="menubar-radio-group" v-bind="forwarded">
|
||||
<slot />
|
||||
</MenubarRadioGroup>
|
||||
</template>
|
||||
35
front/components/ui/menubar/MenubarRadioItem.vue
Normal file
35
front/components/ui/menubar/MenubarRadioItem.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarRadioItemEmits, MenubarRadioItemProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Circle } from 'lucide-vue-next'
|
||||
import { MenubarItemIndicator, MenubarRadioItem, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarRadioItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarRadioItemEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarRadioItem
|
||||
data-slot="menubar-radio-item"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<MenubarItemIndicator>
|
||||
<Circle class="size-2 fill-current" />
|
||||
</MenubarItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</MenubarRadioItem>
|
||||
</template>
|
||||
17
front/components/ui/menubar/MenubarSeparator.vue
Normal file
17
front/components/ui/menubar/MenubarSeparator.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarSeparatorProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarSeparator, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarSeparator data-slot="menubar-separator" :class="cn('bg-border -mx-1 my-1 h-px', props.class)" v-bind="forwardedProps" />
|
||||
</template>
|
||||
14
front/components/ui/menubar/MenubarShortcut.vue
Normal file
14
front/components/ui/menubar/MenubarShortcut.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span data-slot="menubar-shortcut" :class="cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
20
front/components/ui/menubar/MenubarSub.vue
Normal file
20
front/components/ui/menubar/MenubarSub.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarSubEmits } from 'reka-ui'
|
||||
import { MenubarSub, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
interface MenubarSubRootProps {
|
||||
defaultOpen?: boolean
|
||||
open?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<MenubarSubRootProps>()
|
||||
const emits = defineEmits<MenubarSubEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarSub data-slot="menubar-sub" v-bind="forwarded">
|
||||
<slot />
|
||||
</MenubarSub>
|
||||
</template>
|
||||
31
front/components/ui/menubar/MenubarSubContent.vue
Normal file
31
front/components/ui/menubar/MenubarSubContent.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarSubContentEmits, MenubarSubContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarPortal, MenubarSubContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarSubContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<MenubarSubContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarPortal>
|
||||
<MenubarSubContent
|
||||
data-slot="menubar-sub-content"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 min-w-[8rem] origin-(--reka-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarSubContent>
|
||||
</MenubarPortal>
|
||||
</template>
|
||||
30
front/components/ui/menubar/MenubarSubTrigger.vue
Normal file
30
front/components/ui/menubar/MenubarSubTrigger.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarSubTriggerProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { MenubarSubTrigger, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarSubTriggerProps & { class?: HTMLAttributes['class']; inset?: boolean }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'inset')
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarSubTrigger
|
||||
data-slot="menubar-sub-trigger"
|
||||
:data-inset="inset ? '' : undefined"
|
||||
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 data-[inset]:pl-8',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto size-4" />
|
||||
</MenubarSubTrigger>
|
||||
</template>
|
||||
28
front/components/ui/menubar/MenubarTrigger.vue
Normal file
28
front/components/ui/menubar/MenubarTrigger.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenubarTriggerProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MenubarTrigger, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<MenubarTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenubarTrigger
|
||||
data-slot="menubar-trigger"
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</MenubarTrigger>
|
||||
</template>
|
||||
15
front/components/ui/menubar/index.ts
Normal file
15
front/components/ui/menubar/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export { default as Menubar } from './Menubar.vue'
|
||||
export { default as MenubarCheckboxItem } from './MenubarCheckboxItem.vue'
|
||||
export { default as MenubarContent } from './MenubarContent.vue'
|
||||
export { default as MenubarGroup } from './MenubarGroup.vue'
|
||||
export { default as MenubarItem } from './MenubarItem.vue'
|
||||
export { default as MenubarLabel } from './MenubarLabel.vue'
|
||||
export { default as MenubarMenu } from './MenubarMenu.vue'
|
||||
export { default as MenubarRadioGroup } from './MenubarRadioGroup.vue'
|
||||
export { default as MenubarRadioItem } from './MenubarRadioItem.vue'
|
||||
export { default as MenubarSeparator } from './MenubarSeparator.vue'
|
||||
export { default as MenubarShortcut } from './MenubarShortcut.vue'
|
||||
export { default as MenubarSub } from './MenubarSub.vue'
|
||||
export { default as MenubarSubContent } from './MenubarSubContent.vue'
|
||||
export { default as MenubarSubTrigger } from './MenubarSubTrigger.vue'
|
||||
export { default as MenubarTrigger } from './MenubarTrigger.vue'
|
||||
Reference in New Issue
Block a user