Feat: 新增语录页和通知提示组件
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
import React, { useState, useRef, useImperativeHandle, forwardRef, type Ref } from "react";
|
||||
import { createPortal } from 'react-dom';
|
||||
import { clsn } from '~/utils';
|
||||
import { createPortal } from "react-dom";
|
||||
import { X } from "~/components/ui/icons";
|
||||
import { clsn } from "~/utils";
|
||||
|
||||
const parseMeta = (metaStr: string) => {
|
||||
try {
|
||||
@@ -151,6 +152,9 @@ function LightBox({ className, list }: LightBoxProps, ref: Ref<LightBoxInst>) {
|
||||
onWheel={onScroll}
|
||||
>
|
||||
<div className="flex-1 flex relative p-4" role="img" aria-label={title} onClick={onClose}>
|
||||
<button className="absolute top-4 left-4 z-2 p-2 rounded-xl text-xl font-semibold text-pink-700 bg-black/60 cursor-pointer" onClick={onClose}>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
{isVideo ? (
|
||||
<video
|
||||
src={src}
|
||||
|
||||
@@ -6,6 +6,7 @@ const navItems = [
|
||||
{ name: "首页", to: "/" },
|
||||
{ name: "日记", to: "/note" },
|
||||
{ name: "相册", to: "/gallery" },
|
||||
{ name: "语录", to: "/say" },
|
||||
{ name: "关于我", to: "/about" },
|
||||
];
|
||||
|
||||
|
||||
@@ -49,3 +49,11 @@ export const Steam = (props: IconProps) => (
|
||||
export const Feed = (props: IconProps) => (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M3 3C12.9411 3 21 11.0589 21 21H18C18 12.7157 11.2843 6 3 6V3ZM3 10C9.07513 10 14 14.9249 14 21H11C11 16.5817 7.41828 13 3 13V10ZM3 17C5.20914 17 7 18.7909 7 21H3V17Z"></path></svg>
|
||||
);
|
||||
|
||||
export const Heart = (props: IconProps) => (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12.001 4.52853C14.35 2.42 17.98 2.49 20.2426 4.75736C22.5053 7.02472 22.583 10.637 20.4786 12.993L11.9999 21.485L3.52138 12.993C1.41705 10.637 1.49571 7.01901 3.75736 4.75736C6.02157 2.49315 9.64519 2.41687 12.001 4.52853Z"></path></svg>
|
||||
);
|
||||
|
||||
export const X = (props: IconProps) => (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11.9997 10.5865L16.9495 5.63672L18.3637 7.05093L13.4139 12.0007L18.3637 16.9504L16.9495 18.3646L11.9997 13.4149L7.04996 18.3646L5.63574 16.9504L10.5855 12.0007L5.63574 7.05093L7.04996 5.63672L11.9997 10.5865Z"></path></svg>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.progress {
|
||||
animation: progress 1s linear;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
from {
|
||||
width: 100%;
|
||||
}
|
||||
to {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Hooks
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
// UI
|
||||
import styles from "./Notice.module.css";
|
||||
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
|
||||
// Tools
|
||||
import { type NoticeItem, addFn, removeFn } from "./utils";
|
||||
import { useHydrated } from "~/hooks/use-hydrated";
|
||||
import { clsn } from "~/utils";
|
||||
|
||||
|
||||
// Components
|
||||
function Notice() {
|
||||
const [items, setItems] = useState<NoticeItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fn = (notice: NoticeItem) => {
|
||||
const key = `${Math.ceil(performance.now())}-${Math.round(Math.random() * 100)}`;
|
||||
|
||||
setItems((prevItems) => [...prevItems, { ...notice, key }]);
|
||||
|
||||
if (notice.duration && notice.duration > 0) {
|
||||
setTimeout(() => {
|
||||
onCloseWithKey(key);
|
||||
}, notice.duration);
|
||||
}
|
||||
};
|
||||
|
||||
addFn(fn);
|
||||
|
||||
return () => {
|
||||
removeFn(fn);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onCloseWithKey = (key: string) => {
|
||||
setItems((prevItems) => {
|
||||
const index = prevItems.findIndex((item) => item.key === key);
|
||||
|
||||
const nextItems = [...prevItems];
|
||||
|
||||
if (index > -1) {
|
||||
nextItems.splice(index, 1);
|
||||
}
|
||||
|
||||
return nextItems;
|
||||
});
|
||||
}
|
||||
|
||||
const hydrated = useHydrated();
|
||||
|
||||
if (typeof document === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hydrated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal((
|
||||
<div className="fixed top-24 right-0 z-2 mx-4 flex max-w-[30em] flex-col items-end">
|
||||
{items.map((item) => {
|
||||
const showProgress = !!item.duration && item.duration > 0;
|
||||
|
||||
return (
|
||||
<div key={item.key} className="relative overflow-hidden px-5 py-4 pr-14 mb-4 min-w-[20em] rounded-xl bg-pink-100 animate-fade-in-left">
|
||||
<button
|
||||
className="absolute text-xl font-semibold right-6 text-pink-700 cursor-pointer"
|
||||
onClick={() => onCloseWithKey(item.key as string)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h4 className="text-lg font-semibold text-pink-700">{item.title}</h4>
|
||||
<p className="text-sm mt-4">
|
||||
{item.content}
|
||||
</p>
|
||||
{showProgress && (
|
||||
<div className={clsn("absolute h-1 left-0 right-0 bottom-0 bg-pink-500", styles.progress)} style={{ animationDuration: `${(item.duration || 5000) / 1000}s` }}></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
), document.body);
|
||||
}
|
||||
|
||||
export default Notice;
|
||||
@@ -0,0 +1,33 @@
|
||||
export interface NoticeItem {
|
||||
key?: string;
|
||||
title: string;
|
||||
duration?: number;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
||||
type NoticeFn = (notice: NoticeItem) => void;
|
||||
|
||||
const addNoticeFn: NoticeFn[] = [];
|
||||
|
||||
export const add = (notice: NoticeItem) => {
|
||||
addNoticeFn.forEach((item) => {
|
||||
// 如果没传递 duration,默认设置为 5000
|
||||
if (!("duration" in notice)) {
|
||||
notice.duration = 5000;
|
||||
}
|
||||
|
||||
item(notice);
|
||||
});
|
||||
}
|
||||
|
||||
export const addFn = (fn: NoticeFn) => {
|
||||
return addNoticeFn.push(fn) - 1;
|
||||
}
|
||||
|
||||
export const removeFn = (fn: NoticeFn) => {
|
||||
const index = addNoticeFn.indexOf(fn);
|
||||
|
||||
if (index > -1) {
|
||||
addNoticeFn.splice(index, 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user