temp: try to replace selector component in nextui

This commit is contained in:
alikia2x 2024-07-05 16:08:30 +08:00
parent da709d2988
commit 6960ec5b35
11 changed files with 164 additions and 74 deletions

View File

@ -3,6 +3,6 @@
"tabWidth": 4, "tabWidth": 4,
"trailingComma": "none", "trailingComma": "none",
"singleQuote": false, "singleQuote": false,
"printWidth": 120, "printWidth": 100,
"endOfLine": "lf" "endOfLine": "lf"
} }

View File

@ -6,10 +6,8 @@ import { engineTranslation } from "lib/onesearch/translatedEngineList";
import { settingsType } from "global"; import { settingsType } from "global";
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from "jotai";
export default function EngineSelector( export default function EngineSelector(props: { className: string }) {
props: { className: string } const { t } = useTranslation();
) {
const { t } = useTranslation("Search");
const settings: settingsType = useAtomValue(settingsAtom); const settings: settingsType = useAtomValue(settingsAtom);
const items = settings.searchEngines; const items = settings.searchEngines;
const currentEngine: string = settings.currentSearchEngine; const currentEngine: string = settings.currentSearchEngine;
@ -19,10 +17,8 @@ export default function EngineSelector(
const selectedValue = React.useMemo(() => Array.from(selectedKeys).join(", "), [selectedKeys]); const selectedValue = React.useMemo(() => Array.from(selectedKeys).join(", "), [selectedKeys]);
const setSettings = useSetAtom(settingsAtom); const setSettings = useSetAtom(settingsAtom);
function getName(engineKey: string) { function getName(engineKey: string) {
return engineTranslation.includes(engineKey) ? t(`engine.${engineKey}`) : engineKey; return engineTranslation.includes(engineKey) ? t(`search.engine.${engineKey}`) : engineKey;
} }
useEffect(() => { useEffect(() => {
@ -39,39 +35,29 @@ export default function EngineSelector(
} }
}, [currentEngine, selectedValue, setSettings]); }, [currentEngine, selectedValue, setSettings]);
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return ( return (
<div className={props.className}> <div className={props.className}>
{ <Dropdown>
isClient && <DropdownTrigger>
( <Button variant="bordered" className="capitalize">
<Dropdown> {displayEngine}
<DropdownTrigger> </Button>
<Button variant="bordered" className="capitalize"> </DropdownTrigger>
{displayEngine} <DropdownMenu
</Button> aria-label={t("search.engine-aria")}
</DropdownTrigger> variant="light"
<DropdownMenu disallowEmptySelection
aria-label={t("engine-aria")} selectionMode="single"
variant="light" selectedKeys={selectedKeys}
disallowEmptySelection onSelectionChange={setSelectedKeys}
selectionMode="single" >
selectedKeys={selectedKeys} {Object.keys(items).map((item) => (
onSelectionChange={setSelectedKeys} <DropdownItem key={item} suppressHydrationWarning>
> {getName(item)}
{Object.keys(items).map((item) => ( </DropdownItem>
<DropdownItem key={item} suppressHydrationWarning> ))}
{getName(item)} </DropdownMenu>
</DropdownItem> </Dropdown>
))}
</DropdownMenu>
</Dropdown>
)}
</div> </div>
); );
} }

View File

