refactor: batch 2

This commit is contained in:
鲁树人
2025-05-17 11:20:52 +09:00
parent 246ba48135
commit 75b43e1e84
16 changed files with 384 additions and 272 deletions

View File

@@ -10,6 +10,8 @@ import { persistSettings } from '~/features/settings/persistSettings';
import { setupStore } from '~/store';
import { Footer } from '~/components/Footer';
import { FaqTab } from '~/tabs/FaqTab';
import { SETTINGS_TABS } from '~/features/settings/settingsTabs';
import { Bounce, ToastContainer } from 'react-toastify';
// Private to this file only.
const store = setupStore();
@@ -40,11 +42,26 @@ export function AppRoot() {
<main className="flex-1 flex justify-center">
<Routes>
<Route path="/" Component={MainTab} />
<Route path="/settings" Component={SettingsTab} />
<Route path="/settings" Component={SettingsTab}>
{Object.entries(SETTINGS_TABS).map(([key, { Tab }]) => (
<Route key={key} path={key} Component={Tab} />
))}
</Route>
<Route path="/questions" Component={FaqTab} />
</Routes>
</main>
<ToastContainer
position="bottom-center"
autoClose={5000}
newestOnTop
closeOnClick={false}
pauseOnFocusLoss
draggable
theme="colored"
transition={Bounce}
/>
<Footer />
</Provider>
</BrowserRouter>

View File

@@ -27,7 +27,7 @@ export function Dialog({ closeButton, backdropClose, title, children, show, onCl
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
)}
<h3 className="font-bold text-lg">{title}</h3>
<h3 className="font-bold text-lg pb-3">{title}</h3>
{children}
</div>
{backdropClose && (

View File

@@ -7,9 +7,9 @@ export type ExtLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
export function ExtLink({ className, icon = true, children, ...props }: ExtLinkProps) {
return (
<a rel="noreferrer noopener nofollow" className={`link ${className}`} {...props}>
<a rel="noreferrer noopener nofollow" target="_blank" className={`link ${className}`} {...props}>
{children}
{icon && <FiExternalLink />}
{icon && <FiExternalLink className="inline size-sm ml-1" />}
</a>
);
}

View File

@@ -0,0 +1,25 @@
import { Dialog } from '~/components/Dialog.tsx';
import React, { useState } from 'react';
interface InfoModalProps {
title?: React.ReactNode;
description?: React.ReactNode;
children?: React.ReactNode;
}
export function InfoModal(props: InfoModalProps) {
const { title, description, children } = props;
const [showModal, setShowModal] = useState(false);
return (
<div>
<button className="btn btn-info btn-sm" type="button" onClick={() => setShowModal(true)}>
{children || '这是什么?'}
</button>
<Dialog closeButton backdropClose show={showModal} onClose={() => setShowModal(false)} title={title}>
{description}
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,33 @@
import React, { Fragment, useId } from 'react';
export type InstructionTab = {
id: string | number;
label: React.ReactNode;
content: React.ReactNode;
};
export interface InstructionsTabsProps {
tabs: InstructionTab[];
}
export function InstructionsTabs({ tabs }: InstructionsTabsProps) {
const id = useId();
return (
<div className="tabs tabs-lift h-[20rem] pb-4">
{tabs.map(({ id: _tabId, label, content }, index) => (
<Fragment key={_tabId}>
<label className="tab">
<input type="radio" name={id} defaultChecked={index === 0} />
{label}
</label>
<div className="tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto">{content}</div>
</Fragment>
))}
{/*<label className="tab">*/}
{/* <input type="radio" name={id} />a*/}
{/*</label>*/}
{/*<div className="tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto"></div>*/}
{/*<input type="radio" name={id} className="tab" aria-label="安卓" defaultChecked />*/}
</div>
);
}

View File

@@ -0,0 +1,85 @@
import { PiFileAudio } from 'react-icons/pi';
import { MdDelete, MdVpnKey } from 'react-icons/md';
import React from 'react';
export interface KeyInputProps {
sequence: number;
name: string;
value: string;
isValidKey?: boolean;
onSetName: (name: string) => void;
onSetValue: (value: string) => void;
onDelete: () => void;
nameLabel?: React.ReactNode;
valueLabel?: React.ReactNode;
namePlaceholder?: string;
valuePlaceholder?: string;
}
export function KeyInput(props: KeyInputProps) {
const {
nameLabel,
valueLabel,
namePlaceholder,
valuePlaceholder,
sequence,
name,
value,
onSetName,
onSetValue,
onDelete,
isValidKey,
} = props;
return (
<li className="list-row items-center">
<div className="flex items-center justify-center w-8 h-8 text-sm font-bold text-gray-500 bg-gray-200 rounded-full">
{sequence}
</div>
<div className="join join-vertical flex-1">
<label className="input w-full rounded-tl-md rounded-tr-md">
<span className="cucursor-default inline-flex items-center gap-1 select-none">
{nameLabel || (
<>
<PiFileAudio />
</>
)}
</span>
<input
type="text"
className="font-mono"
placeholder={namePlaceholder}
value={name}
onChange={(e) => onSetName(e.target.value)}
/>
</label>
<label className="input w-full rounded-bl-md rounded-br-md mt-[-1px]">
<span className="cursor-default inline-flex items-center gap-1 select-none">
{valueLabel || (
<>
<MdVpnKey />
</>
)}
</span>
<input
type="text"
className="font-mono"
placeholder={valuePlaceholder}
value={value}
onChange={(e) => onSetValue(e.target.value)}
/>
<span className={isValidKey ? 'text-green-600' : 'text-red-600'}>
<code>{value.length || '?'}</code>
</span>
</label>
</div>
<button type="button" className="btn btn-error btn-sm px-1 btn-outline" onClick={onDelete}>
<MdDelete className="size-6" />
</button>
</li>
);
}

20
src/components/Ruby.tsx Normal file
View File

@@ -0,0 +1,20 @@
import React from 'react';
export interface RubyProps {
caption: React.ReactNode;
children: React.ReactNode;
className?: string;
}
export function Ruby(props: RubyProps) {
const { caption, children, ...rest } = props;
return (
<ruby {...rest}>
{children}
<rp>(</rp>
<rt>{caption}</rt>
<rp>)</rp>
</ruby>
);
}