├── .nvmrc ├── .gitattributes ├── packages ├── constants │ ├── index.ts │ ├── key.ts │ └── package.json ├── play │ ├── README.md │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.ts │ │ └── stories │ │ │ ├── Collapse.stories.ts │ │ │ └── Alert.stories.tsx │ ├── .storybook │ │ ├── preview.js │ │ └── main.js │ ├── index.html │ ├── vite.config.ts │ ├── package.json │ └── public │ │ └── vite.svg ├── docs │ ├── .gitignore │ ├── demo │ │ ├── tooltip │ │ │ ├── Slot.vue │ │ │ └── Disabled.vue │ │ ├── alert │ │ │ ├── Desc.vue │ │ │ ├── ShowIcon.vue │ │ │ ├── Theme.vue │ │ │ ├── TextCenter.vue │ │ │ ├── Close.vue │ │ │ ├── Basic.vue │ │ │ └── IconDesc.vue │ │ ├── popconfirm │ │ │ ├── Custom.vue │ │ │ ├── Callback.vue │ │ │ └── Basic.vue │ │ ├── button │ │ │ ├── Tag.vue │ │ │ ├── Icon.vue │ │ │ ├── Throttle.vue │ │ │ ├── Loading.vue │ │ │ ├── Group.vue │ │ │ ├── Disabled.vue │ │ │ ├── Size.vue │ │ │ └── Basic.vue │ │ ├── message │ │ │ ├── Center.vue │ │ │ ├── Basic.vue │ │ │ ├── Test.vue │ │ │ ├── Type.vue │ │ │ └── Closeable.vue │ │ ├── notification │ │ │ ├── Closeable.vue │ │ │ ├── Basic.vue │ │ │ └── Type.vue │ │ ├── messagebox │ │ │ ├── Alert.vue │ │ │ ├── Prompt.vue │ │ │ ├── Confirm.vue │ │ │ ├── Center.vue │ │ │ ├── VNode.vue │ │ │ └── Custom.vue │ │ ├── dropdown │ │ │ ├── Size.vue │ │ │ ├── Command.vue │ │ │ ├── SplitButton.vue │ │ │ ├── InstanceMethod.vue │ │ │ ├── Disabled.vue │ │ │ ├── HideOnClick.vue │ │ │ ├── Basic.vue │ │ │ └── Trigger.vue │ │ ├── loading │ │ │ ├── Fullscreen.vue │ │ │ ├── Custom.vue │ │ │ └── Basic.vue │ │ ├── form │ │ │ ├── Basic.vue │ │ │ ├── Validate.vue │ │ │ ├── CustomValidate.vue │ │ │ └── Position.vue │ │ └── collapse │ │ │ ├── Accordion.vue │ │ │ ├── Disabled.vue │ │ │ ├── Basic.vue │ │ │ └── CustomTitle.vue │ ├── package.json │ ├── .vitepress │ │ ├── theme │ │ │ └── index.ts │ │ └── config.ts │ ├── index.md │ ├── get-started.md │ └── components │ │ ├── collapse.md │ │ ├── notification.md │ │ └── popconfirm.md ├── theme │ ├── package.json │ └── reset.css ├── README.md ├── utils │ ├── package.json │ ├── style.ts │ ├── error.ts │ ├── index.ts │ ├── __tests__ │ │ ├── error.test.tsx │ │ ├── index.test.tsx │ │ └── install.test.tsx │ └── install.ts ├── components │ ├── Icon │ │ ├── index.ts │ │ ├── style.css │ │ ├── Icon.vue │ │ └── types.ts │ ├── Alert │ │ ├── index.ts │ │ ├── types.ts │ │ └── Alert.vue │ ├── Input │ │ ├── index.ts │ │ └── types.ts │ ├── Switch │ │ ├── index.ts │ │ ├── types.ts │ │ └── Switch.test.tsx │ ├── Upload │ │ ├── index.ts │ │ └── types.ts │ ├── Tooltip │ │ ├── index.ts │ │ ├── types.ts │ │ └── useEventsToTiggerNode.ts │ ├── ConfigProvider │ │ ├── types.ts │ │ ├── index.ts │ │ ├── constants.ts │ │ └── ConfigProvider.vue │ ├── Popconfirm │ │ ├── index.ts │ │ ├── style.css │ │ └── types.ts │ ├── Collapse │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── types.ts │ │ ├── transitionEvents.ts │ │ ├── CollapseItem.vue │ │ ├── Collapse.vue │ │ └── style.css │ ├── Dropdown │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── types.ts │ │ ├── DropdownItem.vue │ │ └── style.css │ ├── Message │ │ ├── index.ts │ │ ├── types.ts │ │ └── Message.test.tsx │ ├── Button │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── ButtonGroup.vue │ │ └── types.ts │ ├── Notification │ │ ├── index.ts │ │ ├── Notification.test.tsx │ │ └── types.ts │ ├── Overlay │ │ ├── types.ts │ │ └── Overlay.vue │ ├── Select │ │ ├── index.ts │ │ ├── constants.ts │ │ ├── Option.vue │ │ ├── types.ts │ │ ├── useKeyMap.ts │ │ └── style.css │ ├── Form │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── hooks.ts │ │ └── style.css │ ├── package.json │ ├── index.ts │ ├── Loading │ │ ├── index.ts │ │ ├── types.ts │ │ ├── Loading.test.tsx │ │ ├── Loading.vue │ │ ├── style.css │ │ └── directive.ts │ ├── MessageBox │ │ └── index.ts │ └── index.test.ts ├── locale │ ├── package.json │ └── index.ts ├── hooks │ ├── __test__ │ │ ├── index.test.tsx │ │ ├── useClickOutset.test.tsx │ │ └── useEventListener.test.tsx │ ├── useProp.ts │ ├── useId.ts │ ├── useClickOutside.ts │ ├── useZIndex.ts │ ├── package.json │ ├── index.ts │ ├── useEventListener.ts │ ├── useOffset.ts │ ├── useLocale.ts │ ├── vite.config.ts │ ├── useDisabledStyle.ts │ └── useFocusController.ts └── core │ ├── index.ts │ ├── makeInstaller.ts │ ├── pringLogo.ts │ ├── componens.ts │ ├── package.json │ └── build │ └── vite.umd.config.ts ├── pnpm-workspace.yaml ├── libs ├── README.md ├── vite-plugins │ ├── index.ts │ ├── .dist │ │ ├── index.d.ts │ │ ├── hooksPlugin.d.ts │ │ └── index.js │ ├── package.json │ ├── hooksPlugin.ts │ └── vite.config.ts └── vitepress-preview-component │ ├── types.d.ts │ ├── .dist │ ├── hooks │ │ ├── use-codefold.d.ts │ │ ├── use-codecopy.d.ts │ │ └── use-namespaces.d.ts │ ├── messages │ │ ├── index.d.ts │ │ └── message-notice.vue.d.ts │ ├── icons │ │ ├── code-copy.vue.d.ts │ │ ├── code-open.vue.d.ts │ │ ├── code-close.vue.d.ts │ │ └── copy-success.vue.d.ts │ ├── index.d.ts │ └── container │ │ ├── naive-ui │ │ └── NaiveUI.vue.d.ts │ │ ├── ant-design-ui │ │ └── AntDesign.vue.d.ts │ │ └── element-plus │ │ └── ElementPlus.vue.d.ts │ ├── hooks │ ├── use-codefold.ts │ ├── use-codecopy.ts │ └── use-namespaces.ts │ ├── index.ts │ ├── tsconfig.json │ ├── vite.config.ts │ ├── icons │ ├── copy-success.vue │ ├── code-close.vue │ ├── code-open.vue │ └── code-copy.vue │ ├── messages │ ├── message-notice.scss │ ├── index.ts │ └── message-notice.vue │ ├── package.json │ ├── styles │ └── various.scss │ └── container │ └── naive-ui │ └── naive-ui.scss ├── env.d.ts ├── .vscode └── extensions.json ├── vitest.setup.ts ├── tsconfig.node.json ├── postcss.config.cjs ├── .gitignore ├── vitest.config.ts ├── README.md ├── tsconfig.json ├── tsconfig.build.json └── .github └── workflows └── test-and-deploy.yml /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.17.0 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /packages/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './key' -------------------------------------------------------------------------------- /packages/play/README.md: -------------------------------------------------------------------------------- 1 | # Eric-UI playground 2 | 3 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | .vitepress/dist 2 | .vitepress/cache 3 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "libs/*" -------------------------------------------------------------------------------- /packages/play/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/constants/key.ts: -------------------------------------------------------------------------------- 1 | export const INSTALLED_KEY = Symbol("INSTALLED_KEY"); 2 | -------------------------------------------------------------------------------- /libs/README.md: -------------------------------------------------------------------------------- 1 | 项目私有库 2 | 3 | - vite-plugins 自定义vite 插件 4 | - vitepress-plugins vitepress 组件预览插件 -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | declare const PROD: boolean; 2 | declare const DEV: boolean; 3 | declare const TEST: boolean; -------------------------------------------------------------------------------- /libs/vite-plugins/index.ts: -------------------------------------------------------------------------------- 1 | import hooksPlugin from "./hooksPlugin"; 2 | 3 | export { hooksPlugin }; 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /libs/vite-plugins/.dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { default as hooksPlugin } from './hooksPlugin'; 2 | 3 | export { hooksPlugin }; 4 | -------------------------------------------------------------------------------- /packages/theme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/theme", 3 | "version": "1.0.0", 4 | "main": "index.css" 5 | } 6 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | # 模块介绍 2 | 3 | - components 组件代码 4 | - core 组件库打包入口 5 | - docs 组件库文档 6 | - play 组件开发实验室 7 | - theme 皮肤样式文件 8 | - utils 工具函数 9 | 10 | -------------------------------------------------------------------------------- /vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { library } from "@fortawesome/fontawesome-svg-core"; 2 | import { fas } from "@fortawesome/free-solid-svg-icons"; 3 | 4 | library.add(fas); -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/utils", 3 | "version": "1.0.0", 4 | "main": "index.ts", 5 | "scripts": { 6 | "test": "vitest --coverage" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions } from 'vue' 3 | 4 | const comp: ComponentOptions 5 | export default comp 6 | } 7 | -------------------------------------------------------------------------------- /packages/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | import Icon from "./Icon.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErIcon = withInstall(Icon); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /packages/components/Alert/index.ts: -------------------------------------------------------------------------------- 1 | import Alert from "./Alert.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErAlert = withInstall(Alert); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /packages/components/Input/index.ts: -------------------------------------------------------------------------------- 1 | import Input from "./Input.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErInput = withInstall(Input); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/hooks/use-codefold.d.ts: -------------------------------------------------------------------------------- 1 | export declare const useCodeFold: () => { 2 | isCodeFold: import('vue').Ref; 3 | setCodeFold: (value: boolean) => void; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/components/Switch/index.ts: -------------------------------------------------------------------------------- 1 | import Switch from "./Switch.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErSwitch = withInstall(Switch); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /packages/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | import Upload from "./Upload.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErUpload = withInstall(Upload); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/hooks/use-codecopy.d.ts: -------------------------------------------------------------------------------- 1 | export declare const useCodeCopy: () => { 2 | copyContent: import('vue').Ref; 3 | clickCopy: (value: string) => Promise; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/components/Tooltip/index.ts: -------------------------------------------------------------------------------- 1 | import Tooltip from "./Tooltip.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErTooltip = withInstall(Tooltip); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /packages/components/ConfigProvider/types.ts: -------------------------------------------------------------------------------- 1 | import type { Language, TranslatePair } from "@eric-ui/locale"; 2 | 3 | export interface ConfigProviderProps { 4 | locale?: Language; 5 | extendsI18nMsg?: TranslatePair; 6 | } 7 | -------------------------------------------------------------------------------- /packages/components/Popconfirm/index.ts: -------------------------------------------------------------------------------- 1 | import Popconfirm from "./Popconfirm.vue"; 2 | import { withInstall } from "@eric-ui/utils"; 3 | 4 | export const ErPopconfirm = withInstall(Popconfirm); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /packages/docs/demo/tooltip/Slot.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/components/Collapse/constants.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from "vue"; 2 | import type { CollapseContext } from "./types"; 3 | 4 | export const COLLAPSE_CTX_KEY: InjectionKey = 5 | Symbol("collapseContext"); 6 | -------------------------------------------------------------------------------- /packages/components/Dropdown/constants.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from "vue"; 2 | import type { DropdownContext } from "./types"; 3 | 4 | export const DROPDOWN_CTX_KEY: InjectionKey = 5 | Symbol("dropdownContext"); 6 | -------------------------------------------------------------------------------- /packages/components/Message/index.ts: -------------------------------------------------------------------------------- 1 | import Message from "./methods"; 2 | import { withInstallFunction } from "@eric-ui/utils"; 3 | 4 | export const ErMessage = withInstallFunction(Message, "$message"); 5 | 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /packages/components/Button/constants.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from "vue"; 2 | import type { ButtonGroupContext } from "./types"; 3 | 4 | export const BUTTON_GROUP_CTX_KEY: InjectionKey = 5 | Symbol("buttonGroupContext"); 6 | -------------------------------------------------------------------------------- /packages/constants/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/constants", 3 | "private":true, 4 | "version": "1.0.0", 5 | "main": "index.ts", 6 | "module": "index.ts", 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC" 10 | } 11 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/messages/index.d.ts: -------------------------------------------------------------------------------- 1 | import { default as MessageNotice } from './message-notice.vue'; 2 | 3 | declare const MessageNoticeService: { 4 | open: () => void; 5 | }; 6 | export { MessageNotice, MessageNoticeService }; 7 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/Desc.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /packages/play/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import EricUI from "eric-ui"; 4 | 5 | import "eric-ui/dist/index.css"; 6 | 7 | const app = createApp(App); 8 | app.use(EricUI); 9 | app.mount("#app"); 10 | -------------------------------------------------------------------------------- /packages/components/ConfigProvider/index.ts: -------------------------------------------------------------------------------- 1 | import ConfigProvider from "./ConfigProvider.vue"; 2 | import {withInstall} from '@eric-ui/utils' 3 | 4 | export const ErConfigProvider = withInstall(ConfigProvider) 5 | 6 | export * from './types' 7 | export * from './hooks' -------------------------------------------------------------------------------- /packages/components/Notification/index.ts: -------------------------------------------------------------------------------- 1 | import Notification from "./methods"; 2 | import { withInstallFunction } from "@eric-ui/utils"; 3 | 4 | export const ErNotification = withInstallFunction( 5 | Notification, 6 | "$notify" 7 | ); 8 | 9 | export * from "./types"; 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": ["packages/**/**.config.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler", 8 | "types": ["node"] 9 | } 10 | } -------------------------------------------------------------------------------- /packages/components/Overlay/types.ts: -------------------------------------------------------------------------------- 1 | export interface OverlayProps { 2 | mask?: boolean; 3 | zIndex?: number; 4 | overlayClass?: string | string[] | Record; 5 | } 6 | 7 | export interface OverlayEmits { 8 | (e: "click", value: MouseEvent): void; 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/Select/index.ts: -------------------------------------------------------------------------------- 1 | import Select from "./Select.vue"; 2 | import Option from "./Option.vue"; 3 | import { withInstall } from "@eric-ui/utils"; 4 | 5 | export const ErSelect = withInstall(Select); 6 | export const ErOption = withInstall(Option); 7 | 8 | export * from "./types"; 9 | -------------------------------------------------------------------------------- /packages/components/Popconfirm/style.css: -------------------------------------------------------------------------------- 1 | .er-popconfirm { 2 | .er-popconfirm__main { 3 | display: flex; 4 | align-items: center; 5 | i { 6 | margin-right: 5px; 7 | } 8 | } 9 | .er-popconfirm__action { 10 | text-align: right; 11 | margin-top: 8px; 12 | } 13 | } -------------------------------------------------------------------------------- /packages/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | import Button from "./Button.vue"; 2 | import ButtonGroup from "./ButtonGroup.vue"; 3 | import { withInstall } from "@eric-ui/utils"; 4 | 5 | export const ErButton = withInstall(Button); 6 | export const ErButtonGroup = withInstall(ButtonGroup); 7 | 8 | export * from "./types"; 9 | -------------------------------------------------------------------------------- /libs/vite-plugins/.dist/hooksPlugin.d.ts: -------------------------------------------------------------------------------- 1 | export default function hooksPlugin({ rmFiles, afterBuild, beforeBuild, }: { 2 | beforeBuild?: Function; 3 | afterBuild?: Function; 4 | rmFiles?: string[]; 5 | }): { 6 | name: string; 7 | buildStart(): void; 8 | buildEnd(err?: Error): void; 9 | }; 10 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/icons/code-copy.vue.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly>, {}, {}>; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/icons/code-open.vue.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly>, {}, {}>; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/hooks/use-codefold.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const useCodeFold = () => { 4 | const isCodeFold = ref(true) 5 | const setCodeFold = (value: boolean) => { 6 | isCodeFold.value = value 7 | } 8 | return { 9 | isCodeFold, 10 | setCodeFold 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/components/Collapse/index.ts: -------------------------------------------------------------------------------- 1 | import Collapse from "./Collapse.vue"; 2 | import CollapseItem from "./CollapseItem.vue"; 3 | import { withInstall } from "@eric-ui/utils"; 4 | 5 | export const ErCollapse = withInstall(Collapse); 6 | export const ErCollapseItem = withInstall(CollapseItem); 7 | 8 | export * from "./types"; 9 | -------------------------------------------------------------------------------- /packages/components/Dropdown/index.ts: -------------------------------------------------------------------------------- 1 | import Dropdown from "./Dropdown.vue"; 2 | import DropdownItem from "./DropdownItem.vue"; 3 | import { withInstall } from "@eric-ui/utils"; 4 | 5 | export const ErDropdown = withInstall(Dropdown); 6 | export const ErDropdownItem = withInstall(DropdownItem); 7 | 8 | export * from "./types"; 9 | -------------------------------------------------------------------------------- /packages/components/Form/constants.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from "vue"; 2 | import type { FormContext, FormItemContext } from "./types"; 3 | 4 | export const FORM_CTX_KEY: InjectionKey = Symbol("formContext"); 5 | export const FORM_ITEM_CTX_KEY: InjectionKey = 6 | Symbol("formItemContext"); 7 | -------------------------------------------------------------------------------- /packages/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | import Form from "./Form.vue"; 2 | import FormItem from "./FormItem.vue"; 3 | import { withInstall } from "@eric-ui/utils"; 4 | 5 | export const ErForm = withInstall(Form); 6 | export const ErFormItem = withInstall(FormItem); 7 | 8 | export * from "./types"; 9 | export * from "./hooks"; 10 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/icons/code-close.vue.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly>, {}, {}>; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/icons/copy-success.vue.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly>, {}, {}>; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/index.ts: -------------------------------------------------------------------------------- 1 | import AntDesignContainer from './container/ant-design-ui/AntDesign.vue' 2 | import ElementPlusContainer from './container/element-plus/ElementPlus.vue' 3 | import NaiveUIContainer from './container/naive-ui/NaiveUI.vue' 4 | 5 | export { AntDesignContainer, ElementPlusContainer, NaiveUIContainer } 6 | -------------------------------------------------------------------------------- /packages/locale/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/locale", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.ts", 6 | "module": "index.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /packages/components/ConfigProvider/constants.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { ConfigProviderProps } from './types' 3 | import type { InjectionKey, Ref } from 'vue' 4 | 5 | export type ConfigProviderContext = Partial 6 | 7 | export const configProviderContextKey: InjectionKey< 8 | Ref 9 | > = Symbol() 10 | -------------------------------------------------------------------------------- /packages/docs/demo/popconfirm/Custom.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/play/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import 'eric-ui/dist/theme/index.css' 2 | /** @type { import('@storybook/vue3').Preview } */ 3 | const preview = { 4 | parameters: { 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/i, 9 | }, 10 | }, 11 | }, 12 | }; 13 | 14 | export default preview; 15 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/hooks/use-codecopy.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const useCodeCopy = () => { 4 | const copyContent = ref('') 5 | const clickCopy = async (value: string) => { 6 | await navigator.clipboard.writeText(value) 7 | // TODO: 通知开发者复制成功 8 | } 9 | return { 10 | copyContent, 11 | clickCopy 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/ShowIcon.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Tag.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /packages/docs/demo/message/Center.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/Theme.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { default as AntDesignContainer } from './container/ant-design-ui/AntDesign.vue'; 2 | import { default as ElementPlusContainer } from './container/element-plus/ElementPlus.vue'; 3 | import { default as NaiveUIContainer } from './container/naive-ui/NaiveUI.vue'; 4 | 5 | export { AntDesignContainer, ElementPlusContainer, NaiveUIContainer }; 6 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | plugins: [ 4 | require('postcss-nested'), 5 | require('postcss-each-variables'), 6 | require('postcss-each')({ 7 | plugins: { 8 | beforeEach: [require('postcss-for'), require('postcss-color-mix')] 9 | } 10 | }), 11 | // require('cssnano')({ preset: 'default' }) 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/TextCenter.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /packages/hooks/__test__/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import {useClickOutside,useEventListener} from '..' 3 | 4 | describe("hooks/index", () => { 5 | it('useEventListener should be exported',()=>{ 6 | expect(useEventListener).toBeDefined() 7 | }) 8 | it('useClickOutside should be exported',()=>{ 9 | expect(useClickOutside).toBeDefined() 10 | }) 11 | }); -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/components", 3 | "version": "1.0.0", 4 | "private":true, 5 | "main": "index.ts", 6 | "scripts": { 7 | "test": "cross-env NODE_ENV=test pnpm --filter @eric-ui/hooks build && vitest --coverage" 8 | }, 9 | "devDependencies": { 10 | "axios": "^1.6.8" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /packages/hooks/useProp.ts: -------------------------------------------------------------------------------- 1 | import { computed, getCurrentInstance, type ComputedRef } from "vue"; 2 | 3 | export default function useProp(propName: string): ComputedRef { 4 | const instance = getCurrentInstance(); 5 | if (!instance) { 6 | throw new Error("useProp must be used within a component"); 7 | } 8 | return computed(() => (instance?.proxy?.$props as any)?.[propName] as T); 9 | } 10 | -------------------------------------------------------------------------------- /packages/docs/demo/notification/Closeable.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /libs/vite-plugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/vite-plugins", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "module": "./.dist/index.js", 7 | "types": "./.dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./.dist/index.js", 11 | "types": "./.dist/index.d.ts" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "vite build" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/Close.vue: -------------------------------------------------------------------------------- 1 | 8 | 14 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Icon.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Throttle.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /packages/locale/index.ts: -------------------------------------------------------------------------------- 1 | export {default as en} from './lang/en' 2 | export {default as ja} from './lang/ja' 3 | export {default as ko} from './lang/ko' 4 | export {default as zhCn} from './lang/zh-cn' 5 | export {default as zhTw} from './lang/zh-tw' 6 | 7 | export type TranslatePair = { 8 | [key: string]: string | string[] | TranslatePair 9 | } 10 | 11 | export type Language = { 12 | name: string 13 | el: TranslatePair 14 | } -------------------------------------------------------------------------------- /packages/play/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Vue + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/components/Icon/style.css: -------------------------------------------------------------------------------- 1 | .er-icon { 2 | --er-icon-color: inherit; 3 | display: inline-flex; 4 | justify-content: center; 5 | align-items: center; 6 | position: relative; 7 | fill: currentColor; 8 | color: var(--er-icon-color); 9 | font-size: inherit; 10 | } 11 | 12 | @each $val in primary, info, success, warning, danger { 13 | .er-icon--$(val) { 14 | --er-icon-color: var(--er-color-$(val)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/docs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "index.md", 7 | "scripts": { 8 | "dev": "vitepress dev ./", 9 | "build": "vitepress build ./" 10 | }, 11 | "devDependencies": { 12 | "@vitepress-demo-preview/component": "^2.3.2", 13 | "@vitepress-demo-preview/plugin": "^1.2.3", 14 | "vitepress": "1.1.4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/components/ConfigProvider/ConfigProvider.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /packages/docs/demo/popconfirm/Callback.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Loading.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /packages/docs/demo/tooltip/Disabled.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /packages/hooks/useId.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, computed } from "vue"; 2 | 3 | const defaultIdInjection = { 4 | prefix: Math.floor(Math.random() * 10000), 5 | current: 0, 6 | }; 7 | 8 | export function useId(namespace: string = "er"): Ref { 9 | const idRef = computed( 10 | () => 11 | `${namespace}-id-${ 12 | defaultIdInjection.prefix 13 | }-${defaultIdInjection.current++}` 14 | ); 15 | 16 | return idRef; 17 | } 18 | 19 | export default useId; 20 | -------------------------------------------------------------------------------- /packages/components/Alert/types.ts: -------------------------------------------------------------------------------- 1 | export type AlertType = "success" | "info" | "warning" | "danger"; 2 | 3 | export interface AlertProps { 4 | title?: string; 5 | type?: AlertType; 6 | description?: string; 7 | effect?: "light" | "dark"; 8 | closable?: boolean; 9 | center?: boolean; 10 | showIcon?: boolean; 11 | } 12 | 13 | export interface AlertEmits { 14 | (e: "close"): void; 15 | } 16 | 17 | export interface AlertInstance { 18 | open(): void; 19 | close(): void; 20 | } 21 | -------------------------------------------------------------------------------- /packages/hooks/useClickOutside.ts: -------------------------------------------------------------------------------- 1 | import { type Ref } from 'vue' 2 | import useEventListener from './useEventListener' 3 | 4 | export default function useClickOutside ( 5 | elementRef: Ref, 6 | callback: (e: MouseEvent) => void 7 | ) { 8 | useEventListener(document, 'click', (e: Event) => { 9 | if (elementRef.value && e.target) { 10 | if (!elementRef.value.contains(e.target as HTMLElement)) { 11 | callback(e as MouseEvent) 12 | } 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /packages/hooks/useZIndex.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from "vue"; 2 | 3 | const zIndex = ref(0); 4 | export default function useZIndex(initialValue = 2000) { 5 | const _initialValue = ref(initialValue); 6 | const currentZindex = computed(() => zIndex.value + _initialValue.value); 7 | 8 | const nextZIndex = () => { 9 | zIndex.value++; 10 | return currentZindex.value; 11 | }; 12 | 13 | return { 14 | initialValue: _initialValue, 15 | currentZindex, 16 | nextZIndex, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/play/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | import VueJSX from "@vitejs/plugin-vue-jsx"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [vue(), VueJSX()], 8 | server: { 9 | proxy: { 10 | "/posts": { 11 | target: "https://jsonplaceholder.typicode.com", 12 | changeOrigin: true, 13 | // rewrite:path=>path.replace(/^\/posts/,'') 14 | }, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/docs/demo/messagebox/Alert.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /packages/hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/hooks", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "module": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "exports": { 11 | ".": { 12 | "import": "./dist/index.js", 13 | "types": "./dist/index.d.ts" 14 | } 15 | }, 16 | "scripts": { 17 | "build": "vite build" 18 | }, 19 | "peerDependencies": { 20 | "vue": "^3.4.27", 21 | "lodash-es": "^4.17.21" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | coverage 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | /cyperss/videos/ 17 | /cypress/srceenshots/ 18 | 19 | .vitepress/dist 20 | .vitepress/cache 21 | 22 | # Editor directories and files 23 | .vscode/* 24 | !.vscode/extensions.json 25 | .idea 26 | .DS_Store 27 | *.suo 28 | *.ntvs* 29 | *.njsproj 30 | *.sln 31 | *.sw? 32 | 33 | *storybook.log 34 | storybook-static -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | import { library } from "@fortawesome/fontawesome-svg-core"; 2 | import { fas } from "@fortawesome/free-solid-svg-icons"; 3 | import makeInstaller from "./makeInstaller"; 4 | import componens from "./componens"; 5 | import printLogo from "./pringLogo"; 6 | 7 | import "@eric-ui/theme/index.css"; 8 | 9 | printLogo(); 10 | 11 | library.add(fas); 12 | const installer = makeInstaller(componens); 13 | 14 | export * from '@eric-ui/locale' 15 | export * from "@eric-ui/components"; 16 | export default installer; 17 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme"; 2 | import { type App } from "vue"; 3 | import EricUI, { zhCn } from "eric-ui"; 4 | import { ElementPlusContainer } from "@vitepress-preview/component"; 5 | 6 | import "@vitepress-preview/component/style.css"; 7 | import "eric-ui/dist/index.css"; 8 | 9 | export default { 10 | ...DefaultTheme, 11 | enhanceApp({ app }: { app: App }) { 12 | app.component("demo-preview", ElementPlusContainer); 13 | app.use(EricUI, { locale: zhCn }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/play/.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/vue3-vite').StorybookConfig } */ 2 | const config = { 3 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-essentials", 7 | // "@chromatic-com/storybook", 8 | "@storybook/addon-interactions", 9 | ], 10 | framework: { 11 | name: "@storybook/vue3-vite", 12 | options: {}, 13 | }, 14 | docs: { 15 | autodocs: "tag", 16 | }, 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /libs/vite-plugins/.dist/index.js: -------------------------------------------------------------------------------- 1 | import { each, isFunction } from "lodash-es"; 2 | import shell from "shelljs"; 3 | function hooksPlugin({ 4 | rmFiles = [], 5 | afterBuild, 6 | beforeBuild 7 | }) { 8 | return { 9 | name: "custom-hooks-plugin", 10 | buildStart() { 11 | each(rmFiles, (fName) => shell.rm("-rf", fName)); 12 | isFunction(beforeBuild) && beforeBuild(); 13 | }, 14 | buildEnd(err) { 15 | !err && isFunction(afterBuild) && afterBuild(); 16 | } 17 | }; 18 | } 19 | export { 20 | hooksPlugin 21 | }; 22 | -------------------------------------------------------------------------------- /packages/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ConfigProvider' 2 | export * from "./Icon"; 3 | export * from "./Button"; 4 | export * from "./Collapse"; 5 | export * from "./Dropdown"; 6 | export * from "./Message"; 7 | export * from "./Tooltip"; 8 | export * from "./Input"; 9 | export * from "./Switch"; 10 | export * from "./Select"; 11 | export * from "./Form"; 12 | export * from "./Alert"; 13 | export * from "./Notification"; 14 | export * from "./Loading"; 15 | export * from "./Upload"; 16 | export * from "./Popconfirm"; 17 | export * from "./MessageBox"; -------------------------------------------------------------------------------- /packages/docs/demo/messagebox/Prompt.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /packages/components/Popconfirm/types.ts: -------------------------------------------------------------------------------- 1 | import type { ButtonType } from "../Button"; 2 | 3 | export interface PopconfirmProps { 4 | title: string; 5 | confirmButtonText?: string; 6 | cancelButtonText?: string; 7 | confirmButtonType?: ButtonType; 8 | cancelButtonType?: ButtonType; 9 | icon?: string; 10 | iconColor?: string; 11 | hideIcon?: boolean; 12 | hideAfter?: number; 13 | width?: number | string; 14 | } 15 | 16 | export interface PopconfirmEmits { 17 | (e: "confirm", value: MouseEvent): void; 18 | (e: "cancel", value: MouseEvent): void; 19 | } 20 | -------------------------------------------------------------------------------- /packages/docs/demo/messagebox/Confirm.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /packages/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | import { vLoading } from "./directive"; 2 | import { Loading } from "./service"; 3 | 4 | import type { App } from "vue"; 5 | 6 | export const ErLoading = { 7 | name:'ErLoading', 8 | install(app: App) { 9 | app.directive("loading", vLoading); 10 | app.config.globalProperties.$loading = Loading; 11 | }, 12 | directive: vLoading, 13 | service: Loading, 14 | }; 15 | 16 | export default ErLoading; 17 | 18 | export { 19 | vLoading, 20 | vLoading as ErLoadingDirective, 21 | Loading as ErLoadingService, 22 | }; 23 | 24 | export * from "./types"; 25 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/hooks/use-namespaces.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 钩子函数使用 3 | * const ns = useNameSpace(); 4 | * ns.b() => block 5 | * ns.e(element) => block__element 6 | * ns.m(modifier) => block--modifier 7 | * ns.bem(element,modifier) => block__element--modifier 8 | */ 9 | interface UseNameSpaceReturn { 10 | b: () => string; 11 | e: (element: string) => string; 12 | m: (modifier: string) => string; 13 | bem: (_block?: string, element?: string, modifier?: string) => string; 14 | } 15 | export declare const useNameSpace: (block?: string) => UseNameSpaceReturn; 16 | export {}; 17 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite"; 3 | import {resolve} from 'path' 4 | import vue from "@vitejs/plugin-vue"; 5 | import vueJsx from "@vitejs/plugin-vue-jsx"; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [vue(), vueJsx()], 10 | define: { 11 | PROD: JSON.stringify(false), 12 | DEV: JSON.stringify(false), 13 | TEST: JSON.stringify(true), 14 | }, 15 | test: { 16 | globals: true, 17 | environment: "jsdom", 18 | setupFiles:[resolve(__dirname, './vitest.setup.ts')] 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Group.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /packages/components/MessageBox/index.ts: -------------------------------------------------------------------------------- 1 | import MessageBox from "./methods"; 2 | import { set } from "lodash-es"; 3 | 4 | import type { App } from "vue"; 5 | 6 | export const ErMessageBox = MessageBox; 7 | 8 | set(ErMessageBox, "install", (app: App) => { 9 | app.config.globalProperties.$msgbox = MessageBox; 10 | app.config.globalProperties.$messageBox = MessageBox; 11 | app.config.globalProperties.$alert = MessageBox.alert; 12 | app.config.globalProperties.$confirm = MessageBox.confirm; 13 | app.config.globalProperties.$prompt = MessageBox.prompt; 14 | }); 15 | 16 | export default ErMessageBox; 17 | export * from "./types"; 18 | -------------------------------------------------------------------------------- /libs/vite-plugins/hooksPlugin.ts: -------------------------------------------------------------------------------- 1 | import { each, isFunction } from "lodash-es"; 2 | import shell from "shelljs"; 3 | 4 | export default function hooksPlugin({ 5 | rmFiles = [], 6 | afterBuild, 7 | beforeBuild, 8 | }: { 9 | beforeBuild?: Function; 10 | afterBuild?: Function; 11 | rmFiles?: string[]; 12 | }) { 13 | return { 14 | name: "custom-hooks-plugin", 15 | buildStart() { 16 | each(rmFiles, (fName) => shell.rm("-rf", fName)); 17 | isFunction(beforeBuild) && beforeBuild(); 18 | }, 19 | buildEnd(err?: Error) { 20 | !err && isFunction(afterBuild) && afterBuild(); 21 | }, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import useClickOutside from "./useClickOutside"; 2 | import useEventListener from "./useEventListener"; 3 | import useFocusController from "./useFocusController"; 4 | import useZIndex from "./useZIndex"; 5 | import useProp from "./useProp"; 6 | import useDisabledStyle from "./useDisabledStyle"; 7 | import useId from "./useId"; 8 | import useLocale from "./useLocale"; 9 | import useOffset from "./useOffset"; 10 | 11 | export { 12 | useClickOutside, 13 | useEventListener, 14 | useZIndex, 15 | useProp, 16 | useFocusController, 17 | useDisabledStyle, 18 | useLocale, 19 | useOffset, 20 | useId, 21 | }; 22 | -------------------------------------------------------------------------------- /libs/vite-plugins/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { resolve } from "path"; 3 | import dts from "vite-plugin-dts"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | dts({ 8 | include: ["./**/*.ts"], 9 | exclude: ["./vite.config.ts"] 10 | }), 11 | ], 12 | build: { 13 | minify: false, 14 | outDir: ".dist", 15 | lib: { 16 | entry: resolve(__dirname, "./index.ts"), 17 | name: "vitePlugins", 18 | fileName: "index", 19 | formats: ["es"], 20 | }, 21 | rollupOptions: { 22 | external: ["shelljs", "lodash-es"], 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/components/Tooltip/types.ts: -------------------------------------------------------------------------------- 1 | import type { Placement, Options } from "@popperjs/core"; 2 | 3 | export interface TooltipProps { 4 | content?: string; 5 | trigger?: "hover" | "click" | "contextmenu"; 6 | placement?: Placement; 7 | manual?: boolean; 8 | disabled?: boolean; 9 | popperOptions?: Partial; 10 | transition?: string; 11 | showTimeout?: number; 12 | hideTimeout?: number; 13 | } 14 | 15 | export interface TooltipEmits { 16 | (e: "visible-change", value: boolean): void; 17 | (e: "click-outside"): void; 18 | } 19 | 20 | export interface TooltipInstance { 21 | show(): void; 22 | hide(): void; 23 | } 24 | -------------------------------------------------------------------------------- /packages/docs/demo/message/Basic.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /packages/docs/demo/message/Test.vue: -------------------------------------------------------------------------------- 1 | 14 | 23 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/Basic.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /packages/utils/style.ts: -------------------------------------------------------------------------------- 1 | import { isNumber, isString } from "lodash-es"; 2 | import { debugWarn } from "./error"; 3 | 4 | const SCOPE = "utils/style" as const; 5 | 6 | const isStringNumber = (val: string): boolean => { 7 | if (!isString(val)) { 8 | return false; 9 | } 10 | return !Number.isNaN(Number(val)); 11 | }; 12 | export function addUnit(value?: string | number, defaultUnit = "px") { 13 | if (!value) return ""; 14 | if (isNumber(value) || isStringNumber(value)) { 15 | return `${value}${defaultUnit}`; 16 | } 17 | if (isString(value)) { 18 | return value; 19 | } 20 | debugWarn(SCOPE, "binding value must be a string or number"); 21 | } 22 | -------------------------------------------------------------------------------- /packages/components/Collapse/types.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | export type CollapseItemName = string | number; 3 | 4 | export interface CollapseProps { 5 | modelValue: CollapseItemName[]; 6 | accordion?: boolean; 7 | } 8 | 9 | export interface CollapseItemProps { 10 | name: CollapseItemName; 11 | title?: string; 12 | disabled?: boolean; 13 | } 14 | 15 | export interface CollapseContext { 16 | activeNames: Ref; 17 | handleItemClick(name: CollapseItemName): void; 18 | } 19 | 20 | export interface CollapseEmits { 21 | (e: "update:modelValue", value: CollapseItemName[]): void; 22 | (e: "change", value: CollapseItemName[]): void; 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/makeInstaller.ts: -------------------------------------------------------------------------------- 1 | import type { App, Plugin } from "vue"; 2 | import { INSTALLED_KEY } from "@eric-ui/constants"; 3 | import { each, get, set } from "lodash-es"; 4 | import { 5 | provideGlobalConfig, 6 | type ConfigProviderProps, 7 | } from "@eric-ui/components"; 8 | 9 | export default function makeInstaller(components: Plugin[]) { 10 | const install = (app: App, options?: ConfigProviderProps) => { 11 | if (get(app, INSTALLED_KEY)) return; 12 | set(app, INSTALLED_KEY, true); 13 | 14 | each(components, (c) => { 15 | app.use(c); 16 | }); 17 | 18 | if (options) provideGlobalConfig(options, app, true); 19 | }; 20 | 21 | return install; 22 | } 23 | -------------------------------------------------------------------------------- /packages/components/Loading/types.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from "vue"; 2 | 3 | export interface LoadingOptionsResolved { 4 | parent?: HTMLElement; 5 | target?: HTMLElement; 6 | visible?: MaybeRef; 7 | background?: MaybeRef; 8 | spinner?: MaybeRef; 9 | text?: MaybeRef; 10 | fullscreen?: MaybeRef; 11 | lock?: MaybeRef; 12 | beforeClose?(): boolean; 13 | closed?(): void; 14 | } 15 | 16 | export type LoadingOptions = Partial< 17 | Omit & { 18 | target: HTMLElement | string; 19 | body: boolean; 20 | zIndex?: number; 21 | onAfterLeave(): void; 22 | } 23 | >; 24 | -------------------------------------------------------------------------------- /packages/utils/error.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "lodash-es"; 2 | 3 | class ErUIError extends Error { 4 | constructor(message: string) { 5 | super(message); 6 | this.name = "ErUIError"; 7 | } 8 | } 9 | 10 | export function throwError(scope: string, msg: string) { 11 | throw new ErUIError(`[${scope}] ${msg}`); 12 | } 13 | 14 | export function debugWarn(error: Error): void; 15 | export function debugWarn(scope: string, msg: string): void; 16 | export function debugWarn(scope: string | Error, msg?: string) { 17 | if (process.env.NODE_ENV !== "production") { 18 | const err = isString(scope) ? new ErUIError(`[${scope}] ${msg}`) : scope; 19 | console.warn(err); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/hooks/useEventListener.ts: -------------------------------------------------------------------------------- 1 | import { 2 | onMounted, 3 | onBeforeUnmount, 4 | watch, 5 | isRef, 6 | unref, 7 | type MaybeRef, 8 | } from "vue"; 9 | 10 | export default function useEventListener( 11 | target: MaybeRef, 12 | event: string, 13 | handler: (e: Event) => any 14 | ) { 15 | if (isRef(target)) { 16 | watch(target, (val, oldVal) => { 17 | oldVal?.removeEventListener(event, handler); 18 | val?.addEventListener(event, handler); 19 | }); 20 | } else { 21 | onMounted(() => target?.addEventListener(event, handler)); 22 | } 23 | 24 | onBeforeUnmount(() => unref(target)?.removeEventListener(event, handler)); 25 | } 26 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2018", 5 | "module": "esnext", 6 | "lib": ["esnext", "DOM"], 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "strictNullChecks": true, 11 | "resolveJsonModule": true, 12 | "skipLibCheck": true, 13 | "skipDefaultLibCheck": true, 14 | "paths": { 15 | "tslib": ["node_modules/tslib/tslib.d.ts"] 16 | } 17 | }, 18 | "vueCompilerOptions": { 19 | // "experimentalDisableTemplateSupport": true 20 | }, 21 | "include": ["**/*.vue", "**/*.ts", "**/*.d.ts"], 22 | "exclude": ["**/node_modules", "**/dist"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/components/Select/constants.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from "vue"; 2 | import type { SelectContext } from "./types"; 3 | 4 | export const SELECT_CTX_KEY: InjectionKey = 5 | Symbol("selectContext"); 6 | 7 | export const POPPER_OPTIONS: any = { 8 | modifiers: [ 9 | { 10 | name: "offset", 11 | options: { 12 | offset: [0, 9], 13 | }, 14 | }, 15 | { 16 | name: "sameWidth", 17 | enabled: true, 18 | fn: ({ state }: { state: any }) => { 19 | state.styles.popper.width = `${state.rects.reference.width}px`; 20 | }, 21 | phase: "beforeWrite", 22 | requires: ["computeStyles"], 23 | }, 24 | ], 25 | } as const; 26 | -------------------------------------------------------------------------------- /packages/components/Switch/types.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef } from "vue"; 2 | 3 | export type SwitchValueType = boolean | string | number; 4 | 5 | export interface SwitchProps { 6 | modelValue: SwitchValueType; 7 | disabled?: boolean; 8 | activeText?: string; 9 | inactiveText?: string; 10 | activeValue?: SwitchValueType; 11 | inactiveValue?: SwitchValueType; 12 | name?: string; 13 | id?: string; 14 | size?: "small" | "large"; 15 | } 16 | 17 | export interface SwitchEmits { 18 | (e: "update:modelValue", value: SwitchValueType): void; 19 | (e: "change", value: SwitchValueType): void; 20 | } 21 | 22 | export interface SwitchInstance { 23 | focus(): void; 24 | checked: ComputedRef; 25 | } 26 | -------------------------------------------------------------------------------- /packages/docs/demo/alert/IconDesc.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /packages/docs/demo/messagebox/Center.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /packages/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import { isFunction } from "lodash-es"; 3 | 4 | export const RenderVnode = defineComponent({ 5 | props: { 6 | vNode: { 7 | type: [String, Object, Function], 8 | required: true, 9 | }, 10 | }, 11 | setup(props) { 12 | return () => (isFunction(props.vNode) ? props.vNode() : props.vNode); 13 | }, 14 | }); 15 | 16 | export const typeIconMap = new Map([ 17 | ["info", "circle-info"], 18 | ["success", "check-circle"], 19 | ["warning", "circle-exclamation"], 20 | ["danger", "circle-xmark"], 21 | ["error", "circle-xmark"], 22 | ]); 23 | 24 | export * from "./install"; 25 | export * from "./error"; 26 | export * from "./style"; 27 | -------------------------------------------------------------------------------- /packages/components/Button/ButtonGroup.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /packages/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: Eric-UI 组件库 7 | text: 高仿 ElementPlus 8 | tagline: 基于 Vue3 + TS 开发的组件库 9 | actions: 10 | - theme: brand 11 | text: 开始使用 12 | link: /get-started 13 | 14 | features: 15 | - icon: 📚 16 | title: 功能覆盖和兼容性 17 | details: 组件库能够广泛覆盖 Element Plus 的功能,并且与 Element Plus 的 API 兼容。可以作为 Element Plus 的替代品,提供相同的功能和使用体验,方便用户迁移和使用。 18 | - icon: 📦 19 | title: 易用性和简化开发流程 20 | details: 组件库提供简洁明了的 API 和组件结构,使开发人员能够快速上手并高效构建界面,减少开发时间和工作量。部分组件兼容多种开发范式。 21 | - icon: 🌹 22 | title: 文档和示例丰富 23 | details: 在线文档包含详细的组件文档和示例,以展示你的组件库的功能和使用方法。提供清晰的示例代码、演示和解释,帮助用户理解每个组件的用途、属性和事件,并能够快速集成到他们的项目中。 24 | --- 25 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Disabled.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eric-UI 2 | 3 | Vue3 + TS 高仿 ElementPlus 打造自己的组件库 4 | 5 | 一款功能全面、易用性强、文档丰富的UI组件库,完美兼容Element Plus,为开发者提供高效的界面构建解决方案。 6 | 7 | ## 特性 8 | 9 | ### 功能覆盖和兼容性 10 | - **广泛兼容**:本组件库能够广泛覆盖Element Plus的所有功能,确保无缝迁移。 11 | - **API一致性**:与Element Plus的API保持高度兼容,提供相同的功能和使用体验。 12 | 13 | ### 易用性和简化开发流程 14 | - **简洁API**:提供简洁明了的API和组件结构,助力开发者快速上手。 15 | - **高效构建**:优化组件设计,减少开发时间和工作量,提升开发效率。 16 | - **多范式兼容**:部分组件支持多种开发范式,满足不同开发者的需求。 17 | 18 | ### 文档和示例丰富 19 | - **详细文档**:在线文档包含对每个组件的详尽描述,帮助用户深入理解。 20 | - **实用示例**:提供清晰的示例代码和演示,加速用户的学习和应用过程。 21 | - **易于理解**:通过解释和演示,帮助用户掌握组件的用途、属性和事件。 22 | 23 | ## 开始使用 24 | 25 | - 访问 [Eric-UI (ericwxy.github.io)](https://ericwxy.github.io/eric-ui/#/) 了解更多信息。 26 | 27 | ## 贡献指南 28 | 29 | 我们欢迎并鼓励社区成员为组件库贡献力量。无论是报告问题、提交改进建议还是分享使用经验,都是对我们的宝贵支持。 30 | -------------------------------------------------------------------------------- /packages/docs/demo/notification/Basic.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /packages/utils/__tests__/error.test.tsx: -------------------------------------------------------------------------------- 1 | import {describe,expect,it,vi} from 'vitest' 2 | import {debugWarn,throwError} from '../error' 3 | 4 | describe('error',()=>{ 5 | it('throwError should work',()=>{ 6 | expect(()=>{ 7 | throwError('scope', 'message') 8 | }).toThrowError('[scope] message') 9 | }) 10 | it('debugWarn should work',()=>{ 11 | const warn = vi.spyOn(console, 'warn').mockImplementation(()=> vi.fn) 12 | debugWarn('scope','message') 13 | debugWarn(new SyntaxError('custom error')) 14 | expect(warn.mock.calls).toMatchInlineSnapshot(` 15 | [ 16 | [ 17 | [ErUIError: [scope] message], 18 | ], 19 | [ 20 | [SyntaxError: custom error], 21 | ], 22 | ] 23 | `) 24 | }) 25 | }) 26 | 27 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { resolve } from "path"; 3 | import vue from "@vitejs/plugin-vue"; 4 | import dts from "vite-plugin-dts"; 5 | 6 | export default defineConfig({ 7 | build: { 8 | outDir: ".dist", 9 | lib: { 10 | entry: resolve(__dirname, "./index.ts"), 11 | name: "previewComponent", 12 | fileName: "preview-component", 13 | formats: ["es"], 14 | }, 15 | rollupOptions: { 16 | external: ["vue"], 17 | output: { 18 | globals: { 19 | vue: "Vue", 20 | }, 21 | }, 22 | }, 23 | }, 24 | plugins: [ 25 | dts({ 26 | exclude: ["./vite.config.ts"], 27 | insertTypesEntry: true, 28 | }), 29 | vue(), 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/Size.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /packages/hooks/useOffset.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, computed } from "vue"; 2 | 3 | interface useOffsetOptions { 4 | // id: string; 5 | offset: number; 6 | boxHeight: Ref; 7 | getLastBottomOffset: () => number; 8 | } 9 | 10 | interface useOffsetResult { 11 | topOffset: Ref; 12 | bottomOffset: Ref; 13 | } 14 | 15 | export function useOffset(opts: useOffsetOptions): useOffsetResult { 16 | // 上一个实例最下面的坐标,第一个是0 17 | const lastBottomOffset = computed(() => opts.getLastBottomOffset()); 18 | // 本元素应该的 top 19 | const topOffset = computed(() => opts.offset + lastBottomOffset.value); 20 | // 为下一个实例预留的底部 offset 21 | const bottomOffset = computed(() => topOffset.value + opts.boxHeight.value); 22 | 23 | return { 24 | topOffset, 25 | bottomOffset, 26 | }; 27 | } 28 | 29 | export default useOffset; 30 | -------------------------------------------------------------------------------- /packages/components/Upload/types.ts: -------------------------------------------------------------------------------- 1 | export type UploadFileStatus = "ready" | "uploading" | "success" | "error"; 2 | 3 | export interface UploadFile { 4 | uid: string; 5 | size: number; 6 | name: string; 7 | status?: UploadFileStatus; 8 | percent?: number; 9 | raw?: File; 10 | response?: any; 11 | error?: any; 12 | } 13 | 14 | export interface UploadProps { 15 | action: string; 16 | defaultFileList?: UploadFile[]; 17 | beforeUpload?(file: File): boolean | Promise; 18 | onChange?(file: File): void; 19 | onProgress?(percentage: number, file: File): void; 20 | onSuccess?(data: any, file: File): void; 21 | onError?(err: any, file: File): void; 22 | 23 | onRemove?(file: UploadFile): void; 24 | } 25 | 26 | export interface UploadListProps { 27 | fileList: UploadFile[]; 28 | onRemove?(file: UploadFile): void; 29 | } 30 | -------------------------------------------------------------------------------- /packages/components/Icon/Icon.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /packages/docs/demo/message/Type.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/icons/copy-success.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /packages/core/pringLogo.ts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | if (PROD) { 3 | const logo = ` 4 | ________________________________________________ 5 | 6 | _______ __ _______ _______ 7 | | ___|.----.|__|.----.| | ||_ _| 8 | | ___|| _|| || __|| | | _| |_ 9 | |_______||__| |__||____||_______||_______| 10 | 11 | ________________________________________________ 12 | author:EricWXY 13 | `; 14 | 15 | const rainbowGradient = ` 16 | background: linear-gradient(135deg, orange 60%, cyan); 17 | background-clip: text; 18 | color: transparent; 19 | font-size: 16px; 20 | line-height: 1; 21 | font-family: monospace; 22 | font-weight: 600; 23 | `; 24 | 25 | console.info(`%c${logo}`, rainbowGradient); 26 | } else if (DEV) { 27 | console.log("[EricUI]:dev mode..."); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/docs/demo/loading/Fullscreen.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 37 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/messages/message-notice.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/various.scss'; 2 | 3 | .#{$defaultPrefix}-message-notice__container { 4 | display: flex; 5 | align-items: center; 6 | border: 1px solid var(--component-preview-border); 7 | padding: 8px 30px; 8 | border-radius: 4px; 9 | font-size: 12px; 10 | color: var(--component-preview-primary-color); 11 | position: fixed; 12 | left: 50%; 13 | transform: translateX(-50%); 14 | z-index: 999; 15 | 16 | svg { 17 | display: inline-block; 18 | fill: currentColor; 19 | color: var(--component-preview-primary-color); 20 | cursor: pointer; 21 | margin-right: 4px; 22 | } 23 | } 24 | 25 | .slide-fade-leave-active, 26 | .slide-fade-enter-active { 27 | transition: all 0.25s ease-in-out; 28 | } 29 | 30 | .slide-fade-enter-from, 31 | .slide-fade-leave-to { 32 | transform: translate(-50%, -75px); 33 | opacity: 0; 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "preserve", 17 | "jsxImportSource": "vue", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": [ 26 | "env.d.ts", 27 | "packages/**/*.ts", 28 | "packages/**/*.tsx", 29 | "packages/**/*.vue", 30 | "packages/play/src/stories/*.stories.ts", "vitest.config.ts", 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/hooks/useLocale.ts: -------------------------------------------------------------------------------- 1 | import { inject, type Ref } from "vue"; 2 | import { omit } from "lodash-es"; 3 | import { createI18n, i18nSymbol, type I18nInstance } from "vue3-i18n"; 4 | import type { Language } from "@eric-ui/locale"; 5 | import English from "@eric-ui/locale/lang/en"; 6 | 7 | export function useLocale(localeOverrides?: Ref) { 8 | if (!localeOverrides) { 9 | return omit( 10 | ( 11 | inject( 12 | i18nSymbol, 13 | createI18n({ locale: English.name, messages: { en: English.el } }) 14 | ) 15 | ), 16 | "install" 17 | ); 18 | } 19 | 20 | return omit( 21 | createI18n({ 22 | locale: localeOverrides.value.name, 23 | messages: { 24 | en: English.el, 25 | [localeOverrides.value.name]: localeOverrides.value.el, 26 | }, 27 | }), 28 | "install" 29 | ); 30 | } 31 | 32 | export default useLocale; 33 | -------------------------------------------------------------------------------- /packages/play/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@eric-ui/playground", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview", 10 | "storybook": "storybook dev -p 6006", 11 | "build-storybook": "storybook build", 12 | "chromatic": "npx chromatic --project-token=chpt_3fbd6ca1d87c797" 13 | }, 14 | "devDependencies": { 15 | "@chromatic-com/storybook": "1.3.3", 16 | "@storybook/addon-essentials": "^8.0.10", 17 | "@storybook/addon-interactions": "^8.0.10", 18 | "@storybook/addon-links": "^8.0.10", 19 | "@storybook/blocks": "^8.0.10", 20 | "@storybook/test": "^8.0.10", 21 | "@storybook/vue3": "^8.0.10", 22 | "@storybook/vue3-vite": "^8.0.10", 23 | "@vitejs/plugin-vue": "^5.0.4", 24 | "chromatic": "^11.3.1", 25 | "storybook": "^8.0.10" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/docs/demo/messagebox/VNode.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 34 | -------------------------------------------------------------------------------- /packages/docs/demo/popconfirm/Basic.vue: -------------------------------------------------------------------------------- 1 | 21 | 31 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/icons/code-close.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Size.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /packages/core/componens.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ErButton, 3 | ErButtonGroup, 4 | ErCollapse, 5 | ErCollapseItem, 6 | ErIcon, 7 | ErDropdown, 8 | ErDropdownItem, 9 | ErTooltip, 10 | ErMessage, 11 | ErSwitch, 12 | ErInput, 13 | ErSelect, 14 | ErOption, 15 | ErForm, 16 | ErFormItem, 17 | ErAlert, 18 | ErNotification, 19 | ErLoading, 20 | ErUpload, 21 | ErPopconfirm, 22 | ErMessageBox, 23 | ErConfigProvider 24 | } from "@eric-ui/components"; 25 | import type { Plugin } from "vue"; 26 | 27 | export default [ 28 | ErButton, 29 | ErButtonGroup, 30 | ErCollapse, 31 | ErCollapseItem, 32 | ErIcon, 33 | ErDropdown, 34 | ErDropdownItem, 35 | ErTooltip, 36 | ErMessage, 37 | ErInput, 38 | ErSwitch, 39 | ErSelect, 40 | ErOption, 41 | ErForm, 42 | ErFormItem, 43 | ErAlert, 44 | ErNotification, 45 | ErLoading, 46 | ErUpload, 47 | ErPopconfirm, 48 | ErMessageBox, 49 | ErConfigProvider 50 | ] as Plugin[]; 51 | -------------------------------------------------------------------------------- /packages/components/Input/types.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | export interface InputProps { 3 | id?: string; 4 | modelValue: string; 5 | type?: string; 6 | size?: "large" | "small"; 7 | disabled?: boolean; 8 | clearable?: boolean; 9 | showPassword?: boolean; 10 | 11 | placeholder?: string; 12 | readonly?: boolean; 13 | autocomplete?: string; 14 | autofocus?: boolean; 15 | 16 | form?: string; 17 | } 18 | 19 | export interface InputEmits { 20 | (e: "update:modelValue", value: string): void; 21 | 22 | (e: "input", value: string): void; 23 | // 修改值且 失去焦点 才触发'change' 24 | (e: "change", value: string): void; 25 | (e: "focus", value: FocusEvent): void; 26 | (e: "blur", value: FocusEvent): void; 27 | (e: "clear"): void; 28 | } 29 | 30 | export interface InputInstance { 31 | ref: Ref; 32 | focus(): Promise; 33 | blur(): void; 34 | select(): void; 35 | clear(): void; 36 | } 37 | -------------------------------------------------------------------------------- /packages/hooks/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineConfig } from "vite"; 3 | import { last, split, first, includes } from "lodash-es"; 4 | import { hooksPlugin as hooks } from "@eric-ui/vite-plugins"; 5 | 6 | import dts from "vite-plugin-dts"; 7 | 8 | export default defineConfig({ 9 | plugins: [ 10 | dts({ 11 | include: ["./**/*.ts"], 12 | exclude: ["./vite.config.ts"], 13 | }), 14 | hooks({ 15 | rmFiles: ["./dist"], 16 | }), 17 | ], 18 | build: { 19 | minify: false, 20 | lib: { 21 | entry: resolve(__dirname, "./index.ts"), 22 | name: "hooks", 23 | fileName: "index", 24 | formats: ["es"], 25 | }, 26 | rollupOptions: { 27 | external: ["vue", "lodash-es", "vue3-i18n"], 28 | output: { 29 | manualChunks(id) { 30 | if (includes(id, "/packages/hooks/use")) 31 | return first(split(last(split(id, "/")), ".")); 32 | }, 33 | }, 34 | }, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /packages/hooks/__test__/useClickOutset.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from "vitest"; 2 | import { ref, defineComponent } from "vue"; 3 | import { mount } from "@vue/test-utils"; 4 | 5 | import useClickOutside from "../useClickOutside"; 6 | 7 | describe("hooks/useClickOutside", () => { 8 | it('should add "click-outside" listener', async () => { 9 | const target = ref(); 10 | const btnRef = ref(); 11 | 12 | const handler = vi.fn(); 13 | 14 | mount( 15 | defineComponent({ 16 | setup() { 17 | useClickOutside(target, handler); 18 | return () => ( 19 |
20 | 21 |
22 | ); 23 | }, 24 | }) 25 | ); 26 | 27 | await btnRef.value?.click(); 28 | expect(handler).not.toHaveBeenCalled(); 29 | 30 | await document.body.click(); 31 | expect(handler).toHaveBeenCalled(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/Command.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 28 | 29 | 41 | -------------------------------------------------------------------------------- /packages/components/Collapse/transitionEvents.ts: -------------------------------------------------------------------------------- 1 | const _setHeightZero = (el: HTMLElement) => (el.style.height = "0px"); 2 | const _setHeightScroll = (el: HTMLElement) => 3 | (el.style.height = `${el.scrollHeight}px`); 4 | const _setHeightEmpty = (el: HTMLElement) => (el.style.height = ""); 5 | const _setOverflowHidden = (el: HTMLElement) => (el.style.overflow = "hidden"); 6 | const _setOverflowEmpty = (el: HTMLElement) => (el.style.overflow = ""); 7 | 8 | const transitionEvents: Record void> = { 9 | beforeEnter(el) { 10 | _setHeightZero(el); 11 | _setOverflowHidden(el); 12 | }, 13 | enter: (el) => _setHeightScroll(el), 14 | afterEnter(el) { 15 | _setHeightEmpty(el); 16 | _setOverflowEmpty(el); 17 | }, 18 | beforeLeave(el) { 19 | _setHeightScroll(el); 20 | _setOverflowHidden(el); 21 | }, 22 | leave: (el) => _setHeightZero(el), 23 | afterLeave(el) { 24 | _setHeightEmpty(el); 25 | _setOverflowEmpty(el); 26 | }, 27 | }; 28 | 29 | export default transitionEvents; 30 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/SplitButton.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 40 | -------------------------------------------------------------------------------- /packages/utils/__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { 3 | debugWarn, 4 | throwError, 5 | withInstall, 6 | typeIconMap, 7 | } from "../"; 8 | import { each } from "lodash-es"; 9 | 10 | describe("utils/index", () => { 11 | it("debugWarn should be exported", () => { 12 | expect(debugWarn).toBeDefined(); 13 | }); 14 | it("throwError should be exported", () => { 15 | expect(throwError).toBeDefined(); 16 | }); 17 | it("withInstall should be exported", () => { 18 | expect(withInstall).toBeDefined(); 19 | }); 20 | it("typeIconMap should be worked", () => { 21 | expect(typeIconMap).toBeDefined(); 22 | each( 23 | [ 24 | ["info", "circle-info"], 25 | ["success", "check-circle"], 26 | ["warning", "circle-exclamation"], 27 | ["danger", "circle-xmark"], 28 | ["error", "circle-xmark"], 29 | ], 30 | ([type, icon]) => { 31 | expect(typeIconMap.get(type)).toBe(icon); 32 | } 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/docs/demo/message/Closeable.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 39 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitepress-preview/component", 3 | "version": "2.3.2", 4 | "private": true, 5 | "description": "preview component of code and component in vitepress", 6 | "type": "module", 7 | "main": "./.dist/preview-component.umd.cjs", 8 | "module": "./.dist/preview-component.js", 9 | "types": "./.dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": "./.dist/preview-component.js", 13 | "require": "./.dist/preview-component.umd.cjs" 14 | }, 15 | "./style.css": "./.dist/style.css" 16 | }, 17 | "files": [ 18 | ".dist", 19 | "README.md", 20 | "package.json", 21 | "types.d.ts" 22 | ], 23 | "scripts": { 24 | "dev": "vite build --watch", 25 | "build": "vite build" 26 | }, 27 | "keywords": [ 28 | "vitepress", 29 | "plugins", 30 | "component" 31 | ], 32 | "license": "MIT", 33 | "peerDependencies": { 34 | "vitepress": "*", 35 | "vue": "*" 36 | }, 37 | "devDependencies": { 38 | "sass": "^1.54.8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/docs/demo/notification/Type.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 43 | -------------------------------------------------------------------------------- /packages/components/Dropdown/types.ts: -------------------------------------------------------------------------------- 1 | import type { VNode, ComputedRef } from "vue"; 2 | import type { TooltipProps } from "../Tooltip/types"; 3 | import type { ButtonType, ButtonSize } from "../Button/types"; 4 | 5 | export type DropdownCommand = string | number; 6 | 7 | export interface DropdownItemProps { 8 | command?: DropdownCommand; 9 | label?: string | VNode; 10 | disabled?: boolean; 11 | divided?: boolean; 12 | } 13 | 14 | export interface DropdownProps extends TooltipProps { 15 | type?: ButtonType; 16 | size?: ButtonSize; 17 | items?: DropdownItemProps[]; 18 | hideOnClick?: boolean; 19 | splitButton?: boolean; 20 | } 21 | 22 | export interface DropdownEmits { 23 | (e: "visible-change", value: boolean): void; 24 | (e: "command", value: DropdownCommand): void; 25 | (e: "click", value: MouseEvent): void; 26 | } 27 | 28 | export interface DropdownInstance { 29 | open(): void; 30 | close(): void; 31 | } 32 | 33 | export interface DropdownContext { 34 | handleItemClick(item: DropdownItemProps): void; 35 | size: ComputedRef; 36 | } 37 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/icons/code-open.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /packages/components/Loading/Loading.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import { nextTick } from "vue"; 3 | import { Loading } from "./service"; 4 | 5 | export const rAF = async () => { 6 | return new Promise((res) => { 7 | requestAnimationFrame(() => { 8 | requestAnimationFrame(async () => { 9 | res(null); 10 | await nextTick(); 11 | }); 12 | }); 13 | }); 14 | }; 15 | 16 | describe("Loading", () => { 17 | it("should creat Loading instance", () => { 18 | const instance = Loading(); 19 | expect(instance).toBeTruthy(); 20 | }); 21 | it('should render mask',async()=>{ 22 | Loading(); 23 | await rAF(); 24 | expect(document.querySelector('.er-loading__mask')).toBeTruthy() 25 | }) 26 | it('should close Loading and remove it from DOM',async()=>{ 27 | const instance = Loading(); 28 | 29 | await rAF(); 30 | expect(document.querySelector('.er-loading')).toBeTruthy() 31 | instance.close() 32 | await rAF(); 33 | 34 | expect(document.querySelector('.er-loading')).toBeFalsy() 35 | }) 36 | }); 37 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/InstanceMethod.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | 38 | -------------------------------------------------------------------------------- /packages/components/Overlay/Overlay.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 40 | 41 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "removeComments": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "preserve", 18 | "jsxImportSource": "vue", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "types": ["node"], 26 | "baseUrl": "./", 27 | "paths": { 28 | "@eric-ui/components": ["./packages/components"] 29 | } 30 | }, 31 | "include": [ 32 | "env.d.ts", 33 | "packages/core/index.ts", 34 | "packages/hooks/**/*.ts", 35 | "packages/utils/**/*.ts", 36 | "packages/components/**/*.ts", 37 | ], 38 | "exclude": ["packages/components/vitest.config.ts","packages/components/index.test.ts"] 39 | } 40 | -------------------------------------------------------------------------------- /packages/utils/install.ts: -------------------------------------------------------------------------------- 1 | import type { App, Plugin, Directive } from "vue"; 2 | import { noop } from "lodash-es"; 3 | 4 | type SFCWithInstall = T & Plugin; 5 | 6 | 7 | export const withInstall = (component: T) => { 8 | (component as SFCWithInstall).install = (app: App) => { 9 | const name = (component as any)?.name || "UnnamedComponent"; 10 | app.component(name, component as SFCWithInstall); 11 | }; 12 | return component as SFCWithInstall; 13 | }; 14 | 15 | export const withInstallFunction = (fn: T, name: string) => { 16 | (fn as SFCWithInstall).install = (app: App) => { 17 | app.config.globalProperties[name] = fn; 18 | }; 19 | return fn as SFCWithInstall; 20 | }; 21 | 22 | export const withInstallDirective = ( 23 | directive: T, 24 | name: string 25 | ): SFCWithInstall => { 26 | (directive as SFCWithInstall).install = (app: App) => { 27 | app.directive(name, directive); 28 | }; 29 | return directive as SFCWithInstall; 30 | }; 31 | 32 | export const withNoopInstall = (component: T) => { 33 | (component as SFCWithInstall).install = noop; 34 | return component as SFCWithInstall; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/components/Icon/types.ts: -------------------------------------------------------------------------------- 1 | import type { IconDefinition } from "@fortawesome/fontawesome-svg-core"; 2 | 3 | export interface IconProps { 4 | border?: boolean; 5 | fixedWidth?: boolean; 6 | flip?: "horizontal" | "vertical" | "both"; 7 | icon: object | Array | string | IconDefinition; 8 | mask?: object | Array | string; 9 | listItem?: boolean; 10 | pull?: "right" | "left"; 11 | pulse?: boolean; 12 | rotation?: 90 | 180 | 270 | "90" | "180" | "270"; 13 | swapOpacity?: boolean; 14 | size?: 15 | | "2xs" 16 | | "xs" 17 | | "sm" 18 | | "lg" 19 | | "xl" 20 | | "2xl" 21 | | "1x" 22 | | "2x" 23 | | "3x" 24 | | "4x" 25 | | "5x" 26 | | "6x" 27 | | "7x" 28 | | "8x" 29 | | "9x" 30 | | "10x"; 31 | spin?: boolean; 32 | transform?: object | string; 33 | symbol?: boolean | string; 34 | title?: string; 35 | inverse?: boolean; 36 | bounce?: boolean; 37 | shake?: boolean; 38 | beat?: boolean; 39 | fade?: boolean; 40 | beatFade?: boolean; 41 | spinPulse?: boolean; 42 | spinReverse?: boolean; 43 | type?: "primary" | "success" | "warning" | "danger" | "info"; 44 | color?: string; 45 | } 46 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/styles/various.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --component-preview-bg: var(--vp-c-bg); 3 | --component-preview-soft: var(--vp-c-bg-soft); 4 | --component-preview-mute: var(--vp-c-bg-mute); 5 | --component-preview-border: rgb(240, 240, 240); 6 | --component-preview-text-1: var(--vp-c-text-1); 7 | --component-preview-text-2: var(--vp-c-text-2); 8 | --component-preview-text-3: var(--vp-c-text-3); 9 | --component-preview-text-4: var(--vp-c-text-4); 10 | --component-preview-code-block-bg: #343030; 11 | --component-preview-primary-color: var(--vp-c-brand); 12 | } 13 | 14 | .dark:root { 15 | --component-preview-bg: var(--vp-c-bg); 16 | --component-preview-soft: var(--vp-c-bg-soft); 17 | --component-preview-mute: var(--vp-c-bg-mute); 18 | --component-preview-border: rgb(240, 240, 240, 0.1); 19 | --component-preview-text-1: var(--vp-c-text-1); 20 | --component-preview-text-2: var(--vp-c-text-2); 21 | --component-preview-text-3: var(--vp-c-text-3); 22 | --component-preview-text-4: var(--vp-c-text-4); 23 | --component-preview-code-block-bg: #282626; 24 | --component-preview-primary-color: var(--vp-c-brand); 25 | } 26 | 27 | $defaultPrefix: 'vitepress-demo-preview'; 28 | -------------------------------------------------------------------------------- /packages/utils/__tests__/install.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { defineComponent, createApp } from "vue"; 3 | import { mount } from "@vue/test-utils"; 4 | import { withInstall } from "../install"; 5 | 6 | const AppComp = defineComponent({ 7 | setup() { 8 | return () =>
app
; 9 | }, 10 | }); 11 | 12 | const componentA = withInstall( 13 | defineComponent({ 14 | name: "test", 15 | setup() { 16 | return () =>
test
; 17 | }, 18 | }) 19 | ); 20 | 21 | const componentB = withInstall( 22 | defineComponent({ 23 | name: "test2", 24 | setup() { 25 | return () =>
test2
; 26 | }, 27 | }) 28 | ); 29 | 30 | describe("install", () => { 31 | it("withInstall should be worked", () => { 32 | const wapper = mount(() =>
); 33 | const app = createApp(AppComp); 34 | 35 | app.use(componentA).use(componentB).mount(wapper.element); 36 | 37 | expect(componentA.install).toBeDefined(); 38 | expect(componentB.install).toBeDefined(); 39 | expect(wapper.findComponent(componentA)).toBeTruthy(); 40 | expect(wapper.findComponent(componentB)).toBeTruthy(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/hooks/useDisabledStyle.ts: -------------------------------------------------------------------------------- 1 | import { each, isFunction, cloneDeep, assign } from "lodash-es"; 2 | import { watchEffect, useSlots, getCurrentInstance, type VNode } from "vue"; 3 | 4 | const _dfs = (nodes: VNode[], cb: (node: VNode) => void) => 5 | each(nodes, (node) => { 6 | isFunction(cb) && cb(node); 7 | node.children && _dfs(node.children as VNode[], cb); 8 | }); 9 | 10 | export function useDisabledStyle() { 11 | const nodePropsMap = new Map(); 12 | 13 | const instance = getCurrentInstance()!; 14 | const children = useSlots()?.default?.(); 15 | 16 | watchEffect(() => { 17 | if (!instance.props?.disabled) { 18 | _dfs(children ?? [], (node) => { 19 | if (!nodePropsMap.has(node)) return; 20 | node.props = nodePropsMap.get(node); 21 | }); 22 | return; 23 | } 24 | _dfs(children ?? [], (node) => { 25 | if (!node?.props) return; 26 | 27 | nodePropsMap.set(node, cloneDeep(node.props)); 28 | node.props = assign(node?.props, { 29 | style: { 30 | cursor: "not-allowed", 31 | color: "var(--er-text-color-placeholder)", 32 | }, 33 | }); 34 | }); 35 | }); 36 | } 37 | 38 | export default useDisabledStyle; 39 | -------------------------------------------------------------------------------- /packages/components/Dropdown/DropdownItem.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /packages/components/Button/types.ts: -------------------------------------------------------------------------------- 1 | import { type Component, type ComputedRef, type Ref } from "vue"; 2 | export type ButtonType = "primary" | "success" | "warning" | "danger" | "info"; 3 | export type NativeType = "button" | "submit" | "reset"; 4 | export type ButtonSize = "default" | "large" | "small"; 5 | 6 | export interface ButtonProps { 7 | tag?: string | Component; 8 | type?: ButtonType; 9 | size?: ButtonSize; 10 | plain?: boolean; 11 | round?: boolean; 12 | circle?: boolean; 13 | disabled?: boolean; 14 | autofocus?: boolean; 15 | nativeType?: NativeType; 16 | icon?: string; 17 | loading?: boolean; 18 | loadingIcon?: string; 19 | useThrottle?: boolean; 20 | throttleDuration?: number; 21 | } 22 | 23 | export interface ButtonGroupProps { 24 | size?: ButtonSize; 25 | type?: ButtonType; 26 | disabled?: boolean; 27 | } 28 | 29 | export interface ButtonGroupContext { 30 | size?: ButtonSize; 31 | type?: ButtonType; 32 | disabled?: boolean; 33 | } 34 | 35 | export interface ButtonEmits { 36 | (e: "click", value: MouseEvent): void; 37 | } 38 | 39 | export interface ButtonInstance { 40 | ref: Ref; 41 | disabled: ComputedRef; 42 | size: ComputedRef; 43 | type: ComputedRef; 44 | } 45 | -------------------------------------------------------------------------------- /packages/components/Loading/Loading.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | 36 | 43 | -------------------------------------------------------------------------------- /packages/play/src/stories/Collapse.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/vue3"; 2 | import { ErCollapse, ErCollapseItem } from "eric-ui"; 3 | import 'eric-ui/dist/theme/Collapse.css' 4 | 5 | type Story = StoryObj; 6 | 7 | const meta: Meta = { 8 | title: "Example/Collapse", 9 | component: ErCollapse, 10 | subcomponents: { ErCollapseItem }, 11 | tags: ["autodocs"], 12 | }; 13 | 14 | export const Default: Story = { 15 | render: (args) => ({ 16 | components: { 17 | ErCollapse, 18 | ErCollapseItem, 19 | }, 20 | setup() { 21 | return { 22 | args, 23 | }; 24 | }, 25 | template: ` 26 | 27 | 28 |
this is content a
29 |
30 | 31 |
this is content b
32 |
33 | 34 |
this is content c
35 |
36 |
37 | `, 38 | }), 39 | args: { 40 | accordion: true, 41 | modelValue: ["a"], 42 | }, 43 | }; 44 | 45 | export default meta; 46 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/messages/index.ts: -------------------------------------------------------------------------------- 1 | import { ComponentPublicInstance, createApp } from 'vue' 2 | 3 | import MessageNotice from './message-notice.vue' 4 | 5 | const messageNoticeInstanceList: ComponentPublicInstance[] = [] 6 | 7 | const MessageNoticeService = { 8 | open: () => { 9 | // 创建一个div元素 10 | const messageBox = document.createElement('div') 11 | // 创建一个应用实例 12 | const messageApp = createApp(MessageNotice, { 13 | content: '复制成功!', 14 | close: () => { 15 | document.body.removeChild(messageBox) 16 | messageNoticeInstanceList.pop() 17 | messageApp.unmount() 18 | } 19 | }) 20 | const messageInstance = messageApp.mount(messageBox) 21 | document.body.appendChild(messageBox) 22 | const messageNoticeInstanceListLength = messageNoticeInstanceList.length 23 | const topHeight = 24 | messageNoticeInstanceListLength === 0 25 | ? 10 26 | : (messageNoticeInstanceListLength + 1) * 10 + messageNoticeInstanceListLength * 42 27 | // @ts-ignore 28 | messageInstance.setTopHeight(topHeight) 29 | // @ts-ignore 30 | messageInstance.setVisible(true) 31 | messageNoticeInstanceList.push(messageInstance) 32 | } 33 | } 34 | 35 | export { MessageNotice, MessageNoticeService } 36 | -------------------------------------------------------------------------------- /packages/docs/demo/form/Basic.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 46 | -------------------------------------------------------------------------------- /packages/docs/demo/messagebox/Custom.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/messages/message-notice.vue.d.ts: -------------------------------------------------------------------------------- 1 | interface MessageNotice { 2 | content: string; 3 | close: () => void; 4 | } 5 | declare const _default: import('vue').DefineComponent<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps, { 6 | content: string; 7 | }>, { 8 | setVisible: (value: boolean) => void; 9 | setTopHeight: (value: number) => void; 10 | }, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly, { 11 | content: string; 12 | }>>>, { 13 | content: string; 14 | }, {}>; 15 | export default _default; 16 | type __VLS_NonUndefinedable = T extends undefined ? never : T; 17 | type __VLS_TypePropsToRuntimeProps = { 18 | [K in keyof T]-?: {} extends Pick ? { 19 | type: import('vue').PropType<__VLS_NonUndefinedable>; 20 | } : { 21 | type: import('vue').PropType; 22 | required: true; 23 | }; 24 | }; 25 | type __VLS_WithDefaults = { 26 | [K in keyof Pick]: K extends keyof D ? __VLS_Prettify : P[K]; 29 | }; 30 | type __VLS_Prettify = { 31 | [K in keyof T]: T[K]; 32 | } & {}; 33 | -------------------------------------------------------------------------------- /packages/components/Switch/Switch.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Switch from './Switch.vue'; // 确保这是你的 Switch 组件的路径 4 | 5 | describe('Switch.vue', () => { 6 | it('should render correctly with default props', () => { 7 | const wrapper = mount(Switch); 8 | expect(wrapper.find('.er-switch')).toBeTruthy(); 9 | }); 10 | 11 | it('should handle click event and toggle the checked state', async () => { 12 | const wrapper = mount(Switch, { 13 | props: { 14 | modelValue: false, 15 | }, 16 | }); 17 | 18 | await wrapper.trigger('click'); 19 | expect(wrapper.emitted()['update:modelValue'][0]).toEqual([true]); 20 | expect(wrapper.emitted()['change'][0]).toEqual([true]); 21 | 22 | await wrapper.trigger('click'); 23 | expect(wrapper.emitted()['update:modelValue'][1]).toEqual([false]); 24 | expect(wrapper.emitted()['change'][1]).toEqual([false]); 25 | }); 26 | 27 | it('should not toggle when disabled', async () => { 28 | const wrapper = mount(Switch, { 29 | props: { 30 | modelValue: false, 31 | disabled: true, 32 | }, 33 | }); 34 | 35 | await wrapper.trigger('click'); 36 | expect(wrapper.emitted()).not.toHaveProperty('update:modelValue'); 37 | expect(wrapper.emitted()).not.toHaveProperty('change'); 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /libs/vitepress-preview-component/hooks/use-namespaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 钩子函数使用 3 | * const ns = useNameSpace(); 4 | * ns.b() => block 5 | * ns.e(element) => block__element 6 | * ns.m(modifier) => block--modifier 7 | * ns.bem(element,modifier) => block__element--modifier 8 | */ 9 | 10 | interface UseNameSpaceReturn { 11 | b: () => string 12 | e: (element: string) => string 13 | m: (modifier: string) => string 14 | bem: (_block?: string, element?: string, modifier?: string) => string 15 | } 16 | 17 | const defaultPrefix = 'vitepress-demo-preview' 18 | 19 | const generateName = (prefix: string, block?: string, element?: string, modifier?: string) => { 20 | let defaultName = block === '' ? `${prefix}` : `${prefix}-${block}` 21 | if (element) defaultName += `__${element}` 22 | if (modifier) defaultName += `--${modifier}` 23 | return defaultName 24 | } 25 | 26 | export const useNameSpace = (block: string = ''): UseNameSpaceReturn => { 27 | const b = () => generateName(defaultPrefix, block) 28 | const e = (element: string = '') => generateName(defaultPrefix, block, element) 29 | const m = (modifier: string = '') => generateName(defaultPrefix, block, '', modifier) 30 | const bem = (_block?: string, element?: string, modifier?: string) => 31 | generateName(defaultPrefix, _block, element, modifier) 32 | 33 | return { 34 | b, 35 | e, 36 | m, 37 | bem 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/messages/message-notice.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/components/Select/Option.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /packages/docs/demo/button/Basic.vue: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /packages/play/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/Disabled.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 59 | -------------------------------------------------------------------------------- /packages/components/index.test.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from "vue"; 2 | import { describe, it, expect } from "vitest"; 3 | import { 4 | ErAlert, 5 | ErButton, 6 | ErButtonGroup, 7 | ErCollapse, 8 | ErCollapseItem, 9 | ErDropdown, 10 | ErDropdownItem, 11 | ErForm, 12 | ErFormItem, 13 | ErIcon, 14 | ErInput, 15 | ErLoading, 16 | ErLoadingDirective, 17 | ErLoadingService, 18 | ErMessage, 19 | ErMessageBox, 20 | ErNotification, 21 | ErOption, 22 | ErPopconfirm, 23 | ErSelect, 24 | ErSwitch, 25 | ErTooltip, 26 | ErUpload, 27 | } from "./index"; 28 | import { map, get } from "lodash-es"; 29 | 30 | const components = [ 31 | ErButton, 32 | ErButtonGroup, 33 | ErCollapse, 34 | ErCollapseItem, 35 | ErIcon, 36 | ErDropdown, 37 | ErDropdownItem, 38 | ErTooltip, 39 | ErMessage, 40 | ErInput, 41 | ErSwitch, 42 | ErSelect, 43 | ErOption, 44 | ErForm, 45 | ErFormItem, 46 | ErAlert, 47 | ErNotification, 48 | ErLoading, 49 | ErUpload, 50 | ErPopconfirm, 51 | ErMessageBox, 52 | ] as Plugin[]; 53 | 54 | describe("components/index.ts", () => { 55 | it.each(map(components, (c) => [get(c, "name") ?? "", c]))("%s should be exported", (_, component) => { 56 | expect(component).toBeDefined(); 57 | expect(component.install).toBeDefined() 58 | }); 59 | 60 | it('ErLoadingService and ErLoadingDirective should be exported',()=>{ 61 | expect(ErLoadingService).toBeDefined() 62 | expect(ErLoadingDirective).toBeDefined() 63 | }) 64 | }); 65 | -------------------------------------------------------------------------------- /packages/components/Message/types.ts: -------------------------------------------------------------------------------- 1 | import type { VNode, ComponentInternalInstance } from "vue"; 2 | 3 | export const messageTypes = [ 4 | "info", 5 | "success", 6 | "warning", 7 | "danger", 8 | "error", 9 | ] as const; 10 | export type messageType = (typeof messageTypes)[number]; 11 | 12 | export interface MessageHandler { 13 | close(): void; 14 | } 15 | 16 | export type MessageFn = { 17 | (props: MessageParams): MessageHandler; 18 | closeAll(type?: messageType): void; 19 | }; 20 | 21 | export type MessageTypeFn = (props: MessageParams) => MessageHandler; 22 | 23 | export interface Message extends MessageFn { 24 | success: MessageTypeFn; 25 | warning: MessageTypeFn; 26 | info: MessageTypeFn; 27 | danger: MessageTypeFn; 28 | error: MessageTypeFn; 29 | } 30 | 31 | export interface MessageProps { 32 | id: string; 33 | message?: string | VNode | (() => VNode); 34 | duration?: number; 35 | showClose?: boolean; 36 | center?: boolean; 37 | type?: messageType; 38 | offset?: number; 39 | zIndex: number; 40 | transitionName?: string; 41 | onDestory(): void; 42 | } 43 | 44 | export type MessageOptions = Partial>; 45 | export type MessageParams = string | VNode | MessageOptions; 46 | 47 | export interface MessageInstance { 48 | id: string; 49 | vnode: VNode; 50 | props: MessageProps; 51 | vm: ComponentInternalInstance; 52 | handler: MessageHandler; 53 | } 54 | 55 | export type CreateMessageProps = Omit< 56 | MessageProps, 57 | "onDestory" | "id" | "zIndex" 58 | >; 59 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/HideOnClick.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 59 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/container/naive-ui/NaiveUI.vue.d.ts: -------------------------------------------------------------------------------- 1 | interface DemoBlockProps { 2 | code: string; 3 | showCode: string; 4 | title: string; 5 | description: string; 6 | } 7 | declare const _default: __VLS_WithTemplateSlots, { 8 | title: string; 9 | description: string; 10 | }>, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly, { 11 | title: string; 12 | description: string; 13 | }>>>, { 14 | title: string; 15 | description: string; 16 | }, {}>, { 17 | default?(_: {}): any; 18 | }>; 19 | export default _default; 20 | type __VLS_NonUndefinedable = T extends undefined ? never : T; 21 | type __VLS_TypePropsToRuntimeProps = { 22 | [K in keyof T]-?: {} extends Pick ? { 23 | type: import('vue').PropType<__VLS_NonUndefinedable>; 24 | } : { 25 | type: import('vue').PropType; 26 | required: true; 27 | }; 28 | }; 29 | type __VLS_WithDefaults = { 30 | [K in keyof Pick]: K extends keyof D ? __VLS_Prettify : P[K]; 33 | }; 34 | type __VLS_Prettify = { 35 | [K in keyof T]: T[K]; 36 | } & {}; 37 | type __VLS_WithTemplateSlots = T & { 38 | new (): { 39 | $slots: S; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/components/Loading/style.css: -------------------------------------------------------------------------------- 1 | .er-loading { 2 | --er-loading-icon-color: var(--er-color-primary); 3 | --er-loading-mask-margin: 0; 4 | --er-loading-mask-size: 100%; 5 | --er-loading-icon-size: 42px; 6 | --er-loading-font-size: 14px; 7 | --er-loading-z-index: 20000; 8 | } 9 | .er-loading { 10 | opacity: 1; 11 | transition: opacity var(--er-transition-duration); 12 | &.er-loading__mask { 13 | position: absolute; 14 | margin: var(--er-loading-mask-margin); 15 | top: var(--er-loading-mask-margin); 16 | right: var(--er-loading-mask-margin); 17 | bottom: var(--er-loading-mask-margin); 18 | left: var(--er-loading-mask-margin); 19 | height: var(--er-loading-mask-size); 20 | width: var(--er-loading-mask-size); 21 | z-index: var(--er-loading-z-index); 22 | background: var(--er-loading-bg-color); 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | &.is-fullscreen { 27 | position: fixed; 28 | } 29 | } 30 | .er-loading__spinner { 31 | color: var(--er-loading-icon-color); 32 | text-align: center; 33 | .er-loading-text { 34 | margin: 3px 0; 35 | font-size: var(--er-loading-font-size); 36 | } 37 | i { 38 | font-size: var(--er-loading-icon-size); 39 | } 40 | } 41 | } 42 | .fade-in-linear-enter-from, 43 | .fade-in-linear-leave-to { 44 | opacity: 0; 45 | } 46 | 47 | .er-loading-parent--relative { 48 | position: relative !important; 49 | } 50 | .er-loading-parent--hiden { 51 | overflow: hidden !important; 52 | } 53 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/container/ant-design-ui/AntDesign.vue.d.ts: -------------------------------------------------------------------------------- 1 | interface DemoBlockProps { 2 | code: string; 3 | showCode: string; 4 | title: string; 5 | description: string; 6 | } 7 | declare const _default: __VLS_WithTemplateSlots, { 8 | title: string; 9 | description: string; 10 | }>, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly, { 11 | title: string; 12 | description: string; 13 | }>>>, { 14 | title: string; 15 | description: string; 16 | }, {}>, { 17 | default?(_: {}): any; 18 | }>; 19 | export default _default; 20 | type __VLS_NonUndefinedable = T extends undefined ? never : T; 21 | type __VLS_TypePropsToRuntimeProps = { 22 | [K in keyof T]-?: {} extends Pick ? { 23 | type: import('vue').PropType<__VLS_NonUndefinedable>; 24 | } : { 25 | type: import('vue').PropType; 26 | required: true; 27 | }; 28 | }; 29 | type __VLS_WithDefaults = { 30 | [K in keyof Pick]: K extends keyof D ? __VLS_Prettify : P[K]; 33 | }; 34 | type __VLS_Prettify = { 35 | [K in keyof T]: T[K]; 36 | } & {}; 37 | type __VLS_WithTemplateSlots = T & { 38 | new (): { 39 | $slots: S; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/.dist/container/element-plus/ElementPlus.vue.d.ts: -------------------------------------------------------------------------------- 1 | interface DemoBlockProps { 2 | code: string; 3 | showCode: string; 4 | title: string; 5 | description: string; 6 | } 7 | declare const _default: __VLS_WithTemplateSlots, { 8 | title: string; 9 | description: string; 10 | }>, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly, { 11 | title: string; 12 | description: string; 13 | }>>>, { 14 | title: string; 15 | description: string; 16 | }, {}>, { 17 | default?(_: {}): any; 18 | }>; 19 | export default _default; 20 | type __VLS_NonUndefinedable = T extends undefined ? never : T; 21 | type __VLS_TypePropsToRuntimeProps = { 22 | [K in keyof T]-?: {} extends Pick ? { 23 | type: import('vue').PropType<__VLS_NonUndefinedable>; 24 | } : { 25 | type: import('vue').PropType; 26 | required: true; 27 | }; 28 | }; 29 | type __VLS_WithDefaults = { 30 | [K in keyof Pick]: K extends keyof D ? __VLS_Prettify : P[K]; 33 | }; 34 | type __VLS_Prettify = { 35 | [K in keyof T]: T[K]; 36 | } & {}; 37 | type __VLS_WithTemplateSlots = T & { 38 | new (): { 39 | $slots: S; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/docs/demo/loading/Custom.vue: -------------------------------------------------------------------------------- 1 | 7 | 40 | 41 | 46 | -------------------------------------------------------------------------------- /packages/hooks/useFocusController.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from "lodash-es"; 2 | import { getCurrentInstance, ref, type Ref } from "vue"; 3 | import useEventListener from "./useEventListener"; 4 | 5 | interface UseFocusControllerOptions { 6 | afterFocus?(): void; 7 | beforeBlur?(event: FocusEvent): boolean | void; 8 | afterBlur?(): void; 9 | } 10 | 11 | export function useFocusController( 12 | target: Ref, 13 | { afterFocus, beforeBlur, afterBlur }: UseFocusControllerOptions = {} 14 | ) { 15 | const instance = getCurrentInstance()!; 16 | const { emit } = instance; 17 | const wrapperRef = ref(); 18 | const isFocused = ref(false); 19 | 20 | const handleFocus = (event: FocusEvent) => { 21 | if (isFocused.value) return; 22 | isFocused.value = true; 23 | emit("focus", event); 24 | afterFocus?.(); 25 | }; 26 | 27 | const handleBlur = (event: FocusEvent) => { 28 | const cancelBlur = isFunction(beforeBlur) ? beforeBlur(event) : false; 29 | if ( 30 | cancelBlur || 31 | (event.relatedTarget && 32 | wrapperRef.value?.contains(event.relatedTarget as Node)) 33 | ) 34 | return; 35 | 36 | isFocused.value = false; 37 | emit("blur", event); 38 | afterBlur?.(); 39 | }; 40 | 41 | const handleClick = () => { 42 | target.value?.focus(); 43 | }; 44 | 45 | useEventListener(wrapperRef, "click", handleClick); 46 | 47 | return { 48 | wrapperRef, 49 | isFocused, 50 | handleFocus, 51 | handleBlur, 52 | }; 53 | } 54 | 55 | export default useFocusController; 56 | -------------------------------------------------------------------------------- /packages/components/Select/types.ts: -------------------------------------------------------------------------------- 1 | import type { VNode, ComputedRef } from "vue"; 2 | 3 | export type RenderLabelFunc = (option: SelectOptionProps) => VNode | string; 4 | export type CustomFilterFunc = (value: string) => SelectOptionProps[]; 5 | export type CustomFilterRemoteFunc = ( 6 | value: string 7 | ) => Promise; 8 | 9 | export interface SelectOptionProps { 10 | value: string; 11 | label: string; 12 | disabled?: boolean; 13 | } 14 | 15 | export interface SelectProps { 16 | modelValue: string; 17 | id?: string; 18 | options?: SelectOptionProps[]; 19 | placeholder?: string; 20 | disabled?: boolean; 21 | clearable?: boolean; 22 | renderLabel?: RenderLabelFunc; 23 | filterable?: boolean; 24 | filterMethod?: CustomFilterFunc; 25 | remote?: boolean; 26 | remoteMethod?: CustomFilterRemoteFunc; 27 | } 28 | 29 | export interface SelectStates { 30 | inputValue: string; 31 | selectedOption: SelectOptionProps | void | null; 32 | mouseHover: boolean; 33 | loading: boolean; 34 | highlightedIndex: number; 35 | } 36 | 37 | export interface SelectEmits { 38 | (e: "update:modelValue", value: string): void; 39 | (e: "change", value: string): void; 40 | (e: "visible-change", vlaue: boolean): void; 41 | 42 | (e: "clear"): void; 43 | (e: "focus"): void; 44 | (e: "blur"): void; 45 | } 46 | 47 | export interface SelectContext { 48 | selectStates: SelectStates; 49 | renderLabel?: RenderLabelFunc; 50 | highlightedLine?: ComputedRef; 51 | handleSelect(item: SelectOptionProps): void; 52 | } 53 | 54 | export interface SelectInstance { 55 | focus(): void; 56 | blur(): void; 57 | } 58 | -------------------------------------------------------------------------------- /packages/docs/get-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: false 3 | next: 4 | link: /components/button 5 | text: Button 按钮 6 | --- 7 | # 最新 Vue3 + TS 高仿 ElementPlus 打造自己的组件库 8 | 9 | ## 安装 10 | 11 | 12 | ```bash 13 | npm i @eric-ui --save 14 | ``` 15 | 16 | ## 开始使用 17 | 18 | **全局使用** 19 | 20 | 21 | ```js 22 | // 引入所有组件 23 | import EricUI from 'eric-ui' 24 | // 引入样式 25 | import 'eric-ui/dist/style.css' 26 | 27 | import App from './App.vue' 28 | // 全局使用 29 | createApp(App).use(ErElement).mount('#app') 30 | ``` 31 | 32 | ```vue 33 | 36 | ``` 37 | 38 | **单个导入** 39 | 40 | Eric-UI 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。 41 | 42 | 43 | ```vue 44 | 47 | 53 | ``` 54 | 55 | ## 亮点 56 | 57 | ::: details 58 | - Vite + Vitest + Vitepress 工具链 59 | - monorepo 分包管理 60 | - github actions 实现 CI/CD 自动化部署 61 | - 大模型辅助:使用大模型辅助完成需求分析,设计思路,快速实现组件,提升开发效率。 62 | - 当然,也会展示 发布“开箱即用” 的 npm 包 63 | ::: 64 | 65 | -------------------------------------------------------------------------------- /packages/theme/reset.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: var(--er-font-family); 3 | font-weight: 400; 4 | font-size: var(--er-font-size-base); 5 | line-height: calc(var(--er-font-size-base) * 1.2); 6 | color: var(--er-text-color-primary); 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | -webkit-tap-highlight-color: transparent; 10 | } 11 | 12 | a { 13 | color: var(--er-color-primary); 14 | text-decoration: none; 15 | 16 | &:hover, 17 | &:focus { 18 | color: var(--er-color-primary-light-3); 19 | } 20 | 21 | &:active { 22 | color: var(--er-color-primary-dark-2); 23 | } 24 | } 25 | 26 | h1, 27 | h2, 28 | h3, 29 | h4, 30 | h5, 31 | h6 { 32 | color: var(--er-text-color-regular); 33 | font-weight: inherit; 34 | 35 | &:first-child { 36 | margin-top: 0; 37 | } 38 | 39 | &:last-child { 40 | margin-bottom: 0; 41 | } 42 | } 43 | 44 | h1 { 45 | font-size: calc(var(--er-font-size-base) + 6px); 46 | } 47 | 48 | h2 { 49 | font-size: calc(var(--er-font-size-base) + 4px); 50 | } 51 | 52 | h3 { 53 | font-size: calc(var(--er-font-size-base) + 2px); 54 | } 55 | 56 | h4, 57 | h5, 58 | h6, 59 | p { 60 | font-size: inherit; 61 | } 62 | 63 | p { 64 | line-height: 1.8; 65 | 66 | &:first-child { 67 | margin-top: 0; 68 | } 69 | 70 | &:last-child { 71 | margin-bottom: 0; 72 | } 73 | } 74 | 75 | sup, 76 | sub { 77 | font-size: calc(var(--er-font-size-base) - 1px); 78 | } 79 | 80 | small { 81 | font-size: calc(var(--er-font-size-base) - 2px); 82 | } 83 | 84 | hr { 85 | margin-top: 20px; 86 | margin-bottom: 20px; 87 | border: 0; 88 | border-top: 1px solid var(--er-border-color-lighter); 89 | } 90 | -------------------------------------------------------------------------------- /packages/components/Collapse/CollapseItem.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 56 | 57 | 60 | -------------------------------------------------------------------------------- /packages/play/src/stories/Alert.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { StoryObj, Meta, ArgTypes } from "@storybook/vue3"; 2 | import { ref, watch } from "vue"; 3 | import { fn } from "@storybook/test"; 4 | import { ErAlert, type AlertInstance } from "eric-ui"; 5 | import "eric-ui/dist/theme/Alert.css"; 6 | 7 | type Story = StoryObj & { argTypes?: ArgTypes }; 8 | 9 | const meta: Meta = { 10 | title: "Example/Alert", 11 | component: ErAlert, 12 | tags: ["autodocs"], 13 | argTypes: { 14 | type: { 15 | control: "select", 16 | options: ["success", "warning", "info", "danger"], 17 | }, 18 | effect: { 19 | control: "select", 20 | options: ["light", "dark"], 21 | }, 22 | center: { 23 | control: "boolean", 24 | }, 25 | }, 26 | args: { 27 | onClose: fn(), 28 | }, 29 | }; 30 | 31 | export const Default: Story & { args: { visible: boolean } } = { 32 | args: { 33 | title: "标题", 34 | description: "这是一段描述", 35 | type: "success", 36 | effect: "light", 37 | closable: true, 38 | showIcon: true, 39 | visible: true, 40 | }, 41 | render: (args) => ({ 42 | components: { ErAlert }, 43 | setup() { 44 | const alertRef = ref(); 45 | watch( 46 | () => (args as any).visible, 47 | (val: boolean) => { 48 | if (val) { 49 | alertRef.value?.open(); 50 | } else { 51 | alertRef.value?.close(); 52 | } 53 | } 54 | ); 55 | return { args, alertRef }; 56 | }, 57 | template: ` 58 | 59 | `, 60 | }), 61 | }; 62 | 63 | export default meta; 64 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eric-ui", 3 | "version": "1.0.0", 4 | "description": "Components library by Vue3 + Ts", 5 | "type": "module", 6 | "files": [ 7 | "dist" 8 | ], 9 | "sideEffects": [ 10 | "./dist/index.css", 11 | "./dist/theme/*.css" 12 | ], 13 | "main": "./dist/umd/index.umd.cjs", 14 | "module": "./dist/es/index.js", 15 | "types": "./dist/types/core/index.d.ts", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/es/index.js", 19 | "require": "./dist/umd/index.umd.cjs", 20 | "types": "./dist/types/core/index.d.ts" 21 | }, 22 | "./dist/": { 23 | "import": "./dist/", 24 | "require": "./dist/" 25 | } 26 | }, 27 | "scripts": { 28 | "build": "run-p build-es build-umd", 29 | "build:watch": "run-p build-es:watch build-umd:watch", 30 | "build-es": "vite build --config build/vite.es.config.ts", 31 | "build-umd": "vite build --config build/vite.umd.config.ts", 32 | "build-es:watch": "vite build --watch --config build/vite.es.config.ts", 33 | "build-umd:watch": "vite build --watch --config build/vite.umd.config.ts" 34 | }, 35 | "keywords": [], 36 | "author": "EricWXY", 37 | "license": "ISC", 38 | "devDependencies": { 39 | "@rollup/plugin-terser": "^0.4.4", 40 | "rollup-plugin-visualizer": "^5.12.0", 41 | "terser": "^5.31.0", 42 | "vite-plugin-compression": "^0.5.1" 43 | }, 44 | "dependencies": { 45 | "@fortawesome/fontawesome-svg-core": "^6.5.1", 46 | "@fortawesome/free-solid-svg-icons": "^6.5.1", 47 | "@fortawesome/vue-fontawesome": "^3.0.6", 48 | "@popperjs/core": "^2.11.8", 49 | "async-validator": "^4.2.5" 50 | }, 51 | "peerDependencies": { 52 | "vue": "^3.4.27" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/hooks/__test__/useEventListener.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from "vitest"; 2 | import { ref, defineComponent } from "vue"; 3 | import { mount } from "@vue/test-utils"; 4 | 5 | import useEventListener from "../useEventListener"; 6 | 7 | describe("hooks/useEventListener", () => { 8 | it("should add and remove event listener when target is HTMLElement", async () => { 9 | const target = document.createElement("button"); 10 | const handler = vi.fn(); 11 | const wrapper = mount( 12 | defineComponent({ 13 | setup() { 14 | useEventListener(target, "click", handler); 15 | return () =>
; 16 | }, 17 | }) 18 | ); 19 | wrapper.get("#container").element.appendChild(target); 20 | 21 | await target.click(); 22 | expect(handler).toHaveBeenCalledOnce(); 23 | 24 | await wrapper.unmount(); 25 | await target.click(); 26 | expect(handler).toHaveBeenCalledOnce(); 27 | }); 28 | it("should add and remove event listener when target is Ref", async () => { 29 | const target = ref(); 30 | const handler = vi.fn(); 31 | 32 | mount( 33 | defineComponent({ 34 | setup() { 35 | useEventListener(target, "click", handler); 36 | return () => ; 37 | }, 38 | }) 39 | ); 40 | 41 | await document.getElementById('container')?.click() 42 | await target.value?.click(); 43 | 44 | expect(handler).toHaveBeenCalledOnce(); 45 | 46 | target.value = document.createElement("div"); 47 | await document.getElementById('container')?.click() 48 | expect(handler).toHaveBeenCalledOnce(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/components/Dropdown/style.css: -------------------------------------------------------------------------------- 1 | .er-dropdown .er-dropdown__menu { 2 | --er-dropdown-menuItem-hover-fill: var(--er-color-primary-light-9); 3 | --er-dropdown-menuItem-hover-color: var(--er-color-primary); 4 | --er-dropdown-menuItem-disabled-color: var(--er-border-color-lighter); 5 | --er-dropdown-menuItem-divided-color: var(--er-border-color-lighter); 6 | } 7 | .er-dropdown { 8 | display: inline-block; 9 | .er-tooltip { 10 | --er-popover-padding: 5px 0; 11 | } 12 | &.is-disabled>*{ 13 | color: var(--er-text-color-placeholder) !important; 14 | cursor: not-allowed !important; 15 | } 16 | } 17 | .er-dropdown__menu { 18 | list-style-type: none; 19 | margin: 0; 20 | padding: 0; 21 | .er-dropdown__item { 22 | display: flex; 23 | align-items: center; 24 | white-space: nowrap; 25 | list-style: none; 26 | line-height: 22px; 27 | padding: 5px 16px; 28 | margin: 0; 29 | font-size: var(--er-font-size-base); 30 | color: var(--er-text-color-regular); 31 | cursor: pointer; 32 | outline: none; 33 | &:hover { 34 | background-color: var(--er-dropdown-menuItem-hover-fill); 35 | color: var(--er-dropdown-menuItem-hover-color); 36 | } 37 | &.is-disabled { 38 | color: var(--er-dropdown-menuItem-disabled-color); 39 | cursor: not-allowed; 40 | background-image: none; 41 | } 42 | } 43 | 44 | .er-dropdown__item--large { 45 | padding: 7px 20px; 46 | line-height: 22px; 47 | font-size: 14px; 48 | } 49 | .er-dropdown__item--small { 50 | padding: 2px 12px; 51 | line-height: 20px; 52 | font-size: 12px; 53 | } 54 | .divided-placeholder { 55 | margin: 6px 0; 56 | border-top: 1px solid var(--er-dropdown-menuItem-divided-color); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/test-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Test and deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | test: 10 | name: Run Lint and Test 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | 20 | - name: Install pnpm 21 | run: npm install -g pnpm 22 | 23 | - name: Install dependencies 24 | run: pnpm install --frozen-lockfile 25 | 26 | - name: Build hooks 27 | run: npm run build-hooks 28 | 29 | - name: Run tests 30 | run: npm run test 31 | 32 | build: 33 | name: Build docs 34 | runs-on: ubuntu-latest 35 | needs: test 36 | 37 | steps: 38 | - name: Checkout repo 39 | uses: actions/checkout@v3 40 | 41 | - name: Setup Node 42 | uses: actions/setup-node@v3 43 | 44 | - name: Install pnpm 45 | run: npm install -g pnpm 46 | 47 | - name: Install dependencies 48 | run: pnpm install --frozen-lockfile 49 | 50 | - name: Build docs 51 | run: npm run docs:build 52 | 53 | - name: Upload docs 54 | uses: actions/upload-artifact@v3 55 | with: 56 | name: docs 57 | path: ./packages/docs/.vitepress/dist 58 | 59 | deploy: 60 | name: Deploy to GitHub Pages 61 | runs-on: ubuntu-latest 62 | needs: build 63 | steps: 64 | - name: Download docs 65 | uses: actions/download-artifact@v3 66 | with: 67 | name: docs 68 | 69 | - name: Deploy to GitHub Pages 70 | uses: peaceiris/actions-gh-pages@v3 71 | with: 72 | github_token: ${{ secrets.GH_TOKEN }} 73 | publish_dir: . 74 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/Basic.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | 41 | 65 | -------------------------------------------------------------------------------- /packages/docs/demo/dropdown/Trigger.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | 68 | -------------------------------------------------------------------------------- /packages/components/Loading/directive.ts: -------------------------------------------------------------------------------- 1 | import { type Directive, type DirectiveBinding, type MaybeRef } from "vue"; 2 | import type { LoadingOptions } from "./types"; 3 | import { type LoadingInstance, Loading } from "./service"; 4 | 5 | const INSTANCE_KEY = Symbol("loading"); 6 | 7 | export interface ElementLoading extends HTMLElement { 8 | [INSTANCE_KEY]?: { 9 | instance: LoadingInstance; 10 | options: LoadingOptions; 11 | }; 12 | } 13 | 14 | function createInstance( 15 | el: ElementLoading, 16 | binding: DirectiveBinding 17 | ) { 18 | const getProp = (name: K) => { 19 | return el.getAttribute(`er-loading-${name}`) as MaybeRef; 20 | }; 21 | 22 | const getModifier = (name: K) => { 23 | return binding.modifiers[name]; 24 | }; 25 | 26 | const fullscreen = getModifier("fullscreen"); 27 | 28 | const options: LoadingOptions = { 29 | text: getProp("text"), 30 | spinner: getProp("spinner"), 31 | background: getProp("background"), 32 | target: fullscreen ? void 0 : el, 33 | body: getModifier("body"), 34 | lock: getModifier("lock"), 35 | fullscreen, 36 | }; 37 | el[INSTANCE_KEY] = { 38 | options, 39 | instance: Loading(options), 40 | }; 41 | } 42 | 43 | export const vLoading: Directive = { 44 | mounted(el, binding) { 45 | if (binding.value) createInstance(el, binding); 46 | }, 47 | updated(el, binding) { 48 | if (binding.oldValue === binding.value) return; 49 | 50 | if (binding.value && !binding.oldValue) { 51 | createInstance(el, binding); 52 | return; 53 | } 54 | 55 | el[INSTANCE_KEY]?.instance?.close(); 56 | }, 57 | 58 | unmounted(el) { 59 | el[INSTANCE_KEY]?.instance.close(); 60 | el[INSTANCE_KEY] = void 0; 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /packages/components/Notification/Notification.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest"; 2 | import { nextTick } from "vue"; 3 | import { notification, closeAll } from "./methods"; 4 | 5 | export const rAF = async () => { 6 | return new Promise((res) => { 7 | requestAnimationFrame(() => { 8 | requestAnimationFrame(async () => { 9 | res(null); 10 | await nextTick(); 11 | }); 12 | }); 13 | }); 14 | }; 15 | 16 | function getTopValue(element: Element) { 17 | const styles = window.getComputedStyle(element); 18 | const topValue = styles.getPropertyValue("top"); 19 | return Number.parseFloat(topValue); 20 | } 21 | 22 | describe("createMessage", () => { 23 | test("call notification()", async () => { 24 | const handler = notification({ message: "hello msg", duration: 0 }); 25 | await rAF(); 26 | expect(document.querySelector(".er-notification")).toBeTruthy(); 27 | handler.close(); 28 | await rAF(); 29 | expect(document.querySelector(".er-notification")).toBeFalsy(); 30 | }); 31 | 32 | test('call notification() more times',async()=> { 33 | notification({ message: "hello msg", duration: 0 }); 34 | notification({ message: "hello msg", duration: 0 }); 35 | await rAF(); 36 | expect(document.querySelectorAll(".er-notification").length).toBe(2); 37 | notification.closeAll() 38 | await rAF(); 39 | expect(document.querySelectorAll(".er-notification").length).toBe(0); 40 | }) 41 | 42 | test('offset',async()=>{ 43 | notification({ message: "hello msg", duration: 0,offset:100 }); 44 | notification({ message: "hello msg", duration: 0,offset:50 }); 45 | await rAF() 46 | const elements = document.querySelectorAll(".er-notification") 47 | expect(elements.length).toBe(2) 48 | 49 | expect(getTopValue(elements[0])).toBe(100) 50 | expect(getTopValue(elements[1])).toBe(150) 51 | }) 52 | }); 53 | -------------------------------------------------------------------------------- /packages/components/Collapse/Collapse.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /packages/components/Message/Message.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest"; 2 | import { nextTick } from "vue"; 3 | import { message, closeAll } from "./methods"; 4 | 5 | export const rAF = async () => { 6 | return new Promise((res) => { 7 | requestAnimationFrame(() => { 8 | requestAnimationFrame(async () => { 9 | res(null); 10 | await nextTick(); 11 | }); 12 | }); 13 | }); 14 | }; 15 | 16 | function getTopValue(element: Element) { 17 | const styles = window.getComputedStyle(element); 18 | const topValue = styles.getPropertyValue("top"); 19 | return Number.parseFloat(topValue); 20 | } 21 | 22 | describe("createMessage", () => { 23 | test("调用方法应该创建对应的 Message 组件", async () => { 24 | const handler = message({ message: "hello msg", duration: 0 }); 25 | await rAF(); 26 | expect(document.querySelector(".er-message")).toBeTruthy(); 27 | handler.close(); 28 | await rAF(); 29 | expect(document.querySelector(".er-message")).toBeFalsy(); 30 | }); 31 | 32 | test("多次调用应该创建多个实例", async () => { 33 | message({ message: "hello msg", duration: 0 }); 34 | message({ message: "hello msg2", duration: 0 }); 35 | await rAF(); 36 | expect(document.querySelectorAll(".er-message").length).toBe(2); 37 | closeAll(); 38 | await rAF(); 39 | expect(document.querySelectorAll(".er-message").length).toBe(0); 40 | }); 41 | 42 | test("创建多个实例应该设置正确的 offset", async () => { 43 | message({ message: "hello msg", duration: 0, offset: 100 }); 44 | message({ message: "hello msg2", duration: 0, offset: 50 }); 45 | await rAF(); 46 | const elements = document.querySelectorAll(".er-message"); 47 | expect(elements.length).toBe(2); 48 | // https://github.com/jsdom/jsdom/issues/1590 49 | // jsdom 中获取height的数值都为 0 50 | expect(getTopValue(elements[0])).toBe(100); 51 | expect(getTopValue(elements[1])).toBe(150); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/components/Form/hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | computed, 3 | inject, 4 | unref, 5 | ref, 6 | type MaybeRef, 7 | type WatchStopHandle, 8 | onMounted, 9 | watch, 10 | toRef, 11 | onUnmounted, 12 | } from "vue"; 13 | import type { FormItemContext } from "./types"; 14 | import { FORM_CTX_KEY, FORM_ITEM_CTX_KEY } from "./constants"; 15 | import { useProp, useId } from "@eric-ui/hooks"; 16 | 17 | export function useFormItem() { 18 | const form = inject(FORM_CTX_KEY, void 0); 19 | const formItem = inject(FORM_ITEM_CTX_KEY, void 0); 20 | return { form, formItem }; 21 | } 22 | 23 | export function useFormDisabled(fallback?: MaybeRef) { 24 | const disabled = useProp("disabled"); 25 | const form = inject(FORM_CTX_KEY, void 0); 26 | const formItem = inject(FORM_ITEM_CTX_KEY, void 0); 27 | return computed( 28 | () => 29 | disabled.value || 30 | unref(fallback) || 31 | form?.disabled || 32 | formItem?.disabled || 33 | false 34 | ); 35 | } 36 | 37 | interface UseFormItemInputCommonProps extends Record { 38 | id?: string; 39 | } 40 | 41 | export function useFormItemInputId( 42 | props: UseFormItemInputCommonProps, 43 | formItemContext?: FormItemContext 44 | ) { 45 | const inputId = ref(""); 46 | let unwatch: WatchStopHandle | void; 47 | 48 | onMounted(() => { 49 | unwatch = watch( 50 | toRef(() => props.id), 51 | (id) => { 52 | const newId = id ?? useId().value; 53 | if (newId !== inputId.value) { 54 | inputId.value && formItemContext?.removeInputId(inputId.value); 55 | formItemContext?.addInputId(newId); 56 | inputId.value = newId; 57 | } 58 | }, 59 | { immediate: true } 60 | ); 61 | }); 62 | 63 | onUnmounted(() => { 64 | unwatch && unwatch(); 65 | inputId.value && formItemContext?.removeInputId(inputId.value); 66 | }); 67 | 68 | return { 69 | inputId, 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /packages/components/Notification/types.ts: -------------------------------------------------------------------------------- 1 | import type { VNode, ComponentInternalInstance } from "vue"; 2 | 3 | export const notificationTypes = [ 4 | "info", 5 | "success", 6 | "warning", 7 | "danger", 8 | ] as const; 9 | export type notificationType = (typeof notificationTypes)[number]; 10 | 11 | export const notificationPosition = [ 12 | "top-right", 13 | "top-left", 14 | "bottom-right", 15 | "bottom-left", 16 | ] as const; 17 | export type NotificationPosition = (typeof notificationPosition)[number]; 18 | 19 | export interface NotificationProps { 20 | title: string; 21 | id: string; 22 | zIndex: number; 23 | position: NotificationPosition; 24 | type?: "success" | "info" | "warning" | "danger" | "error"; 25 | message?: string | VNode; 26 | duration?: number; 27 | showClose?: boolean; 28 | offset?: number; 29 | transitionName?: string; 30 | icon?: string; 31 | onClick?(): void; 32 | onClose?(): void; 33 | onDestory(): void; 34 | } 35 | export interface NotificationInstance { 36 | id: string; 37 | vnode: VNode; 38 | vm: ComponentInternalInstance; 39 | props: NotificationProps; 40 | handler: NotificationHandler; 41 | } 42 | export type CreateNotificationProps = Omit< 43 | NotificationProps, 44 | "onDestory" | "id" | "zIndex" 45 | >; 46 | 47 | export interface NotificationHandler { 48 | close(): void; 49 | } 50 | 51 | export type NotificationOptions = Partial>; 52 | export type NotificationParams = string | VNode | NotificationOptions; 53 | 54 | export type NotificationFn = { 55 | (props: NotificationParams): NotificationHandler; 56 | closeAll(type?: notificationType): void; 57 | }; 58 | 59 | export type NotificationTypeFn = ( 60 | props: NotificationParams 61 | ) => NotificationHandler; 62 | 63 | export interface Notification extends NotificationFn { 64 | success: NotificationTypeFn; 65 | warning: NotificationTypeFn; 66 | info: NotificationTypeFn; 67 | danger: NotificationTypeFn; 68 | } 69 | -------------------------------------------------------------------------------- /packages/docs/demo/form/Validate.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 64 | -------------------------------------------------------------------------------- /packages/docs/demo/form/CustomValidate.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 66 | -------------------------------------------------------------------------------- /packages/docs/demo/form/Position.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 64 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/container/naive-ui/naive-ui.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/various.scss'; 2 | 3 | $componentPrefix: 'naive-ui'; 4 | $containerPrefix: #{$defaultPrefix}__#{$componentPrefix}; 5 | 6 | .#{$containerPrefix}__container > * { 7 | font-size: 14px; 8 | } 9 | 10 | .#{$containerPrefix}__container { 11 | div[class*='language-'] { 12 | margin-top: 0; 13 | margin-bottom: 0; 14 | border-radius: 0; 15 | background-color: var(--component-preview-bg); 16 | } 17 | } 18 | 19 | .#{$containerPrefix}__container { 20 | width: 100%; 21 | border-radius: 4px; 22 | border: 1px solid var(--component-preview-border); 23 | box-shadow: 0px 0px 10px var(--component-preview-border); 24 | margin: 10px 0; 25 | overflow: hidden; 26 | 27 | .#{$defaultPrefix}-name_handle, 28 | .#{$defaultPrefix}-description, 29 | .#{$defaultPrefix}-source { 30 | width: 100%; 31 | } 32 | } 33 | 34 | .#{$containerPrefix}__container > .#{$defaultPrefix}-name_handle { 35 | padding: 20px 20px 20px 20px; 36 | display: flex; 37 | align-items: center; 38 | 39 | & > .#{$defaultPrefix}-component__name { 40 | font-size: 20px; 41 | } 42 | 43 | & > .#{$defaultPrefix}-description__btns { 44 | display: flex; 45 | align-items: center; 46 | margin-left: auto; 47 | 48 | svg { 49 | width: 16px; 50 | height: 16px; 51 | fill: currentColor; 52 | color: var(--component-preview-text-1); 53 | cursor: pointer; 54 | } 55 | svg:not(:last-of-type) { 56 | margin-right: 8px; 57 | } 58 | } 59 | } 60 | 61 | .#{$containerPrefix}__container > .#{$defaultPrefix}-description { 62 | padding: 0px 20px 20px 20px; 63 | } 64 | 65 | .#{$containerPrefix}__container > .#{$defaultPrefix}-preview { 66 | padding: 0px 20px 20px 20px; 67 | 68 | & > p { 69 | margin: 0; 70 | padding: 0; 71 | } 72 | } 73 | 74 | .#{$containerPrefix}__container > .#{$defaultPrefix}-source { 75 | overflow: hidden; 76 | transition: all 0.3s ease-in-out; 77 | } 78 | -------------------------------------------------------------------------------- /packages/components/Tooltip/useEventsToTiggerNode.ts: -------------------------------------------------------------------------------- 1 | import { each, isElement } from "lodash-es"; 2 | import { onMounted, onUnmounted, watch } from "vue"; 3 | import type { ComputedRef, Ref, WatchStopHandle } from "vue"; 4 | import type { TooltipProps } from "./types"; 5 | 6 | export function useEvenstToTiggerNode( 7 | props: TooltipProps & { virtualTriggering?: boolean }, 8 | triggerNode: ComputedRef, 9 | events: Ref>, 10 | closeMethod: () => void 11 | ) { 12 | let watchEventsStopHandle: WatchStopHandle | void; 13 | let watchTriggerNodeStopHandle: WatchStopHandle | void; 14 | 15 | const _eventHandleMap = new Map(); 16 | 17 | const _bindEventToVirtualTiggerNode = () => { 18 | const el = triggerNode.value; 19 | isElement(el) && 20 | each(events.value, (fn, event) => { 21 | _eventHandleMap.set(event, fn); 22 | el?.addEventListener(event as keyof HTMLElementEventMap, fn); 23 | }); 24 | }; 25 | const _unbindEventToVirtualTiggerNode = () => { 26 | const el = triggerNode.value; 27 | isElement(el) && 28 | each( 29 | ["mouseenter", "click", "contextmenu"], 30 | (key) => 31 | _eventHandleMap.has(key) && 32 | el?.removeEventListener(key, _eventHandleMap.get(key)) 33 | ); 34 | }; 35 | 36 | onMounted(() => { 37 | watchTriggerNodeStopHandle = watch( 38 | triggerNode, 39 | () => props.virtualTriggering && _bindEventToVirtualTiggerNode(), 40 | { immediate: true } 41 | ); 42 | 43 | watchEventsStopHandle = watch( 44 | events, 45 | () => { 46 | if (!props.virtualTriggering) return; 47 | _unbindEventToVirtualTiggerNode(); 48 | _bindEventToVirtualTiggerNode(); 49 | closeMethod(); 50 | }, 51 | { deep: true } 52 | ); 53 | }); 54 | 55 | onUnmounted(() => { 56 | watchTriggerNodeStopHandle?.(); 57 | watchEventsStopHandle?.(); 58 | }); 59 | } 60 | 61 | export default useEvenstToTiggerNode; 62 | -------------------------------------------------------------------------------- /packages/components/Select/useKeyMap.ts: -------------------------------------------------------------------------------- 1 | import type { Ref, ComputedRef } from "vue"; 2 | import type { SelectOptionProps, SelectStates } from "./types"; 3 | 4 | interface KeyMapParams { 5 | isDropdownVisible: Ref; 6 | highlightedLine: ComputedRef; 7 | hasData: ComputedRef; 8 | lastIndex: ComputedRef; 9 | selectStates: SelectStates; 10 | controlVisible(visible: boolean): void; 11 | handleSelect(option: SelectOptionProps): void; 12 | } 13 | 14 | export default function useKeyMap({ 15 | isDropdownVisible, 16 | controlVisible, 17 | selectStates, 18 | highlightedLine, 19 | handleSelect, 20 | hasData, 21 | lastIndex, 22 | }: KeyMapParams) { 23 | const keyMap: Map = new Map(); 24 | 25 | keyMap.set("Enter", () => { 26 | if (!isDropdownVisible.value) { 27 | controlVisible(true); 28 | } else { 29 | if (selectStates.highlightedIndex >= 0 && highlightedLine.value) { 30 | handleSelect(highlightedLine.value); 31 | } else { 32 | controlVisible(false); 33 | } 34 | } 35 | }); 36 | keyMap.set( 37 | "Escape", 38 | () => isDropdownVisible.value && controlVisible(!isDropdownVisible.value) 39 | ); 40 | keyMap.set("ArrowUp", (e: KeyboardEvent) => { 41 | e.preventDefault(); 42 | if (!hasData.value) return; 43 | if ( 44 | selectStates.highlightedIndex === -1 || 45 | selectStates.highlightedIndex === 0 46 | ) { 47 | selectStates.highlightedIndex = lastIndex.value; 48 | return; 49 | } 50 | selectStates.highlightedIndex--; 51 | }); 52 | 53 | keyMap.set("ArrowDown", (e: KeyboardEvent) => { 54 | e.preventDefault(); 55 | if (!hasData.value) return; 56 | if ( 57 | selectStates.highlightedIndex === -1 || 58 | selectStates.highlightedIndex === lastIndex.value 59 | ) { 60 | selectStates.highlightedIndex = 0; 61 | return; 62 | } 63 | selectStates.highlightedIndex++; 64 | }); 65 | 66 | return keyMap; 67 | } 68 | -------------------------------------------------------------------------------- /packages/docs/demo/collapse/Accordion.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | -------------------------------------------------------------------------------- /packages/docs/demo/collapse/Disabled.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | -------------------------------------------------------------------------------- /packages/components/Alert/Alert.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /libs/vitepress-preview-component/icons/code-copy.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /packages/docs/components/collapse.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collapse 3 | description: Collapse 组件文档 4 | 5 | next: 6 | link: components/dropdown 7 | text: Dropdown 下拉菜单 8 | 9 | prev: 10 | link: components/button 11 | text: Button 按钮 12 | --- 13 | 14 | # Collapse 折叠面板 15 | 16 | 通过折叠面板收纳内容区域 17 | 18 | ## 基础用法 19 | 20 | 可同时展开多个面板,面板之间不影响 21 | 22 | ::: preview 23 | demo-preview=../demo/collapse/Basic.vue 24 | ::: 25 | 26 | ## 手风琴模式 27 | 28 | 通过 `accordion` 属性来设置是否以手风琴模式显示。 29 | 30 | ::: preview 31 | demo-preview=../demo/collapse/Accordion.vue 32 | ::: 33 | 34 | ## 自定义面板标题 35 | 36 | 通过具名 slot 来实现自定义面板的标题内容,以实现增加图标等效果。 37 | 38 | ::: preview 39 | demo-preview=../demo/collapse/CustomTitle.vue 40 | ::: 41 | 42 | ## 禁用状态 43 | 44 | 通过 `disabled` 属性来设置 CollapseItem 是否禁用。 45 | 46 | ::: preview 47 | demo-preview=../demo/collapse/Disabled.vue 48 | ::: 49 | 50 | ## Collapse API 51 | 52 | ### Props 53 | 54 | | Name | Description | Type | Default | 55 | | --------- | ------------------ | -------------------- | ------- | 56 | | v-model | 当前展开项的 name | `CollapseItemName[]` | [] | 57 | | accordion | 是否开启手风琴模式 | `boolean` | false | 58 | 59 | ### Events 60 | 61 | | Name | Description | Type | 62 | | ------ | -------------- | ------------------------------------ | 63 | | change | 切换面板时触发 | `(name: CollapseItemName[]) => void` | 64 | 65 | ### Slots 66 | 67 | | Name | Description | Sub Component | 68 | | ------- | ----------- | ------------- | 69 | | default | 默认插槽 | CollapseItem | 70 | 71 | ## CollapseItem API 72 | 73 | ### Props 74 | 75 | | Name | Description | Type | Default | 76 | | -------- | ----------- | ------------------ | ------- | 77 | | name | 唯一标识符 | `CollapseItemName` | - | 78 | | title | 面板标题 | `string` | "" | 79 | | disabled | 是否禁用 | `boolean` | false | 80 | 81 | ### Slots 82 | 83 | | Name | Description | 84 | | ------- | --------------------------- | 85 | | default | 默认插槽 ,CollapseItem 内容 | 86 | | title | CollapseItem 的标题 | 87 | 88 | ::: tip 89 | ps: 上面提到的 `CollapseItemName` 类型,可以理解为 `string | number` 类型。 90 | ::: 91 | -------------------------------------------------------------------------------- /packages/docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | import { 3 | containerPreview, 4 | componentPreview, 5 | } from "@vitepress-demo-preview/plugin"; 6 | 7 | // https://vitepress.dev/reference/site-config 8 | export default defineConfig({ 9 | title: "Eric-UI", 10 | description: "基于Vue3 高仿 element-ui 组件库", 11 | appearance: false, // 关闭 darkMode @todo 深色模式完成后打开 12 | base: "/eric-ui/", 13 | themeConfig: { 14 | // https://vitepress.dev/reference/default-theme-config 15 | nav: [ 16 | { text: "开始使用", link: "/get-started" }, 17 | { text: "组件", link: "/components/button" }, 18 | ], 19 | search: { 20 | provider: "local", 21 | }, 22 | sidebar: [ 23 | { 24 | text: "指南", 25 | collapsed: false, 26 | items: [{ text: "快速开始", link: "/get-started" }], 27 | }, 28 | { 29 | text: "基础组件", 30 | collapsed: false, 31 | items: [ 32 | { text: "Button 按钮", link: "components/button" }, 33 | { text: "Collapse 折叠面板", link: "components/collapse" }, 34 | { text: "Dropdown 下拉菜单", link: "components/dropdown" }, 35 | ], 36 | }, 37 | { 38 | text: "反馈组件", 39 | collapsed: false, 40 | items: [ 41 | { text: "Alert 提示", link: "components/alert" }, 42 | { text: "Loading 加载", link: "components/loading" }, 43 | { text: "Message 消息提示", link: "components/message" }, 44 | { text: "MessageBox 消息弹出框", link: "components/messagebox" }, 45 | { text: "Notification 通知", link: "components/notification" }, 46 | { text: "Popconfirm 气泡确认框", link: "components/popconfirm" }, 47 | { text: "Tooltip 文字提示", link: "components/tooltip" }, 48 | ], 49 | }, 50 | { 51 | text: "表单组件", 52 | collapsed: false, 53 | items: [{ text: "Form 表单", link: "components/form" }], 54 | }, 55 | ], 56 | 57 | socialLinks: [ 58 | { icon: "github", link: "https://github.com/EricWXY/eric-ui" }, 59 | ], 60 | }, 61 | markdown: { 62 | config(md) { 63 | md.use(containerPreview); 64 | md.use(componentPreview); 65 | }, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /packages/components/Select/style.css: -------------------------------------------------------------------------------- 1 | .er-select { 2 | --er-select-item-hover-bg-color: var(--er-fill-color-light); 3 | --er-select-item-font-size: var(--er-font-size-base); 4 | --er-select-item-font-color: var(--er-text-color-regular); 5 | --er-select-item-selected-font-color: var(--er-color-primary); 6 | --er-select-item-disabled-font-color: var(--er-text-color-placeholder); 7 | --er-select-input-focus-border-color: var(--er-color-primary); 8 | } 9 | 10 | .er-select{ 11 | display: inline-block; 12 | vertical-align: middle; 13 | line-height: 32px; 14 | :deep(.er-tooltip__popper) { 15 | padding: 0; 16 | } 17 | 18 | :deep(.er-input){ 19 | .header-angle { 20 | transition: transform var(--er-transition-duration); 21 | &.is-active { 22 | transform: rotate(180deg); 23 | } 24 | } 25 | } 26 | 27 | .er-select__nodata, .er-select__loading { 28 | padding: 10px 0; 29 | margin: 0; 30 | text-align: center; 31 | color: var(--er-text-color-secondary); 32 | font-size: var(--er-select-font-size); 33 | } 34 | .er-select__menu { 35 | list-style: none; 36 | margin: 6px 0; 37 | padding: 0; 38 | box-sizing: border-box; 39 | } 40 | .er-select__menu-item { 41 | margin: 0; 42 | font-size: var(--er-select-item-font-size); 43 | padding: 0 32px 0 20px; 44 | position: relative; 45 | white-space: nowrap; 46 | overflow: hidden; 47 | text-overflow: ellipsis; 48 | color: var(--er-select-item-font-color); 49 | height: 34px; 50 | line-height: 34px; 51 | box-sizing: border-box; 52 | cursor: pointer; 53 | &:hover { 54 | background-color: var(--er-select-item-hover-bg-color); 55 | } 56 | &.is-selected { 57 | color: var(--er-select-item-selected-font-color); 58 | font-weight: 700; 59 | } 60 | &.is-highlighted { 61 | background-color: var(--er-select-item-hover-bg-color); 62 | } 63 | &.is-disabled { 64 | color: var(--er-select-item-disabled-font-color); 65 | cursor: not-allowed; 66 | &:hover { 67 | background-color: transparent; 68 | } 69 | } 70 | 71 | } 72 | 73 | :deep(.er-input__inner) { 74 | cursor: pointer; 75 | } 76 | } -------------------------------------------------------------------------------- /packages/docs/components/notification.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Notification 3 | description: Notification 组件文档 4 | 5 | next: 6 | link: /components/popconfirm 7 | text: PopConfirm 气泡确认框 8 | 9 | prev: 10 | link: /components/messagebox 11 | text: MessageBox 消息弹出框 12 | --- 13 | 14 | # Notification 通知 15 | 16 | 悬浮出现在页面角落,显示全局的通知提醒消息。 17 | 18 | ## 基础用法 19 | 20 | ::: preview 21 | demo-preview=../demo/notification/Basic.vue 22 | ::: 23 | 24 | ## 不同类型的通知 25 | 26 | 提供了四种不同类型的提醒框:`success`、`warning`、`info` 和 `error` (danger 效果和 error 相同)。 27 | 28 | ::: preview 29 | demo-preview=../demo/notification/Type.vue 30 | ::: 31 | 32 | ## 隐藏关闭按钮 33 | 34 | 可以通过设置 `closable` 属性来隐藏关闭按钮。 35 | 36 | ::: preview 37 | demo-preview=../demo/notification/Closeable.vue 38 | ::: 39 | 40 | ## 全局方法 41 | 42 | 通过全局方法 `$notify` 调用,可以弹出通知。 43 | 44 | ## 单独引用 45 | 46 | ```typescript 47 | import { ErNotification } from "eric-ui"; 48 | ``` 49 | 50 | ## Notification API 51 | 52 | ### Options 53 | 54 | | Name | Description | Type | Default | 55 | | --------- | ---------------- | -------------------------------------------------------- | ------- | 56 | | title | 标题 | `string` | - | 57 | | message | 通知正文内容 | `string \| VNode` | - | 58 | | type | 通知的类型 | `enum` - `info \| success \| warning \| error \| danger` | info | 59 | | icon | 自定义图标 | `string` | - | 60 | | duration | 显示时间 | `number` | 3000 | 61 | | showClose | 是否显示关闭按钮 | `boolean` | true | 62 | | onClose | 关闭时的回调函数 | `() => void` | - | 63 | | onClick | 点击时的回调函数 | `() => void` | - | 64 | | offset | 偏移 | `number` | 20 | 65 | 66 | ### Handler 67 | 68 | | Name | Description | Type | 69 | | ----- | ----------------- | ------------ | 70 | | close | 关闭 Notification | `() => void` | 71 | -------------------------------------------------------------------------------- /packages/components/Collapse/style.css: -------------------------------------------------------------------------------- 1 | .er-collapse { 2 | --er-collapse-border-color: var(--er-border-color-light); 3 | --er-collapse-header-height: 48px; 4 | --er-collapse-header-bg-color: var(--er-fill-color-blank); 5 | --er-collapse-header-text-color: var(--er-text-color-primary); 6 | --er-collapse-header-font-size: 13px; 7 | --er-collapse-content-bg-color: var(--er-fill-color-blank); 8 | --er-collapse-content-font-size: 13px; 9 | --er-collapse-content-text-color: var(--er-text-color-primary); 10 | --er-collapse-disabled-text-color: var(--er-disabled-text-color); 11 | --er-collapse-disabled-border-color: var(--er-border-color-lighter); 12 | border-top: 1px solid var(--er-collapse-border-color); 13 | border-bottom: 1px solid var(--er-collapse-border-color); 14 | } 15 | 16 | .er-collapse-item__header { 17 | display: flex; 18 | align-items: center; 19 | justify-content: space-between; 20 | height: var(--er-collapse-header-height); 21 | line-height: var(--er-collapse-header-height); 22 | background-color: var(--er-collapse-header-bg-color); 23 | color: var(--er-collapse-header-text-color); 24 | cursor: pointer; 25 | font-size: var(--er-collapse-header-font-size); 26 | font-weight: 500; 27 | transition: border-bottom-color var(--er-transition-duration); 28 | outline: none; 29 | border-bottom: 1px solid var(--er-collapse-border-color); 30 | &.is-disabled { 31 | color: var(--er-collapse-disabled-text-color); 32 | cursor: not-allowed; 33 | background-image: none; 34 | } 35 | &.is-active { 36 | border-bottom-color: transparent; 37 | .header-angle { 38 | transform: rotate(90deg); 39 | } 40 | } 41 | .header-angle { 42 | transition: transform var(--er-transition-duration); 43 | } 44 | } 45 | .er-collapse-item__content { 46 | will-change: height; 47 | background-color: var(--er-collapse-content-bg-color); 48 | overflow: hidden; 49 | box-sizing: border-box; 50 | font-size: var(--er-collapse-content-font-size); 51 | color: var(--er-collapse-content-text-color); 52 | border-bottom: 1px solid var(--er-collapse-border-color); 53 | padding-bottom: 25px; 54 | } 55 | .slide-enter-active, 56 | .slide-leave-active { 57 | transition: height var(--er-transition-duration) ease-in-out; 58 | } 59 | -------------------------------------------------------------------------------- /packages/docs/demo/collapse/Basic.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 59 | -------------------------------------------------------------------------------- /packages/docs/demo/collapse/CustomTitle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 58 | -------------------------------------------------------------------------------- /packages/core/build/vite.umd.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { readFile } from "fs"; 3 | import { resolve } from "path"; 4 | import { defer, delay } from "lodash-es"; 5 | import { visualizer } from "rollup-plugin-visualizer"; 6 | import { hooksPlugin as hooks } from "@eric-ui/vite-plugins"; 7 | import shell from "shelljs"; 8 | 9 | import vue from "@vitejs/plugin-vue"; 10 | import compression from "vite-plugin-compression"; 11 | import terser from "@rollup/plugin-terser"; 12 | 13 | const TRY_MOVE_STYLES_DELAY = 750 as const; 14 | 15 | const isProd = process.env.NODE_ENV === "production"; 16 | const isDev = process.env.NODE_ENV === "development"; 17 | const isTest = process.env.NODE_ENV === "test"; 18 | 19 | function moveStyles() { 20 | readFile("./dist/umd/index.css.gz", (err) => { 21 | if (err) return delay(moveStyles, TRY_MOVE_STYLES_DELAY); 22 | defer(() => shell.cp("./dist/umd/index.css", "./dist/index.css")); 23 | }); 24 | } 25 | 26 | export default defineConfig({ 27 | plugins: [ 28 | vue(), 29 | compression({ 30 | filter: /.(cjs|css)$/i, 31 | }), 32 | visualizer({ 33 | filename: "dist/stats.umd.html", 34 | }), 35 | terser({ 36 | compress: { 37 | drop_console: ["log"], 38 | drop_debugger: true, 39 | passes: 3, 40 | global_defs: { 41 | "@DEV": JSON.stringify(isDev), 42 | "@PROD": JSON.stringify(isProd), 43 | "@TEST": JSON.stringify(isTest), 44 | }, 45 | }, 46 | }), 47 | hooks({ 48 | rmFiles: ["./dist/umd", "./dist/index.css", "./dist/stats.umd.html"], 49 | afterBuild: moveStyles, 50 | }), 51 | ], 52 | build: { 53 | outDir: "dist/umd", 54 | lib: { 55 | entry: resolve(__dirname, "../index.ts"), 56 | name: "EricUI", 57 | fileName: "index", 58 | formats: ["umd"], 59 | }, 60 | rollupOptions: { 61 | external: ["vue"], 62 | output: { 63 | exports: "named", 64 | globals: { 65 | vue: "Vue", 66 | }, 67 | assetFileNames: (chunkInfo) => { 68 | if (chunkInfo.name === "style.css") { 69 | return "index.css"; 70 | } 71 | return chunkInfo.name as string; 72 | }, 73 | }, 74 | }, 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /packages/docs/components/popconfirm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Popconfirm 3 | description: Popconfirm 组件文档 4 | 5 | prev: 6 | link: /components/notification 7 | text: Notification 通知 8 | 9 | next: 10 | link: /components/tooltip 11 | text: Tooltip 文字提示 12 | --- 13 | 14 | # Popconfirm 气泡确认框 15 | 16 | 点击某个元素会弹出一个气泡式的确认框。 17 | 18 | ## 基础用法 19 | 20 | ::: preview 21 | demo-preview=../demo/popconfirm/Basic.vue 22 | ::: 23 | 24 | ## 自定义弹出框内容 25 | 26 | 可以通过 props 来自定义 Popconfirm 中内容 27 | 28 | ::: preview 29 | demo-preview=../demo/popconfirm/Custom.vue 30 | ::: 31 | 32 | ## 按钮回调 33 | 34 | 可以通过 `confirm` 和 `cancel` 两个事件的监听来获取点击按钮后的回调 35 | 36 | ::: preview 37 | demo-preview=../demo/popconfirm/Callback.vue 38 | ::: 39 | 40 | ## Popconfirm API 41 | 42 | ### Props 43 | 44 | | Name | Description | Type | Default | 45 | | ------------------- | ---------------------------- | --------- | --------------- | 46 | | title | 提示文字 | `string` | -- | 47 | | confirm-button-text | 确认按钮文字 | `string` | Yes | 48 | | cancel-button-text | 取消按钮文字 | `string` | No | 49 | | confirm-button-type | 确认按钮类型 | `string` | primary | 50 | | cancel-button-type | 取消按钮类型 | `string` | -- | 51 | | icon | 图标 | `string` | question-circle | 52 | | icon-color | 图标颜色 | `string` | #f90 | 53 | | hide-icon | 隐藏图标 | `boolean` | false | 54 | | hide-after | 触发关闭的延时(单位:毫秒) | `number` | 200 | 55 | | width | 宽度 | `string` | 150px | 56 | 57 | ### Events 58 | 59 | | Name | Description | Type | 60 | | ------- | ------------------ | ----------------------------- | 61 | | confirm | 点击确认按钮时触发 | `(event: MouseEvent) => void` | 62 | | cancel | 点击取消按钮时触发 | `(event: MouseEvent) => void` | 63 | 64 | ### Slots 65 | 66 | | Name | Description | 67 | | --------- | ----------------------------------------------- | 68 | | default | 默认插槽, 用于触发 Popconfirm 显示的 HTML 元素 | 69 | | reference | 同上,(default 别名) | 70 | -------------------------------------------------------------------------------- /packages/components/Form/style.css: -------------------------------------------------------------------------------- 1 | .er-form-item { 2 | --er-form-label-width: 150px; 3 | --er-form-label-font-size: var(--er-font-size-base); 4 | --er-form-content-font-size: var(--er-font-size-base); 5 | } 6 | 7 | .er-form-item { 8 | display: flex; 9 | margin-bottom: 18px; 10 | &:has(> .position-top){ 11 | flex-direction: column; 12 | } 13 | 14 | .er-form-item__label { 15 | height: 32px; 16 | line-height: 32px; 17 | width: var(--er-form-label-width); 18 | padding: 0; 19 | padding-right: 10px; 20 | box-sizing: border-box; 21 | display: inline-flex; 22 | font-size: var(--er-form-label-font-size); 23 | color: var(--er-text-color-regular); 24 | &.position-right { 25 | padding-left: 12px; 26 | justify-content: flex-end; 27 | } 28 | &.position-left { 29 | padding-right: 12px; 30 | justify-content: flex-start; 31 | } 32 | &.position-top { 33 | padding-bottom: 12px; 34 | justify-content: flex-start; 35 | } 36 | } 37 | 38 | .er-form-item__content { 39 | display: flex; 40 | flex-wrap: wrap; 41 | align-items: center; 42 | flex: 1; 43 | line-height: 32px; 44 | font-size: var(--er-form-content-font-size); 45 | min-width: 0; 46 | position: relative; 47 | } 48 | 49 | .er-form-item__error-msg { 50 | position: absolute; 51 | top: 100%; 52 | left: 0; 53 | padding-top: 2px; 54 | color: var(--er-color-danger); 55 | font-size: 12px; 56 | line-height: 1; 57 | } 58 | 59 | &.is-error { 60 | :deep(.er-input__wrapper){ 61 | box-shadow: 0 0 0 1px var(--er-color-danger) inset; 62 | } 63 | } 64 | 65 | &.is-required.asterisk-right > .er-form-item__label::after{ 66 | content: '*'; 67 | color: var(--er-color-danger); 68 | margin-left: 4px; 69 | } 70 | 71 | &.is-required.asterisk-left > .er-form-item__label::before{ 72 | content: '*'; 73 | color: var(--er-color-danger); 74 | margin-right: 4px; 75 | } 76 | 77 | &.is-disabled > .er-form-item__label{ 78 | color: var(--er-disabled-text-color); 79 | cursor: not-allowed; 80 | &::before,&::after{ 81 | cursor: not-allowed; 82 | } 83 | } 84 | 85 | &.is-disabled > .er-form-item__content{ 86 | color: var(--er-disabled-text-color); 87 | cursor: not-allowed; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/docs/demo/loading/Basic.vue: -------------------------------------------------------------------------------- 1 | 7 | 57 | 58 | 63 | --------------------------------------------------------------------------------