feat: 前端组件准备
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '.'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '.'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant']
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
data-slot="button"
|
||||
:data-variant="variant"
|
||||
:data-size="size"
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*=size-])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
outline: 'border-border bg-background dark:bg-transparent hover:bg-muted hover:text-foreground dark:hover:bg-input/30 aria-expanded:bg-muted aria-expanded:text-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
||||
ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
|
||||
destructive: 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
'default': 'h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
|
||||
'xs': 'h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*=size-])]:size-3',
|
||||
'sm': 'h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
||||
'lg': 'h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3',
|
||||
'icon': 'size-9',
|
||||
'icon-xs': 'size-6 [&_svg:not([class*=size-])]:size-3',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationRootEmits, PaginationRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { PaginationRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<PaginationRootProps & {
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
const emits = defineEmits<PaginationRootEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationRoot
|
||||
v-slot="slotProps"
|
||||
data-slot="pagination"
|
||||
v-bind="forwarded"
|
||||
:class="cn('mx-auto flex w-full justify-center', props.class)"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</PaginationRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationListProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { PaginationList } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<PaginationListProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationList
|
||||
v-slot="slotProps"
|
||||
data-slot="pagination-content"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('gap-1 flex items-center', props.class)"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</PaginationList>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationEllipsisProps } from 'reka-ui'
|
||||
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { MoreHorizontalIcon } from 'lucide-vue-next'
|
||||
import { PaginationEllipsis } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<PaginationEllipsisProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationEllipsis
|
||||
data-slot="pagination-ellipsis"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('size-9 [&_svg:not([class*=size-])]:size-4 flex items-center justify-center', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<MoreHorizontalIcon />
|
||||
<span class="sr-only">More pages</span>
|
||||
</slot>
|
||||
</PaginationEllipsis>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationFirstProps } from 'reka-ui'
|
||||
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronsLeftIcon } from 'lucide-vue-next'
|
||||
import { PaginationFirst, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationFirstProps & {
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}>(), {
|
||||
size: 'default',
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'size')
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationFirst
|
||||
data-slot="pagination-first"
|
||||
:class="cn(buttonVariants({ variant: 'ghost', size }), '', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot>
|
||||
<ChevronsLeftIcon data-icon="inline-start" />
|
||||
<span class="hidden sm:block">First</span>
|
||||
</slot>
|
||||
</PaginationFirst>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationListItemProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { PaginationListItem } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationListItemProps & {
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
isActive?: boolean
|
||||
}>(), {
|
||||
size: 'icon',
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'size', 'isActive')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationListItem
|
||||
data-slot="pagination-item"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? 'outline' : 'ghost',
|
||||
size,
|
||||
}),
|
||||
props.class)"
|
||||
>
|
||||
<slot />
|
||||
</PaginationListItem>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationLastProps } from 'reka-ui'
|
||||
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronsRightIcon } from 'lucide-vue-next'
|
||||
import { PaginationLast, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationLastProps & {
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}>(), {
|
||||
size: 'default',
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'size')
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationLast
|
||||
data-slot="pagination-last"
|
||||
:class="cn(buttonVariants({ variant: 'ghost', size }), '', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot>
|
||||
<span class="hidden sm:block">Last</span>
|
||||
<ChevronsRightIcon data-icon="inline-end" />
|
||||
</slot>
|
||||
</PaginationLast>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
href?: string
|
||||
size?: ButtonVariants['size']
|
||||
isActive?: boolean
|
||||
class?: HTMLAttributes['class']
|
||||
}>(), {
|
||||
size: 'icon',
|
||||
isActive: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
:href="href"
|
||||
data-slot="pagination-link"
|
||||
:data-active="isActive ? '' : undefined"
|
||||
:aria-current="isActive ? 'page' : undefined"
|
||||
:class="cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? 'outline' : 'ghost',
|
||||
size,
|
||||
}),
|
||||
'',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationNextProps } from 'reka-ui'
|
||||
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronRightIcon } from 'lucide-vue-next'
|
||||
import { PaginationNext, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationNextProps & {
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}>(), {
|
||||
size: 'default',
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'size')
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationNext
|
||||
data-slot="pagination-next"
|
||||
:class="cn(buttonVariants({ variant: 'ghost', size }), 'pr-2!', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot>
|
||||
<span class="hidden sm:block">Next</span>
|
||||
<ChevronRightIcon data-icon="inline-end" class="cn-rtl-flip" />
|
||||
</slot>
|
||||
</PaginationNext>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationPrevProps } from 'reka-ui'
|
||||
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronLeftIcon } from 'lucide-vue-next'
|
||||
import { PaginationPrev, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationPrevProps & {
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}>(), {
|
||||
size: 'default',
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'size')
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationPrev
|
||||
data-slot="pagination-previous"
|
||||
:class="cn(buttonVariants({ variant: 'ghost', size }), 'pl-2!', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot>
|
||||
<ChevronLeftIcon data-icon="inline-start" class="cn-rtl-flip" />
|
||||
<span class="hidden sm:block">Previous</span>
|
||||
</slot>
|
||||
</PaginationPrev>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
export { default as Pagination } from './Pagination.vue'
|
||||
export { default as PaginationContent } from './PaginationContent.vue'
|
||||
export { default as PaginationEllipsis } from './PaginationEllipsis.vue'
|
||||
export { default as PaginationFirst } from './PaginationFirst.vue'
|
||||
export { default as PaginationItem } from './PaginationItem.vue'
|
||||
export { default as PaginationLast } from './PaginationLast.vue'
|
||||
export { default as PaginationLink } from './PaginationLink.vue'
|
||||
export { default as PaginationNext } from './PaginationNext.vue'
|
||||
export { default as PaginationPrevious } from './PaginationPrevious.vue'
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-slot="table-container" class="relative w-full overflow-x-auto">
|
||||
<table data-slot="table" :class="cn('w-full caption-bottom text-sm', props.class)">
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tbody
|
||||
data-slot="table-body"
|
||||
:class="cn('[&_tr:last-child]:border-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</tbody>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<caption
|
||||
data-slot="table-caption"
|
||||
:class="cn('text-muted-foreground mt-4 text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</caption>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
:class="cn('p-3 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</td>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { cn } from '@/lib/utils'
|
||||
import TableCell from './TableCell.vue'
|
||||
import TableRow from './TableRow.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
colspan?: number
|
||||
}>(), {
|
||||
colspan: 1,
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
:class="
|
||||
cn(
|
||||
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<div class="flex items-center justify-center py-10">
|
||||
<slot />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tfoot
|
||||
data-slot="table-footer"
|
||||
:class="cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</tfoot>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<th
|
||||
data-slot="table-head"
|
||||
:class="cn('text-foreground h-12 px-3 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</th>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
:class="cn('[&_tr]:border-b', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</thead>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr
|
||||
data-slot="table-row"
|
||||
:class="cn('hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors has-aria-expanded:bg-muted/50', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</tr>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
export { default as Table } from './Table.vue'
|
||||
export { default as TableBody } from './TableBody.vue'
|
||||
export { default as TableCaption } from './TableCaption.vue'
|
||||
export { default as TableCell } from './TableCell.vue'
|
||||
export { default as TableEmpty } from './TableEmpty.vue'
|
||||
export { default as TableFooter } from './TableFooter.vue'
|
||||
export { default as TableHead } from './TableHead.vue'
|
||||
export { default as TableHeader } from './TableHeader.vue'
|
||||
export { default as TableRow } from './TableRow.vue'
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Updater } from '@tanstack/vue-table'
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
import { isFunction } from '@tanstack/vue-table'
|
||||
|
||||
export function valueUpdater<T>(updaterOrValue: Updater<T>, ref: Ref<T>) {
|
||||
ref.value = isFunction(updaterOrValue)
|
||||
? updaterOrValue(ref.value)
|
||||
: updaterOrValue
|
||||
}
|
||||
Reference in New Issue
Block a user