mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-02-17 16:20:05 +08:00
🚧 add tag selection
This commit is contained in:
parent
ab2c73856d
commit
56677616b4
@ -32,7 +32,6 @@
|
||||
"react": "^17.0.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-paginate": "^8.1.0",
|
||||
"react-use-pagination": "^2.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"url-loader": "^4.1.1"
|
||||
|
@ -1,9 +1,10 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import Link from "@docusaurus/Link";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import type { Obj } from "../../libs/store";
|
||||
import type { Obj, Tag as TagType } from "../../libs/store";
|
||||
|
||||
function pickTextColor(bgColor, lightColor, darkColor) {
|
||||
var color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
|
||||
@ -13,6 +14,32 @@ function pickTextColor(bgColor, lightColor, darkColor) {
|
||||
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? darkColor : lightColor;
|
||||
}
|
||||
|
||||
export function Tag({
|
||||
label,
|
||||
color,
|
||||
className,
|
||||
onClick,
|
||||
}: TagType & {
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
"inline-flex px-3 rounded-full items-center align-middle mr-2",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
color: pickTextColor(color, "#fff", "#000"),
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Card({
|
||||
module_name,
|
||||
name,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useCallback, useRef } from "react";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { usePagination } from "react-use-pagination";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@ -9,11 +9,19 @@ import styles from "./styles.module.css";
|
||||
|
||||
export default function Paginate({
|
||||
totalPages,
|
||||
setPreviousPage,
|
||||
setNextPage,
|
||||
setPage,
|
||||
currentPage,
|
||||
previousEnabled,
|
||||
nextEnabled,
|
||||
}: ReturnType<typeof usePagination>): JSX.Element {
|
||||
const ref = useRef<HTMLElement>();
|
||||
const maxWidth = useContentWidth(ref.current?.parentElement ?? undefined);
|
||||
const maxLength = Math.min(
|
||||
(maxWidth && Math.floor(maxWidth / 50) - 2) || totalPages,
|
||||
totalPages
|
||||
);
|
||||
|
||||
const onPageChange = useCallback(
|
||||
(selectedItem: { selected: number }) => {
|
||||
@ -21,27 +29,74 @@ export default function Paginate({
|
||||
},
|
||||
[setPage]
|
||||
);
|
||||
const range = useCallback((start: number, end: number) => {
|
||||
const result = [];
|
||||
start = start > 0 ? start : 1;
|
||||
for (let i = start; i <= end; i++) {
|
||||
result.push(i);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
// FIXME: responsive width
|
||||
const pages: (React.ReactNode | number)[] = [];
|
||||
const ellipsis = <FontAwesomeIcon icon="ellipsis-h" />;
|
||||
|
||||
const even = maxLength % 2 === 0 ? 1 : 0;
|
||||
const left = Math.floor(maxLength / 2);
|
||||
const right = totalPages - left + even + 1;
|
||||
currentPage = currentPage + 1;
|
||||
|
||||
if (totalPages <= maxLength) {
|
||||
pages.push(...range(1, totalPages));
|
||||
} else if (currentPage > left && currentPage < right) {
|
||||
const firstItem = 1;
|
||||
const lastItem = totalPages;
|
||||
const start = currentPage - left + 2;
|
||||
const end = currentPage + left - 2 - even;
|
||||
const secondItem = start - 1 === firstItem + 1 ? 2 : ellipsis;
|
||||
const beforeLastItem = end + 1 === lastItem - 1 ? end + 1 : ellipsis;
|
||||
|
||||
pages.push(1, secondItem, ...range(start, end), beforeLastItem, totalPages);
|
||||
} else if (currentPage === left) {
|
||||
const end = currentPage + left - 1 - even;
|
||||
pages.push(...range(1, end), ellipsis, totalPages);
|
||||
} else if (currentPage === right) {
|
||||
const start = currentPage - left + 1;
|
||||
pages.push(1, ellipsis, ...range(start, totalPages));
|
||||
} else {
|
||||
pages.push(...range(1, left), ellipsis, ...range(right, totalPages));
|
||||
}
|
||||
|
||||
return (
|
||||
<nav role="navigation" aria-label="Pagination Navigation" ref={ref}>
|
||||
<ReactPaginate
|
||||
pageCount={totalPages}
|
||||
forcePage={currentPage}
|
||||
onPageChange={onPageChange}
|
||||
containerClassName={styles.container}
|
||||
pageClassName={styles.li}
|
||||
pageLinkClassName={styles.a}
|
||||
previousClassName={styles.li}
|
||||
previousLinkClassName={styles.a}
|
||||
nextClassName={styles.li}
|
||||
nextLinkClassName={styles.a}
|
||||
activeLinkClassName={styles.active}
|
||||
disabledLinkClassName={styles.disabled}
|
||||
breakLabel={<FontAwesomeIcon icon="ellipsis-h" />}
|
||||
previousLabel={<FontAwesomeIcon icon="chevron-left" />}
|
||||
nextLabel={<FontAwesomeIcon icon="chevron-right" />}
|
||||
></ReactPaginate>
|
||||
<ul className={styles.container}>
|
||||
<li
|
||||
className={clsx(styles.li, { [styles.disabled]: !previousEnabled })}
|
||||
>
|
||||
<button className={styles.button} onClick={setPreviousPage}>
|
||||
<FontAwesomeIcon icon="chevron-left" />
|
||||
</button>
|
||||
</li>
|
||||
{pages.map((page, index) => (
|
||||
<li className={styles.li} key={index}>
|
||||
<button
|
||||
className={clsx(styles.button, {
|
||||
[styles.active]: page === currentPage,
|
||||
"pointer-events-none": typeof page !== "number",
|
||||
})}
|
||||
onClick={() => typeof page === "number" && setPage(page - 1)}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li className={clsx(styles.li, { [styles.disabled]: !nextEnabled })}>
|
||||
<button className={styles.button} onClick={setNextPage}>
|
||||
<FontAwesomeIcon icon="chevron-right" />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.a {
|
||||
.button {
|
||||
height: 34px;
|
||||
width: auto;
|
||||
min-width: 34px;
|
||||
@ -15,12 +15,12 @@
|
||||
@apply text-black bg-light-nonepress-100;
|
||||
}
|
||||
|
||||
:global(.dark) .a {
|
||||
:global(.dark) .button {
|
||||
@apply border-dark-nonepress-200 shadow-dark-nonepress-300;
|
||||
@apply text-white bg-dark-nonepress-100;
|
||||
}
|
||||
|
||||
.a.active {
|
||||
.button.active {
|
||||
@apply bg-hero text-white border-hero;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { useState } from "react";
|
||||
import { ChromePicker } from "react-color";
|
||||
import { usePagination } from "react-use-pagination";
|
||||
|
||||
import plugins from "../../static/plugins.json";
|
||||
import { useFilteredObjs } from "../libs/store";
|
||||
import Card from "./Card";
|
||||
import { Tag, useFilteredObjs } from "../libs/store";
|
||||
import Card, { Tag as TagComponent } from "./Card";
|
||||
import Modal from "./Modal";
|
||||
import Paginate from "./Paginate";
|
||||
|
||||
@ -29,8 +31,45 @@ export default function Adapter(): JSX.Element {
|
||||
moduleName: string;
|
||||
homepage: string;
|
||||
}>({ name: "", desc: "", projectLink: "", moduleName: "", homepage: "" });
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [label, setLabel] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("#ea5252");
|
||||
const onSubmit = () => {
|
||||
console.log(form);
|
||||
setModalOpen(false);
|
||||
const title = encodeURIComponent(`Plugin: ${form.name}`).replace(
|
||||
/%2B/gi,
|
||||
"+"
|
||||
);
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**插件名称:**
|
||||
|
||||
${form.name}
|
||||
|
||||
**插件功能:**
|
||||
|
||||
${form.desc}
|
||||
|
||||
**PyPI 项目名:**
|
||||
|
||||
${form.projectLink}
|
||||
|
||||
**插件 import 包名:**
|
||||
|
||||
${form.moduleName}
|
||||
|
||||
**插件项目仓库/主页链接:**
|
||||
|
||||
${form.homepage}
|
||||
|
||||
**标签:**
|
||||
|
||||
${JSON.stringify(tags)}
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Plugin`
|
||||
);
|
||||
};
|
||||
const onChange = (event) => {
|
||||
const target = event.target;
|
||||
@ -43,6 +82,27 @@ export default function Adapter(): JSX.Element {
|
||||
});
|
||||
event.preventDefault();
|
||||
};
|
||||
const onChangeLabel = (event) => {
|
||||
setLabel(event.target.value);
|
||||
};
|
||||
const onChangeColor = (color) => {
|
||||
setColor(color.hex);
|
||||
};
|
||||
const validateTag = () => {
|
||||
return label.length >= 1 && label.length <= 10;
|
||||
};
|
||||
const newTag = () => {
|
||||
if (tags.length >= 3) {
|
||||
return;
|
||||
}
|
||||
if (validateTag()) {
|
||||
const tag = { label, color };
|
||||
setTags([...tags, tag]);
|
||||
}
|
||||
};
|
||||
const delTag = (index: number) => {
|
||||
setTags(tags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -128,9 +188,47 @@ export default function Adapter(): JSX.Element {
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
<div className="px-4">
|
||||
<label className="flex flex-wrap">
|
||||
<span className="mr-2">标签:</span>
|
||||
{tags.map((tag, index) => (
|
||||
<TagComponent
|
||||
key={index}
|
||||
{...tag}
|
||||
className="cursor-pointer"
|
||||
onClick={() => delTag(index)}
|
||||
/>
|
||||
))}
|
||||
</label>
|
||||
</div>
|
||||
<div className="px-4 pt-4">
|
||||
<input
|
||||
type="text"
|
||||
className="px-2 flex-grow rounded bg-light-nonepress-200 dark:bg-dark-nonepress-200"
|
||||
onChange={onChangeLabel}
|
||||
/>
|
||||
<ChromePicker
|
||||
className="mt-2"
|
||||
color={color}
|
||||
disableAlpha={true}
|
||||
onChangeComplete={onChangeColor}
|
||||
/>
|
||||
<div className="flex mt-2">
|
||||
<TagComponent label={label} color={color} />
|
||||
<button
|
||||
className={clsx(
|
||||
"px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]",
|
||||
{ "pointer-events-none opacity-60": !validateTag() }
|
||||
)}
|
||||
onClick={newTag}
|
||||
>
|
||||
添加标签
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-2 flex justify-end">
|
||||
<button className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]">
|
||||
<button className="px-2 h-9 min-w-[64px] rounded text-hero hover:bg-hero hover:bg-opacity-[.08]" onClick={() => setModalOpen(false)}>
|
||||
关闭
|
||||
</button>
|
||||
<button
|
||||
|
Loading…
x
Reference in New Issue
Block a user