fix(search): Add preview to options

This commit is contained in:
alexsparkes
2024-12-17 20:31:36 +00:00
parent 15f1e523b4
commit 94b33388b2
4 changed files with 78 additions and 76 deletions

View File

@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { useState, useCallback, memo, useMemo } from 'react';
import { toast } from 'react-toastify';
import ReactSlider from 'react-slider';
import { MdRefresh, MdEdit } from 'react-icons/md';
import { MdRefresh } from 'react-icons/md';
import EventBus from 'utils/eventbus';
import clsx from 'clsx';
@@ -15,15 +15,6 @@ const buttonStyles = {
transitions: 'transition-colors duration-200',
};
const inputStyles = {
base: 'w-20 text-sm px-3 py-1.5 rounded-md font-medium text-center',
colors: 'bg-neutral-200 hover:bg-neutral-300 focus:bg-neutral-300',
darkMode: 'dark:bg-white/10 dark:hover:bg-white/15 dark:focus:bg-white/15',
text: 'text-neutral-800 dark:text-white',
focus: 'focus:outline-none focus:ring-2 focus:ring-neutral-400 dark:focus:ring-white/40',
transitions: 'transition-colors duration-200',
};
const thumbStyles = {
base: [
'w-5 h-5 rounded-full cursor-pointer',
@@ -76,68 +67,15 @@ const MULTIPLIER_MARKS = {
};
// Memoized value display component
const ValueDisplay = memo(({ value, display, onChange }) => {
const [isEditing, setIsEditing] = useState(false);
const [inputValue, setInputValue] = useState(value);
const ValueDisplay = memo(({ value, display }) => (
<span className="text-sm bg-neutral-200 dark:bg-white/15 text-neutral-800 dark:text-white px-3 py-1.5 rounded-md font-medium">
{value}
{display}
</span>
));
const handleBlur = () => {
setIsEditing(false);
const newValue = Number(inputValue);
if (!isNaN(newValue)) {
onChange(newValue);
} else {
setInputValue(value);
}
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
handleBlur();
} else if (e.key === 'Escape') {
setIsEditing(false);
setInputValue(value);
}
};
if (isEditing) {
return (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
className={clsx(
inputStyles.base,
inputStyles.colors,
inputStyles.darkMode,
inputStyles.text,
inputStyles.focus,
inputStyles.transitions,
)}
autoFocus
/>
);
}
return (
<div className="flex items-center gap-2">
<span className="text-sm bg-neutral-200 dark:bg-white/15 text-neutral-800 dark:text-white px-3 py-1.5 rounded-md font-medium">
{value}
{display}
</span>
<button
onClick={() => setIsEditing(true)}
className="p-1.5 rounded-md hover:bg-neutral-300 dark:hover:bg-white/15 transition-colors duration-200"
aria-label="Edit value"
>
<MdEdit className="w-4 h-4 text-neutral-600 dark:text-white/70" />
</button>
</div>
);
});
// Memoized reset button component
const ResetButton = memo(({ onClick, defaultValue }) => (
const ResetButton = memo(({ onClick }) => (
<button
onClick={onClick}
className={clsx(
@@ -146,11 +84,9 @@ const ResetButton = memo(({ onClick, defaultValue }) => (
buttonStyles.darkMode,
buttonStyles.text,
buttonStyles.transitions,
'group focus:outline-none focus:ring-2 focus:ring-neutral-400 dark:focus:ring-white/40',
)}
aria-label={`Reset to default value: ${defaultValue}`}
>
<MdRefresh className="w-4 h-4 mr-1.5 group-hover:rotate-180 transition-transform duration-300" />
<MdRefresh className="w-4 h-4 mr-1.5" />
{variables.getMessage('settings:buttons.reset')}
</button>
));
@@ -258,8 +194,8 @@ function SliderComponent(props) {
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-neutral-800 dark:text-white">{props.title}</span>
<div className="flex items-center gap-2 justify-between w-full">
<ValueDisplay value={value} display={props.display} onChange={handleChange} />
<ResetButton onClick={resetItem} defaultValue={props.default} />
<ValueDisplay value={value} display={props.display} />
<ResetButton onClick={resetItem} />
</div>
</div>

View File

@@ -17,7 +17,7 @@ import './search.scss';
import defaults from './options/default';
import searchEngines from './search_engines.json';
function Search() {
function Search({ isPreview }) {
const [url, setURL] = useState('');
const [query, setQuery] = useState('');
const [microphone, setMicrophone] = useState(null);
@@ -140,6 +140,29 @@ function Search() {
const getSuggestionsDebounced = useDebouncedCallback(getSuggestions, 100);
if (isPreview) {
return (
<div className="searchComponents">
<div className="searchMain">
<div className={classList}>
<Icon currentSearch={currentSearch} setSearchDropdown={setSearchDropdown} />
</div>
<form className="searchBar">
<div className={classList}>
<MdSearch />
</div>
<input
placeholder={variables.getMessage('widgets.search')}
id="searchPreviewText"
disabled={true}
className="previewInput"
/>
</form>
</div>
</div>
);
}
return (
<div className="searchComponents">
<div className="searchMain">

View File

@@ -4,12 +4,14 @@ import { toast } from 'react-toastify';
import { TextField } from '@mui/material';
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
import { Hero, Preview, Controls } from 'components/Layout/Settings/Hero';
import { Dropdown, Checkbox } from 'components/Form/Settings';
import EventBus from 'utils/eventbus';
import searchEngines from '../search_engines.json';
import defaults from './default';
import SearchPreview from './SearchPreview';
function SearchOptions() {
const [customEnabled, setCustomEnabled] = useState(false);
@@ -148,6 +150,17 @@ function SearchOptions() {
category="widgets"
visibilityToggle={true}
/>
<Hero>
<Preview>
<SearchPreview />
</Preview>
<Controls
title={variables.getMessage(`${SEARCH_SECTION}.title`)}
setting="searchBar"
category="widgets"
visibilityToggle={true}
/>
</Hero>
<PreferencesWrapper setting="searchBar" category="widgets" visibilityToggle={true}>
<SearchEngineSelection />
<AdditionalOptions />

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import Search from 'features/search/Search';
const SearchPreview = ({ zoomLevel = 100 }) => {
const searchEngine = localStorage.getItem('searchEngine') || 'duckduckgo';
return (
<AnimatePresence mode="crossfade">
<motion.div
className="absolute"
key={`search-preview-${searchEngine}`}
initial={{ opacity: 0, scale: 0.95 }}
animate={{
opacity: 1,
scale: zoomLevel / 100,
}}
exit={{ opacity: 0, scale: 0.95 }}
transition={{
opacity: { duration: 0.2 },
scale: { duration: 0.3, ease: 'anticipate' },
}}
>
<Search isPreview={true} />
</motion.div>
</AnimatePresence>
);
};
export { SearchPreview, SearchPreview as default };