chore: rename @vben-core -> @core
This commit is contained in:
16
packages/@core/uikit/shadcn-ui/components.json
Normal file
16
packages/@core/uikit/shadcn-ui/components.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "new-york",
|
||||
"typescript": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.mjs",
|
||||
"css": "src/assets/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
},
|
||||
"framework": "vite",
|
||||
"aliases": {
|
||||
"components": "#/components",
|
||||
"utils": "#/lib/utils"
|
||||
}
|
||||
}
|
61
packages/@core/uikit/shadcn-ui/package.json
Normal file
61
packages/@core/uikit/shadcn-ui/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "packages/@vben-core/uikit/shadcn-ui"
|
||||
},
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"scripts": {
|
||||
"build": "pnpm vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"imports": {
|
||||
"#*": "./src/*"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./*": {
|
||||
"types": "./src/*.ts",
|
||||
"development": "./src/*.ts",
|
||||
"default": "./dist/*.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@vben-core/iconify": "workspace:*",
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.10.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "2.1.1",
|
||||
"radix-vue": "^1.8.3",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"vue": "3.4.27",
|
||||
"vue-sonner": "^1.1.2"
|
||||
}
|
||||
}
|
1
packages/@core/uikit/shadcn-ui/postcss.config.mjs
Normal file
1
packages/@core/uikit/shadcn-ui/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialog as AlertDialogRoot,
|
||||
AlertDialogTitle,
|
||||
} from '#/components/ui/alert-dialog';
|
||||
|
||||
interface Props {
|
||||
cancelText?: string;
|
||||
content?: string;
|
||||
submitText?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
cancelText: '取消',
|
||||
submitText: '确认',
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
cancel: [];
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const openModal = defineModel<boolean>('open');
|
||||
|
||||
function handleSubmit() {
|
||||
emits('submit');
|
||||
openModal.value = false;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emits('cancel');
|
||||
openModal.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogRoot v-model:open="openModal">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ title }}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{{ content }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleSubmit">
|
||||
{{ submitText }}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogRoot>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenAlertDialog } from './alert-dialog.vue';
|
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
AvatarFallbackProps,
|
||||
AvatarImageProps,
|
||||
AvatarRootProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '#/components/ui/avatar';
|
||||
|
||||
interface Props extends AvatarRootProps, AvatarFallbackProps, AvatarImageProps {
|
||||
alt?: string;
|
||||
class?: HTMLAttributes['class'];
|
||||
dot?: boolean;
|
||||
dotClass?: HTMLAttributes['class'];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
alt: 'avatar',
|
||||
as: 'button',
|
||||
dot: false,
|
||||
dotClass: 'bg-green-500',
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
return props.alt.slice(0, 2).toUpperCase();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="props.class" class="relative flex-shrink-0">
|
||||
<Avatar :class="props.class" class="size-full">
|
||||
<AvatarImage :alt="alt" :src="src" />
|
||||
<AvatarFallback>{{ text }}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span
|
||||
v-if="dot"
|
||||
class="border-background absolute bottom-0 right-0 size-3 rounded-full border-2"
|
||||
:class="dotClass"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenAvatar } from './avatar.vue';
|
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BacktopProps } from './backtop';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { IcRoundArrowUpward } from '@vben-core/iconify';
|
||||
|
||||
import { VbenButton } from '../button';
|
||||
import { useBackTop } from './use-backtop';
|
||||
|
||||
interface Props extends BacktopProps {}
|
||||
|
||||
defineOptions({ name: 'BackTop' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
bottom: 40,
|
||||
isGroup: false,
|
||||
right: 40,
|
||||
target: '',
|
||||
visibilityHeight: 200,
|
||||
});
|
||||
|
||||
const backTopStyle = computed(() => ({
|
||||
bottom: `${props.bottom}px`,
|
||||
right: `${props.right}px`,
|
||||
}));
|
||||
|
||||
const { handleClick, visible } = useBackTop(props);
|
||||
</script>
|
||||
<template>
|
||||
<transition name="fade-down">
|
||||
<VbenButton
|
||||
v-if="visible"
|
||||
:style="backTopStyle"
|
||||
class="bg-accent data fixed bottom-10 right-5 h-10 w-10 rounded-full"
|
||||
size="icon"
|
||||
variant="icon"
|
||||
@click="handleClick"
|
||||
>
|
||||
<IcRoundArrowUpward />
|
||||
</VbenButton>
|
||||
</transition>
|
||||
</template>
|
@@ -0,0 +1,38 @@
|
||||
export const backtopProps = {
|
||||
/**
|
||||
* @zh_CN bottom distance.
|
||||
*/
|
||||
bottom: {
|
||||
default: 40,
|
||||
type: Number,
|
||||
},
|
||||
/**
|
||||
* @zh_CN right distance.
|
||||
*/
|
||||
right: {
|
||||
default: 40,
|
||||
type: Number,
|
||||
},
|
||||
/**
|
||||
* @zh_CN the target to trigger scroll.
|
||||
*/
|
||||
target: {
|
||||
default: '',
|
||||
type: String,
|
||||
},
|
||||
/**
|
||||
* @zh_CN the button will not show until the scroll height reaches this value.
|
||||
*/
|
||||
visibilityHeight: {
|
||||
default: 200,
|
||||
type: Number,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface BacktopProps {
|
||||
bottom?: number;
|
||||
isGroup?: boolean;
|
||||
right?: number;
|
||||
target?: string;
|
||||
visibilityHeight?: number;
|
||||
}
|
@@ -0,0 +1 @@
|
||||
export { default as VbenBackTop } from './back-top.vue';
|
@@ -0,0 +1,45 @@
|
||||
import type { BacktopProps } from './backtop';
|
||||
|
||||
import { onMounted, ref, shallowRef } from 'vue';
|
||||
|
||||
import { useEventListener, useThrottleFn } from '@vueuse/core';
|
||||
|
||||
export const useBackTop = (props: BacktopProps) => {
|
||||
const el = shallowRef<HTMLElement>();
|
||||
const container = shallowRef<Document | HTMLElement>();
|
||||
const visible = ref(false);
|
||||
|
||||
const handleScroll = () => {
|
||||
if (el.value) {
|
||||
visible.value = el.value.scrollTop >= (props?.visibilityHeight ?? 0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
el.value?.scrollTo({ behavior: 'smooth', top: 0 });
|
||||
};
|
||||
|
||||
const handleScrollThrottled = useThrottleFn(handleScroll, 300, true);
|
||||
|
||||
useEventListener(container, 'scroll', handleScrollThrottled);
|
||||
onMounted(() => {
|
||||
container.value = document;
|
||||
el.value = document.documentElement;
|
||||
|
||||
if (props.target) {
|
||||
el.value = document.querySelector<HTMLElement>(props.target) ?? undefined;
|
||||
|
||||
if (!el.value) {
|
||||
throw new Error(`target does not exist: ${props.target}`);
|
||||
}
|
||||
container.value = el.value;
|
||||
}
|
||||
// Give visible an initial value, fix #13066
|
||||
handleScroll();
|
||||
});
|
||||
|
||||
return {
|
||||
handleClick,
|
||||
visible,
|
||||
};
|
||||
};
|
@@ -0,0 +1,111 @@
|
||||
<script lang="ts" setup>
|
||||
import type { IBreadcrumb } from './interface';
|
||||
|
||||
import { VbenIcon } from '../icon';
|
||||
|
||||
interface Props {
|
||||
breadcrumbs: IBreadcrumb[];
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'Breadcrumb' });
|
||||
withDefaults(defineProps<Props>(), {
|
||||
showIcon: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ select: [string] }>();
|
||||
|
||||
function handleClick(path?: string) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
emit('select', path);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<ul class="flex">
|
||||
<TransitionGroup name="breadcrumb-transition">
|
||||
<template
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
:key="`${item.path}-${item.title}-${index}`"
|
||||
>
|
||||
<li>
|
||||
<a href="javascript:void 0" @click.stop="handleClick(item.path)">
|
||||
<span class="flex-center h-full">
|
||||
<VbenIcon
|
||||
v-if="item.icon && showIcon"
|
||||
class="mr-1 size-5 flex-shrink-0"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
<span
|
||||
:class="{
|
||||
'text-foreground font-normal':
|
||||
index === breadcrumbs.length - 1,
|
||||
}"
|
||||
>{{ item.title }}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
</TransitionGroup>
|
||||
</ul>
|
||||
</template>
|
||||
<style scoped>
|
||||
li {
|
||||
@apply h-7;
|
||||
}
|
||||
|
||||
li a {
|
||||
@apply text-muted-foreground bg-accent relative mr-9 flex h-7 items-center py-0 pl-[5px] pr-2 text-[13px];
|
||||
}
|
||||
|
||||
li a > span {
|
||||
@apply -ml-3;
|
||||
}
|
||||
|
||||
li:first-child a > span {
|
||||
@apply -ml-1;
|
||||
}
|
||||
|
||||
li:first-child a {
|
||||
@apply rounded-[4px_0_0_4px] pl-[15px];
|
||||
}
|
||||
|
||||
li:first-child a::before {
|
||||
@apply border-none;
|
||||
}
|
||||
|
||||
li:last-child a {
|
||||
@apply rounded-[0_4px_4px_0] pr-[15px];
|
||||
}
|
||||
|
||||
li:last-child a::after {
|
||||
@apply border-none;
|
||||
}
|
||||
|
||||
li a::before,
|
||||
li a::after {
|
||||
@apply border-accent absolute top-0 h-0 w-0 border-[14px] border-solid content-[''];
|
||||
}
|
||||
|
||||
li a::before {
|
||||
@apply -left-7 z-10 border-l-transparent;
|
||||
}
|
||||
|
||||
li a::after {
|
||||
@apply border-l-accent left-full border-transparent;
|
||||
}
|
||||
|
||||
li:not(:last-child) a:hover {
|
||||
@apply bg-accent-hover;
|
||||
}
|
||||
|
||||
li:not(:last-child) a:hover::before {
|
||||
@apply border-accent-hover border-l-transparent;
|
||||
}
|
||||
|
||||
li:not(:last-child) a:hover::after {
|
||||
@apply border-l-accent-hover;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,108 @@
|
||||
<script lang="ts" setup>
|
||||
import type { IBreadcrumb } from './interface';
|
||||
|
||||
import { IcRoundKeyboardArrowDown } from '@vben-core/iconify';
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '#/components/ui/breadcrumb';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '#/components/ui/dropdown-menu';
|
||||
|
||||
import { VbenIcon } from '../';
|
||||
|
||||
interface Props {
|
||||
breadcrumbs: IBreadcrumb[];
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'Breadcrumb' });
|
||||
withDefaults(defineProps<Props>(), {
|
||||
showIcon: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ select: [string] }>();
|
||||
|
||||
function handleClick(path?: string) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
emit('select', path);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<TransitionGroup name="breadcrumb-transition">
|
||||
<template
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
:key="`${item.path}-${item.title}-${index}`"
|
||||
>
|
||||
<BreadcrumbItem>
|
||||
<div v-if="item.items?.length ?? 0 > 0">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="flex items-center gap-1">
|
||||
<VbenIcon
|
||||
v-if="item.icon && showIcon"
|
||||
class="size-5"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
{{ item.title }}
|
||||
<IcRoundKeyboardArrowDown class="size-5" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<template
|
||||
v-for="menuItem in item.items"
|
||||
:key="`sub-${menuItem.path}`"
|
||||
>
|
||||
<DropdownMenuItem @click.stop="handleClick(menuItem.path)">
|
||||
{{ menuItem.title }}
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<BreadcrumbLink
|
||||
v-else-if="index !== breadcrumbs.length - 1"
|
||||
href="javascript:void 0"
|
||||
@click.stop="handleClick(item.path)"
|
||||
>
|
||||
<div class="flex-center">
|
||||
<VbenIcon
|
||||
v-if="item.icon && showIcon"
|
||||
class="mr-1 size-4"
|
||||
:class="{ 'size-5': item.isHome }"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</BreadcrumbLink>
|
||||
<BreadcrumbPage v-else>
|
||||
<div class="flex-center">
|
||||
<VbenIcon
|
||||
v-if="item.icon && showIcon"
|
||||
class="mr-1 size-4"
|
||||
:class="{ 'size-5': item.isHome }"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</BreadcrumbPage>
|
||||
<BreadcrumbSeparator
|
||||
v-if="index < breadcrumbs.length - 1 && !item.isHome"
|
||||
/>
|
||||
</BreadcrumbItem>
|
||||
</template>
|
||||
</TransitionGroup>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</template>
|
@@ -0,0 +1,4 @@
|
||||
export { default as VbenBreadcrumb } from './breadcrumb.vue';
|
||||
export { default as VbenBackgroundBreadcrumb } from './breadcrumb-background.vue';
|
||||
|
||||
export type * from './interface';
|
@@ -0,0 +1,9 @@
|
||||
interface IBreadcrumb {
|
||||
icon?: string;
|
||||
isHome?: boolean;
|
||||
items?: IBreadcrumb[];
|
||||
path?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export type { IBreadcrumb };
|
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { MdiLoading } from '@vben-core/iconify';
|
||||
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
|
||||
import { type ButtonVariants, buttonVariants } from '#/components/ui/button';
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
class?: HTMLAttributes['class'];
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
size?: ButtonVariants['size'];
|
||||
variant?: ButtonVariants['variant'];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
class: '',
|
||||
disabled: false,
|
||||
loading: false,
|
||||
size: 'default',
|
||||
variant: 'default',
|
||||
});
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return props.disabled || props.loading;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
<MdiLoading
|
||||
v-if="loading"
|
||||
class="text-md mr-2 flex-shrink-0 animate-spin"
|
||||
/>
|
||||
<slot></slot>
|
||||
</Primitive>
|
||||
</template>
|
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed, useSlots } from 'vue';
|
||||
|
||||
import { type PrimitiveProps } from 'radix-vue';
|
||||
|
||||
import { VbenTooltip } from '#/components/tooltip';
|
||||
import { ButtonVariants } from '#/components/ui/button';
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
import VbenButton from './button.vue';
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
class?: HTMLAttributes['class'];
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
tooltip?: string;
|
||||
tooltipSide?: 'bottom' | 'left' | 'right' | 'top';
|
||||
variant?: ButtonVariants['variant'];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
onClick: () => {},
|
||||
tooltipSide: 'bottom',
|
||||
variant: 'icon',
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const showTooltip = computed(() => !!slots.tooltip || !!props.tooltip);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VbenButton
|
||||
v-if="!showTooltip"
|
||||
:class="cn('rounded-full', props.class)"
|
||||
:disabled="disabled"
|
||||
:variant="variant"
|
||||
size="icon"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</VbenButton>
|
||||
|
||||
<VbenTooltip v-else :side="tooltipSide">
|
||||
<template #trigger>
|
||||
<VbenButton
|
||||
:class="cn('rounded-full', props.class)"
|
||||
:disabled="disabled"
|
||||
:variant="variant"
|
||||
size="icon"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</VbenButton>
|
||||
</template>
|
||||
<slot v-if="slots.tooltip" name="tooltip"> </slot>
|
||||
<template v-else>
|
||||
{{ tooltip }}
|
||||
</template>
|
||||
</VbenTooltip>
|
||||
</template>
|
@@ -0,0 +1,2 @@
|
||||
export { default as VbenButton } from './button.vue';
|
||||
export { default as VbenIconButton } from './icon-button.vue';
|
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
import { Checkbox } from '#/components/ui/checkbox';
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
name: string;
|
||||
} & CheckboxRootProps
|
||||
>();
|
||||
|
||||
const emits = defineEmits<CheckboxRootEmits>();
|
||||
|
||||
const checked = defineModel<boolean>('checked');
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Checkbox v-bind="forwarded" :id="name" v-model:checked="checked" />
|
||||
<label :for="name" class="ml-2 cursor-pointer text-sm"> <slot></slot> </label>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenCheckbox } from './checkbox.vue';
|
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ContextMenuContentProps,
|
||||
ContextMenuRootEmits,
|
||||
ContextMenuRootProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import type { IContextMenuItem } from './interface';
|
||||
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuTrigger,
|
||||
} from '#/components/ui/context-menu';
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
class?: HTMLAttributes['class'];
|
||||
contentClass?: HTMLAttributes['class'];
|
||||
contentProps?: ContextMenuContentProps;
|
||||
handlerData?: Record<string, any>;
|
||||
itemClass?: HTMLAttributes['class'];
|
||||
menus: (data: any) => IContextMenuItem[];
|
||||
} & ContextMenuRootProps
|
||||
>();
|
||||
|
||||
const emits = defineEmits<ContextMenuRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _cls,
|
||||
contentClass: _,
|
||||
contentProps: _cProps,
|
||||
itemClass: _iCls,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
const menusView = computed(() => {
|
||||
return props.menus?.(props.handlerData);
|
||||
});
|
||||
|
||||
function handleClick(menu: IContextMenuItem) {
|
||||
if (menu.disabled) {
|
||||
return;
|
||||
}
|
||||
menu?.handler?.(props.handlerData);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenu v-bind="forwarded">
|
||||
<ContextMenuTrigger as-child>
|
||||
<slot></slot>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent
|
||||
:class="contentClass"
|
||||
v-bind="contentProps"
|
||||
class="side-content z-[1000]"
|
||||
>
|
||||
<template v-for="menu in menusView" :key="menu.key">
|
||||
<ContextMenuItem
|
||||
:inset="menu.inset || !menu.icon"
|
||||
:disabled="menu.disabled"
|
||||
class="cursor-pointer"
|
||||
:class="itemClass"
|
||||
@click="handleClick(menu)"
|
||||
>
|
||||
<component
|
||||
:is="menu.icon"
|
||||
v-if="menu.icon"
|
||||
class="mr-1 w-6 text-lg"
|
||||
/>
|
||||
|
||||
{{ menu.text }}
|
||||
<ContextMenuShortcut v-if="menu.shortcut">
|
||||
{{ menu.shortcut }}
|
||||
</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator v-if="menu.separator" />
|
||||
</template>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</template>
|
@@ -0,0 +1,3 @@
|
||||
export { default as VbenContextMenu } from './context-menu.vue';
|
||||
|
||||
export type * from './interface';
|
@@ -0,0 +1,38 @@
|
||||
import type { Component } from 'vue';
|
||||
|
||||
interface IContextMenuItem {
|
||||
/**
|
||||
* @zh_CN 是否禁用
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* @zh_CN 点击事件处理
|
||||
* @param data
|
||||
*/
|
||||
handler?: (data: any) => void;
|
||||
/**
|
||||
* @zh_CN 图标
|
||||
*/
|
||||
icon?: Component;
|
||||
/**
|
||||
* @zh_CN 是否显示图标
|
||||
*/
|
||||
inset?: boolean;
|
||||
/**
|
||||
* @zh_CN 唯一标识
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* @zh_CN 是否是分割线
|
||||
*/
|
||||
separator?: boolean;
|
||||
/**
|
||||
* @zh_CN 快捷键
|
||||
*/
|
||||
shortcut?: string;
|
||||
/**
|
||||
* @zh_CN 标题
|
||||
*/
|
||||
text: string;
|
||||
}
|
||||
export type { IContextMenuItem };
|
@@ -0,0 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
DropdownMenuProps,
|
||||
VbenDropdownMenuItem as IDropdownMenuItem,
|
||||
} from './interface';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '#/components/ui/dropdown-menu';
|
||||
|
||||
interface Props extends DropdownMenuProps {}
|
||||
|
||||
defineOptions({ name: 'DropdownMenu' });
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
function handleItemClick(menu: IDropdownMenuItem) {
|
||||
if (menu.disabled) {
|
||||
return;
|
||||
}
|
||||
menu?.handler?.(props);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="flex h-full items-center gap-1">
|
||||
<slot></slot>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<template v-for="menu in menus" :key="menu.key">
|
||||
<DropdownMenuItem
|
||||
:disabled="menu.disabled"
|
||||
class="data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground text-foreground/80 mb-1 cursor-pointer"
|
||||
@click="handleItemClick(menu)"
|
||||
>
|
||||
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
||||
{{ menu.text }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator v-if="menu.separator" class="bg-border" />
|
||||
</template>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
@@ -0,0 +1,50 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DropdownMenuProps } from './interface';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '#/components/ui/dropdown-menu';
|
||||
|
||||
interface Props extends DropdownMenuProps {}
|
||||
|
||||
defineOptions({ name: 'DropdownRadioMenu' });
|
||||
withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const modelValue = defineModel<string>();
|
||||
|
||||
function handleItemClick(value: string) {
|
||||
modelValue.value = value;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="flex items-center gap-1" as-child>
|
||||
<slot></slot>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<template v-for="menu in menus" :key="menu.key">
|
||||
<DropdownMenuItem
|
||||
class="data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground text-foreground/80 mb-1 cursor-pointer"
|
||||
:class="
|
||||
menu.key === modelValue ? 'bg-accent text-accent-foreground' : ''
|
||||
"
|
||||
@click="handleItemClick(menu.key)"
|
||||
>
|
||||
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
||||
<span
|
||||
v-if="!menu.icon"
|
||||
class="mr-2 size-1.5 rounded-full"
|
||||
:class="menu.key === modelValue ? 'bg-foreground' : ''"
|
||||
></span>
|
||||
{{ menu.text }}
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
@@ -0,0 +1,4 @@
|
||||
export { default as VbenDropdownMenu } from './dropdown-menu.vue';
|
||||
export { default as VbenDropdownRadioMenu } from './dropdown-radio-menu.vue';
|
||||
|
||||
export type * from './interface';
|
@@ -0,0 +1,32 @@
|
||||
import type { Component } from 'vue';
|
||||
|
||||
interface VbenDropdownMenuItem {
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* @zh_CN 点击事件处理
|
||||
* @param data
|
||||
*/
|
||||
handler?: (data: any) => void;
|
||||
/**
|
||||
* @zh_CN 图标
|
||||
*/
|
||||
icon?: Component;
|
||||
/**
|
||||
* @zh_CN 唯一标识
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* @zh_CN 是否是分割线
|
||||
*/
|
||||
separator?: boolean;
|
||||
/**
|
||||
* @zh_CN 标题
|
||||
*/
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface DropdownMenuProps {
|
||||
menus: VbenDropdownMenuItem[];
|
||||
}
|
||||
|
||||
export type { DropdownMenuProps, VbenDropdownMenuItem };
|
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const isMenuOpen = ref(false);
|
||||
const menuItems = ref(['1', '2', '3', '4']);
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen.value = !isMenuOpen.value;
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (_item) => {
|
||||
// console.log(111, item);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed bottom-5 right-5 flex flex-col-reverse items-center gap-2">
|
||||
<button
|
||||
class="flex h-12 w-12 items-center justify-center rounded-full bg-blue-500 text-xl text-white transition-transform duration-300"
|
||||
:class="{ 'rotate-45': isMenuOpen }"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
✖
|
||||
</button>
|
||||
<div
|
||||
class="absolute bottom-16 right-0 flex flex-col-reverse gap-2 transition-all duration-300"
|
||||
:class="{
|
||||
'visible translate-y-0 opacity-100': isMenuOpen,
|
||||
'invisible translate-y-2 opacity-0': !isMenuOpen,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="index"
|
||||
class="flex h-12 w-12 items-center justify-center rounded-full bg-blue-500 text-xl text-white"
|
||||
@click="handleMenuItemClick(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 可以在这里添加任何需要的额外样式 */
|
||||
</style>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenFloatingButtonGroup } from './floating-button-group.vue';
|
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import { IcRoundFullscreen, IcRoundFullscreenExit } from '@vben-core/iconify';
|
||||
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
|
||||
import { VbenIconButton } from '../button';
|
||||
|
||||
defineOptions({ name: 'FullScreen' });
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
|
||||
// 重新检查全屏状态
|
||||
isFullscreen.value = !!(
|
||||
document.fullscreenElement ||
|
||||
// @ts-ignore
|
||||
document.webkitFullscreenElement ||
|
||||
// @ts-ignore
|
||||
document.mozFullScreenElement ||
|
||||
// @ts-ignore
|
||||
document.msFullscreenElement
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<VbenIconButton @click="toggle">
|
||||
<IcRoundFullscreenExit v-if="isFullscreen" class="size-6" />
|
||||
<IcRoundFullscreen v-else class="size-6" />
|
||||
</VbenIconButton>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenFullScreen } from './full-screen.vue';
|
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import type { HoverCardRootEmits, HoverCardRootProps } from 'radix-vue';
|
||||
|
||||
import { HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { HoverCardContentProps, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '#/components/ui/hover-card';
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
class?: HTMLAttributes['class'];
|
||||
contentClass?: HTMLAttributes['class'];
|
||||
contentProps?: HoverCardContentProps;
|
||||
} & HoverCardRootProps
|
||||
>();
|
||||
|
||||
const emits = defineEmits<HoverCardRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _cls,
|
||||
contentClass: _,
|
||||
contentProps: _cProps,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HoverCard v-bind="forwarded">
|
||||
<HoverCardTrigger as-child class="h-full">
|
||||
<div class="h-full cursor-pointer">
|
||||
<slot name="trigger"></slot>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
:class="contentClass"
|
||||
v-bind="contentProps"
|
||||
class="side-content z-[1000]"
|
||||
>
|
||||
<slot></slot>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</template>
|
@@ -0,0 +1,2 @@
|
||||
export { default as VbenHoverCard } from './hover-card.vue';
|
||||
export type { HoverCardContentProps } from 'radix-vue';
|
27
packages/@core/uikit/shadcn-ui/src/components/icon/icon.vue
Normal file
27
packages/@core/uikit/shadcn-ui/src/components/icon/icon.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { type Component, computed } from 'vue';
|
||||
|
||||
import { Icon, IconDefault } from '@vben-core/iconify';
|
||||
import { isHttpUrl, isObject, isString } from '@vben-core/toolkit';
|
||||
|
||||
const props = defineProps<{
|
||||
// 没有是否显示默认图标
|
||||
fallback?: boolean;
|
||||
icon?: Component | string;
|
||||
}>();
|
||||
|
||||
const isRemoteIcon = computed(() => {
|
||||
return isString(props.icon) && isHttpUrl(props.icon);
|
||||
});
|
||||
|
||||
const isComponent = computed(() => {
|
||||
return !isString(props.icon) && isObject(props.icon);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="icon as Component" v-if="isComponent" v-bind="$attrs" />
|
||||
<img v-else-if="isRemoteIcon" :src="icon as string" v-bind="$attrs" />
|
||||
<Icon v-else-if="icon" v-bind="$attrs" :icon="icon as string" />
|
||||
<IconDefault v-else-if="fallback" v-bind="$attrs" />
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenIcon } from './icon.vue';
|
42
packages/@core/uikit/shadcn-ui/src/components/index.ts
Normal file
42
packages/@core/uikit/shadcn-ui/src/components/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// 修改过的button
|
||||
export * from './alert-dialog';
|
||||
export * from './avatar';
|
||||
export * from './back-top';
|
||||
export * from './breadcrumb';
|
||||
export * from './button';
|
||||
export * from './checkbox';
|
||||
export * from './context-menu';
|
||||
export * from './dropdown-menu';
|
||||
export * from './floating-button-group';
|
||||
export * from './full-screen';
|
||||
export * from './hover-card';
|
||||
export * from './icon';
|
||||
export * from './input';
|
||||
export * from './input-password';
|
||||
export * from './logo';
|
||||
export * from './menu-badge';
|
||||
export * from './pin-input';
|
||||
export * from './popover';
|
||||
export * from './segmented';
|
||||
export * from './sheet';
|
||||
export * from './tooltip';
|
||||
export * from './ui/alert-dialog';
|
||||
export * from './ui/avatar';
|
||||
export * from './ui/badge';
|
||||
export * from './ui/breadcrumb';
|
||||
export * from './ui/button';
|
||||
export * from './ui/checkbox';
|
||||
export * from './ui/dialog';
|
||||
export * from './ui/dropdown-menu';
|
||||
export * from './ui/hover-card';
|
||||
export * from './ui/pin-input';
|
||||
export * from './ui/popover';
|
||||
export * from './ui/scroll-area';
|
||||
export * from './ui/select';
|
||||
export * from './ui/sheet';
|
||||
export * from './ui/sonner';
|
||||
export * from './ui/switch';
|
||||
export * from './ui/tabs';
|
||||
export * from './ui/toggle';
|
||||
export * from './ui/toggle-group';
|
||||
export * from './ui/tooltip';
|
@@ -0,0 +1 @@
|
||||
export { default as VbenInputPassword } from './input-password.vue';
|
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, useSlots } from 'vue';
|
||||
|
||||
import {
|
||||
IcOutlineVisibility,
|
||||
IcOutlineVisibilityOff,
|
||||
} from '@vben-core/iconify';
|
||||
|
||||
import { useForwardProps } from 'radix-vue';
|
||||
|
||||
import { type InputProps, VbenInput } from '#/components/input/index';
|
||||
|
||||
import PasswordStrength from './password-strength.vue';
|
||||
|
||||
interface Props extends InputProps {}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const modelValue = defineModel<string>();
|
||||
|
||||
const slots = useSlots();
|
||||
const forward = useForwardProps(props);
|
||||
|
||||
const show = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="relative">
|
||||
<VbenInput
|
||||
v-model="modelValue"
|
||||
v-bind="{ ...forward, ...$attrs }"
|
||||
:type="show ? 'text' : 'password'"
|
||||
>
|
||||
<template v-if="passwordStrength">
|
||||
<PasswordStrength :password="modelValue" />
|
||||
<p
|
||||
v-if="slots.strengthText"
|
||||
class="text-muted-foreground mt-1.5 text-xs"
|
||||
>
|
||||
<slot name="strengthText"> </slot>
|
||||
</p>
|
||||
</template>
|
||||
</VbenInput>
|
||||
<div
|
||||
class="hover:text-foreground text-foreground/60 absolute inset-y-0 right-0 top-3 flex cursor-pointer pr-3 text-lg leading-5"
|
||||
@click="show = !show"
|
||||
>
|
||||
<IcOutlineVisibility v-if="show" />
|
||||
<IcOutlineVisibilityOff v-else />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = withDefaults(defineProps<{ password?: string }>(), {
|
||||
password: '',
|
||||
});
|
||||
|
||||
const strengthList: string[] = [
|
||||
'',
|
||||
'#e74242',
|
||||
'#ED6F6F',
|
||||
'#EFBD47',
|
||||
'#55D18780',
|
||||
'#55D187',
|
||||
];
|
||||
|
||||
const currentStrength = computed(() => {
|
||||
return checkPasswordStrength(props.password);
|
||||
});
|
||||
|
||||
const currentColor = computed(() => {
|
||||
return strengthList[currentStrength.value];
|
||||
});
|
||||
|
||||
/**
|
||||
* Check the strength of a password
|
||||
*/
|
||||
function checkPasswordStrength(password: string) {
|
||||
let strength = 0;
|
||||
|
||||
// Check length
|
||||
if (password.length >= 8) strength++;
|
||||
|
||||
// Check for lowercase letters
|
||||
if (/[a-z]/.test(password)) strength++;
|
||||
|
||||
// Check for uppercase letters
|
||||
if (/[A-Z]/.test(password)) strength++;
|
||||
|
||||
// Check for numbers
|
||||
if (/\d/.test(password)) strength++;
|
||||
|
||||
// Check for special characters
|
||||
if (/[^\da-z]/i.test(password)) strength++;
|
||||
|
||||
return strength;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative mt-2 flex items-center justify-between">
|
||||
<template v-for="index in 5" :key="index">
|
||||
<div
|
||||
class="dark:bg-input-background bg-heavy relative mr-1 h-1.5 w-1/5 rounded-sm last:mr-0"
|
||||
>
|
||||
<span
|
||||
class="absolute left-0 h-full w-0 rounded-sm transition-all duration-500"
|
||||
:style="{
|
||||
backgroundColor: currentColor,
|
||||
width: currentStrength >= index ? '100%' : '',
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,2 @@
|
||||
export { default as VbenInput } from './input.vue';
|
||||
export type * from './interface';
|
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import type { InputProps } from './interface';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps<InputProps>();
|
||||
|
||||
const modelValue = defineModel<number | string>();
|
||||
|
||||
const inputClass = computed(() => {
|
||||
if (props.status === 'error') {
|
||||
return 'border-destructive';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative mb-6">
|
||||
<label
|
||||
v-if="!label"
|
||||
:for="name"
|
||||
class="mb-2 block text-sm font-medium dark:text-white"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
<input
|
||||
:id="name"
|
||||
v-model="modelValue"
|
||||
:class="[props.class, inputClass]"
|
||||
class="border-input bg-input-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring focus:border-primary flex h-10 w-full rounded-md border p-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
autocomplete="off"
|
||||
required
|
||||
type="text"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<Transition name="slide-up">
|
||||
<p
|
||||
v-if="status === 'error'"
|
||||
class="text-destructive bottom-130 absolute mt-1 text-xs"
|
||||
>
|
||||
{{ errorTip }}
|
||||
</p>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,27 @@
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
interface InputProps {
|
||||
class?: HTMLAttributes['class'];
|
||||
/**
|
||||
* 错误提示信息
|
||||
*/
|
||||
errorTip?: string;
|
||||
/**
|
||||
* 输入框的 label
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* 输入框的 name
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 是否显示密码强度
|
||||
*/
|
||||
passwordStrength?: boolean;
|
||||
/**
|
||||
* 输入框的校验状态
|
||||
*/
|
||||
status?: 'default' | 'error';
|
||||
}
|
||||
|
||||
export type { InputProps };
|
@@ -0,0 +1 @@
|
||||
export { default as VbenLogo } from './logo.vue';
|
76
packages/@core/uikit/shadcn-ui/src/components/logo/logo.vue
Normal file
76
packages/@core/uikit/shadcn-ui/src/components/logo/logo.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Logo 图标 alt
|
||||
*/
|
||||
alt?: string;
|
||||
/**
|
||||
* 是否收起文本
|
||||
*/
|
||||
collapse?: boolean;
|
||||
/**
|
||||
* Logo 跳转地址
|
||||
*/
|
||||
href?: string;
|
||||
/**
|
||||
* Logo 图片大小
|
||||
*/
|
||||
logoSize?: number;
|
||||
/**
|
||||
* Logo 图标
|
||||
*/
|
||||
src?: string;
|
||||
/**
|
||||
* Logo 文本
|
||||
*/
|
||||
text?: string;
|
||||
/**
|
||||
* Logo 主题
|
||||
*/
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'Logo',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
alt: 'Vben',
|
||||
collapse: false,
|
||||
href: 'javascript:void 0',
|
||||
logoSize: 36,
|
||||
src: '',
|
||||
text: '',
|
||||
theme: 'light',
|
||||
});
|
||||
const logoClass = computed(() => {
|
||||
return [props.theme, props.collapse ? 'collapsed' : ''];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="group flex h-full items-center text-lg" :class="logoClass">
|
||||
<a
|
||||
:href="href"
|
||||
class="flex h-full items-center gap-2 overflow-hidden px-3 font-semibold leading-normal transition-all duration-500"
|
||||
:class="$attrs.class"
|
||||
>
|
||||
<img
|
||||
v-if="src"
|
||||
:src="src"
|
||||
:alt="alt"
|
||||
:width="logoSize"
|
||||
class="relative rounded-none bg-transparent"
|
||||
/>
|
||||
<span
|
||||
v-if="!collapse"
|
||||
class="text-primary truncate text-nowrap group-[.dark]:text-[hsl(var(--color-dark-foreground))]"
|
||||
>
|
||||
{{ text }}
|
||||
<!-- <span class="text-primary ml-1 align-super text-[smaller]">Pro</span> -->
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenMenuBadge } from './menu-badge.vue';
|
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
interface Props {
|
||||
dotClass?: string;
|
||||
dotStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
dotClass: '',
|
||||
dotStyle: () => ({}),
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<span class="relative mr-1 flex size-1.5">
|
||||
<span
|
||||
class="absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
|
||||
:class="dotClass"
|
||||
:style="dotStyle"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex size-1.5 rounded-full"
|
||||
:class="dotClass"
|
||||
:style="dotStyle"
|
||||
></span>
|
||||
</span>
|
||||
</template>
|
@@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuRecordBadgeRaw } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { isValidColor } from '@vben-core/toolkit';
|
||||
|
||||
import BadgeDot from './menu-badge-dot.vue';
|
||||
|
||||
interface Props extends MenuRecordBadgeRaw {
|
||||
hasChildren?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const variantsMap: Record<string, string> = {
|
||||
default: 'bg-green-500',
|
||||
destructive: ' bg-destructive',
|
||||
primary: 'bg-primary',
|
||||
success: ' bg-green-500',
|
||||
warning: ' bg-yellow-500',
|
||||
};
|
||||
|
||||
const isDot = computed(() => props.badgeType === 'dot');
|
||||
|
||||
const badgeClass = computed(() => {
|
||||
const { badgeVariants } = props;
|
||||
|
||||
if (!badgeVariants) {
|
||||
return variantsMap.default;
|
||||
}
|
||||
|
||||
return variantsMap[badgeVariants] || badgeVariants;
|
||||
});
|
||||
|
||||
const badgeStyle = computed(() => {
|
||||
if (badgeClass.value && isValidColor(badgeClass.value)) {
|
||||
return {
|
||||
backgroundColor: badgeClass.value,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<span v-if="isDot || badge" class="absolute right-5" :class="$attrs.class">
|
||||
<BadgeDot v-if="isDot" :dot-class="badgeClass" :dot-style="badgeStyle" />
|
||||
<div
|
||||
v-else
|
||||
:class="badgeClass"
|
||||
:style="badgeStyle"
|
||||
class="rounded-md px-1.5 py-0.5 text-xs"
|
||||
>
|
||||
{{ badge }}
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
@@ -0,0 +1,3 @@
|
||||
export { default as VbenPinInput } from './input.vue';
|
||||
|
||||
export type * from './interface';
|
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import type { PinInputProps } from './interface';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { VbenButton } from '#/components/button';
|
||||
import {
|
||||
PinInput,
|
||||
PinInputGroup,
|
||||
PinInputInput,
|
||||
} from '#/components/ui/pin-input';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<PinInputProps>(), {
|
||||
btnLoading: false,
|
||||
codeLength: 6,
|
||||
handleSendCode: async () => {},
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
complete: [];
|
||||
}>();
|
||||
|
||||
const modelValue = defineModel<string>();
|
||||
|
||||
const inputValue = ref<string[]>([]);
|
||||
|
||||
const inputClass = computed(() => {
|
||||
if (props.status === 'error') {
|
||||
return 'border-destructive';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
watch(
|
||||
() => modelValue.value,
|
||||
() => {
|
||||
inputValue.value = modelValue.value?.split('') ?? [];
|
||||
},
|
||||
);
|
||||
|
||||
function handleComplete(e: string[]) {
|
||||
modelValue.value = e.join('');
|
||||
emit('complete');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative mb-6">
|
||||
<label :for="name" class="mb-2 block text-sm font-medium">
|
||||
{{ label }}
|
||||
</label>
|
||||
<PinInput
|
||||
:id="name"
|
||||
v-model="inputValue"
|
||||
otp
|
||||
placeholder="○"
|
||||
class="flex justify-between"
|
||||
:class="inputClass"
|
||||
type="number"
|
||||
@complete="handleComplete"
|
||||
>
|
||||
<PinInputGroup>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in codeLength"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
</PinInputGroup>
|
||||
<VbenButton
|
||||
class="w-[300px] xl:w-full"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
:loading="btnLoading"
|
||||
@click="handleSendCode"
|
||||
>
|
||||
{{ btnText }}
|
||||
</VbenButton>
|
||||
</PinInput>
|
||||
<p
|
||||
v-if="status === 'error'"
|
||||
class="text-destructive bottom-130 absolute mt-1 text-xs"
|
||||
>
|
||||
{{ errorTip }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,40 @@
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
interface PinInputProps {
|
||||
/**
|
||||
* 发送验证码按钮loading
|
||||
*/
|
||||
btnLoading?: boolean;
|
||||
/**
|
||||
* 发送验证码按钮文本
|
||||
*/
|
||||
btnText?: string;
|
||||
class?: HTMLAttributes['class'];
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
codeLength?: number;
|
||||
/**
|
||||
* 错误提示信息
|
||||
*/
|
||||
errorTip?: string;
|
||||
/**
|
||||
* 自定义验证码发送逻辑
|
||||
* @returns
|
||||
*/
|
||||
handleSendCode?: () => Promise<void>;
|
||||
/**
|
||||
* 输入框的 label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* 输入框的 name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 输入框的校验状态
|
||||
*/
|
||||
status?: 'default' | 'error';
|
||||
}
|
||||
|
||||
export type { PinInputProps };
|
@@ -0,0 +1 @@
|
||||
export { default as VbenPopover } from './popover.vue';
|
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
PopoverContentProps,
|
||||
PopoverRootEmits,
|
||||
PopoverRootProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
import {
|
||||
PopoverContent,
|
||||
Popover as PopoverRoot,
|
||||
PopoverTrigger,
|
||||
} from '#/components/ui/popover';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
{
|
||||
class?: HTMLAttributes['class'];
|
||||
contentClass?: HTMLAttributes['class'];
|
||||
contentProps?: PopoverContentProps;
|
||||
} & PopoverRootProps
|
||||
>(),
|
||||
{},
|
||||
);
|
||||
|
||||
const emits = defineEmits<PopoverRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _cls,
|
||||
contentClass: _,
|
||||
contentProps: _cProps,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverRoot v-bind="forwarded">
|
||||
<PopoverTrigger>
|
||||
<slot name="trigger"></slot>
|
||||
|
||||
<PopoverContent
|
||||
:class="contentClass"
|
||||
class="side-content z-[1000]"
|
||||
v-bind="contentProps"
|
||||
>
|
||||
<slot></slot>
|
||||
</PopoverContent>
|
||||
</PopoverTrigger>
|
||||
</PopoverRoot>
|
||||
</template>
|
@@ -0,0 +1,3 @@
|
||||
export type * from './interface';
|
||||
|
||||
export { default as VbenSegmented } from './segmented.vue';
|
@@ -0,0 +1,6 @@
|
||||
interface SegmentedItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type { SegmentedItem };
|
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import type { SegmentedItem } from './interface';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { TabsTrigger } from 'radix-vue';
|
||||
|
||||
import { Tabs, TabsContent, TabsList } from '#/components/ui/tabs';
|
||||
|
||||
import TabsIndicator from './tabs-indicator.vue';
|
||||
|
||||
interface Props {
|
||||
defaultValue?: string;
|
||||
tabs: SegmentedItem[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultValue: '',
|
||||
tabs: () => [],
|
||||
});
|
||||
|
||||
const getDefaultValue = computed(() => {
|
||||
return props.defaultValue || props.tabs[0]?.value;
|
||||
});
|
||||
|
||||
const tabsStyle = computed(() => {
|
||||
return {
|
||||
'grid-template-columns': `repeat(${props.tabs.length}, minmax(0, 1fr))`,
|
||||
};
|
||||
});
|
||||
|
||||
const tabsIndicatorStyle = computed(() => {
|
||||
return {
|
||||
width: `${(100 / props.tabs.length).toFixed(0)}%`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tabs :default-value="getDefaultValue">
|
||||
<TabsList :style="tabsStyle" class="bg-accent relative grid w-full">
|
||||
<TabsIndicator :style="tabsIndicatorStyle" />
|
||||
<template v-for="tab in tabs" :key="tab.value">
|
||||
<TabsTrigger
|
||||
:value="tab.value"
|
||||
class="z-20 inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</TabsTrigger>
|
||||
</template>
|
||||
</TabsList>
|
||||
<template v-for="tab in tabs" :key="tab.value">
|
||||
<TabsContent :value="tab.value">
|
||||
<slot :name="tab.value"></slot>
|
||||
</TabsContent>
|
||||
</template>
|
||||
</Tabs>
|
||||
</template>
|
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import {
|
||||
TabsIndicator,
|
||||
type TabsIndicatorProps,
|
||||
useForwardProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & TabsIndicatorProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsIndicator
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'absolute bottom-0 left-0 z-10 h-full w-1/2 translate-x-[--radix-tabs-indicator-position] rounded-full px-0 py-1 pr-1 transition-[width,transform] duration-300',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="bg-background text-foreground inline-flex h-full w-full items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</TabsIndicator>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenSheet } from './sheet.vue';
|
113
packages/@core/uikit/shadcn-ui/src/components/sheet/sheet.vue
Normal file
113
packages/@core/uikit/shadcn-ui/src/components/sheet/sheet.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
import { Cross2Icon } from '@radix-icons/vue';
|
||||
|
||||
import { VbenButton, VbenIconButton } from '#/components/button';
|
||||
import { ScrollArea } from '#/components/ui/scroll-area';
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '#/components/ui/sheet';
|
||||
|
||||
interface Props {
|
||||
cancelText?: string;
|
||||
description?: string;
|
||||
showFooter?: boolean;
|
||||
submitText?: string;
|
||||
title?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
cancelText: '关闭',
|
||||
description: '',
|
||||
showFooter: false,
|
||||
submitText: '确认',
|
||||
title: '',
|
||||
width: 400,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
cancel: [];
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const openModal = defineModel<boolean>('open');
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const contentStyle = computed(() => {
|
||||
return {
|
||||
width: `${props.width}px`,
|
||||
};
|
||||
});
|
||||
|
||||
function handlerSubmit() {
|
||||
emits('submit');
|
||||
openModal.value = false;
|
||||
}
|
||||
|
||||
// function handleCancel() {
|
||||
// emits('cancel');
|
||||
// openModal.value = false;
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sheet v-model:open="openModal">
|
||||
<SheetTrigger>
|
||||
<slot name="trigger"></slot>
|
||||
</SheetTrigger>
|
||||
<SheetContent :style="contentStyle" class="!w-full pb-12 sm:rounded-l-lg">
|
||||
<SheetHeader
|
||||
:class="description ? 'h-16' : 'h-12'"
|
||||
class="border-border flex flex-row items-center justify-between border-b pl-5 pr-3"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div>
|
||||
<SheetTitle class="text-left text-lg">{{ title }}</SheetTitle>
|
||||
<SheetDescription class="text-muted-foreground text-xs">
|
||||
{{ description }}
|
||||
</SheetDescription>
|
||||
</div>
|
||||
<slot v-if="slots.extra" name="extra"></slot>
|
||||
</div>
|
||||
<SheetClose
|
||||
as-child
|
||||
class="data-[state=open]:bg-secondary cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<VbenIconButton>
|
||||
<Cross2Icon class="size-4" />
|
||||
</VbenIconButton>
|
||||
</SheetClose>
|
||||
</SheetHeader>
|
||||
<div class="h-full pb-16">
|
||||
<ScrollArea class="h-full">
|
||||
<slot></slot>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<SheetFooter v-if="showFooter || slots.footer" as-child>
|
||||
<div
|
||||
class="border-border absolute bottom-0 flex h-12 w-full items-center justify-end border-t"
|
||||
>
|
||||
<slot v-if="slots.footer" name="footer"></slot>
|
||||
<template v-else>
|
||||
<SheetClose as-child>
|
||||
<VbenButton class="mr-2" variant="outline">
|
||||
{{ cancelText }}
|
||||
</VbenButton>
|
||||
</SheetClose>
|
||||
<VbenButton @click="handlerSubmit">{{ submitText }}</VbenButton>
|
||||
</template>
|
||||
</div>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as VbenTooltip } from './tooltip.vue';
|
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { TooltipContentProps } from 'radix-vue';
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '#/components/ui/tooltip';
|
||||
|
||||
interface Props {
|
||||
delayDuration?: number;
|
||||
side: TooltipContentProps['side'];
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
delayDuration: 0,
|
||||
side: 'right',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipProvider :delay-duration="delayDuration">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child tabindex="-1">
|
||||
<slot name="trigger"></slot>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
:side="side"
|
||||
class="side-content text-popover-foreground bg-popover"
|
||||
>
|
||||
<slot></slot>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</template>
|
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type AlertDialogEmits,
|
||||
type AlertDialogProps,
|
||||
AlertDialogRoot,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogProps>();
|
||||
const emits = defineEmits<AlertDialogEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</AlertDialogRoot>
|
||||
</template>
|
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue';
|
||||
|
||||
import { buttonVariants } from '#/components/ui/button';
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogActionProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogAction
|
||||
v-bind="delegatedProps"
|
||||
:class="cn(buttonVariants(), props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogAction>
|
||||
</template>
|
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue';
|
||||
|
||||
import { buttonVariants } from '#/components/ui/button';
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogCancelProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogCancel
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogCancel>
|
||||
</template>
|
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import {
|
||||
AlertDialogContent,
|
||||
type AlertDialogContentEmits,
|
||||
type AlertDialogContentProps,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogContentProps
|
||||
>();
|
||||
const emits = defineEmits<AlertDialogContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay
|
||||
class="bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[1000] backdrop-blur-sm"
|
||||
/>
|
||||
<AlertDialogContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-1/2 top-1/2 z-[1000] grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</template>
|
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import {
|
||||
AlertDialogDescription,
|
||||
type AlertDialogDescriptionProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogDescriptionProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogDescription
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogDescription>
|
||||
</template>
|
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</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>
|
||||
<div
|
||||
:class="cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogTitleProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogTitle
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-lg font-semibold', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogTitle>
|
||||
</template>
|
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogTriggerProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogTrigger v-bind="props">
|
||||
<slot></slot>
|
||||
</AlertDialogTrigger>
|
||||
</template>
|
@@ -0,0 +1,9 @@
|
||||
export { default as AlertDialog } from './AlertDialog.vue';
|
||||
export { default as AlertDialogAction } from './AlertDialogAction.vue';
|
||||
export { default as AlertDialogCancel } from './AlertDialogCancel.vue';
|
||||
export { default as AlertDialogContent } from './AlertDialogContent.vue';
|
||||
export { default as AlertDialogDescription } from './AlertDialogDescription.vue';
|
||||
export { default as AlertDialogFooter } from './AlertDialogFooter.vue';
|
||||
export { default as AlertDialogHeader } from './AlertDialogHeader.vue';
|
||||
export { default as AlertDialogTitle } from './AlertDialogTitle.vue';
|
||||
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue';
|
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { AvatarRoot } from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
import { type AvatarVariants, avatarVariant } from './avatar';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
shape?: AvatarVariants['shape'];
|
||||
size?: AvatarVariants['size'];
|
||||
}>(),
|
||||
{
|
||||
shape: 'circle',
|
||||
size: 'sm',
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AvatarRoot :class="cn(avatarVariant({ size, shape }), props.class)">
|
||||
<slot></slot>
|
||||
</AvatarRoot>
|
||||
</template>
|
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AvatarFallbackProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AvatarFallback v-bind="props">
|
||||
<slot></slot>
|
||||
</AvatarFallback>
|
||||
</template>
|
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { AvatarImage, type AvatarImageProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AvatarImageProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AvatarImage v-bind="props" class="h-full w-full object-cover" />
|
||||
</template>
|
@@ -0,0 +1,20 @@
|
||||
import { type VariantProps, cva } from 'class-variance-authority';
|
||||
|
||||
export const avatarVariant = cva(
|
||||
'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden',
|
||||
{
|
||||
variants: {
|
||||
shape: {
|
||||
circle: 'rounded-full',
|
||||
square: 'rounded-md',
|
||||
},
|
||||
size: {
|
||||
base: 'h-16 w-16 text-2xl',
|
||||
lg: 'h-32 w-32 text-5xl',
|
||||
sm: 'h-10 w-10 text-xs',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type AvatarVariants = VariantProps<typeof avatarVariant>;
|
@@ -0,0 +1,4 @@
|
||||
export { default as Avatar } from './Avatar.vue';
|
||||
export { default as AvatarFallback } from './AvatarFallback.vue';
|
||||
export { default as AvatarImage } from './AvatarImage.vue';
|
||||
export * from './avatar';
|
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
import { type BadgeVariants, badgeVariants } from './badge';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
variant?: BadgeVariants['variant'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(badgeVariants({ variant }), props.class)">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,23 @@
|
||||
import { type VariantProps, cva } from 'class-variance-authority';
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
{
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-accent hover:bg-accent text-primary-foreground shadow',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
||||
outline: 'text-foreground',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>;
|
@@ -0,0 +1,3 @@
|
||||
export { default as Badge } from './Badge.vue';
|
||||
|
||||
export * from './badge';
|
@@ -0,0 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav aria-label="breadcrumb" role="navigation" :class="props.class">
|
||||
<slot></slot>
|
||||
</nav>
|
||||
</template>
|
@@ -0,0 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { DotsHorizontalIcon } from '@radix-icons/vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
:class="cn('flex h-9 w-9 items-center justify-center', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<DotsHorizontalIcon class="h-4 w-4" />
|
||||
</slot>
|
||||
<span class="sr-only">More</span>
|
||||
</span>
|
||||
</template>
|
@@ -0,0 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
:class="
|
||||
cn('hover:text-foreground inline-flex items-center gap-1.5', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
@@ -0,0 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ class?: HTMLAttributes['class'] } & PrimitiveProps>(),
|
||||
{
|
||||
as: 'a',
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn('hover:text-foreground transition-colors', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</Primitive>
|
||||
</template>
|
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ol
|
||||
:class="
|
||||
cn(
|
||||
'text-muted-foreground flex flex-wrap items-center gap-1.5 break-words text-sm sm:gap-2.5',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ol>
|
||||
</template>
|
@@ -0,0 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
:class="cn('text-foreground font-normal', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { ChevronRightIcon } from '@radix-icons/vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
:class="cn('[&>svg]:size-3.5', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronRightIcon />
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
@@ -0,0 +1,7 @@
|
||||
export { default as Breadcrumb } from './Breadcrumb.vue';
|
||||
export { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue';
|
||||
export { default as BreadcrumbItem } from './BreadcrumbItem.vue';
|
||||
export { default as BreadcrumbLink } from './BreadcrumbLink.vue';
|
||||
export { default as BreadcrumbList } from './BreadcrumbList.vue';
|
||||
export { default as BreadcrumbPage } from './BreadcrumbPage.vue';
|
||||
export { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue';
|
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
import { type ButtonVariants, buttonVariants } from './button';
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
class?: HTMLAttributes['class'];
|
||||
size?: ButtonVariants['size'];
|
||||
variant?: 'heavy' & ButtonVariants['variant'];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
class: '',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</Primitive>
|
||||
</template>
|
@@ -0,0 +1,36 @@
|
||||
import { type VariantProps, cva } from 'class-variance-authority';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
defaultVariants: {
|
||||
size: 'default',
|
||||
variant: 'default',
|
||||
},
|
||||
variants: {
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
icon: 'h-8 w-8 rounded-sm px-1 text-lg',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
xs: 'h-8 w-8 rounded-sm px-1 text-xs',
|
||||
},
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
heavy: 'hover:bg-heavy hover:text-heavy-foreground',
|
||||
icon: 'hover:bg-accent hover:text-accent-foreground text-foreground/80',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
outline:
|
||||
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
@@ -0,0 +1,3 @@
|
||||
export { default as Button } from './Button.vue';
|
||||
|
||||
export * from './button';
|
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue';
|
||||
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { CheckIcon } from '@radix-icons/vue';
|
||||
import {
|
||||
CheckboxIndicator,
|
||||
CheckboxRoot,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & CheckboxRootProps
|
||||
>();
|
||||
const emits = defineEmits<CheckboxRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CheckboxRoot
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground border-border peer h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<CheckboxIndicator
|
||||
class="flex h-full w-full items-center justify-center text-current"
|
||||
>
|
||||
<slot>
|
||||
<CheckIcon class="h-4 w-4" />
|
||||
</slot>
|
||||
</CheckboxIndicator>
|
||||
</CheckboxRoot>
|
||||
</template>
|
@@ -0,0 +1 @@
|
||||
export { default as Checkbox } from './Checkbox.vue';
|
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuRootEmits, ContextMenuRootProps } from 'radix-vue';
|
||||
|
||||
import { ContextMenuRoot, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuRootProps>();
|
||||
const emits = defineEmits<ContextMenuRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</ContextMenuRoot>
|
||||
</template>
|
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { CheckIcon } from '@radix-icons/vue';
|
||||
import {
|
||||
ContextMenuCheckboxItem,
|
||||
type ContextMenuCheckboxItemEmits,
|
||||
type ContextMenuCheckboxItemProps,
|
||||
ContextMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & ContextMenuCheckboxItemProps
|
||||
>();
|
||||
const emits = defineEmits<ContextMenuCheckboxItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuCheckboxItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuItemIndicator>
|
||||
<CheckIcon class="h-4 w-4" />
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</ContextMenuCheckboxItem>
|
||||
</template>
|
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import {
|
||||
ContextMenuContent,
|
||||
type ContextMenuContentEmits,
|
||||
type ContextMenuContentProps,
|
||||
ContextMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & ContextMenuContentProps
|
||||
>();
|
||||
const emits = defineEmits<ContextMenuContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal>
|
||||
<ContextMenuContent
|
||||
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-[1000] min-w-32 overflow-hidden rounded-md border p-1 shadow-md',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ContextMenuContent>
|
||||
</ContextMenuPortal>
|
||||
</template>
|
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ContextMenuGroup, type ContextMenuGroupProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuGroupProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuGroup v-bind="props">
|
||||
<slot></slot>
|
||||
</ContextMenuGroup>
|
||||
</template>
|
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import {
|
||||
ContextMenuItem,
|
||||
type ContextMenuItemEmits,
|
||||
type ContextMenuItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class']; inset?: boolean } & ContextMenuItemProps
|
||||
>();
|
||||
const emits = defineEmits<ContextMenuItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ContextMenuItem>
|
||||
</template>
|
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class']; inset?: boolean } & ContextMenuLabelProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</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></slot>
|
||||
</ContextMenuLabel>
|
||||
</template>
|
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ContextMenuPortal, type ContextMenuPortalProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuPortalProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal v-bind="props">
|
||||
<slot></slot>
|
||||
</ContextMenuPortal>
|
||||
</template>
|
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ContextMenuRadioGroup,
|
||||
type ContextMenuRadioGroupEmits,
|
||||
type ContextMenuRadioGroupProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuRadioGroupProps>();
|
||||
const emits = defineEmits<ContextMenuRadioGroupEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRadioGroup v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</ContextMenuRadioGroup>
|
||||
</template>
|
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue';
|
||||
|
||||
import { DotFilledIcon } from '@radix-icons/vue';
|
||||
import {
|
||||
ContextMenuItemIndicator,
|
||||
ContextMenuRadioItem,
|
||||
type ContextMenuRadioItemEmits,
|
||||
type ContextMenuRadioItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '#/lib/utils';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & ContextMenuRadioItemProps
|
||||
>();
|
||||
const emits = defineEmits<ContextMenuRadioItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRadioItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuItemIndicator>
|
||||
<DotFilledIcon class="h-4 w-4 fill-current" />
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</ContextMenuRadioItem>
|
||||
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user