temp: try to replace selector component in nextui
This commit is contained in:
parent
da709d2988
commit
6960ec5b35
@ -3,6 +3,6 @@
|
|||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"printWidth": 120,
|
"printWidth": 100,
|
||||||
"endOfLine": "lf"
|
"endOfLine": "lf"
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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
85
components/selector.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
12
components/selectorItem.tsx
Normal file
12
components/selectorItem.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -32,7 +32,7 @@ export default function Time(props: {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%]
|
className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%]
|
||||||
left-1/2 w-11/12 sm:w-[700px] text:black
|
left-1/2 w-11/12 sm:w-[700px] text:black
|
||||||
dark:text-white text-3xl text-left text-shadow-lg"
|
dark:text-white text-3xl text-left text-shadow-lg"
|
||||||
>
|
>
|
||||||
{formatTime()}{" "}
|
{formatTime()}{" "}
|
||||||
|
@ -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": {
|
||||||
|
@ -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
|
||||||
left-1/2 w-11/12 sm:w-[700px] text:black text-right
|
className="absolute top-20 lg:top-44 short:top-0 translate-x-[-50%] translate-y-[-0.2rem]
|
||||||
dark:text-white text-3xl text-shadow-lg z-10"/>
|
left-1/2 w-11/12 sm:w-[700px] text:black text-right
|
||||||
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
36
src/app.tsx
36
src/app.tsx
@ -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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user