🚧 add tag selection

This commit is contained in:
yanyongyu 2021-12-31 11:27:37 +08:00
parent ab2c73856d
commit 56677616b4
5 changed files with 207 additions and 28 deletions

View File

@ -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"

View File

@ -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,

View File

@ -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>
);
}

View File

@ -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;
}

View File

@ -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