提交新版本
This commit is contained in:
54
packages/@core/ui-kit/shadcn-ui/package.json
Normal file
54
packages/@core/ui-kit/shadcn-ui/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "5.5.9",
|
||||
"#main": "./dist/index.mjs",
|
||||
"#module": "./dist/index.mjs",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "packages/@vben-core/uikit/shadcn-ui"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"#build": "pnpm unbuild",
|
||||
"#prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./src/index.ts",
|
||||
"//default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/composables": "workspace:*",
|
||||
"@vben-core/icons": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"class-variance-authority": "catalog:",
|
||||
"lucide-vue-next": "catalog:",
|
||||
"reka-ui": "catalog:",
|
||||
"vee-validate": "catalog:",
|
||||
"vue": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
AvatarFallbackProps,
|
||||
AvatarImageProps,
|
||||
AvatarRootProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '../../ui';
|
||||
|
||||
interface Props extends AvatarFallbackProps, AvatarImageProps, AvatarRootProps {
|
||||
alt?: string;
|
||||
class?: ClassType;
|
||||
dot?: boolean;
|
||||
dotClass?: ClassType;
|
||||
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||
size?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
alt: 'avatar',
|
||||
as: 'button',
|
||||
dot: false,
|
||||
dotClass: 'bg-green-500',
|
||||
fit: 'cover',
|
||||
});
|
||||
|
||||
const imageStyle = computed<CSSProperties>(() => {
|
||||
const { fit } = props;
|
||||
if (fit) {
|
||||
return { objectFit: fit };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
return props.alt.slice(-2).toUpperCase();
|
||||
});
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
return props.size !== undefined && props.size > 0
|
||||
? {
|
||||
height: `${props.size}px`,
|
||||
width: `${props.size}px`,
|
||||
}
|
||||
: {};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="props.class"
|
||||
:style="rootStyle"
|
||||
class="relative flex flex-shrink-0 items-center"
|
||||
>
|
||||
<Avatar :class="props.class" class="size-full">
|
||||
<AvatarImage :alt="alt" :src="src" :style="imageStyle" />
|
||||
<AvatarFallback>{{ text }}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span
|
||||
v-if="dot"
|
||||
:class="dotClass"
|
||||
class="absolute bottom-0 right-0 size-3 rounded-full border-2 border-background"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BacktopProps } from './backtop';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { ArrowUpToLine } from '@vben-core/icons';
|
||||
|
||||
import { VbenButton } from '../button';
|
||||
import { useBackTop } from './use-backtop';
|
||||
|
||||
interface Props extends BacktopProps {}
|
||||
|
||||
defineOptions({ name: 'BackTop' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
bottom: 20,
|
||||
isGroup: false,
|
||||
right: 24,
|
||||
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="data z-popup fixed bottom-10 size-10 rounded-full bg-background shadow-float duration-500 hover:bg-heavy dark:bg-accent dark:hover:bg-heavy"
|
||||
size="icon"
|
||||
variant="icon"
|
||||
@click="handleClick"
|
||||
>
|
||||
<ArrowUpToLine class="size-4" />
|
||||
</VbenButton>
|
||||
</transition>
|
||||
</template>
|
||||
@@ -0,0 +1,109 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BreadcrumbProps } from './types';
|
||||
|
||||
import { VbenIcon } from '../icon';
|
||||
|
||||
interface Props extends BreadcrumbProps {}
|
||||
|
||||
defineOptions({ name: 'Breadcrumb' });
|
||||
const { breadcrumbs, showIcon } = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{ select: [string] }>();
|
||||
|
||||
function handleClick(index: number, path?: string) {
|
||||
if (!path || index === breadcrumbs.length - 1) {
|
||||
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(index, item.path)"
|
||||
>
|
||||
<span class="flex-center z-10 h-full">
|
||||
<VbenIcon
|
||||
v-if="showIcon"
|
||||
:icon="item.icon"
|
||||
class="mr-1 size-4 flex-shrink-0"
|
||||
/>
|
||||
<span
|
||||
:class="{
|
||||
'font-normal text-foreground':
|
||||
index === breadcrumbs.length - 1,
|
||||
}"
|
||||
>{{ item.title }}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
</TransitionGroup>
|
||||
</ul>
|
||||
</template>
|
||||
<style scoped>
|
||||
li {
|
||||
@apply h-7;
|
||||
}
|
||||
|
||||
li a {
|
||||
@apply relative mr-9 flex h-7 items-center bg-accent py-0 pl-[5px] pr-2 text-[13px] text-muted-foreground;
|
||||
}
|
||||
|
||||
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 absolute top-0 h-0 w-0 border-[.875rem] border-solid border-accent content-[''];
|
||||
}
|
||||
|
||||
li a::before {
|
||||
@apply -left-7 z-10 border-l-transparent;
|
||||
}
|
||||
|
||||
li a::after {
|
||||
@apply left-full border-transparent border-l-accent;
|
||||
}
|
||||
|
||||
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,39 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BreadcrumbProps } from './types';
|
||||
|
||||
import { useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import BreadcrumbBackground from './breadcrumb-background.vue';
|
||||
import Breadcrumb from './breadcrumb.vue';
|
||||
|
||||
interface Props extends BreadcrumbProps {
|
||||
class?: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const emit = defineEmits<{ select: [string] }>();
|
||||
|
||||
const forward = useForwardPropsEmits(props, emit);
|
||||
</script>
|
||||
<template>
|
||||
<Breadcrumb
|
||||
v-if="styleType === 'normal'"
|
||||
v-bind="forward"
|
||||
class="vben-breadcrumb"
|
||||
/>
|
||||
<BreadcrumbBackground
|
||||
v-if="styleType === 'background'"
|
||||
v-bind="forward"
|
||||
class="vben-breadcrumb"
|
||||
/>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
/** 修复全局引入Antd时,ol和ul的默认样式会被修改的问题 */
|
||||
.vben-breadcrumb {
|
||||
:deep(ol),
|
||||
:deep(ul) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { AsTag } from 'reka-ui';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { ButtonVariants, ButtonVariantSize } from '../../ui';
|
||||
|
||||
export interface VbenButtonProps {
|
||||
/**
|
||||
* The element or component this component should render as. Can be overwrite by `asChild`
|
||||
* @defaultValue "div"
|
||||
*/
|
||||
as?: AsTag | Component;
|
||||
/**
|
||||
* Change the default rendered element for the one passed as a child, merging their props and behavior.
|
||||
*
|
||||
* Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details.
|
||||
*/
|
||||
asChild?: boolean;
|
||||
class?: any;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
size?: ButtonVariantSize;
|
||||
variant?: ButtonVariants;
|
||||
}
|
||||
|
||||
export type CustomRenderType = (() => Component | string) | string;
|
||||
|
||||
export type ValueType = boolean | number | string;
|
||||
|
||||
export interface VbenButtonGroupProps
|
||||
extends Pick<VbenButtonProps, 'disabled'> {
|
||||
/** 单选模式下允许清除选中 */
|
||||
allowClear?: boolean;
|
||||
/** 值改变前的回调 */
|
||||
beforeChange?: (
|
||||
value: ValueType,
|
||||
isChecked: boolean,
|
||||
) => boolean | PromiseLike<boolean | undefined> | undefined;
|
||||
/** 按钮样式 */
|
||||
btnClass?: any;
|
||||
/** 按钮间隔距离 */
|
||||
gap?: number;
|
||||
/** 多选模式下限制最多选择的数量。0表示不限制 */
|
||||
maxCount?: number;
|
||||
/** 是否允许多选 */
|
||||
multiple?: boolean;
|
||||
/** 选项 */
|
||||
options?: { [key: string]: any; label: CustomRenderType; value: ValueType }[];
|
||||
/** 显示图标 */
|
||||
showIcon?: boolean;
|
||||
/** 尺寸 */
|
||||
size?: 'large' | 'middle' | 'small';
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenButtonProps } from './button';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { LoaderCircle } from '@vben-core/icons';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Primitive } from 'reka-ui';
|
||||
|
||||
import { buttonVariants } from '../../ui';
|
||||
|
||||
interface Props extends VbenButtonProps {}
|
||||
|
||||
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"
|
||||
>
|
||||
<LoaderCircle
|
||||
v-if="loading"
|
||||
class="text-md mr-2 size-4 flex-shrink-0 animate-spin"
|
||||
/>
|
||||
<slot></slot>
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'reka-ui';
|
||||
|
||||
import { useId } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import { Checkbox } from '../../ui/checkbox';
|
||||
|
||||
const props = defineProps<CheckboxRootProps & { indeterminate?: boolean }>();
|
||||
|
||||
const emits = defineEmits<CheckboxRootEmits>();
|
||||
|
||||
const checked = defineModel<boolean>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
|
||||
const id = useId();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<Checkbox v-bind="forwarded" :id="id" v-model="checked" />
|
||||
<label :for="id" class="ml-2 cursor-pointer text-sm"> <slot></slot> </label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ContextMenuContentProps,
|
||||
ContextMenuRootEmits,
|
||||
ContextMenuRootProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import type { IContextMenuItem } from './interface';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuTrigger,
|
||||
} from '../../ui/context-menu';
|
||||
|
||||
const props = defineProps<
|
||||
ContextMenuRootProps & {
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: ContextMenuContentProps;
|
||||
handlerData?: Record<string, any>;
|
||||
itemClass?: ClassType;
|
||||
menus: (data: any) => IContextMenuItem[];
|
||||
}
|
||||
>();
|
||||
|
||||
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-popup"
|
||||
>
|
||||
<template v-for="menu in menusView" :key="menu.key">
|
||||
<ContextMenuItem
|
||||
:class="itemClass"
|
||||
:disabled="menu.disabled"
|
||||
:inset="menu.inset || !menu.icon"
|
||||
class="cursor-pointer"
|
||||
@click="handleClick(menu)"
|
||||
>
|
||||
<component
|
||||
:is="menu.icon"
|
||||
v-if="menu.icon"
|
||||
class="mr-2 size-4 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,49 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
DropdownMenuProps,
|
||||
VbenDropdownMenuItem as IDropdownMenuItem,
|
||||
} from './interface';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '../../ui';
|
||||
|
||||
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.value">
|
||||
<DropdownMenuItem
|
||||
:disabled="menu.disabled"
|
||||
class="mb-1 cursor-pointer text-foreground/80 data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground"
|
||||
@click="handleItemClick(menu)"
|
||||
>
|
||||
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
||||
{{ menu.label }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator v-if="menu.separator" class="bg-border" />
|
||||
</template>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DropdownMenuProps } from './interface';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '../../ui';
|
||||
|
||||
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 as-child class="flex items-center gap-1">
|
||||
<slot></slot>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<template v-for="menu in menus" :key="menu.key">
|
||||
<DropdownMenuItem
|
||||
:class="
|
||||
menu.value === modelValue
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: ''
|
||||
"
|
||||
class="mb-1 cursor-pointer text-foreground/80 data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground"
|
||||
@click="handleItemClick(menu.value)"
|
||||
>
|
||||
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
||||
<span
|
||||
v-if="!menu.icon"
|
||||
:class="menu.value === modelValue ? 'bg-foreground' : ''"
|
||||
class="mr-2 size-1.5 rounded-full"
|
||||
></span>
|
||||
{{ menu.label }}
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { Maximize, Minimize } from '@vben-core/icons';
|
||||
|
||||
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
|
||||
class="hover:animate-[shrink_0.3s_ease-in-out]"
|
||||
@click="toggle"
|
||||
>
|
||||
<Minimize v-if="isFullscreen" class="size-4 text-foreground" />
|
||||
<Maximize v-else class="size-4 text-foreground" />
|
||||
</VbenIconButton>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
HoverCardContentProps,
|
||||
HoverCardRootEmits,
|
||||
HoverCardRootProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '../../ui';
|
||||
|
||||
interface Props extends HoverCardRootProps {
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: HoverCardContentProps;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
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-popup"
|
||||
>
|
||||
<slot></slot>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</template>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as VbenHoverCard } from './hover-card.vue';
|
||||
export type { HoverCardContentProps } from 'reka-ui';
|
||||
@@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, useSlots } from 'vue';
|
||||
|
||||
import { Eye, EyeOff } from '@vben-core/icons';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Input } from '../../ui';
|
||||
import PasswordStrength from './password-strength.vue';
|
||||
|
||||
interface Props {
|
||||
class?: any;
|
||||
/**
|
||||
* 是否显示密码强度
|
||||
*/
|
||||
passwordStrength?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const modelValue = defineModel<string>();
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const show = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full">
|
||||
<Input
|
||||
v-bind="$attrs"
|
||||
v-model="modelValue"
|
||||
:class="cn(props.class)"
|
||||
:type="show ? 'text' : 'password'"
|
||||
/>
|
||||
<template v-if="passwordStrength">
|
||||
<PasswordStrength :password="modelValue" />
|
||||
<p v-if="slots.strengthText" class="mt-1.5 text-xs text-muted-foreground">
|
||||
<slot name="strengthText"> </slot>
|
||||
</p>
|
||||
</template>
|
||||
<div
|
||||
:class="{
|
||||
'top-3': !!passwordStrength,
|
||||
'top-1/2 -translate-y-1/2 items-center': !passwordStrength,
|
||||
}"
|
||||
class="absolute inset-y-0 right-0 flex cursor-pointer pr-3 text-lg leading-5 text-foreground/60 hover:text-foreground"
|
||||
@click="show = !show"
|
||||
>
|
||||
<Eye v-if="show" class="size-4" />
|
||||
<EyeOff v-else class="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
</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="relative mr-1 h-1.5 w-1/5 rounded-sm bg-heavy last:mr-0 dark:bg-input-background"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
backgroundColor: currentColor,
|
||||
width: currentStrength >= index ? '100%' : '',
|
||||
}"
|
||||
class="absolute left-0 h-full w-0 rounded-sm transition-all duration-500"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
92
packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue
Normal file
92
packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { VbenAvatar } from '../avatar';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @zh_CN 是否收起文本
|
||||
*/
|
||||
collapsed?: boolean;
|
||||
/**
|
||||
* @zh_CN Logo 图片适应方式
|
||||
*/
|
||||
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||
/**
|
||||
* @zh_CN Logo 跳转地址
|
||||
*/
|
||||
href?: string;
|
||||
/**
|
||||
* @zh_CN Logo 图片大小
|
||||
*/
|
||||
logoSize?: number;
|
||||
/**
|
||||
* @zh_CN Logo 图标
|
||||
*/
|
||||
src?: string;
|
||||
/**
|
||||
* @zh_CN 暗色主题 Logo 图标 (可选,若不设置则使用 src)
|
||||
*/
|
||||
srcDark?: string;
|
||||
/**
|
||||
* @zh_CN Logo 文本
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* @zh_CN Logo 主题
|
||||
*/
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'VbenLogo',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
collapsed: false,
|
||||
href: 'javascript:void 0',
|
||||
logoSize: 32,
|
||||
src: '',
|
||||
srcDark: '',
|
||||
theme: 'light',
|
||||
fit: 'cover',
|
||||
});
|
||||
|
||||
/**
|
||||
* @zh_CN 根据主题选择合适的 logo 图标
|
||||
*/
|
||||
const logoSrc = computed(() => {
|
||||
// 如果是暗色主题且提供了 srcDark,则使用暗色主题的 logo
|
||||
if (props.theme === 'dark' && props.srcDark) {
|
||||
return props.srcDark;
|
||||
}
|
||||
// 否则使用默认的 src
|
||||
return props.src;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="theme" class="flex h-full items-center text-lg">
|
||||
<a
|
||||
:class="$attrs.class"
|
||||
:href="href"
|
||||
class="flex h-full items-center gap-2 overflow-hidden px-3 text-lg leading-normal transition-all duration-500"
|
||||
>
|
||||
<VbenAvatar
|
||||
v-if="logoSrc"
|
||||
:alt="text"
|
||||
:src="logoSrc"
|
||||
:size="logoSize"
|
||||
:fit="fit"
|
||||
class="relative rounded-none bg-transparent"
|
||||
/>
|
||||
<template v-if="!collapsed">
|
||||
<slot name="text">
|
||||
<span class="truncate text-nowrap font-semibold text-foreground">
|
||||
{{ text }}
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
import type { PinInputProps } from './types';
|
||||
|
||||
import { computed, onBeforeUnmount, ref, useId, watch } from 'vue';
|
||||
|
||||
import { PinInput, PinInputGroup, PinInputInput } from '../../ui';
|
||||
import { VbenButton } from '../button';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const {
|
||||
codeLength = 6,
|
||||
createText = async () => {},
|
||||
disabled = false,
|
||||
handleSendCode = async () => {},
|
||||
loading = false,
|
||||
maxTime = 60,
|
||||
} = defineProps<PinInputProps>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
complete: [];
|
||||
sendError: [error: any];
|
||||
}>();
|
||||
|
||||
const timer = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const modelValue = defineModel<string>();
|
||||
|
||||
const inputValue = ref<string[]>([]);
|
||||
const countdown = ref(0);
|
||||
|
||||
const btnText = computed(() => {
|
||||
const countdownValue = countdown.value;
|
||||
return createText?.(countdownValue);
|
||||
});
|
||||
|
||||
const btnLoading = computed(() => {
|
||||
return loading || countdown.value > 0;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => modelValue.value,
|
||||
() => {
|
||||
inputValue.value = modelValue.value?.split('') ?? [];
|
||||
},
|
||||
);
|
||||
|
||||
watch(inputValue, (val) => {
|
||||
modelValue.value = val.join('');
|
||||
});
|
||||
|
||||
function handleComplete(e: string[]) {
|
||||
modelValue.value = e.join('');
|
||||
emit('complete');
|
||||
}
|
||||
|
||||
async function handleSend(e: Event) {
|
||||
try {
|
||||
e?.preventDefault();
|
||||
countdown.value = maxTime;
|
||||
startCountdown();
|
||||
await handleSendCode();
|
||||
} catch (error) {
|
||||
console.error('Failed to send code:', error);
|
||||
// Consider emitting an error event or showing a notification
|
||||
emit('sendError', error);
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
if (countdown.value > 0) {
|
||||
timer.value = setTimeout(() => {
|
||||
countdown.value--;
|
||||
startCountdown();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
countdown.value = 0;
|
||||
clearTimeout(timer.value);
|
||||
});
|
||||
|
||||
const id = useId();
|
||||
|
||||
const pinType = 'text' as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PinInput
|
||||
:id="id"
|
||||
v-model="inputValue"
|
||||
:disabled="disabled"
|
||||
class="flex w-full justify-between"
|
||||
otp
|
||||
placeholder="○"
|
||||
:type="pinType"
|
||||
@complete="handleComplete"
|
||||
>
|
||||
<div class="relative flex w-full">
|
||||
<PinInputGroup class="mr-2">
|
||||
<PinInputInput
|
||||
v-for="(item, index) in codeLength"
|
||||
:key="item"
|
||||
:index="index"
|
||||
/>
|
||||
</PinInputGroup>
|
||||
<VbenButton
|
||||
:disabled="disabled"
|
||||
:loading="btnLoading"
|
||||
class="flex-grow"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click="handleSend"
|
||||
>
|
||||
{{ btnText }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
</PinInput>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
PopoverContentProps,
|
||||
PopoverRootEmits,
|
||||
PopoverRootProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import {
|
||||
PopoverContent,
|
||||
Popover as PopoverRoot,
|
||||
PopoverTrigger,
|
||||
} from '../../ui';
|
||||
|
||||
interface Props extends PopoverRootProps {
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: PopoverContentProps;
|
||||
triggerClass?: ClassType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const emits = defineEmits<PopoverRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _cls,
|
||||
contentClass: _,
|
||||
contentProps: _cProps,
|
||||
triggerClass: _tClass,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverRoot v-bind="forwarded">
|
||||
<PopoverTrigger :class="triggerClass">
|
||||
<slot name="trigger"></slot>
|
||||
|
||||
<PopoverContent
|
||||
:class="contentClass"
|
||||
class="side-content z-popup"
|
||||
v-bind="contentProps"
|
||||
>
|
||||
<slot></slot>
|
||||
</PopoverContent>
|
||||
</PopoverTrigger>
|
||||
</PopoverRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ScrollArea, ScrollBar } from '../../ui';
|
||||
|
||||
interface Props {
|
||||
class?: ClassType;
|
||||
horizontal?: boolean;
|
||||
scrollBarClass?: ClassType;
|
||||
shadow?: boolean;
|
||||
shadowBorder?: boolean;
|
||||
shadowBottom?: boolean;
|
||||
shadowLeft?: boolean;
|
||||
shadowRight?: boolean;
|
||||
shadowTop?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
class: '',
|
||||
horizontal: false,
|
||||
shadow: false,
|
||||
shadowBorder: false,
|
||||
shadowBottom: true,
|
||||
shadowLeft: false,
|
||||
shadowRight: false,
|
||||
shadowTop: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
scrollAt: [{ bottom: boolean; left: boolean; right: boolean; top: boolean }];
|
||||
}>();
|
||||
|
||||
const isAtTop = ref(true);
|
||||
const isAtRight = ref(false);
|
||||
const isAtBottom = ref(false);
|
||||
const isAtLeft = ref(true);
|
||||
|
||||
/**
|
||||
* We have to check if the scroll amount is close enough to some threshold in order to
|
||||
* more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
|
||||
* numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
|
||||
*/
|
||||
const ARRIVED_STATE_THRESHOLD_PIXELS = 1;
|
||||
|
||||
const showShadowTop = computed(() => props.shadow && props.shadowTop);
|
||||
const showShadowBottom = computed(() => props.shadow && props.shadowBottom);
|
||||
const showShadowLeft = computed(() => props.shadow && props.shadowLeft);
|
||||
const showShadowRight = computed(() => props.shadow && props.shadowRight);
|
||||
|
||||
const computedShadowClasses = computed(() => {
|
||||
return {
|
||||
'both-shadow':
|
||||
!isAtLeft.value &&
|
||||
!isAtRight.value &&
|
||||
showShadowLeft.value &&
|
||||
showShadowRight.value,
|
||||
'left-shadow': !isAtLeft.value && showShadowLeft.value,
|
||||
'right-shadow': !isAtRight.value && showShadowRight.value,
|
||||
};
|
||||
});
|
||||
|
||||
function handleScroll(event: Event) {
|
||||
const target = event.target as HTMLElement;
|
||||
const scrollTop = target?.scrollTop ?? 0;
|
||||
const scrollLeft = target?.scrollLeft ?? 0;
|
||||
const clientHeight = target?.clientHeight ?? 0;
|
||||
const clientWidth = target?.clientWidth ?? 0;
|
||||
const scrollHeight = target?.scrollHeight ?? 0;
|
||||
const scrollWidth = target?.scrollWidth ?? 0;
|
||||
isAtTop.value = scrollTop <= 0;
|
||||
isAtLeft.value = scrollLeft <= 0;
|
||||
isAtBottom.value =
|
||||
Math.abs(scrollTop) + clientHeight >=
|
||||
scrollHeight - ARRIVED_STATE_THRESHOLD_PIXELS;
|
||||
isAtRight.value =
|
||||
Math.abs(scrollLeft) + clientWidth >=
|
||||
scrollWidth - ARRIVED_STATE_THRESHOLD_PIXELS;
|
||||
|
||||
emit('scrollAt', {
|
||||
bottom: isAtBottom.value,
|
||||
left: isAtLeft.value,
|
||||
right: isAtRight.value,
|
||||
top: isAtTop.value,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScrollArea
|
||||
:class="[cn(props.class), computedShadowClasses]"
|
||||
:on-scroll="handleScroll"
|
||||
class="vben-scrollbar relative"
|
||||
>
|
||||
<div
|
||||
v-if="showShadowTop"
|
||||
:class="{
|
||||
'opacity-100': !isAtTop,
|
||||
'border-t border-border': shadowBorder && !isAtTop,
|
||||
}"
|
||||
class="scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
|
||||
></div>
|
||||
<slot></slot>
|
||||
<div
|
||||
v-if="showShadowBottom"
|
||||
:class="{
|
||||
'opacity-100': !isAtTop && !isAtBottom,
|
||||
'border-b border-border': shadowBorder && !isAtTop && !isAtBottom,
|
||||
}"
|
||||
class="scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
|
||||
></div>
|
||||
<ScrollBar
|
||||
v-if="horizontal"
|
||||
:class="scrollBarClass"
|
||||
orientation="horizontal"
|
||||
/>
|
||||
</ScrollArea>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vben-scrollbar {
|
||||
&:not(.both-shadow).left-shadow {
|
||||
mask-image: linear-gradient(90deg, transparent, #000 16px);
|
||||
}
|
||||
|
||||
&:not(.both-shadow).right-shadow {
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
#000 0%,
|
||||
#000 calc(100% - 16px),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&.both-shadow {
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
#000 16px,
|
||||
#000 calc(100% - 16px),
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-top-shadow {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--scroll-shadow, var(--background))),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.scrollbar-bottom-shadow {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
hsl(var(--scroll-shadow, var(--background))),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import type { SegmentedItem } from './types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { TabsTrigger } from 'reka-ui';
|
||||
|
||||
import { Tabs, TabsContent, TabsList } from '../../ui';
|
||||
import TabsIndicator from './tabs-indicator.vue';
|
||||
|
||||
interface Props {
|
||||
defaultValue?: string;
|
||||
tabs?: SegmentedItem[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultValue: '',
|
||||
tabs: () => [],
|
||||
});
|
||||
|
||||
const activeTab = defineModel<string>();
|
||||
|
||||
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)}%`,
|
||||
};
|
||||
});
|
||||
|
||||
function activeClass(tab: string): string[] {
|
||||
return tab === activeTab.value ? ['!font-bold', 'text-primary'] : [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tabs v-model="activeTab" :default-value="getDefaultValue">
|
||||
<TabsList
|
||||
:style="tabsStyle"
|
||||
class="relative grid w-full bg-accent !outline !outline-2 !outline-heavy"
|
||||
>
|
||||
<TabsIndicator :style="tabsIndicatorStyle" />
|
||||
<template v-for="tab in tabs" :key="tab.value">
|
||||
<TabsTrigger
|
||||
:value="tab.value"
|
||||
:class="activeClass(tab.value)"
|
||||
class="z-20 inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium hover:text-primary 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,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { TabsIndicatorProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { TabsIndicator, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<TabsIndicatorProps & { class?: any }>();
|
||||
|
||||
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-[--reka-tabs-indicator-position] rounded-full px-0 py-1 pr-0.5 transition-[width,transform] duration-300',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="inline-flex h-full w-full items-center justify-center whitespace-nowrap rounded-md bg-background px-3 py-1 text-sm font-medium text-foreground 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,140 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
/**
|
||||
* @zh_CN 最小加载时间
|
||||
* @en_US Minimum loading time
|
||||
*/
|
||||
minLoadingTime?: number;
|
||||
|
||||
/**
|
||||
* @zh_CN loading状态开启
|
||||
*/
|
||||
spinning?: boolean;
|
||||
/**
|
||||
* @zh_CN 文字
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'VbenLoading',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
minLoadingTime: 50,
|
||||
text: '',
|
||||
});
|
||||
// const startTime = ref(0);
|
||||
const showSpinner = ref(false);
|
||||
const renderSpinner = ref(false);
|
||||
const timer = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
watch(
|
||||
() => props.spinning,
|
||||
(show) => {
|
||||
if (!show) {
|
||||
showSpinner.value = false;
|
||||
clearTimeout(timer.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// startTime.value = performance.now();
|
||||
timer.value = setTimeout(() => {
|
||||
// const loadingTime = performance.now() - startTime.value;
|
||||
|
||||
showSpinner.value = true;
|
||||
if (showSpinner.value) {
|
||||
renderSpinner.value = true;
|
||||
}
|
||||
}, props.minLoadingTime);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function onTransitionEnd() {
|
||||
if (!showSpinner.value) {
|
||||
renderSpinner.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'absolute left-0 top-0 z-100 flex size-full flex-col items-center justify-center bg-overlay-content transition-all duration-500 dark:bg-overlay',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
@transitionend="onTransitionEnd"
|
||||
>
|
||||
<slot name="icon" v-if="renderSpinner">
|
||||
<span class="dot relative inline-block size-9 text-3xl">
|
||||
<i
|
||||
v-for="index in 4"
|
||||
:key="index"
|
||||
class="absolute block size-4 origin-[50%_50%] scale-75 rounded-full bg-primary opacity-30"
|
||||
></i>
|
||||
</span>
|
||||
</slot>
|
||||
|
||||
<div v-if="text" class="mt-4 text-xs text-primary">{{ text }}</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dot {
|
||||
transform: rotate(45deg);
|
||||
animation: rotate-ani 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.dot i {
|
||||
animation: spin-move-ani 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.dot i:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dot i:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
@keyframes rotate-ani {
|
||||
to {
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-move-ani {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
/**
|
||||
* @zh_CN 最小加载时间
|
||||
* @en_US Minimum loading time
|
||||
*/
|
||||
minLoadingTime?: number;
|
||||
/**
|
||||
* @zh_CN loading状态开启
|
||||
*/
|
||||
spinning?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'VbenSpinner',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
minLoadingTime: 50,
|
||||
});
|
||||
// const startTime = ref(0);
|
||||
const showSpinner = ref(false);
|
||||
const renderSpinner = ref(false);
|
||||
const timer = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
watch(
|
||||
() => props.spinning,
|
||||
(show) => {
|
||||
if (!show) {
|
||||
showSpinner.value = false;
|
||||
clearTimeout(timer.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// startTime.value = performance.now();
|
||||
timer.value = setTimeout(() => {
|
||||
// const loadingTime = performance.now() - startTime.value;
|
||||
|
||||
showSpinner.value = true;
|
||||
if (showSpinner.value) {
|
||||
renderSpinner.value = true;
|
||||
}
|
||||
}, props.minLoadingTime);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function onTransitionEnd() {
|
||||
if (!showSpinner.value) {
|
||||
renderSpinner.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex-center absolute left-0 top-0 z-100 size-full bg-overlay-content backdrop-blur-sm transition-all duration-500',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
@transitionend="onTransitionEnd"
|
||||
>
|
||||
<div
|
||||
:class="{ paused: !renderSpinner }"
|
||||
v-if="renderSpinner"
|
||||
class="loader relative size-12 before:absolute before:left-0 before:top-[60px] before:h-[5px] before:w-12 before:rounded-[50%] before:bg-primary/50 before:content-[''] after:absolute after:left-0 after:top-0 after:h-full after:w-full after:rounded after:bg-primary after:content-['']"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.paused {
|
||||
&::before {
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
|
||||
&::after {
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
&::before {
|
||||
animation: loader-shadow-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
&::after {
|
||||
animation: loader-jump-ani 0.5s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-jump-ani {
|
||||
15% {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(9px) rotate(22.5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-bottom-right-radius: 40px;
|
||||
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(9px) rotate(67.5deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-shadow-ani {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { CircleHelp } from 'lucide-vue-next';
|
||||
|
||||
import Tooltip from './tooltip.vue';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
defineProps<{ triggerClass?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tooltip :delay-duration="300" side="right">
|
||||
<template #trigger>
|
||||
<slot name="trigger">
|
||||
<CircleHelp
|
||||
:class="
|
||||
cn(
|
||||
'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',
|
||||
triggerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</Tooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import type { TooltipContentProps } from 'reka-ui';
|
||||
|
||||
import type { StyleValue } from 'vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '../../ui';
|
||||
|
||||
interface Props {
|
||||
contentClass?: ClassType;
|
||||
contentStyle?: StyleValue;
|
||||
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
|
||||
:class="contentClass"
|
||||
:side="side"
|
||||
:style="contentStyle"
|
||||
class="side-content rounded-md bg-accent text-popover-foreground"
|
||||
>
|
||||
<slot></slot>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
3
packages/@core/ui-kit/shadcn-ui/src/index.ts
Normal file
3
packages/@core/ui-kit/shadcn-ui/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './components';
|
||||
export * from './ui';
|
||||
export { createContext, Slot, VisuallyHidden } from 'reka-ui';
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { AccordionRootEmits, AccordionRootProps } from 'reka-ui';
|
||||
|
||||
import { AccordionRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AccordionRootProps>();
|
||||
const emits = defineEmits<AccordionRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</AccordionRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { AccordionContentProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AccordionContent } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AccordionContentProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionContent
|
||||
v-bind="delegatedProps"
|
||||
class="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
>
|
||||
<div :class="cn('pb-4 pt-0', props.class)">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { AccordionItemProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AccordionItem, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AccordionItemProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionItem v-bind="forwardedProps" :class="cn('border-b', props.class)">
|
||||
<slot></slot>
|
||||
</AccordionItem>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import type { AccordionTriggerProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import { AccordionHeader, AccordionTrigger } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AccordionTriggerProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AccordionHeader class="flex">
|
||||
<AccordionTrigger
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
<slot name="icon">
|
||||
<ChevronDown
|
||||
class="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"
|
||||
/>
|
||||
</slot>
|
||||
</AccordionTrigger>
|
||||
</AccordionHeader>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogEmits, AlertDialogProps } from 'reka-ui';
|
||||
|
||||
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
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,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogActionProps } from 'reka-ui';
|
||||
|
||||
import { AlertDialogAction } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AlertDialogActionProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogAction v-bind="props">
|
||||
<slot></slot>
|
||||
</AlertDialogAction>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogCancelProps } from 'reka-ui';
|
||||
|
||||
import { AlertDialogCancel } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AlertDialogCancelProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogCancel v-bind="props">
|
||||
<slot></slot>
|
||||
</AlertDialogCancel>
|
||||
</template>
|
||||
@@ -0,0 +1,98 @@
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogContentEmits, AlertDialogContentProps } from 'reka-ui';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
AlertDialogContent,
|
||||
AlertDialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
import AlertDialogOverlay from './AlertDialogOverlay.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
AlertDialogContentProps & {
|
||||
centered?: boolean;
|
||||
class?: ClassType;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
zIndex?: number;
|
||||
}
|
||||
>(),
|
||||
{ modal: true },
|
||||
);
|
||||
const emits = defineEmits<
|
||||
AlertDialogContentEmits & { close: []; closed: []; opened: [] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, modal: _modal, open: _open, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
const contentRef = ref<InstanceType<typeof AlertDialogContent> | null>(null);
|
||||
function onAnimationEnd(event: AnimationEvent) {
|
||||
// 只有在 contentRef 的动画结束时才触发 opened/closed 事件
|
||||
if (event.target === contentRef.value?.$el) {
|
||||
if (props.open) {
|
||||
emits('opened');
|
||||
} else {
|
||||
emits('closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
getContentRef: () => contentRef.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<Transition name="fade" appear>
|
||||
<AlertDialogOverlay
|
||||
v-if="open && modal"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position: 'fixed',
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
@click="() => emits('close')"
|
||||
/>
|
||||
</Transition>
|
||||
<AlertDialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
||||
{
|
||||
'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':
|
||||
!centered,
|
||||
'data-[state=closed]:slide-out-to-top-[148%] data-[state=open]:slide-in-from-top-[98%]':
|
||||
centered,
|
||||
'top-[10vh]': !centered,
|
||||
'top-1/2 -translate-y-1/2': centered,
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AlertDialogDescriptionProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AlertDialogDescription, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AlertDialogDescriptionProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogDescription
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogDescription>
|
||||
</template>
|
||||
@@ -0,0 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { useScrollLock } from '@vben-core/composables';
|
||||
|
||||
useScrollLock();
|
||||
</script>
|
||||
<template>
|
||||
<div class="z-popup inset-0 bg-overlay"></div>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogTitleProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AlertDialogTitle, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AlertDialogTitleProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogTitle
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn('text-lg font-semibold leading-none tracking-tight', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogTitle>
|
||||
</template>
|
||||
27
packages/@core/ui-kit/shadcn-ui/src/ui/avatar/Avatar.vue
Normal file
27
packages/@core/ui-kit/shadcn-ui/src/ui/avatar/Avatar.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import type { AvatarVariants } from './avatar';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AvatarRoot } from 'reka-ui';
|
||||
|
||||
import { avatarVariant } from './avatar';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: any;
|
||||
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,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { AvatarFallbackProps } from 'reka-ui';
|
||||
|
||||
import { AvatarFallback } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AvatarFallbackProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AvatarFallback v-bind="props">
|
||||
<slot></slot>
|
||||
</AvatarFallback>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { AvatarImageProps } from 'reka-ui';
|
||||
|
||||
import { AvatarImage } from 'reka-ui';
|
||||
|
||||
const props = defineProps<AvatarImageProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AvatarImage v-bind="props" class="h-full w-full object-cover" />
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
:class="
|
||||
cn('inline-flex items-center gap-1.5 hover:text-foreground', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PrimitiveProps } from 'reka-ui';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Primitive } from 'reka-ui';
|
||||
|
||||
const props = withDefaults(defineProps<PrimitiveProps & { class?: any }>(), {
|
||||
as: 'a',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn('transition-colors hover:text-foreground', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ol
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ol>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
:class="cn('font-normal text-foreground', props.class)"
|
||||
aria-current="page"
|
||||
aria-disabled="true"
|
||||
role="link"
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
32
packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue
Normal file
32
packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui';
|
||||
|
||||
import type { ButtonVariants, ButtonVariantSize } from './types';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Primitive } from 'reka-ui';
|
||||
|
||||
import { buttonVariants } from './button';
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
class?: any;
|
||||
size?: ButtonVariantSize;
|
||||
variant?: ButtonVariants;
|
||||
}
|
||||
|
||||
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>
|
||||
20
packages/@core/ui-kit/shadcn-ui/src/ui/card/Card.vue
Normal file
20
packages/@core/ui-kit/shadcn-ui/src/ui/card/Card.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'rounded-xl border border-border bg-card text-card-foreground',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<slot></slot>
|
||||
</p>
|
||||
</template>
|
||||
43
packages/@core/ui-kit/shadcn-ui/src/ui/checkbox/Checkbox.vue
Normal file
43
packages/@core/ui-kit/shadcn-ui/src/ui/checkbox/Checkbox.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Check, Minus } from 'lucide-vue-next';
|
||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
CheckboxRootProps & { class?: any; indeterminate?: boolean }
|
||||
>();
|
||||
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(
|
||||
'peer h-4 w-4 shrink-0 rounded-sm border border-border transition hover:border-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<CheckboxIndicator
|
||||
class="flex h-full w-full items-center justify-center text-current"
|
||||
>
|
||||
<slot>
|
||||
<component :is="indeterminate ? Minus : Check" class="h-4 w-4" />
|
||||
</slot>
|
||||
</CheckboxIndicator>
|
||||
</CheckboxRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuRootEmits, ContextMenuRootProps } from 'reka-ui';
|
||||
|
||||
import { ContextMenuRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = withDefaults(defineProps<ContextMenuRootProps>(), {
|
||||
modal: false,
|
||||
});
|
||||
const emits = defineEmits<ContextMenuRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</ContextMenuRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ContextMenuCheckboxItemEmits,
|
||||
ContextMenuCheckboxItemProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import {
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuCheckboxItemProps & { class?: any }>();
|
||||
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(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground 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>
|
||||
<Check class="h-4 w-4" />
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</ContextMenuCheckboxItem>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuContentEmits, ContextMenuContentProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
ContextMenuContent,
|
||||
ContextMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuContentProps & { class?: any }>();
|
||||
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(
|
||||
'z-popup min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md 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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ContextMenuContent>
|
||||
</ContextMenuPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuGroupProps } from 'reka-ui';
|
||||
|
||||
import { ContextMenuGroup } from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuGroupProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuGroup v-bind="props">
|
||||
<slot></slot>
|
||||
</ContextMenuGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuItemEmits, ContextMenuItemProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ContextMenuItem, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
ContextMenuItemProps & { class?: any; inset?: boolean }
|
||||
>();
|
||||
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(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuLabelProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ContextMenuLabel } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
ContextMenuLabelProps & { class?: any; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuLabel
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'px-2 py-1.5 text-sm font-semibold text-foreground',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ContextMenuLabel>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuPortalProps } from 'reka-ui';
|
||||
|
||||
import { ContextMenuPortal } from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuPortalProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal v-bind="props">
|
||||
<slot></slot>
|
||||
</ContextMenuPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ContextMenuRadioGroupEmits,
|
||||
ContextMenuRadioGroupProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { ContextMenuRadioGroup, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuRadioGroupProps>();
|
||||
const emits = defineEmits<ContextMenuRadioGroupEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRadioGroup v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</ContextMenuRadioGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ContextMenuRadioItemEmits,
|
||||
ContextMenuRadioItemProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Circle } from 'lucide-vue-next';
|
||||
import {
|
||||
ContextMenuItemIndicator,
|
||||
ContextMenuRadioItem,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuRadioItemProps & { class?: any }>();
|
||||
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(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground 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>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</ContextMenuRadioItem>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuSeparatorProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ContextMenuSeparator } from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuSeparatorProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSeparator
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('-mx-1 my-1 h-px bg-border', props.class)"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
:class="
|
||||
cn('ml-auto text-xs tracking-widest text-muted-foreground', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuSubEmits, ContextMenuSubProps } from 'reka-ui';
|
||||
|
||||
import { ContextMenuSub, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuSubProps>();
|
||||
const emits = defineEmits<ContextMenuSubEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSub v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</ContextMenuSub>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DropdownMenuSubContentEmits,
|
||||
DropdownMenuSubContentProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ContextMenuSubContent, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: any }>();
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSubContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 min-w-32 overflow-hidden rounded-md',
|
||||
'border border-border',
|
||||
'bg-popover p-1 text-popover-foreground shadow-lg',
|
||||
'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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</ContextMenuSubContent>
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuSubTriggerProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ChevronRight } from 'lucide-vue-next';
|
||||
import { ContextMenuSubTrigger, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
ContextMenuSubTriggerProps & {
|
||||
class?: any;
|
||||
inset?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSubTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</ContextMenuSubTrigger>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuTriggerProps } from 'reka-ui';
|
||||
|
||||
import { ContextMenuTrigger, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<ContextMenuTriggerProps>();
|
||||
|
||||
const forwardedProps = useForwardProps(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuTrigger v-bind="forwardedProps">
|
||||
<slot></slot>
|
||||
</ContextMenuTrigger>
|
||||
</template>
|
||||
16
packages/@core/ui-kit/shadcn-ui/src/ui/dialog/Dialog.vue
Normal file
16
packages/@core/ui-kit/shadcn-ui/src/ui/dialog/Dialog.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogRootEmits, DialogRootProps } from 'reka-ui';
|
||||
|
||||
import { DialogRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DialogRootProps>();
|
||||
const emits = defineEmits<DialogRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogCloseProps } from 'reka-ui';
|
||||
|
||||
import { DialogClose } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DialogCloseProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogClose v-bind="props">
|
||||
<slot></slot>
|
||||
</DialogClose>
|
||||
</template>
|
||||
131
packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue
Normal file
131
packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { DialogClose, DialogContent, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import DialogOverlay from './DialogOverlay.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
DialogContentProps & {
|
||||
animationType?: 'scale' | 'slide';
|
||||
appendTo?: HTMLElement | string;
|
||||
class?: ClassType;
|
||||
closeClass?: ClassType;
|
||||
closeDisabled?: boolean;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
showClose?: boolean;
|
||||
zIndex?: number;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
appendTo: 'body',
|
||||
animationType: 'slide',
|
||||
closeDisabled: false,
|
||||
showClose: true,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<
|
||||
DialogContentEmits & { close: []; closed: []; opened: [] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _,
|
||||
modal: _modal,
|
||||
open: _open,
|
||||
showClose: __,
|
||||
animationType: ___,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
function isAppendToBody() {
|
||||
return (
|
||||
props.appendTo === 'body' ||
|
||||
props.appendTo === document.body ||
|
||||
!props.appendTo
|
||||
);
|
||||
}
|
||||
|
||||
const position = computed(() => {
|
||||
return isAppendToBody() ? 'fixed' : 'absolute';
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
|
||||
function onAnimationEnd(event: AnimationEvent) {
|
||||
// 只有在 contentRef 的动画结束时才触发 opened/closed 事件
|
||||
if (event.target === contentRef.value?.$el) {
|
||||
if (props.open) {
|
||||
emits('opened');
|
||||
} else {
|
||||
emits('closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
getContentRef: () => contentRef.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport defer :to="appendTo">
|
||||
<Transition name="fade">
|
||||
<DialogOverlay
|
||||
v-if="open && modal"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
@click="() => emits('close')"
|
||||
/>
|
||||
</Transition>
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position }"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup w-full bg-background p-6 shadow-lg outline-none 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 sm:rounded-xl',
|
||||
{
|
||||
'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':
|
||||
animationType === 'slide',
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
<DialogClose
|
||||
v-if="showClose"
|
||||
:disabled="closeDisabled"
|
||||
:class="
|
||||
cn(
|
||||
'flex-center absolute right-3 top-3 h-6 w-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
|
||||
props.closeClass,
|
||||
)
|
||||
"
|
||||
@click="() => emits('close')"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</Teleport>
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogDescriptionProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DialogDescription, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</DialogDescription>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { useScrollLock } from '@vben-core/composables';
|
||||
|
||||
useScrollLock();
|
||||
const id = inject('DISMISSABLE_MODAL_ID');
|
||||
</script>
|
||||
<template>
|
||||
<div :data-dismissable-modal="id" class="z-popup inset-0 bg-overlay"></div>
|
||||
</template>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { X } from 'lucide-vue-next';
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DialogContentProps & { class?: any; zIndex?: number }>(),
|
||||
{ zIndex: 1000 },
|
||||
);
|
||||
const emits = defineEmits<DialogContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
:style="{ zIndex }"
|
||||
class="absolute inset-0 grid place-items-center overflow-y-auto border border-border bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||
>
|
||||
<DialogContent
|
||||
:class="
|
||||
cn(
|
||||
'relative z-50 my-8 grid w-full max-w-lg gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
:style="{ zIndex }"
|
||||
v-bind="forwarded"
|
||||
@pointer-down-outside="
|
||||
(event) => {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target as HTMLElement;
|
||||
if (
|
||||
originalEvent.offsetX > target.clientWidth ||
|
||||
originalEvent.offsetY > target.clientHeight
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
<DialogClose
|
||||
class="absolute right-4 top-4 rounded-md p-0.5 transition-colors hover:bg-secondary"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogOverlay>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogTitleProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DialogTitle, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn('text-lg font-semibold leading-none tracking-tight', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</DialogTitle>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogTriggerProps } from 'reka-ui';
|
||||
|
||||
import { DialogTrigger } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DialogTriggerProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTrigger v-bind="props">
|
||||
<slot></slot>
|
||||
</DialogTrigger>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuRootEmits, DropdownMenuRootProps } from 'reka-ui';
|
||||
|
||||
import { DropdownMenuRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = withDefaults(defineProps<DropdownMenuRootProps>(), {
|
||||
modal: false,
|
||||
});
|
||||
const emits = defineEmits<DropdownMenuRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</DropdownMenuRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DropdownMenuCheckboxItemEmits,
|
||||
DropdownMenuCheckboxItemProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: any }>();
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground 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">
|
||||
<DropdownMenuItemIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</DropdownMenuCheckboxItem>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DropdownMenuContentEmits,
|
||||
DropdownMenuContentProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: any }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<DropdownMenuContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md 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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuGroupProps } from 'reka-ui';
|
||||
|
||||
import { DropdownMenuGroup } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuGroupProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuGroup v-bind="props">
|
||||
<slot></slot>
|
||||
</DropdownMenuGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuItemProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DropdownMenuItem, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuItemProps & { class?: any; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuItem
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuLabelProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DropdownMenuLabel, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuLabelProps & { class?: any; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuLabel
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</DropdownMenuLabel>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DropdownMenuRadioGroupEmits,
|
||||
DropdownMenuRadioGroupProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { DropdownMenuRadioGroup, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuRadioGroupProps>();
|
||||
const emits = defineEmits<DropdownMenuRadioGroupEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioGroup v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</DropdownMenuRadioGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DropdownMenuRadioItemEmits,
|
||||
DropdownMenuRadioItemProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Circle } from 'lucide-vue-next';
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: any }>();
|
||||
|
||||
const emits = defineEmits<DropdownMenuRadioItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground 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">
|
||||
<DropdownMenuItemIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</DropdownMenuRadioItem>
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSeparatorProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DropdownMenuSeparator } from 'reka-ui';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuSeparatorProps & {
|
||||
class?: any;
|
||||
}
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSeparator
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('-mx-1 my-1 h-px bg-border', props.class)"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSubEmits, DropdownMenuSubProps } from 'reka-ui';
|
||||
|
||||
import { DropdownMenuSub, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuSubProps>();
|
||||
const emits = defineEmits<DropdownMenuSubEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSub v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</DropdownMenuSub>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DropdownMenuSubContentEmits,
|
||||
DropdownMenuSubContentProps,
|
||||
} from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { DropdownMenuSubContent, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: any }>();
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 min-w-32 overflow-hidden rounded-md',
|
||||
'border border-border',
|
||||
'bg-popover p-1 text-popover-foreground shadow-lg',
|
||||
'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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</DropdownMenuSubContent>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSubTriggerProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ChevronRight } from 'lucide-vue-next';
|
||||
import { DropdownMenuSubTrigger, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuSubTrigger>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuTriggerProps } from 'reka-ui';
|
||||
|
||||
import { DropdownMenuTrigger, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuTriggerProps>();
|
||||
|
||||
const forwardedProps = useForwardProps(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuTrigger class="outline-none" v-bind="forwardedProps">
|
||||
<slot></slot>
|
||||
</DropdownMenuTrigger>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
export { default as DropdownMenu } from './DropdownMenu.vue';
|
||||
|
||||
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue';
|
||||
export { default as DropdownMenuContent } from './DropdownMenuContent.vue';
|
||||
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue';
|
||||
export { default as DropdownMenuItem } from './DropdownMenuItem.vue';
|
||||
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue';
|
||||
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue';
|
||||
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue';
|
||||
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue';
|
||||
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue';
|
||||
export { default as DropdownMenuSub } from './DropdownMenuSub.vue';
|
||||
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue';
|
||||
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue';
|
||||
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue';
|
||||
export { DropdownMenuPortal } from 'reka-ui';
|
||||
19
packages/@core/ui-kit/shadcn-ui/src/ui/form/FormControl.vue
Normal file
19
packages/@core/ui-kit/shadcn-ui/src/ui/form/FormControl.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { Slot } from 'reka-ui';
|
||||
|
||||
import { useFormField } from './useFormField';
|
||||
|
||||
const { error, formDescriptionId, formItemId, formMessageId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot
|
||||
:id="formItemId"
|
||||
:aria-describedby="
|
||||
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||
"
|
||||
:aria-invalid="!!error"
|
||||
>
|
||||
<slot></slot>
|
||||
</Slot>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { useFormField } from './useFormField';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
}>();
|
||||
|
||||
const { formDescriptionId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p
|
||||
:id="formDescriptionId"
|
||||
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</p>
|
||||
</template>
|
||||
18
packages/@core/ui-kit/shadcn-ui/src/ui/form/FormLabel.vue
Normal file
18
packages/@core/ui-kit/shadcn-ui/src/ui/form/FormLabel.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LabelProps } from 'reka-ui';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Label } from '../label';
|
||||
import { useFormField } from './useFormField';
|
||||
|
||||
const props = defineProps<LabelProps & { class?: any }>();
|
||||
|
||||
const { formItemId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label :class="cn(props.class)" :for="formItemId">
|
||||
<slot></slot>
|
||||
</Label>
|
||||
</template>
|
||||
18
packages/@core/ui-kit/shadcn-ui/src/ui/form/FormMessage.vue
Normal file
18
packages/@core/ui-kit/shadcn-ui/src/ui/form/FormMessage.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { toValue } from 'vue';
|
||||
|
||||
import { ErrorMessage } from 'vee-validate';
|
||||
|
||||
import { useFormField } from './useFormField';
|
||||
|
||||
const { formMessageId, name } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ErrorMessage
|
||||
:id="formMessageId"
|
||||
:name="toValue(name)"
|
||||
as="p"
|
||||
class="text-[0.8rem] text-destructive"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { HoverCardRootEmits, HoverCardRootProps } from 'reka-ui';
|
||||
|
||||
import { HoverCardRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<HoverCardRootProps>();
|
||||
const emits = defineEmits<HoverCardRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HoverCardRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</HoverCardRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import type { HoverCardContentProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { HoverCardContent, HoverCardPortal, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<HoverCardContentProps & { class?: any }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
);
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HoverCardPortal>
|
||||
<HoverCardContent
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup w-64 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none 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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</HoverCardContent>
|
||||
</HoverCardPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { HoverCardTriggerProps } from 'reka-ui';
|
||||
|
||||
import { HoverCardTrigger } from 'reka-ui';
|
||||
|
||||
const props = defineProps<HoverCardTriggerProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HoverCardTrigger v-bind="props">
|
||||
<slot></slot>
|
||||
</HoverCardTrigger>
|
||||
</template>
|
||||
37
packages/@core/ui-kit/shadcn-ui/src/ui/input/Input.vue
Normal file
37
packages/@core/ui-kit/shadcn-ui/src/ui/input/Input.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
defaultValue?: number | string;
|
||||
modelValue?: number | string;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: number | string): void;
|
||||
}>();
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
defaultValue: props.defaultValue,
|
||||
passive: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
input {
|
||||
--ring: var(--primary);
|
||||
}
|
||||
</style>
|
||||
31
packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue
Normal file
31
packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import type { LabelProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Label } from 'reka-ui';
|
||||
|
||||
const props = defineProps<LabelProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</Label>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { NumberFieldRootEmits, NumberFieldRootProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { NumberFieldRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<NumberFieldRootProps & { class?: any }>();
|
||||
const emits = defineEmits<NumberFieldRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldRoot v-bind="forwarded" :class="cn('grid gap-1.5', props.class)">
|
||||
<slot></slot>
|
||||
</NumberFieldRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { NumberFieldDecrementProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Minus } from 'lucide-vue-next';
|
||||
import { NumberFieldDecrement, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<NumberFieldDecrementProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldDecrement
|
||||
data-slot="decrement"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'absolute left-0 top-1/2 -translate-y-1/2 p-3 disabled:cursor-not-allowed disabled:opacity-20',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot>
|
||||
<Minus class="h-4 w-4" />
|
||||
</slot>
|
||||
</NumberFieldDecrement>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { NumberFieldIncrementProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Plus } from 'lucide-vue-next';
|
||||
import { NumberFieldIncrement, useForwardProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<NumberFieldIncrementProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldIncrement
|
||||
data-slot="increment"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'absolute right-0 top-1/2 -translate-y-1/2 p-3 disabled:cursor-not-allowed disabled:opacity-20',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot>
|
||||
<Plus class="h-4 w-4" />
|
||||
</slot>
|
||||
</NumberFieldIncrement>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { NumberFieldInput } from 'reka-ui';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldInput
|
||||
:class="
|
||||
cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent py-1 text-center text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
)
|
||||
"
|
||||
data-slot="input"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationEllipsisProps } from 'reka-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { MoreHorizontal } from 'lucide-vue-next';
|
||||
import { PaginationEllipsis } from 'reka-ui';
|
||||
|
||||
const props = defineProps<PaginationEllipsisProps & { class?: any }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationEllipsis
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('flex size-8 items-center justify-center', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<MoreHorizontal class="size-4" />
|
||||
</slot>
|
||||
</PaginationEllipsis>
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user