;
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 |
44 |
45 |
46 |
47 | {{ content }}
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/packages/components/Select/Option.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
47 |
48 |
49 |
52 |
--------------------------------------------------------------------------------
/packages/docs/demo/button/Basic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Default
4 | Primary
5 | Success
6 | Info
7 | Warning
8 | Danger
9 |
10 |
11 |
12 | Plain
13 | Primary
14 | Success
15 | Info
16 | Warning
17 | Danger
18 |
19 |
20 |
21 | Round
22 | Primary
23 | Success
24 | Info
25 | Warning
26 | Danger
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/packages/play/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/docs/demo/dropdown/Disabled.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
disabled
16 |
17 |
18 | Dropdown List
19 |
20 |
21 |
22 |
23 |
24 |
undisabled
25 |
26 |
27 | Dropdown List
28 |
29 |
30 |
31 |
32 |
33 |
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 |
13 |
14 |
15 |
hide-on-click="true"(default)
16 |
17 |
18 | Dropdown List
19 |
20 |
21 |
22 |
23 |
24 |
hide-on-click="false"
25 |
26 |
27 | Dropdown List
28 |
29 |
30 |
31 |
32 |
33 |
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 |
8 |
9 |
10 |
18 |
19 | Consistent with real life: in line with the process and logic of real
20 | life, and comply with languages and habits that the users are used to;
21 |
22 |
23 | Consistent within interface: all elements should be consistent, such
24 | as: design style, icons and texts, position of elements, etc.
25 |
26 |
27 |
28 |
29 | Operation feedback: enable the users to clearly perceive their
30 | operations by style updates and interactive effects;
31 |
32 |
33 | Visual feedback: reflect current state by updating or rearranging
34 | elements of the page.
35 |
36 |
37 |
38 |
39 |
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 |
34 | 我是 Button
35 |
36 | ```
37 |
38 | **单个导入**
39 |
40 | Eric-UI 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。
41 |
42 |
43 | ```vue
44 |
45 | 我是 Button
46 |
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 |
25 |
55 |
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 |
13 |
14 |
15 |
通过 slot 传入 item
16 |
17 |
18 | Dropdown List
19 |
20 |
21 |
22 | Action 1
23 |
24 | Action 3
25 | Action 4
26 |
27 |
28 |
29 |
30 |
通过 props 传入 item
31 |
32 |
33 | Dropdown List
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
65 |
--------------------------------------------------------------------------------
/packages/docs/demo/dropdown/Trigger.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
hover to trigger
16 |
17 |
18 | Dropdown List
19 |
20 |
21 |
22 |
23 |
24 |
click to trigger
25 |
26 |
27 | Dropdown List
28 |
29 |
30 |
31 |
32 |
33 |
right click to trigger
34 |
35 |
36 | Dropdown List
37 |
38 |
39 |
40 |
41 |
42 |
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 |
65 |
66 |
67 |
68 |
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 |
41 |
42 |
43 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Create
60 | Reset
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/packages/docs/demo/form/CustomValidate.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Create
62 | Reset
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/packages/docs/demo/form/Position.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 | Left
29 | Right
34 | Top
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Create
60 | Cancel
61 |
62 |
63 |
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 |
8 |
9 |
10 |
11 | Consistent with real life: in line with the process and logic of real
12 | life, and comply with languages and habits that the users are used to;
13 |
14 |
15 | Consistent within interface: all elements should be consistent, such as:
16 | design style, icons and texts, position of elements, etc.
17 |
18 |
19 |
20 |
21 | Operation feedback: enable the users to clearly perceive their
22 | operations by style updates and interactive effects;
23 |
24 |
25 | Visual feedback: reflect current state by updating or rearranging
26 | elements of the page.
27 |
28 |
29 |
30 |
31 | Simplify the process: keep operating process simple and intuitive;
32 |
33 |
34 | Definite and clear: enunciate your intentions clearly so that the users
35 | can quickly understand and make decisions;
36 |
37 |
38 | Easy to identify: the interface should be straightforward, which helps
39 | the users to identify and frees them from memorizing and recalling.
40 |
41 |
42 |
43 |
44 | Decision making: giving advices about operations is acceptable, but do
45 | not make decisions for the users;
46 |
47 |
48 | Controlled consequences: users should be granted the freedom to operate,
49 | including canceling, aborting or terminating current operation.
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/packages/docs/demo/collapse/Disabled.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 | Consistent with real life: in line with the process and logic of real
12 | life, and comply with languages and habits that the users are used to;
13 |
14 |
15 | Consistent within interface: all elements should be consistent, such as:
16 | design style, icons and texts, position of elements, etc.
17 |
18 |
19 |
20 |
21 | Operation feedback: enable the users to clearly perceive their
22 | operations by style updates and interactive effects;
23 |
24 |
25 | Visual feedback: reflect current state by updating or rearranging
26 | elements of the page.
27 |
28 |
29 |
30 |
31 | Simplify the process: keep operating process simple and intuitive;
32 |
33 |
34 | Definite and clear: enunciate your intentions clearly so that the users
35 | can quickly understand and make decisions;
36 |
37 |
38 | Easy to identify: the interface should be straightforward, which helps
39 | the users to identify and frees them from memorizing and recalling.
40 |
41 |
42 |
43 |
44 | Decision making: giving advices about operations is acceptable, but do
45 | not make decisions for the users;
46 |
47 |
48 | Controlled consequences: users should be granted the freedom to operate,
49 | including canceling, aborting or terminating current operation.
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/packages/components/Alert/Alert.vue:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
40 |
50 |
56 |
57 |
62 | {{ title }}
63 |
64 |
65 | {{ description }}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/libs/vitepress-preview-component/icons/code-copy.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
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 |
13 |
14 |
15 |
16 | Consistent with real life: in line with the process and logic of real
17 | life, and comply with languages and habits that the users are used to;
18 |
19 |
20 | Consistent within interface: all elements should be consistent, such as:
21 | design style, icons and texts, position of elements, etc.
22 |
23 |
24 |
25 |
26 | Operation feedback: enable the users to clearly perceive their
27 | operations by style updates and interactive effects;
28 |
29 |
30 | Visual feedback: reflect current state by updating or rearranging
31 | elements of the page.
32 |
33 |
34 |
35 |
36 | Simplify the process: keep operating process simple and intuitive;
37 |
38 |
39 | Definite and clear: enunciate your intentions clearly so that the users
40 | can quickly understand and make decisions;
41 |
42 |
43 | Easy to identify: the interface should be straightforward, which helps
44 | the users to identify and frees them from memorizing and recalling.
45 |
46 |
47 |
48 |
49 | Decision making: giving advices about operations is acceptable, but do
50 | not make decisions for the users;
51 |
52 |
53 | Controlled consequences: users should be granted the freedom to operate,
54 | including canceling, aborting or terminating current operation.
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/packages/docs/demo/collapse/CustomTitle.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 | Consistency
12 |
13 |
14 |
15 | Consistent with real life: in line with the process and logic of real
16 | life, and comply with languages and habits that the users are used to;
17 |
18 |
19 | Consistent within interface: all elements should be consistent, such as:
20 | design style, icons and texts, position of elements, etc.
21 |
22 |
23 |
24 |
25 | Operation feedback: enable the users to clearly perceive their
26 | operations by style updates and interactive effects;
27 |
28 |
29 | Visual feedback: reflect current state by updating or rearranging
30 | elements of the page.
31 |
32 |
33 |
34 |
35 | Simplify the process: keep operating process simple and intuitive;
36 |
37 |
38 | Definite and clear: enunciate your intentions clearly so that the users
39 | can quickly understand and make decisions;
40 |
41 |
42 | Easy to identify: the interface should be straightforward, which helps
43 | the users to identify and frees them from memorizing and recalling.
44 |
45 |
46 |
47 |
48 | Decision making: giving advices about operations is acceptable, but do
49 | not make decisions for the users;
50 |
51 |
52 | Controlled consequences: users should be granted the freedom to operate,
53 | including canceling, aborting or terminating current operation.
54 |
55 |
56 |
57 |
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 |
8 |
9 |
10 |
11 |
12 | Consistent with real life: in line with the process and logic of real
13 | life, and comply with languages and habits that the users are used to;
14 |
15 |
16 | Consistent within interface: all elements should be consistent, such
17 | as: design style, icons and texts, position of elements, etc.
18 |
19 |
20 |
21 |
22 | Operation feedback: enable the users to clearly perceive their
23 | operations by style updates and interactive effects;
24 |
25 |
26 | Visual feedback: reflect current state by updating or rearranging
27 | elements of the page.
28 |
29 |
30 |
31 |
32 | Simplify the process: keep operating process simple and intuitive;
33 |
34 |
35 | Definite and clear: enunciate your intentions clearly so that the
36 | users can quickly understand and make decisions;
37 |
38 |
39 | Easy to identify: the interface should be straightforward, which helps
40 | the users to identify and frees them from memorizing and recalling.
41 |
42 |
43 |
44 |
45 | Decision making: giving advices about operations is acceptable, but do
46 | not make decisions for the users;
47 |
48 |
49 | Controlled consequences: users should be granted the freedom to
50 | operate, including canceling, aborting or terminating current
51 | operation.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
63 |
--------------------------------------------------------------------------------