From a7769f1d3340bb255b034ac34a640bd5cf87f7a1 Mon Sep 17 00:00:00 2001 From: titix Date: Wed, 31 Jul 2024 13:21:44 -0300 Subject: [PATCH] feat: header components styling --- public/locales/en/common.json | 2 +- public/locales/es/common.json | 2 +- src/assets/icons/arrowDown.svg | 3 + src/assets/icons/lightMode.svg | 11 +++ src/assets/icons/search.svg | 4 + src/components/BasicSelect.tsx | 147 +++++++++++++++++++++++------ src/components/SearchBar.tsx | 56 ++++++++--- src/components/Theme/theme.ts | 9 +- src/containers/Dashboard/index.tsx | 12 +-- src/containers/Header/index.tsx | 81 ++++++++++------ src/providers/StateProvider.tsx | 6 ++ src/types/Theme.ts | 1 + 12 files changed, 253 insertions(+), 81 deletions(-) create mode 100644 src/assets/icons/arrowDown.svg create mode 100644 src/assets/icons/lightMode.svg create mode 100644 src/assets/icons/search.svg diff --git a/public/locales/en/common.json b/public/locales/en/common.json index f19797c..948b147 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -11,7 +11,7 @@ "nativeToken": "Native token", "tvl": "TVL - L1", "type": "Type", - "search": "Search by chain name or id...", + "search": "Search", "notFound": "Chain not found" } }, diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 5591452..b2a9b7e 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -11,7 +11,7 @@ "nativeToken": "Token nativo", "tvl": "TVL - L1", "type": "Tipo", - "search": "Buscar por nombre o ID de la cadena...", + "search": "Buscar", "notFound": "Cadena no encontrada" } }, diff --git a/src/assets/icons/arrowDown.svg b/src/assets/icons/arrowDown.svg new file mode 100644 index 0000000..aaca543 --- /dev/null +++ b/src/assets/icons/arrowDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/lightMode.svg b/src/assets/icons/lightMode.svg new file mode 100644 index 0000000..e4b80e4 --- /dev/null +++ b/src/assets/icons/lightMode.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/search.svg b/src/assets/icons/search.svg new file mode 100644 index 0000000..5194990 --- /dev/null +++ b/src/assets/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/BasicSelect.tsx b/src/components/BasicSelect.tsx index 17d98c8..d28afc4 100644 --- a/src/components/BasicSelect.tsx +++ b/src/components/BasicSelect.tsx @@ -1,6 +1,12 @@ import { useState } from 'react'; +import { styled, MenuProps, Menu, Box, Button, MenuItem } from '@mui/material'; +import Image from 'next/image'; + +import arrowDown from '~/assets/icons/arrowDown.svg'; +import { useCustomTheme } from '~/hooks'; interface BasicSelectProps { + label?: string; value: string; setValue: (explorer: string) => void; list: string[]; @@ -8,44 +14,127 @@ interface BasicSelectProps { dataTest?: string; } -export const BasicSelect = ({ list, value, setValue }: BasicSelectProps) => { - const [isOpen, setIsOpen] = useState(false); +export const BasicSelect = ({ list, value, setValue, disabled, dataTest }: BasicSelectProps) => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); - const handleClick = () => { - setIsOpen(!isOpen); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); }; const selectItem = (explorer?: string) => { - setIsOpen(false); + setAnchorEl(null); + if (!explorer) return; setValue(explorer); }; - const handleKeyDown = (event: React.KeyboardEvent, explorer?: string) => { - if (event.key === 'Enter' || event.key === ' ') { - selectItem(explorer); - } - }; + const endIcon = disabled ? null : arrow-down; return ( -
- - {isOpen && ( -
    - {list.map((item) => ( -
  • selectItem(item)} - onKeyDown={(event) => handleKeyDown(event, item)} - > - {item} -
  • - ))} -
- )} -
+ + + {value} + + + selectItem()} + MenuListProps={{ + 'aria-labelledby': 'basic-select-button', + }} + > + {list.map((explorer) => ( + selectItem(explorer)}> + {explorer} + + ))} + + ); }; + +const SBox = styled(Box)({ + display: 'flex', + flexDirection: 'column', + gap: '0.5rem', +}); + +const MenuButton = styled(Button)(() => { + const { currentTheme } = useCustomTheme(); + return { + width: '7.5rem', + height: '3.5rem', + backgroundColor: `${currentTheme.backgroundSecondary}`, + borderRadius: `${currentTheme.borderRadius}`, + textTransform: 'none', + fontSize: '1rem', + color: `${currentTheme.textPrimary}`, + '&:hover': { + backgroundColor: `${currentTheme.backgroundSecondary}`, + }, + }; +}); + +const StyledMenu = styled((props: MenuProps) => ( + +))(() => { + const { currentTheme } = useCustomTheme(); + return { + '& .MuiPaper-root': { + marginTop: '0.4rem', + width: '7.5rem', + + '& .MuiMenu-list': { + padding: '0.4rem 0', + }, + + '& .MuiMenuItem-root': { + padding: '1.2rem 1.6rem', + gap: '0.8rem', + + '&:hover': { + backgroundColor: currentTheme.backgroundSecondary, + }, + + '&:active': { + backgroundColor: currentTheme.backgroundSecondary, + }, + }, + + '@media (max-width: 600px)': { + minWidth: 'calc(100% - 7.4rem)', + ul: { + padding: '0.2rem 0', + }, + + '& .MuiMenuItem-root': { + fontSize: '1.4rem', + }, + }, + }, + }; +}); diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index f4bdda0..2dc0870 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,20 +1,54 @@ import { useTranslation } from 'next-i18next'; +import { useCustomTheme, useStateContext } from '~/hooks'; +import { styled, TextField, InputAdornment } from '@mui/material'; +import Image from 'next/image'; -interface SearchBarProps { - value: string; - onChange: (value: string) => void; -} +import Search from '~/assets/icons/search.svg'; -export const SearchBar = ({ value, onChange }: SearchBarProps) => { +export const SearchBar = () => { const { t } = useTranslation(); - + const { searchTerm, setSearchTerm } = useStateContext(); const handleChange = (event: React.ChangeEvent) => { - onChange(event.target.value); + const value = event.target.value; + setSearchTerm(value); }; - return ( -
- -
+ + search + + ), + }} + /> ); }; + +const StyledTextField = styled(TextField)(() => { + const { currentTheme } = useCustomTheme(); + + return { + width: '15rem', + height: '3.5rem', + borderRadius: `${currentTheme.borderRadius}`, + border: `1px solid ${currentTheme.backgroundSecondary}`, + backgroundColor: `${currentTheme.backgroundTertiary}`, + opacity: 1, + '& .MuiOutlinedInput-root': { + '& fieldset': { + border: 'none', + }, + '&:hover fieldset': { + border: 'none', + }, + '&.Mui-focused fieldset': { + border: 'none', + }, + }, + }; +}); diff --git a/src/components/Theme/theme.ts b/src/components/Theme/theme.ts index bfc0281..770353c 100644 --- a/src/components/Theme/theme.ts +++ b/src/components/Theme/theme.ts @@ -6,24 +6,27 @@ export const darkTheme: Theme = { textPrimary: '#ffffff', textSecondary: '#99A4B8', backgroundPrimary: '#000000', - backgroundSecondary: '#161616', + backgroundSecondary: '#262B33', + backgroundTertiary: '#11141A', titleFontFamily: 'Open Sans', textFontFamily: 'Open Sans', - borderRadius: '0.8rem', + borderRadius: '1.5rem', secondaryBorderRadius: '0.4rem', border: '0.1rem solid rgba(153, 164, 184, 0.1)', }; export const lightTheme: Theme = { + // TBD type: 'light', titleColor: '#000000', textPrimary: '#000000', textSecondary: '#717171', backgroundPrimary: '#ffffff', backgroundSecondary: '#f8f8f8', + backgroundTertiary: '#f8f8f8', titleFontFamily: 'Open Sans', textFontFamily: 'Open Sans', - borderRadius: '0.8rem', + borderRadius: '1.5rem', secondaryBorderRadius: '0.4rem', border: '0.1rem solid rgba(183, 183, 183, 0.3)', }; diff --git a/src/containers/Dashboard/index.tsx b/src/containers/Dashboard/index.tsx index 8410897..fb425bc 100644 --- a/src/containers/Dashboard/index.tsx +++ b/src/containers/Dashboard/index.tsx @@ -1,13 +1,12 @@ import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; -import { NotFound, SearchBar, Table, Title } from '~/components'; -import { useData } from '~/hooks'; +import { NotFound, Table, Title } from '~/components'; +import { useData, useStateContext } from '~/hooks'; export const Dashboard = () => { const { t } = useTranslation(); const { ecosystemData } = useData(); - const [searchTerm, setSearchTerm] = useState(''); + const { searchTerm } = useStateContext(); const filteredChains = ecosystemData?.chains.filter((chain) => { const chainIdStr = String(chain.id); @@ -22,15 +21,10 @@ export const Dashboard = () => { const availableChains = filteredChains?.length > 0; - const handleChange = (value: string) => { - setSearchTerm(value); - }; - return (
- <SearchBar value={searchTerm} onChange={handleChange} /> </header> {availableChains && <Table chains={filteredChains} />} diff --git a/src/containers/Header/index.tsx b/src/containers/Header/index.tsx index f10a036..c144c82 100644 --- a/src/containers/Header/index.tsx +++ b/src/containers/Header/index.tsx @@ -1,15 +1,17 @@ import { styled } from '@mui/material/styles'; -import { IconButton } from '@mui/material'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; +import { IconButton, Box } from '@mui/material'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; +import Image from 'next/image'; -import { BasicSelect } from '~/components'; +import { BasicSelect, SearchBar } from '~/components'; import { useCustomTheme } from '~/hooks/useContext/useTheme'; -import { zIndex, HEADER_HEIGHT } from '~/utils'; +import { zIndex } from '~/utils'; import { getConfig } from '~/config'; +import LightMode from '~/assets/icons/lightMode.svg'; +// temporary awaiting dark mode icon +import DarkMode from '~/assets/icons/lightMode.svg'; const { DEFAULT_LANG } = getConfig(); @@ -42,37 +44,62 @@ export const Header = () => { return ( <StyledHeader> - <Link href='/' passHref> - <Logo>ZKchainHub</Logo> - </Link> - <SIconButton onClick={changeTheme}>{theme === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}</SIconButton> - <BasicSelect value={t(`LOCALES.${language}`)} setValue={handleChangeLanguage} list={Object.values(localesMap)} /> + <Box> + <StyledLink href='/' passHref> + <Logo>ZKchainHub</Logo> + </StyledLink> + </Box> + <SBox> + <SearchBar /> + <BasicSelect + value={t(`LOCALES.${language}`)} + setValue={handleChangeLanguage} + list={Object.values(localesMap)} + /> + <SIconButton onClick={changeTheme}> + {theme === 'dark' ? <Image src={LightMode} alt='light mode' /> : <Image src={DarkMode} alt='dark mode' />} + </SIconButton> + </SBox> </StyledHeader> ); }; -//Styles -const StyledHeader = styled('header')(() => { - const { currentTheme } = useCustomTheme(); - return { - display: 'flex', - height: `${HEADER_HEIGHT}rem`, - padding: '0 8rem', - alignItems: 'center', - justifyContent: 'space-between', - backgroundColor: currentTheme.backgroundSecondary, - width: '100%', - zIndex: zIndex.HEADER, - }; +const StyledHeader = styled('header')({ + display: 'flex', + height: '5.5rem', + padding: '1rem', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + zIndex: zIndex.HEADER, }); const Logo = styled('h1')({ fontSize: '1.5rem', - fontWeight: 'bold', cursor: 'pointer', }); -const SIconButton = styled(IconButton)({ - position: 'absolute', - left: '50%', +const StyledLink = styled(Link)({ + textDecoration: 'none', + color: 'inherit', +}); + +const SIconButton = styled(IconButton)(() => { + const { currentTheme } = useCustomTheme(); + return { + marginRight: '1rem', + color: `${currentTheme.textPrimary}`, + backgroundColor: `${currentTheme.backgroundSecondary}`, + borderRadius: `${currentTheme.borderRadius}`, + padding: '1rem', + gap: '0.5rem', + width: '3.5rem', + height: '3.5rem', + }; +}); + +const SBox = styled(Box)({ + display: 'flex', + alignItems: 'center', + gap: '0.25rem', }); diff --git a/src/providers/StateProvider.tsx b/src/providers/StateProvider.tsx index 280aa75..1b8c7f0 100644 --- a/src/providers/StateProvider.tsx +++ b/src/providers/StateProvider.tsx @@ -6,6 +6,9 @@ type ContextType = { isError: boolean; setIsError: (val: boolean) => void; + + searchTerm: string; + setSearchTerm: (val: string) => void; }; interface StateProps { @@ -17,6 +20,7 @@ export const StateContext = createContext({} as ContextType); export const StateProvider = ({ children }: StateProps) => { const [loading, setLoading] = useState<boolean>(false); const [isError, setIsError] = useState<boolean>(false); + const [searchTerm, setSearchTerm] = useState<string>(''); return ( <StateContext.Provider @@ -25,6 +29,8 @@ export const StateProvider = ({ children }: StateProps) => { setLoading, isError, setIsError, + searchTerm, + setSearchTerm, }} > {children} diff --git a/src/types/Theme.ts b/src/types/Theme.ts index 22f9536..e2b0e90 100644 --- a/src/types/Theme.ts +++ b/src/types/Theme.ts @@ -6,6 +6,7 @@ export interface Theme { textSecondary: string; backgroundPrimary: string; backgroundSecondary: string; + backgroundTertiary: string; titleColor: string; titleFontFamily: string; textFontFamily: string;