Skip to content

Commit

Permalink
feat: Notification 加入 position
Browse files Browse the repository at this point in the history
  • Loading branch information
EricWXY committed Jul 8, 2024
1 parent cb7fa45 commit cca573e
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 62 deletions.
19 changes: 9 additions & 10 deletions packages/components/Message/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import type { MessageProps } from "./types";
import { computed, onMounted, ref, watch } from "vue";
import { getLastBottomOffset } from "./methods";
import { delay } from "lodash-es";
import { useEventListener } from "@eric-ui/hooks";
import { delay, bind } from "lodash-es";
import { useEventListener, useOffset } from "@eric-ui/hooks";
import { RenderVnode, typeIconMap } from "@eric-ui/utils";
import ErIcon from "../Icon/Icon.vue";
Expand All @@ -21,17 +21,16 @@ const props = withDefaults(defineProps<MessageProps>(), {
const visible = ref(false);
const messageRef = ref<HTMLDivElement>();
const iconName = computed(() => typeIconMap.get(props.type) ?? "circle-info");
// div 的高度
const boxHeight = ref(0);
const iconName = computed(() => typeIconMap.get(props.type) ?? "circle-info");
// 上一个实例最下面的坐标,第一个是0
const lastBottomOffset = computed(() => getLastBottomOffset(props.id));
// 本元素应该的 top
const topOffset = computed(() => props.offset + lastBottomOffset.value);
// 为下一个实例预留的底部 offset
const bottomOffset = computed(() => boxHeight.value + topOffset.value);
const { topOffset, bottomOffset } = useOffset({
getLastBottomOffset: bind(getLastBottomOffset, props),
offset: props.offset,
boxHeight,
});
const cssStyle = computed(() => ({
top: topOffset.value + "px",
Expand Down
21 changes: 10 additions & 11 deletions packages/components/Message/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ import type {
Message,
MessageParams,
MessageHandler,
MessageProps,
messageType,
} from "./types";
import { messageTypes } from "./types";
import { render, h, shallowReactive, isVNode } from "vue";
import { findIndex, get, each, set, isString } from "lodash-es";
import { useZIndex } from "@eric-ui/hooks";
import { useZIndex, useId } from "@eric-ui/hooks";
import MessageConstructor from "./Message.vue";

let seed = 0;

const instances: MessageInstance[] = shallowReactive([]);
const { nextZIndex } = useZIndex();

Expand All @@ -25,7 +24,7 @@ export const messageDefaults = {
transitionName: "fade-up",
} as const;

const normalizeOptions = (options: MessageParams): CreateMessageProps => {
function normalizeOptions(options: MessageParams): CreateMessageProps {
const result =
!options || isVNode(options) || isString(options)
? {
Expand All @@ -34,10 +33,10 @@ const normalizeOptions = (options: MessageParams): CreateMessageProps => {
: options;

return { ...messageDefaults, ...result } as CreateMessageProps;
};
}

const createMessage = (props: CreateMessageProps): MessageInstance => {
const id = `message_${seed++}`;
function createMessage(props: CreateMessageProps): MessageInstance {
const id = useId().value;
const container = document.createElement("div");
const destory = () => {
const idx = findIndex(instances, { id });
Expand Down Expand Up @@ -75,17 +74,17 @@ const createMessage = (props: CreateMessageProps): MessageInstance => {
instances.push(instance);

return instance;
};
}

export const message: MessageFn & Partial<Message> = (options = {}) => {
export const message: MessageFn & Partial<Message> = function (options = {}) {
const normalized = normalizeOptions(options);
const instance = createMessage(normalized);

return instance.handler;
};

export function getLastBottomOffset(id: string) {
const idx = findIndex(instances, { id });
export function getLastBottomOffset(this: MessageProps) {
const idx = findIndex(instances, { id: this.id });
if (idx <= 0) return 0;

return get(instances, [idx - 1, "vm", "exposed", "bottomOffset", "value"]);
Expand Down
2 changes: 1 addition & 1 deletion packages/components/Message/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface Message extends MessageFn {

export interface MessageProps {
id: string;
message?: string | VNode;
message?: string | VNode | (() => VNode);
duration?: number;
showClose?: boolean;
center?: boolean;
Expand Down
35 changes: 24 additions & 11 deletions packages/components/Notification/Notification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import type { NotificationProps } from "./types";
import { ref, computed, onMounted } from "vue";
import { getLastBottomOffset } from "./methods";
import { delay, isString } from "lodash-es";
import { bind, delay, isString } from "lodash-es";
import { RenderVnode, typeIconMap } from "@eric-ui/utils";
import { useOffset } from "@eric-ui/hooks";
import ErIcon from "../Icon/Icon.vue";
Expand All @@ -17,30 +18,40 @@ const props = withDefaults(defineProps<NotificationProps>(), {
const visible = ref(false);
const notifyRef = ref<HTMLDivElement>();
// 这个 div 的高度
const boxHeight = ref(0);
const { topOffset, bottomOffset } = useOffset({
getLastBottomOffset: bind(getLastBottomOffset, props),
offset: props.offset,
boxHeight,
});
const iconName = computed(() => {
if (isString(props.icon)) return props.icon;
return typeIconMap.get(props.type);
});
// 这个 div 的高度
const boxHeight = ref(0);
// 上一个实例的最下面的坐标数字,第一个是 0
const lastBottomOffset = computed(() => getLastBottomOffset(props.id));
// 这个元素应该使用的 top
const topOffset = computed(() => props.offset + lastBottomOffset.value);
// 这个元素为下一个元素预留的 offset,也就是它最低端 bottom 的 值
const bottomOffset = computed(() => boxHeight.value + topOffset.value);
const horizontalClass = computed(() =>
props.position.endsWith("right") ? "right" : "left"
);
const verticalProperty = computed(() =>
props.position.startsWith("top") ? "top" : "bottom"
);
const cssStyle = computed(() => ({
top: topOffset.value + "px",
[verticalProperty.value]: topOffset.value + "px",
zIndex: props.zIndex,
}));
let timer: any;
let timer: number;
function startTimer() {
if (props.duration === 0) return;
timer = delay(close, props.duration);
}
function clearTimer() {
clearTimeout(timer);
}
Expand All @@ -54,6 +65,7 @@ onMounted(() => {
visible.value = true;
startTimer();
});
defineExpose({
close,
bottomOffset,
Expand All @@ -72,6 +84,7 @@ defineExpose({
:class="{
[`er-notification--${type}`]: type,
'show-close': showClose,
[horizontalClass]: true,
}"
:style="cssStyle"
v-show="visible"
Expand Down
60 changes: 36 additions & 24 deletions packages/components/Notification/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,54 @@ import type {
Notification,
NotificationParams,
NotificationHandler,
NotificationProps,
notificationType,
} from "./types";
import { notificationTypes } from "./types";
import { notificationTypes, notificationPosition } from "./types";
import { shallowReactive, isVNode, render, h } from "vue";
import { each, findIndex, isString, set, get } from "lodash-es";
import { useZIndex } from "@eric-ui/hooks";
import { useZIndex, useId } from "@eric-ui/hooks";
import NotificationConstructor from "./Notification.vue";

let seed = 0;

const instances: NotificationInstance[] = shallowReactive([]);
const { nextZIndex } = useZIndex();

export const notificationDefaults = {
type: "info",
position: "top-right",
duration: 3000,
offset: 20,
transitionName: "fade",
showClose: true,
} as const;

const normalizeOptions = (
const instancesMap: Map<NotificationProps["position"], NotificationInstance[]> =
new Map();
each(notificationPosition, (key) => instancesMap.set(key, shallowReactive([])));

const getInstancesByPosition = (
position: NotificationProps["position"]
): NotificationInstance[] => instancesMap.get(position)!;

function normalizeOptions(
options: NotificationParams
): CreateNotificationProps => {
): CreateNotificationProps {
const result =
!options || isVNode(options) || isString(options)
? { message: options }
: options;

return { ...notificationDefaults, ...result } as CreateNotificationProps;
};
}

export const createNotification = (
function createNotification(
props: CreateNotificationProps
): NotificationInstance => {
const id = `message_${seed++}`;
): NotificationInstance {
const id = useId().value;
const container = document.createElement("div");

const instances = getInstancesByPosition(props.position || "top-right");
const destory = () => {
const idx = findIndex(instances, { id });

if (idx === -1) return;

instances.splice(idx, 1);
Expand Down Expand Up @@ -77,32 +85,36 @@ export const createNotification = (
};
instances.push(instance);
return instance;
};
}

export const notification: NotificationFn & Partial<Notification> = (
export const notification: NotificationFn & Partial<Notification> = function (
options = {}
) => {
) {
const normalized = normalizeOptions(options);
const instance = createNotification(normalized);
return instance.handler;
};

export function closeAll(type?: notificationType) {
each(instances, (instance) => {
if (type) {
instance.props.type === type && instance.handler.close();
return;
}
instance.handler.close();
instancesMap.forEach((instances) => {
each(instances, (instance) => {
if (type) {
instance.props.type === type && instance.handler.close();
return;
}
instance.handler.close();
});
});
}

export const getLastBottomOffset = (id: string) => {
const idx = findIndex(instances, { id });
export function getLastBottomOffset(this: NotificationProps) {
const instances = getInstancesByPosition(this.position || "top-right");
const idx = findIndex(instances, { id: this.id });

if (idx <= 0) return 0;

return get(instances, [idx - 1, "vm", "exposed", "bottomOffset", "value"]);
};
}

each(notificationTypes, (type) => {
set(notification, type, (options: NotificationParams) => {
Expand Down
20 changes: 16 additions & 4 deletions packages/components/Notification/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@
overflow-wrap: anywhere;
overflow: hidden;
z-index: 9999;
right: 10px;
top: 0;

&.right {
right: 10px;
}

&.left {
left: 10px;
}

.er-notification__text {
margin: 0 10px;
Expand Down Expand Up @@ -75,8 +81,14 @@
}

.er-notification-fade-enter-from {
right: 0;
transform: translate(100%);
&.right{
right: 0;
transform: translate(100%);
}
&.left{
left:0;
transform: translate(-100%);
}
}
.er-notification-fade-leave-to {
opacity: 0;
Expand Down
9 changes: 9 additions & 0 deletions packages/components/Notification/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ export const notificationTypes = [
] as const;
export type notificationType = (typeof notificationTypes)[number];

export const notificationPosition = [
"top-right",
"top-left",
"bottom-right",
"bottom-left",
] as const;
export type NotificationPosition = (typeof notificationPosition)[number];

export interface NotificationProps {
title: string;
id: string;
zIndex: number;
position: NotificationPosition;
type?: "success" | "info" | "warning" | "danger" | "error";
message?: string | VNode;
duration?: number;
Expand Down
2 changes: 2 additions & 0 deletions packages/docs/demo/notification/Basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function openNotify1() {
ErNotification({
title: "Title",
message: h("i", { style: "color:teal" }, "This is a remider"),
position:'bottom-right'
});
}
Expand All @@ -14,6 +15,7 @@ function openNotify2() {
title: "Prompt",
message: "This is a message that does not auto close",
duration: 0,
position:'top-left'
});
}
</script>
Expand Down
4 changes: 3 additions & 1 deletion packages/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import useZIndex from "./useZIndex";
import useProp from "./useProp";
import useDisabledStyle from "./useDisabledStyle";
import useId from "./useId";
import useLocale from './useLocale'
import useLocale from "./useLocale";
import useOffset from "./useOffset";

export {
useClickOutside,
Expand All @@ -15,5 +16,6 @@ export {
useFocusController,
useDisabledStyle,
useLocale,
useOffset,
useId,
};
Loading

0 comments on commit cca573e

Please sign in to comment.