@ -7,7 +7,6 @@ import handleEnter from "lib/onesearch/handleEnter";
import { suggestionAtom } from "lib/state/suggestion"; import { suggestionAtom } from "lib/state/suggestion";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export default function Search(props: { onFocus: () => void }) { export default function Search(props: { onFocus: () => void }) {
const { t } = useTranslation(); const { t } = useTranslation();
const settings = useAtomValue(settingsAtom); const settings = useAtomValue(settingsAtom);
@ -40,12 +39,13 @@ export default function Search(props: { onFocus: () => void }) {
<div className="absolute w-full top-[8.5rem] lg:top-56 short:top-24 z-1 left-1/2 translate-x-[-50%] "> <div className="absolute w-full top-[8.5rem] lg:top-56 short:top-24 z-1 left-1/2 translate-x-[-50%] ">
<input <input
className="absolute z-1 w-11/12 sm:w-[700px] h-10 rounded-lg left-1/2 translate-x-[-50%] className="absolute z-1 w-11/12 sm:w-[700px] h-10 rounded-lg left-1/2 translate-x-[-50%]
text-center outline-none border-[1px] focus:border-2 duration-200 pr-2 shadow-md focus:shadow-none dark:shadow-zinc-800 dark:shadow-md bg-white dark:bg-[rgb(23,25,29)] text-center outline-none border-2 focus:border-2 duration-200 pr-2 shadow-md focus:shadow-none
dark:shadow-zinc-800 dark:shadow-md bg-white dark:bg-[rgb(23,25,29)]
dark:border-neutral-500 dark:focus:border-neutral-300 placeholder:text-slate-500 dark:border-neutral-500 dark:focus:border-neutral-300 placeholder:text-slate-500
dark:placeholder:text-slate-400 text-slate-900 dark:text-white" dark:placeholder:text-slate-400 text-slate-900 dark:text-white"
id="searchBox" id="searchBox"
type="text" type="text"
placeholder={t('search.placeholder')} placeholder={t("search.placeholder")}
onFocus={props.onFocus} onFocus={props.onFocus}
onKeyDown={handleKeydown} onKeyDown={handleKeydown}
onChange={(e) => onChange={(e) =>

85
components/selector.tsx Normal file
View File

@ -0,0 +1,85 @@
import { HTMLAttributes, ReactNode, RefObject, useEffect, useRef } from "react";
import { selectedOnChange } from "./selectorItem";
import { createPortal } from "react-dom";
export type selectionType = string | number | boolean;
interface PickerPropsChildrenStyle extends HTMLAttributes<HTMLDivElement> {
selected: selectionType;
selectionOnChange: selectedOnChange;
displayContent: string;
children: ReactNode;
}
interface PickerPropsParamStyle extends HTMLAttributes<HTMLDivElement> {
selected: selectionType;
selectionOnChange: selectedOnChange;
displayContent: string;
selectionItems: PickedItem;
}
interface PickedItem {
[key: string]: selectionType;
}
export default function Picker(props: PickerPropsChildrenStyle | PickerPropsParamStyle) {
const itemListRef: RefObject<HTMLDivElement> = useRef(null);
const buttonRef: RefObject<HTMLButtonElement> = useRef(null);
const updatePosition = () => {
if (itemListRef.current == null || buttonRef.current == null) {
return;
}
const buttonRect = buttonRef.current.getBoundingClientRect();
const listWidth = itemListRef.current.getBoundingClientRect().width;
// Align to center
itemListRef.current.style.left = buttonRect.x + buttonRect.width / 2 - listWidth / 2 + "px";
itemListRef.current.style.top = buttonRect.y + buttonRect.height + 16 + "px";
};
useEffect(() => {
updatePosition();
const handleResize = () => {
updatePosition();
};
window.addEventListener('resize', handleResize);
// Cleanup event listener on component unmount
return () => {
window.removeEventListener('resize', handleResize);
};
}, [itemListRef, buttonRef]);
if ("selectionItems" in props) {
const { selectionItems, displayContent, selectionOnChange, ...rest } = props;
return (
<div {...rest}>
<button
className="relative border-2 border-gray-500 dark:border-gray-300 rounded-xl dark:text-white
px-4 py-2"
ref={buttonRef}
>
{displayContent}
</button>
{createPortal(
<div ref={itemListRef} className="absolute w-fit text-white">
{Object.keys(selectionItems).map((key: string, index) => {
return <div key={index}>{selectionItems[key]}</div>;
})}
</div>,
document.body
)}
</div>
);
} else {
return (
<div {...props}>
<button className="relative" ref={buttonRef}>
{props.displayContent}
</button>
{createPortal(<div ref={itemListRef}>{props.children}</div>, document.body)}
</div>
);
}
}

View File

@ -0,0 +1,12 @@
import { ReactNode } from "react";
import { selectionType } from "./selector";
export type selectedOnChange = (target: selectionType) => void;
export default function SelectionItem(props: {key: selectionType, children: ReactNode, onChange: selectedOnChange}){
return (
<div onClick={() => props.onChange(props.key)}>
{props.children}
</div>
)
}

View File

@ -1,5 +1,5 @@
{ {
"Search": { "search": {
"placeholder": "Search or type a URL", "placeholder": "Search or type a URL",
"engine-aria": "Switch search engine", "engine-aria": "Switch search engine",
"engine": { "engine": {
@ -16,7 +16,7 @@
"404": { "404": {
"title": "Page Not Found" "title": "Page Not Found"
}, },
"About": { "about": {
"title": "SparkHome" "title": "SparkHome"
}, },
"tools": { "tools": {

View File

@ -6,6 +6,7 @@ import { settingsAtom } from "lib/state/settings";
import { bgFocusAtom } from "lib/state/background"; import { bgFocusAtom } from "lib/state/background";
import EngineSelector from "components/engineSelector"; import EngineSelector from "components/engineSelector";
import OneSearch from "components/onesearch/onesearch"; import OneSearch from "components/onesearch/onesearch";
import Picker from "components/selector";
export default function Homepage() { export default function Homepage() {
const settings = useAtomValue(settingsAtom); const settings = useAtomValue(settingsAtom);
@ -14,12 +15,21 @@ export default function Homepage() {
return ( return (
<div className="h-full fixed overflow-hidden w-full bg-black"> <div className="h-full fixed overflow-hidden w-full bg-black">
<Background /> <Background />
<EngineSelector className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%] translate-y-[-0.2rem] <EngineSelector
className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%] translate-y-[-0.2rem]
left-1/2 w-11/12 sm:w-[700px] text:black text-right left-1/2 w-11/12 sm:w-[700px] text:black text-right
dark:text-white text-3xl text-shadow-lg z-10"/> dark:text-white text-3xl text-shadow-lg z-10"
/>
<Search onFocus={() => setBgFocus(true)} /> <Search onFocus={() => setBgFocus(true)} />
<Time showSecond={settings.timeShowSecond} /> <Time showSecond={settings.timeShowSecond} />
<OneSearch /> <OneSearch />
<Picker
selectionItems={{ "1": "Item1", "2": "Item2" }}
selected="2"
selectionOnChange={() => {}}
displayContent="Item1"
className="absolute w-fit h-8 top-20 lg:top-44 short:top-0 -translate-x-1/2 -translate-y-1 left-1/2 z-20"
/>
</div> </div>
); );
} }

View File

@ -3,20 +3,19 @@ import { useRoutes } from "react-router-dom";
import routes from "~react-pages"; import routes from "~react-pages";
import i18n from "i18next"; import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from 'i18next-browser-languagedetector';
import ICU from "i18next-icu"; import ICU from 'i18next-icu';
import * as en from "i18n/en.json"; import * as en from "i18n/en.json"
import * as zh from "i18n/zh.json"; import * as zh from "i18n/zh.json"
import * as ja from "i18n/ja.json"; import * as ja from "i18n/ja.json"
import * as ar from "i18n/ar.json"; import * as ar from "i18n/ar.json"
import * as de from "i18n/de.json"; import * as de from "i18n/de.json"
import * as es from "i18n/es.json"; import * as es from "i18n/es.json"
import * as fr from "i18n/fr.json"; import * as fr from "i18n/fr.json"
import * as it from "i18n/it.json"; import * as it from "i18n/it.json"
import * as ko from "i18n/ko.json"; import * as ko from "i18n/ko.json"
import * as pt from "i18n/pt.json"; import * as pt from "i18n/pt.json"
import * as ru from "i18n/ru.json"; import * as ru from "i18n/ru.json"
import { NextUIProvider } from "@nextui-org/react";
i18n.use(initReactI18next) // passes i18n down to react-i18next i18n.use(initReactI18next) // passes i18n down to react-i18next
.use(LanguageDetector) .use(LanguageDetector)
@ -64,15 +63,12 @@ i18n.use(initReactI18next) // passes i18n down to react-i18next
}, },
detection: { detection: {
order: ["navigator"], order: ['navigator'],
caches: [] caches: []
} }
}); });
export function App() { export function App() {
return ( return <Suspense fallback={<p>Loading...</p>}>{useRoutes(routes)}</Suspense>;
<NextUIProvider>
<Suspense fallback={<p>Loading...</p>}>{useRoutes(routes)}</Suspense>
</NextUIProvider>
);
} }

View File

@ -3,13 +3,16 @@ import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { App } from "./app"; import { App } from "./app";
import "./index.css"; import "./index.css";
import { NextUIProvider } from "@nextui-org/react";
const app = createRoot(document.getElementById("root")!); const app = createRoot(document.getElementById("root")!);
app.render( app.render(
<StrictMode> <StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <NextUIProvider>
<App />
</NextUIProvider>
</BrowserRouter> </BrowserRouter>
</StrictMode> </StrictMode>
); );

View File

@ -6,8 +6,7 @@ const config: Config = {
content: [ content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}", "./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", "./node_modules/@nextui-org/theme/**/*.{js,ts,jsx,tsx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}"
], ],
theme: { theme: {
extend: { extend: {
@ -17,7 +16,6 @@ const config: Config = {
} }
} }
}, },
darkMode: "class", plugins: [nextui()]
plugins: [nextui()],
}; };
export default config; export default config;