Move forms to sidepanel
表单组件迁移到 SidePanel 原 Popup 引导打开 SidePanel 优化表单组件和 Hooks,获取 Form 组件 Ref
This commit is contained in:
parent
288de31580
commit
0240b13123
|
|
@ -17,9 +17,11 @@ h2 {
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
|
padding: .75em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: transparent;
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|
@ -46,10 +48,6 @@ button, input, textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { sendToBackground } from "@plasmohq/messaging";
|
import { sendToBackground } from "@plasmohq/messaging";
|
||||||
import useForm from "~hooks/useForm";
|
import useForm from "~hooks/useForm";
|
||||||
import Tab from "~components/ui/tab";
|
import Tab from "~components/ui/tab";
|
||||||
import Form from "~components/ui/form";
|
import Form from "~components/ui/form";
|
||||||
import { add } from "~components/ui/message/utils";
|
import { add } from "~components/ui/message/utils";
|
||||||
|
|
||||||
interface ReadProps {
|
|
||||||
onBack: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormValue {
|
interface FormValue {
|
||||||
title: string;
|
title: string;
|
||||||
link: string;
|
link: string;
|
||||||
|
|
@ -46,10 +42,18 @@ const submitForm = (body: FormValue) => {
|
||||||
content: res.msg,
|
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>({
|
const { bindInput, setValues, onSubmit } = useForm<FormValue>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
tags: "",
|
tags: "",
|
||||||
|
|
@ -68,9 +72,8 @@ function Read({ onBack }: ReadProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab>
|
<Tab>
|
||||||
<Tab.Header title="在看" onBack={onBack} />
|
|
||||||
<Tab.Body>
|
<Tab.Body>
|
||||||
<Form onSubmit={onSubmit(submitForm)}>
|
<Form ref={formRef} onSubmit={onSubmit(submitForm)}>
|
||||||
<input {...bindInput("title")} required placeholder="标题" />
|
<input {...bindInput("title")} required placeholder="标题" />
|
||||||
<input {...bindInput("link")} required placeholder="链接" />
|
<input {...bindInput("link")} required placeholder="链接" />
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -97,9 +100,11 @@ function Read({ onBack }: ReadProps) {
|
||||||
<label htmlFor="tags">标签</label>
|
<label htmlFor="tags">标签</label>
|
||||||
<input {...bindInput("tags")} placeholder="标签" />
|
<input {...bindInput("tags")} placeholder="标签" />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">提交</button>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Tab.Body>
|
</Tab.Body>
|
||||||
|
<Tab.Footer>
|
||||||
|
<button onClick={() => formRef.current?.requestSubmit()}>提交</button>
|
||||||
|
</Tab.Footer>
|
||||||
</Tab>
|
</Tab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { sendToBackground } from "@plasmohq/messaging";
|
import { sendToBackground } from "@plasmohq/messaging";
|
||||||
import useForm from "~hooks/useForm";
|
import useForm from "~hooks/useForm";
|
||||||
import Tab from "~components/ui/tab";
|
import Tab from "~components/ui/tab";
|
||||||
|
|
@ -6,11 +6,7 @@ import Form from "~components/ui/form";
|
||||||
import { add } from "~components/ui/message/utils";
|
import { add } from "~components/ui/message/utils";
|
||||||
import Placeholder from "~components/ui/placeholder";
|
import Placeholder from "~components/ui/placeholder";
|
||||||
|
|
||||||
import styles from "./bili.module.less";
|
import styles from "./toy.module.less";
|
||||||
|
|
||||||
interface ReadProps {
|
|
||||||
onBack: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormValue {
|
interface FormValue {
|
||||||
name: string;
|
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>({
|
const { bindInput, setValues, onSubmit } = useForm<FormValue>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
type: 3,
|
type: 3,
|
||||||
|
|
@ -89,10 +87,9 @@ function Toy({ onBack }: ReadProps) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab>
|
<Placeholder className={styles.bili} show={imgs.length === 0} value="非 B 站会员购页面">
|
||||||
<Tab.Header title="添加手办" onBack={onBack} />
|
<Tab>
|
||||||
<Tab.Body>
|
<Tab.Body>
|
||||||
<Placeholder className={styles.bili} show={imgs.length === 0} value="非 B 站会员购页面">
|
|
||||||
<div className={styles.image}>
|
<div className={styles.image}>
|
||||||
<img src={imgs[currentImg]} alt="" />
|
<img src={imgs[currentImg]} alt="" />
|
||||||
<div className={styles.selector}>
|
<div className={styles.selector}>
|
||||||
|
|
@ -134,12 +131,13 @@ function Toy({ onBack }: ReadProps) {
|
||||||
<label htmlFor="desc">描述</label>
|
<label htmlFor="desc">描述</label>
|
||||||
<textarea {...bindInput("desc")} rows={5} placeholder="描述"></textarea>
|
<textarea {...bindInput("desc")} rows={5} placeholder="描述"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit">提交</button>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Placeholder>
|
</Tab.Body>
|
||||||
</Tab.Body>
|
<Tab.Footer>
|
||||||
</Tab>
|
<button onClick={() => formRef.current?.requestSubmit()}>提交</button>
|
||||||
|
</Tab.Footer>
|
||||||
|
</Tab>
|
||||||
|
</Placeholder>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
.bili {
|
.bili {
|
||||||
gap: 1em;
|
|
||||||
width: 600px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
flex: 0 0 40%;
|
margin-bottom: 2em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector {
|
.selector {
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
width: 3em;
|
width: 3em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: .5em;
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { FormHTMLAttributes } from "react";
|
import { useImperativeHandle, type FormHTMLAttributes, useRef, forwardRef, type Ref } from "react";
|
||||||
import { clsn } from "~utils";
|
import { clsn } from "~utils";
|
||||||
import styles from "./form.module.less";
|
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 (
|
return (
|
||||||
<form className={clsn(styles.form, className)} {...props}>
|
<form ref={formRef} className={clsn(styles.form, className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Form;
|
export default forwardRef(Form);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
export default Tab;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
.tab {
|
.tab {
|
||||||
|
.header, .footer {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: .75em 1em;
|
||||||
|
position: sticky;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
padding: .75em;
|
top: 0;
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
|
|
||||||
|
|
@ -27,4 +34,12 @@
|
||||||
.body {
|
.body {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
bottom: 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Menu from "./menu";
|
import Menu from "./menu";
|
||||||
import Read from "./read";
|
|
||||||
import Toy from "./bili";
|
|
||||||
import Message from "~components/ui/message";
|
import Message from "~components/ui/message";
|
||||||
import { clsn } from "~utils";
|
import { clsn } from "~utils";
|
||||||
import "assets/global.less";
|
import "assets/global.less";
|
||||||
|
|
@ -17,6 +15,7 @@ function IndexPopup() {
|
||||||
const onClickMenu = (value: string) => {
|
const onClickMenu = (value: string) => {
|
||||||
if (value === "options") {
|
if (value === "options") {
|
||||||
chrome.runtime.openOptionsPage();
|
chrome.runtime.openOptionsPage();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (value === "home") {
|
else if (value === "home") {
|
||||||
|
|
@ -29,24 +28,20 @@ function IndexPopup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTab(value);
|
chrome.tabs.query({ active: true, lastFocusedWindow: true }).then((tabs) => {
|
||||||
}
|
const { id, windowId } = tabs[0];
|
||||||
|
|
||||||
const renderBody = () => {
|
chrome.sidePanel.setOptions({ path: `sidepanel.html?type=${value}` })
|
||||||
if (tab === "read") {
|
chrome.sidePanel.open({ tabId: id, windowId });
|
||||||
return <Read onBack={onBack} />;
|
|
||||||
}
|
|
||||||
if (tab === "toy") {
|
|
||||||
return <Toy onBack={onBack} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Menu onClick={onClickMenu} />;
|
window.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={clsn(styles.root, tab && styles.hasTab)}>
|
<div className={clsn(styles.root, tab && styles.hasTab)}>
|
||||||
{renderBody()}
|
<Menu onClick={onClickMenu} />
|
||||||
</div>
|
</div>
|
||||||
<Message />
|
<Message />
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue