improve: theming

This commit is contained in:
ryana mittens 2024-09-06 13:52:24 +08:00
parent 43a99125c3
commit e0b5c6f1d5
4 changed files with 113 additions and 68 deletions

View file

@ -2,6 +2,9 @@ import { useState, useEffect } from 'react'
import SearchBar from './SearchBar'
import Link from 'next/link'
import ThemeSwitcher from './ThemeSwitcher'
import { IoSparkles } from 'react-icons/io5'
import { FaHamburger } from 'react-icons/fa'
import { GiHamburgerMenu } from 'react-icons/gi'
interface NavigationBarProps {
onSelectSymbol: (symbol: string) => void
@ -21,9 +24,9 @@ const NavigationBar = ({ onSelectSymbol }: NavigationBarProps) => {
}
return (
<nav className="bg-base text-text">
<div className="container mx-auto max-w-8xl flex items-center justify-between py-4 px-4 lg:px-8">
<Link className="text-lg font-bold" href="/">TWL
<nav className="bg-crust transition-all text-text border-b-2 border-surface0 px-4">
<div className="container mx-auto max-w-8xl flex items-center justify-between py-1">
<Link className="text-lg font-bold flex flex-row align-middle items-center gap-2" href="/"><IoSparkles /> <span className="md:block hidden">twinkle</span>
</Link>
<div className="flex-grow max-w-md mx-auto lg:max-w-lg w-full">
<SearchBar onSelectSymbol={onSelectSymbol} />
@ -34,7 +37,7 @@ const NavigationBar = ({ onSelectSymbol }: NavigationBarProps) => {
onClick={() => setDropdownOpen(!dropdownOpen)}
className="bg-mantle px-4 py-2 rounded focus:outline-none focus:bg-overlay0"
>
X
<GiHamburgerMenu />
</button>
{dropdownOpen && (
<div className="absolute right-0 mt-2 w-48 bg-surface0 rounded-md shadow-lg z-20">

View file

@ -1,90 +1,126 @@
import { useState, useEffect, useCallback } from 'react'
import debounce from 'lodash.debounce'
import { useState, useEffect, useCallback, useRef } from "react";
import debounce from "lodash.debounce";
import { FaSearch, FaTimes } from "react-icons/fa";
interface SearchBarProps {
onSelectSymbol: (symbol: string) => void
onSelectSymbol: (symbol: string) => void;
}
const SearchBar = ({ onSelectSymbol }: SearchBarProps) => {
const [query, setQuery] = useState('')
const [suggestions, setSuggestions] = useState<{ symbol: string, description: string }[]>([])
const [loading, setLoading] = useState(false)
const [notFound, setNotFound] = useState(false)
const [query, setQuery] = useState("");
const [selectedQuery, setSelectedQuery] = useState("");
const [suggestions, setSuggestions] = useState<{ symbol: string; description: string }[]>([]);
const [loading, setLoading] = useState(false);
const [notFound, setNotFound] = useState(false);
const [isPickerVisible, setIsPickerVisible] = useState(false);
const pickerRef = useRef<HTMLDivElement>(null);
const fetchSuggestions = async (query: string) => {
setLoading(true)
setNotFound(false)
setLoading(true);
setNotFound(false);
try {
const res = await fetch(`/api/search?query=${query}`)
const data = await res.json()
const res = await fetch(`/api/search?query=${query}`);
const data = await res.json();
if (data.result && data.result.length > 0) {
setSuggestions(data.result)
setSuggestions(data.result.slice(0, 5));
} else {
setNotFound(true)
setSuggestions([])
setNotFound(true);
setSuggestions([]);
}
} catch (error) {
console.error('Error fetching suggestions:', error)
setNotFound(true)
setSuggestions([])
console.error("Error fetching suggestions:", error);
setNotFound(true);
setSuggestions([]);
} finally {
setLoading(false)
setLoading(false);
}
}
};
const debouncedFetchSuggestions = useCallback(debounce((query: string) => {
fetchSuggestions(query)
}, 300), [])
fetchSuggestions(query);
}, 300), []);
useEffect(() => {
if (query.length > 1) {
debouncedFetchSuggestions(query)
debouncedFetchSuggestions(query);
setIsPickerVisible(true);
} else {
setSuggestions([])
setNotFound(false)
setSuggestions([]);
setNotFound(false);
setIsPickerVisible(false);
}
}, [query, debouncedFetchSuggestions])
}, [query, debouncedFetchSuggestions]);
const handleSelect = (symbol: string) => {
setQuery('')
setSuggestions([])
onSelectSymbol(symbol)
}
setQuery("");
setSelectedQuery(symbol);
setSuggestions([]);
setIsPickerVisible(false);
onSelectSymbol(symbol);
};
const handleClickOutside = (event: MouseEvent) => {
if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {
setIsPickerVisible(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
return (
<div className="relative">
<input
type="text"
placeholder="Search stocks..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="border p-2 w-full"
/>
{loading && (
<div className="absolute border bg-white p-2 w-full mt-1 text-center">
Loading...
</div>
)}
{!loading && notFound && (
<div className="absolute border bg-white p-2 w-full mt-1 text-center text-red-500">
No results found
</div>
)}
{suggestions.length > 0 && (
<ul className="absolute border bg-white w-full mt-1">
{suggestions.map((item) => (
<li
key={item.symbol}
onClick={() => handleSelect(item.symbol)}
className="p-2 hover:bg-gray-200 cursor-pointer"
>
{item.description} ({item.symbol})
</li>
))}
</ul>
<div className="relative w-full" ref={pickerRef}>
<div className="relative flex items-center border p-1 w-full border-none md:bg-mantle md:border-surface0 md:rounded-md">
<input
type="text"
placeholder={selectedQuery || "Search stocks..."}
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-8 w-full bg-transparent outline-none text-text"
onFocus={() => setIsPickerVisible(true)}
/>
<span className="absolute left-2 text-surface0">
<FaSearch className="text-text" />
</span>
{query && (
<span className="absolute right-2 cursor-pointer" onClick={() => setQuery('')}>
<FaTimes className="text-text" />
</span>
)}
</div>
{isPickerVisible && (
<>
{loading && (
<div className="absolute border bg-mantle p-2 w-full mt-1 text-center">
Loading...
</div>
)}
{!loading && notFound && (
<div className="absolute border bg-mantle p-2 w-full mt-1 text-center text-red-500">
No results found
</div>
)}
{suggestions.length > 0 && (
<ul className="absolute border bg-mantle w-full mt-1">
{suggestions.map((item) => (
<li
key={item.symbol}
onClick={() => handleSelect(item.symbol)}
className="p-2 hover:bg-crust cursor-pointer"
>
{item.description} ({item.symbol})
</li>
))}
</ul>
)}
</>
)}
</div>
)
}
);
};
export default SearchBar
export default SearchBar;

View file

@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { FaMoon, FaSun } from 'react-icons/fa';
const ThemeSwitcher = () => {
const [theme, setTheme] = useState('light');
@ -18,9 +19,11 @@ const ThemeSwitcher = () => {
return (
<button
onClick={handleThemeToggle}
className="px-4 py-2 bg-blue-600 text-white rounded-md"
className="px-4 py-2 bg-text text-crust rounded-md transition-all hover:bg-overlay1 focus:outline-none"
>
O
{
theme === 'light' ? <FaSun /> : <FaMoon />
}
</button>
);
};

View file

@ -39,4 +39,7 @@
body {
background-color: var(--background);
color: var(--foreground);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}