Skip to content

Commit 69cbf7c

Browse files
authored
feat: improve search navbar (#144)
Closes FE-86
1 parent b4dbbda commit 69cbf7c

File tree

10 files changed

+163
-166
lines changed

10 files changed

+163
-166
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"author": "Fuel Labs <contact@fuel.sh> (https://fuel.network/)",
66
"license": "Apache-2.0",
77
"engines": {
8-
"node": ">=18",
8+
"node": "18.x",
99
"pnpm": ">=8.1.0"
1010
},
1111
"homepage": "https://github.com/FuelLabs/fuel-explorer",

packages/app/src/systems/Core/components/SearchForm/SearchForm.tsx packages/app/src/systems/Core/components/Search/SearchForm.tsx

+6-10
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ import { useContext } from 'react';
33
import { useFormState } from 'react-dom';
44
import { search } from '~/systems/Core/actions/search';
55

6-
import { SearchInput } from '../SearchInput/SearchInput';
7-
import { SearchContext } from '../SearchWidget/SearchWidget';
6+
import { SearchInput } from './SearchInput';
7+
import { SearchContext } from './SearchWidget';
8+
import { styles } from './styles';
89

910
type SearchFormProps = {
1011
className: string;
1112
autoFocus?: boolean;
12-
alwaysDisplayActionButtons?: boolean;
1313
};
1414

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

2825
return (
29-
<form action={action}>
26+
<form action={action} className={classes.searchSize()}>
3027
<SearchInput
3128
className={className}
3229
searchResult={results}
3330
autoFocus={autoFocus}
34-
alwaysDisplayActionButtons={alwaysDisplayActionButtons}
3531
onClear={onClear}
3632
/>
3733
</form>

packages/app/src/systems/Core/components/SearchInput/SearchInput.tsx packages/app/src/systems/Core/components/Search/SearchInput.tsx

+66-36
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,41 @@ import {
1313
Link,
1414
VStack,
1515
useBreakpoints,
16+
Box,
1617
} from '@fuels/ui';
1718
import { IconCheck, IconSearch, IconX } from '@tabler/icons-react';
1819
import NextLink from 'next/link';
1920
import type { KeyboardEvent } from 'react';
2021
import { forwardRef, useContext, useEffect, useRef, useState } from 'react';
2122
import { useFormStatus } from 'react-dom';
22-
import { tv } from 'tailwind-variants';
2323
import { Routes } from '~/routes';
2424

2525
import { cx } from '../../utils/cx';
26-
import { SearchContext } from '../SearchWidget/SearchWidget';
26+
27+
import { SearchContext } from './SearchWidget';
28+
import { styles } from './styles';
2729

2830
type SearchDropdownProps = {
2931
searchResult?: Maybe<SearchResult>;
3032
openDropdown: boolean;
3133
onOpenChange: () => void;
3234
searchValue: string;
35+
width: number;
36+
onSelectItem: () => void;
3337
};
3438

3539
const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
36-
({ searchResult, searchValue, openDropdown, onOpenChange }, ref) => {
40+
(
41+
{
42+
searchResult,
43+
searchValue,
44+
openDropdown,
45+
onOpenChange,
46+
width,
47+
onSelectItem,
48+
},
49+
ref,
50+
) => {
3751
const classes = styles();
3852
const { isMobile } = useBreakpoints();
3953
const trimL = isMobile ? 15 : 20;
@@ -42,9 +56,13 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
4256
return (
4357
<Dropdown open={openDropdown} onOpenChange={onOpenChange}>
4458
<Dropdown.Trigger>
45-
<div></div>
59+
<Box className="w-full"></Box>
4660
</Dropdown.Trigger>
47-
<Dropdown.Content ref={ref} className="w-[311px] tablet:w-[400px]">
61+
<Dropdown.Content
62+
ref={ref}
63+
className={cx(classes.dropdownContent(), classes.searchSize())}
64+
style={{ width: width - 0.5 }}
65+
>
4866
{!searchResult && (
4967
<>
5068
<Dropdown.Item className="hover:bg-transparent focus:bg-transparent text-error hover:text-error focus:text-error">
@@ -60,6 +78,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
6078
as={NextLink}
6179
href={Routes.accountAssets(searchResult.account.address!)}
6280
className="text-color"
81+
onClick={onSelectItem}
6382
>
6483
{shortAddress(
6584
searchResult.account.address || '',
@@ -80,6 +99,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
8099
as={NextLink}
81100
href={Routes.txSimple(transaction!.id!)}
82101
className="text-color"
102+
onClick={onSelectItem}
83103
>
84104
{shortAddress(transaction?.id || '', trimL, trimR)}
85105
</Link>
@@ -96,6 +116,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
96116
as={NextLink}
97117
href={Routes.blockSimple(searchResult.block.id!)}
98118
className="text-color"
119+
onClick={onSelectItem}
99120
>
100121
{shortAddress(searchResult.block.id || '', trimL, trimR)}
101122
</Link>
@@ -105,6 +126,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
105126
as={NextLink}
106127
href={Routes.blockSimple(searchResult.block.height!)}
107128
className="text-color"
129+
onClick={onSelectItem}
108130
>
109131
{searchResult.block.height}
110132
</Link>
@@ -119,6 +141,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
119141
as={NextLink}
120142
href={Routes.contractAssets(searchResult.contract.id!)}
121143
className="text-color"
144+
onClick={onSelectItem}
122145
>
123146
{shortAddress(searchResult.contract.id || '', trimL, trimR)}
124147
</Link>
@@ -133,6 +156,7 @@ const SearchResultDropdown = forwardRef<HTMLDivElement, SearchDropdownProps>(
133156
as={NextLink}
134157
href={Routes.txSimple(searchResult.transaction.id!)}
135158
className="text-color"
159+
onClick={onSelectItem}
136160
>
137161
{shortAddress(
138162
searchResult.transaction.id || '',
@@ -163,13 +187,14 @@ export function SearchInput({
163187
autoFocus,
164188
placeholder = 'Search here...',
165189
searchResult,
166-
alwaysDisplayActionButtons,
167190
...props
168191
}: SearchInputProps) {
192+
const classes = styles();
169193
const [value, setValue] = useState<string>(initialValue as string);
170194
const [openDropdown, setOpenDropdown] = useState(false);
171195
const [hasSubmitted, setHasSubmitted] = useState(false);
172196
const inputRef = useRef<HTMLInputElement>(null);
197+
const inputWrapperRef = useRef<HTMLInputElement>(null);
173198
const { pending } = useFormStatus();
174199
const { dropdownRef } = useContext(SearchContext);
175200

@@ -196,12 +221,15 @@ export function SearchInput({
196221
}
197222

198223
return (
199-
<VStack gap="0" className="justify-center">
224+
<VStack gap="0" className="justify-center items-center">
200225
<Focus.ArrowNavigator autoFocus={autoFocus}>
201226
<Input
202-
className={cx(className)}
227+
ref={inputWrapperRef}
228+
variant="surface"
203229
radius="large"
204230
size="3"
231+
data-opened={openDropdown}
232+
className={cx(className, classes.inputWrapper())}
205233
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
206234
if (e.key === 'Enter') {
207235
e.preventDefault();
@@ -212,9 +240,6 @@ export function SearchInput({
212240
}
213241
}}
214242
>
215-
<Input.Slot className="mx-1">
216-
<Icon icon={IconSearch} size={16} />
217-
</Input.Slot>
218243
<Input.Field
219244
{...props}
220245
ref={inputRef}
@@ -223,37 +248,48 @@ export function SearchInput({
223248
value={value}
224249
onChange={handleChange}
225250
/>
226-
{(alwaysDisplayActionButtons || !!value.length) && (
227-
<Input.Slot className="mx-1">
228-
<IconButton
229-
aria-label="Clear"
230-
icon={IconX}
231-
iconColor="text-icon"
232-
variant="link"
233-
className="!ml-0 tablet:ml-2"
234-
onClick={handleClear}
235-
/>
236-
<Tooltip content="Submit">
251+
{value?.length ? (
252+
<>
253+
<Input.Slot className="">
254+
<Tooltip content="Submit">
255+
<IconButton
256+
type="submit"
257+
aria-label="Submit"
258+
icon={IconCheck}
259+
iconColor="text-brand"
260+
variant="link"
261+
className="!ml-0 tablet:ml-2"
262+
isLoading={pending}
263+
onClick={handleSubmit}
264+
/>
265+
</Tooltip>
237266
<IconButton
238-
type="submit"
239-
aria-label="Submit"
240-
icon={IconCheck}
241-
iconColor="text-brand"
267+
aria-label="Clear"
268+
icon={IconX}
269+
iconColor="text-gray-11"
242270
variant="link"
243-
className="!ml-0 tablet:ml-2"
244-
isLoading={pending}
245-
onClick={handleSubmit}
271+
className="m-0"
272+
onClick={handleClear}
246273
/>
247-
</Tooltip>
274+
</Input.Slot>
275+
</>
276+
) : (
277+
<Input.Slot>
278+
<Icon icon={IconSearch} size={16} />
248279
</Input.Slot>
249280
)}
250281
</Input>
251282
</Focus.ArrowNavigator>
252283
<SearchResultDropdown
253284
ref={dropdownRef}
285+
width={inputWrapperRef.current?.offsetWidth || 0}
254286
searchResult={searchResult}
255287
searchValue={value}
256288
openDropdown={openDropdown}
289+
onSelectItem={() => {
290+
setOpenDropdown(false);
291+
handleClear();
292+
}}
257293
onOpenChange={() => {
258294
if (openDropdown) {
259295
setHasSubmitted(false);
@@ -264,9 +300,3 @@ export function SearchInput({
264300
</VStack>
265301
);
266302
}
267-
268-
const styles = tv({
269-
slots: {
270-
dropdownItem: 'hover:bg-border focus:bg-border',
271-
},
272-
});
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
1-
import { HStack, IconButton, Tooltip } from '@fuels/ui';
2-
import { IconSearch } from '@tabler/icons-react';
3-
import { AnimatePresence, motion } from 'framer-motion';
1+
import { HStack } from '@fuels/ui';
42
import type { MutableRefObject } from 'react';
53
import { useEffect, useRef, createContext } from 'react';
6-
import { tv } from 'tailwind-variants';
74

8-
import { SearchForm } from '../SearchForm/SearchForm';
5+
import { SearchForm } from './SearchForm';
6+
import { styles } from './styles';
97

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

1513
type SearchWidgetProps = {
16-
setIsExitComplete: (value: boolean) => void;
17-
isExitComplete: boolean;
14+
autoFocus?: boolean;
1815
isSearchOpen: boolean;
1916
setIsSearchOpen: (value: boolean) => void;
2017
};
2118

2219
export const SearchWidget = ({
23-
setIsExitComplete,
24-
isExitComplete,
25-
isSearchOpen,
20+
autoFocus,
2621
setIsSearchOpen,
2722
}: SearchWidgetProps) => {
2823
const classes = styles();
@@ -74,63 +69,14 @@ export const SearchWidget = ({
7469
};
7570
}, []);
7671

77-
useEffect(() => {
78-
if (isSearchOpen) {
79-
setIsExitComplete(false);
80-
}
81-
}, [isSearchOpen]);
82-
8372
return (
8473
<SearchContext.Provider value={{ dropdownRef, onClear }}>
8574
<HStack
8675
ref={widgetRef}
87-
className="items-center gap-0 laptop:gap-4 justify-center"
76+
className="items-center gap-0 laptop:gap-4 justify-center flex-1"
8877
>
89-
<AnimatePresence
90-
onExitComplete={() => {
91-
setIsExitComplete(true);
92-
}}
93-
>
94-
{isSearchOpen && (
95-
<>
96-
<motion.div
97-
initial="closed"
98-
animate="open"
99-
exit="closed"
100-
transition={{ duration: 0.2 }}
101-
variants={{
102-
open: { scaleX: '100%' },
103-
closed: { scaleX: '0%' },
104-
}}
105-
>
106-
<SearchForm
107-
className={classes.input()}
108-
autoFocus={true}
109-
alwaysDisplayActionButtons={true}
110-
/>
111-
</motion.div>
112-
</>
113-
)}
114-
</AnimatePresence>
115-
{isExitComplete && (
116-
<Tooltip content="Search by address, contract id, transaction id, or block id">
117-
<IconButton
118-
icon={IconSearch}
119-
variant="link"
120-
className="mr-1 text-color laptop:mr-0"
121-
onClick={() => {
122-
setIsSearchOpen(!isSearchOpen);
123-
}}
124-
/>
125-
</Tooltip>
126-
)}
78+
<SearchForm className={classes.searchSize()} autoFocus={autoFocus} />
12779
</HStack>
12880
</SearchContext.Provider>
12981
);
13082
};
131-
132-
const styles = tv({
133-
slots: {
134-
input: 'w-full tablet:w-[400px]',
135-
},
136-
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { tv } from 'tailwind-variants';
2+
3+
export const styles = tv({
4+
slots: {
5+
dropdownItem: 'hover:bg-border focus:bg-border',
6+
inputWrapper:
7+
'[&_.rt-TextFieldChrome]:[&[data-opened=true]]:rounded-b-none',
8+
searchSize: 'w-full tablet:w-[400px]',
9+
dropdownContent:
10+
'mt-[-4px] rounded-t-none shadow-none border border-t-0 border-border',
11+
},
12+
});

0 commit comments

Comments
 (0)