Move forms to sidepanel

表单组件迁移到 SidePanel
原 Popup 引导打开 SidePanel
优化表单组件和 Hooks,获取 Form 组件 Ref
This commit is contained in:
奇趣保罗 2024-04-12 14:09:38 +08:00
parent 288de31580
commit 0240b13123
10 changed files with 152 additions and 54 deletions

View File

@ -17,9 +17,11 @@ h2 {
button {
padding: 0;
width: 100%;
border: none;
padding: .75em;
cursor: pointer;
background-color: transparent;
border-radius: .5em;
}
img {
@ -46,10 +48,6 @@ button, input, textarea {
font: inherit;
}
form button {
background-color: #eee;
}
textarea {
resize: vertical;
}

View File

@ -1,14 +1,10 @@
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { sendToBackground } from "@plasmohq/messaging";
import useForm from "~hooks/useForm";
import Tab from "~components/ui/tab";
import Form from "~components/ui/form";
import { add } from "~components/ui/message/utils";
interface ReadProps {
onBack: () => void;
}
interface FormValue {
title: string;
link: string;
@ -46,10 +42,18 @@ const submitForm = (body: FormValue) => {
content: res.msg,
});
}
}).catch((e) => {
if (e instanceof Error) {
add({
content: e.message,
});
}
});
}
function Read({ onBack }: ReadProps) {
function Read() {
const formRef = useRef<HTMLFormElement>();
const { bindInput, setValues, onSubmit } = useForm<FormValue>({
initialValues: {
tags: "",
@ -68,9 +72,8 @@ function Read({ onBack }: ReadProps) {
return (
<Tab>
<Tab.Header title="在看" onBack={onBack} />
<Tab.Body>
<Form onSubmit={onSubmit(submitForm)}>
<Form ref={formRef} onSubmit={onSubmit(submitForm)}>
<input {...bindInput("title")} required placeholder="标题" />
<input {...bindInput("link")} required placeholder="链接" />
<div>
@ -97,9 +100,11 @@ function Read({ onBack }: ReadProps) {
<label htmlFor="tags"></label>
<input {...bindInput("tags")} placeholder="标签" />
</div>
<button type="submit"></button>
</Form>
</Tab.Body>
<Tab.Footer>
<button onClick={() => formRef.current?.requestSubmit()}></button>
</Tab.Footer>
</Tab>
);
}

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState, useEffect, useRef } from "react";
import { sendToBackground } from "@plasmohq/messaging";
import useForm from "~hooks/useForm";
import Tab from "~components/ui/tab";
@ -6,11 +6,7 @@ import Form from "~components/ui/form";
import { add } from "~components/ui/message/utils";
import Placeholder from "~components/ui/placeholder";
import styles from "./bili.module.less";
interface ReadProps {
onBack: () => void;
}
import styles from "./toy.module.less";
interface FormValue {
name: string;
@ -36,7 +32,9 @@ const getInfo = async () => {
}
}
function Toy({ onBack }: ReadProps) {
function Toy() {
const formRef = useRef<HTMLFormElement>();
const { bindInput, setValues, onSubmit } = useForm<FormValue>({
initialValues: {
type: 3,
@ -89,10 +87,9 @@ function Toy({ onBack }: ReadProps) {
}, []);
return (
<Tab>
<Tab.Header title="添加手办" onBack={onBack} />
<Tab.Body>
<Placeholder className={styles.bili} show={imgs.length === 0} value="非 B 站会员购页面">
<Placeholder className={styles.bili} show={imgs.length === 0} value="非 B 站会员购页面">
<Tab>
<Tab.Body>
<div className={styles.image}>
<img src={imgs[currentImg]} alt="" />
<div className={styles.selector}>
@ -134,12 +131,13 @@ function Toy({ onBack }: ReadProps) {
<label htmlFor="desc"></label>
<textarea {...bindInput("desc")} rows={5} placeholder="描述"></textarea>
</div>
<button type="submit"></button>
</Form>
</Placeholder>
</Tab.Body>
</Tab>
</Tab.Body>
<Tab.Footer>
<button onClick={() => formRef.current?.requestSubmit()}></button>
</Tab.Footer>
</Tab>
</Placeholder>
);
}

View File

@ -1,10 +1,10 @@
.bili {
gap: 1em;
width: 600px;
display: flex;
.image {
flex: 0 0 40%;
margin-bottom: 2em;
img {
border-radius: .5em;
}
}
.selector {
@ -17,6 +17,7 @@
width: 3em;
height: 3em;
cursor: pointer;
border-radius: .5em;
border: 1px solid #eee;
}
}

View File

@ -1,4 +1,4 @@
import type { FormHTMLAttributes } from "react";
import { useImperativeHandle, type FormHTMLAttributes, useRef, forwardRef, type Ref } from "react";
import { clsn } from "~utils";
import styles from "./form.module.less";
@ -6,12 +6,18 @@ interface FormProps extends FormHTMLAttributes<HTMLFormElement> {
}
function Form({ children, className, ...props }: FormProps) {
function Form({ children, className, ...props }: FormProps, ref: Ref<HTMLFormElement>) {
const formRef = useRef();
useImperativeHandle(ref, () => {
return formRef.current;
}, []);
return (
<form className={clsn(styles.form, className)} {...props}>
<form ref={formRef} className={clsn(styles.form, className)} {...props}>
{children}
</form>
);
}
export default Form;
export default forwardRef(Form);

View File

@ -35,4 +35,12 @@ Tab.Body = function Body({ children }: PropsWithChildren) {
);
}
Tab.Footer = function Footer({ children }: PropsWithChildren) {
return (
<footer className={styles.footer}>
{children}
</footer>
);
}
export default Tab;

View File

@ -1,7 +1,14 @@
.tab {
.header, .footer {
left: 0;
right: 0;
padding: .75em 1em;
position: sticky;
background-color: #fff;
}
.header {
padding: .75em;
position: relative;
top: 0;
text-align: center;
border-bottom: 1px solid #eee;
@ -27,4 +34,12 @@
.body {
padding: 1em;
}
.footer {
bottom: 0;
border-top: 1px solid #eee;
display: flex;
align-items: flex-end;
}
}

View File

@ -1,7 +1,5 @@
import { useState } from "react";
import Menu from "./menu";
import Read from "./read";
import Toy from "./bili";
import Message from "~components/ui/message";
import { clsn } from "~utils";
import "assets/global.less";
@ -17,6 +15,7 @@ function IndexPopup() {
const onClickMenu = (value: string) => {
if (value === "options") {
chrome.runtime.openOptionsPage();
return;
}
else if (value === "home") {
@ -29,24 +28,20 @@ function IndexPopup() {
return;
}
setTab(value);
}
chrome.tabs.query({ active: true, lastFocusedWindow: true }).then((tabs) => {
const { id, windowId } = tabs[0];
const renderBody = () => {
if (tab === "read") {
return <Read onBack={onBack} />;
}
if (tab === "toy") {
return <Toy onBack={onBack} />;
}
chrome.sidePanel.setOptions({ path: `sidepanel.html?type=${value}` })
chrome.sidePanel.open({ tabId: id, windowId });
return <Menu onClick={onClickMenu} />;
window.close();
});
}
return (
<>
<div className={clsn(styles.root, tab && styles.hasTab)}>
{renderBody()}
<Menu onClick={onClickMenu} />
</div>
<Message />
</>

39
sidepanel/index.tsx Normal file
View File

@ -0,0 +1,39 @@
import { useState } from "react";
import Read from "~components/biz/read";
import Toy from "~components/biz/toy";
import Message from "~components/ui/message";
import { clsn } from "~utils";
import "~assets/global.less";
import styles from "./sidepanel.module.less";
const initialType = new URLSearchParams(location.search).get("type") || "read";
const tabItems = [
{ title: "在读", value: "read" },
{ title: "手办", value: "toy" },
{ title: "语录", value: "say" },
];
function SidePanel() {
const [type, setType] = useState(initialType);
return (
<div className={styles.root}>
<nav className={styles.nav}>
{tabItems.map((item) => (
<a className={clsn(type === item.value && styles.active)}
onClick={() => setType(item.value)}
>
{item.title}
</a>
))}
</nav>
{type === "read" && <Read />}
{type === "toy" && <Toy />}
<Message />
</div>
);
}
export default SidePanel;

View File

@ -0,0 +1,33 @@
.root {
color: #333;
font-size: 14px;
.nav {
display: flex;
text-align: center;
border-bottom: 1px solid #eee;
a {
flex: 1;
cursor: pointer;
position: relative;
padding: .75em 1em;
&.active {
color: #28b9be;
&::after {
margin-top: .25em;
position: absolute;
left: calc(50% - .25em);
content: "";
width: .75em;
height: 2px;
display: block;
background-color: currentColor;
}
}
}
}
}