Skip to content

Commit

Permalink
feat: improve search navbar (#144)
Browse files Browse the repository at this point in the history
Closes FE-86
  • Loading branch information
LuizAsFight authored Jan 26, 2024
1 parent b4dbbda commit 69cbf7c
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 166 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "Fuel Labs <[email protected]> (https://fuel.network/)",
"license": "Apache-2.0",
"engines": {
"node": ">=18",
"node": "18.x",
"pnpm": ">=8.1.0"
},
"homepage": "https://github.com/FuelLabs/fuel-explorer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@ import { useContext } from 'react';
import { useFormState } from 'react-dom';
import { search } from '~/systems/Core/actions/search';

import { SearchInput } from '../SearchInput/SearchInput';
import { SearchContext } from '../SearchWidget/SearchWidget';
import { SearchInput } from './SearchInput';
import { SearchContext } from './SearchWidget';
import { styles } from './styles';

type SearchFormProps = {
className: string;
autoFocus?: boolean;
alwaysDisplayActionButtons?: boolean;
};

export function SearchForm({
className,
autoFocus,
alwaysDisplayActionButtons,
}: SearchFormProps) {
export function SearchForm({ className, autoFocus }: SearchFormProps) {
const classes = styles();
const [results, action] = useFormState(
(_: SearchResult | null, formData: FormData) => {
return search({ query: formData.get('query')?.toString() || '' });
Expand All @@ -26,12 +23,11 @@ export function SearchForm({
const { onClear } = useContext(SearchContext);

return (
<form action={action}>
<form action={action} className={classes.searchSize()}>
<SearchInput
className={className}
searchResult={results}
autoFocus={autoFocus}
alwaysDisplayActionButtons={alwaysDisplayActionButtons}
onClear={onClear}
/>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,41 @@ import {
Link,
VStack,
useBreakpoints,
Box,
} from '@fuels/ui';
import { IconCheck, IconSearch, IconX } from '@tabler/icons-react';
import NextLink from 'next/link';
import type { KeyboardEvent } from 'react';
import { forwardRef, useContext, useEffect, useRef, useState } from 'react';
import { useFormStatus } from 'react-dom';
import { tv } from 'tailwind-variants';
import { Routes } from '~/routes';

import { cx } from '../../utils/cx';
import { SearchContext } from '../SearchWidget/SearchWidget';

import { SearchContext } from './SearchWidget';
import { styles } from './styles';

type SearchDropdownProps = {
searchResult?: Maybe<SearchResult>;
openDropdown: boolean;
onOpenChange: () => void;
searchValue: string;
width: number;
onSelectItem: () => void;
};

const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
({ searchResult, searchValue, openDropdown, onOpenChange }, ref) => {
(
{
searchResult,
searchValue,
openDropdown,
onOpenChange,
width,
onSelectItem,
},
ref,
) => {
const classes = styles();
const { isMobile } = useBreakpoints();
const trimL = isMobile ? 15 : 20;
Expand All @@ -42,9 +56,13 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
return (
<Dropdown open={openDropdown} onOpenChange={onOpenChange}>
<Dropdown.Trigger>
<div></div>
<Box className="w-full"></Box>
</Dropdown.Trigger>
<Dropdown.Content ref={ref} className="w-[311px] tablet:w-[400px]">
<Dropdown.Content
ref={ref}
className={cx(classes.dropdownContent(), classes.searchSize())}
style={{ width: width - 0.5 }}
>
{!searchResult && (
<>
<Dropdown.Item className="hover:bg-transparent focus:bg-transparent text-error hover:text-error focus:text-error">
Expand All @@ -60,6 +78,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
as={NextLink}
href={Routes.accountAssets(searchResult.account.address!)}
className="text-color"
onClick={onSelectItem}
>
{shortAddress(
searchResult.account.address || '',
Expand All @@ -80,6 +99,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
as={NextLink}
href={Routes.txSimple(transaction!.id!)}
className="text-color"
onClick={onSelectItem}
>
{shortAddress(transaction?.id || '', trimL, trimR)}
</Link>
Expand All @@ -96,6 +116,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
as={NextLink}
href={Routes.blockSimple(searchResult.block.id!)}
className="text-color"
onClick={onSelectItem}
>
{shortAddress(searchResult.block.id || '', trimL, trimR)}
</Link>
Expand All @@ -105,6 +126,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
as={NextLink}
href={Routes.blockSimple(searchResult.block.height!)}
className="text-color"
onClick={onSelectItem}
>
{searchResult.block.height}
</Link>
Expand All @@ -119,6 +141,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
as={NextLink}
href={Routes.contractAssets(searchResult.contract.id!)}
className="text-color"
onClick={onSelectItem}
>
{shortAddress(searchResult.contract.id || '', trimL, trimR)}
</Link>
Expand All @@ -133,6 +156,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
as={NextLink}
href={Routes.txSimple(searchResult.transaction.id!)}
className="text-color"
onClick={onSelectItem}
>
{shortAddress(
searchResult.transaction.id || '',
Expand Down Expand Up @@ -163,13 +187,14 @@ export function SearchInput({
autoFocus,
placeholder = 'Search here...',
searchResult,
alwaysDisplayActionButtons,
...props
}: SearchInputProps) {
const classes = styles();
const [value, setValue] = useState<string>(initialValue as string);
const [openDropdown, setOpenDropdown] = useState(false);
const [hasSubmitted, setHasSubmitted] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const inputWrapperRef = useRef<HTMLInputElement>(null);
const { pending } = useFormStatus();
const { dropdownRef } = useContext(SearchContext);

Expand All @@ -196,12 +221,15 @@ export function SearchInput({
}

return (
<VStack gap="0" className="justify-center">
<VStack gap="0" className="justify-center items-center">
<Focus.ArrowNavigator autoFocus={autoFocus}>
<Input
className={cx(className)}
ref={inputWrapperRef}
variant="surface"
radius="large"
size="3"
data-opened={openDropdown}
className={cx(className, classes.inputWrapper())}
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
Expand All @@ -212,9 +240,6 @@ export function SearchInput({
}
}}
>
<Input.Slot className="mx-1">
<Icon icon={IconSearch} size={16} />
</Input.Slot>
<Input.Field
{...props}
ref={inputRef}
Expand All @@ -223,37 +248,48 @@ export function SearchInput({
value={value}
onChange={handleChange}
/>
{(alwaysDisplayActionButtons || !!value.length) && (
<Input.Slot className="mx-1">
<IconButton
aria-label="Clear"
icon={IconX}
iconColor="text-icon"
variant="link"
className="!ml-0 tablet:ml-2"
onClick={handleClear}
/>
<Tooltip content="Submit">
{value?.length ? (
<>
<Input.Slot className="">
<Tooltip content="Submit">
<IconButton
type="submit"
aria-label="Submit"
icon={IconCheck}
iconColor="text-brand"
variant="link"
className="!ml-0 tablet:ml-2"
isLoading={pending}
onClick={handleSubmit}
/>
</Tooltip>
<IconButton
type="submit"
aria-label="Submit"
icon={IconCheck}
iconColor="text-brand"
aria-label="Clear"
icon={IconX}
iconColor="text-gray-11"
variant="link"
className="!ml-0 tablet:ml-2"
isLoading={pending}
onClick={handleSubmit}
className="m-0"
onClick={handleClear}
/>
</Tooltip>
</Input.Slot>
</>
) : (
<Input.Slot>
<Icon icon={IconSearch} size={16} />
</Input.Slot>
)}
</Input>
</Focus.ArrowNavigator>
<SearchResultDropdown
ref={dropdownRef}
width={inputWrapperRef.current?.offsetWidth || 0}
searchResult={searchResult}
searchValue={value}
openDropdown={openDropdown}
onSelectItem={() => {
setOpenDropdown(false);
handleClear();
}}
onOpenChange={() => {
if (openDropdown) {
setHasSubmitted(false);
Expand All @@ -264,9 +300,3 @@ export function SearchInput({
</VStack>
);
}

const styles = tv({
slots: {
dropdownItem: 'hover:bg-border focus:bg-border',
},
});
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { HStack, IconButton, Tooltip } from '@fuels/ui';
import { IconSearch } from '@tabler/icons-react';
import { AnimatePresence, motion } from 'framer-motion';
import { HStack } from '@fuels/ui';
import type { MutableRefObject } from 'react';
import { useEffect, useRef, createContext } from 'react';
import { tv } from 'tailwind-variants';

import { SearchForm } from '../SearchForm/SearchForm';
import { SearchForm } from './SearchForm';
import { styles } from './styles';

export const SearchContext = createContext<{
dropdownRef: null | MutableRefObject<HTMLDivElement | null>;
onClear: (value: string) => void;
}>({ dropdownRef: null, onClear: () => {} });

type SearchWidgetProps = {
setIsExitComplete: (value: boolean) => void;
isExitComplete: boolean;
autoFocus?: boolean;
isSearchOpen: boolean;
setIsSearchOpen: (value: boolean) => void;
};

export const SearchWidget = ({
setIsExitComplete,
isExitComplete,
isSearchOpen,
autoFocus,
setIsSearchOpen,
}: SearchWidgetProps) => {
const classes = styles();
Expand Down Expand Up @@ -74,63 +69,14 @@ export const SearchWidget = ({
};
}, []);

useEffect(() => {
if (isSearchOpen) {
setIsExitComplete(false);
}
}, [isSearchOpen]);

return (
<SearchContext.Provider value={{ dropdownRef, onClear }}>
<HStack
ref={widgetRef}
className="items-center gap-0 laptop:gap-4 justify-center"
className="items-center gap-0 laptop:gap-4 justify-center flex-1"
>
<AnimatePresence
onExitComplete={() => {
setIsExitComplete(true);
}}
>
{isSearchOpen && (
<>
<motion.div
initial="closed"
animate="open"
exit="closed"
transition={{ duration: 0.2 }}
variants={{
open: { scaleX: '100%' },
closed: { scaleX: '0%' },
}}
>
<SearchForm
className={classes.input()}
autoFocus={true}
alwaysDisplayActionButtons={true}
/>
</motion.div>
</>
)}
</AnimatePresence>
{isExitComplete && (
<Tooltip content="Search by address, contract id, transaction id, or block id">
<IconButton
icon={IconSearch}
variant="link"
className="mr-1 text-color laptop:mr-0"
onClick={() => {
setIsSearchOpen(!isSearchOpen);
}}
/>
</Tooltip>
)}
<SearchForm className={classes.searchSize()} autoFocus={autoFocus} />
</HStack>
</SearchContext.Provider>
);
};

const styles = tv({
slots: {
input: 'w-full tablet:w-[400px]',
},
});
12 changes: 12 additions & 0 deletions packages/app/src/systems/Core/components/Search/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { tv } from 'tailwind-variants';

export const styles = tv({
slots: {
dropdownItem: 'hover:bg-border focus:bg-border',
inputWrapper:
'[&_.rt-TextFieldChrome]:[&[data-opened=true]]:rounded-b-none',
searchSize: 'w-full tablet:w-[400px]',
dropdownContent:
'mt-[-4px] rounded-t-none shadow-none border border-t-0 border-border',
},
});
Loading

0 comments on commit 69cbf7c

Please sign in to comment.