🚧 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": "^17.0.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-paginate": "^8.1.0",
"react-use-pagination": "^2.0.1", "react-use-pagination": "^2.0.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"url-loader": "^4.1.1" "url-loader": "^4.1.1"

View File

@ -1,9 +1,10 @@
import clsx from "clsx";
import React from "react"; import React from "react";
import Link from "@docusaurus/Link"; import Link from "@docusaurus/Link";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 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) { function pickTextColor(bgColor, lightColor, darkColor) {
var color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor; 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; 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({ export default function Card({
module_name, module_name,
name, name,

View File

@ -1,5 +1,5 @@
import clsx from "clsx";
import React, { useCallback, useRef } from "react"; import React, { useCallback, useRef } from "react";
import ReactPaginate from "react-paginate";
import { usePagination } from "react-use-pagination"; import { usePagination } from "react-use-pagination";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -9,11 +9,19 @@ import styles from "./styles.module.css";
export default function Paginate({ export default function Paginate({
totalPages, totalPages,
setPreviousPage,
setNextPage,
setPage, setPage,
currentPage, currentPage,
previousEnabled,
nextEnabled,
}: ReturnType<typeof usePagination>): JSX.Element { }: ReturnType<typeof usePagination>): JSX.Element {
const ref = useRef<HTMLElement>(); const ref = useRef<HTMLElement>();
const maxWidth = useContentWidth(ref.current?.parentElement ?? undefined); const maxWidth = useContentWidth(ref.current?.parentElement ?? undefined);
const maxLength = Math.min(
(maxWidth && Math.floor(maxWidth / 50) - 2) || totalPages,
totalPages
);
const onPageChange = useCallback( const onPageChange = useCallback(
(selectedItem: { selected: number }) => { (selectedItem: { selected: number }) => {
@ -21,27 +29,74 @@ export default function Paginate({
}, },
[setPage] [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 // 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 ( return (
<nav role="navigation" aria-label="Pagination Navigation" ref={ref}> <nav role="navigation" aria-label="Pagination Navigation" ref={ref}>
<ReactPaginate <ul className={styles.container}>
pageCount={totalPages} <li
forcePage={currentPage} className={clsx(styles.li, { [styles.disabled]: !previousEnabled })}
onPageChange={onPageChange} >
containerClassName={styles.container} <button className={styles.button} onClick={setPreviousPage}>
pageClassName={styles.li} <FontAwesomeIcon icon="chevron-left" />
pageLinkClassName={styles.a} </button>
previousClassName={styles.li} </li>
previousLinkClassName={styles.a} {pages.map((page, index) => (
nextClassName={styles.li} <li className={styles.li} key={index}>
nextLinkClassName={styles.a} <button
activeLinkClassName={styles.active} className={clsx(styles.button, {
disabledLinkClassName={styles.disabled} [styles.active]: page === currentPage,
breakLabel={<FontAwesomeIcon icon="ellipsis-h" />} "pointer-events-none": typeof page !== "number",
previousLabel={<FontAwesomeIcon icon="chevron-left" />} })}
nextLabel={<FontAwesomeIcon icon="chevron-right" />} onClick={() => typeof page === "number" && setPage(page - 1)}
></ReactPaginate> >
{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> </nav>
); );
} }

View File

@ -6,7 +6,7 @@
@apply flex items-center; @apply flex items-center;
} }
.a { .button {
height: 34px; height: 34px;
width: auto; width: auto;
min-width: 34px; min-width: 34px;
@ -15,12 +15,12 @@
@apply text-black bg-light-nonepress-100; @apply text-black bg-light-nonepress-100;
} }
:global(.dark) .a { :global(.dark) .button {
@apply border-dark-nonepress-200 shadow-dark-nonepress-300; @apply border-dark-nonepress-200 shadow-dark-nonepress-300;
@apply text-white bg-dark-nonepress-100; @apply text-white bg-dark-nonepress-100;
} }
.a.active { .button.active {
@apply bg-hero text-white border-hero; @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 { usePagination } from "react-use-pagination";
import plugins from "../../static/plugins.json"; import plugins from "../../static/plugins.json";
import { useFilteredObjs } from "../libs/store"; import { Tag, useFilteredObjs } from "../libs/store";
import Card from "./Card"; import Card, { Tag as TagComponent } from "./Card";
import Modal from "./Modal"; import Modal from "./Modal";
import Paginate from "./Paginate"; import Paginate from "./Paginate";
@ -29,8 +31,45 @@ export default function Adapter(): JSX.Element {
moduleName: string; moduleName: string;
homepage: string; homepage: string;
}>({ name: "", desc: "", projectLink: "", moduleName: "", homepage: "" }); }>({ name: "", desc: "", projectLink: "", moduleName: "", homepage: "" });
const [tags, setTags] = useState<Tag[]>([]);
const [label, setLabel] = useState<string>("");
const [color, setColor] = useState<string>("#ea5252");
const onSubmit = () => { 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 onChange = (event) => {
const target = event.target; const target = event.target;
@ -43,6 +82,27 @@ export default function Adapter(): JSX.Element {
}); });
event.preventDefault(); 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 ( return (
<> <>
@ -128,9 +188,47 @@ export default function Adapter(): JSX.Element {
</label> </label>
</div> </div>
</form> </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>
<div className="px-4 py-2 flex justify-end"> <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>
<button <button