Skip to content

Commit 2c84443

Browse files
authored
NTP: Reset omnibar after form submission (#1819)
* Refactor omnibar to use prop callbacks and reset form on submit * Rename props and handlers from set*/on* to onChange/handle* for consistency * Put back event.preventDefault() * setTerm -> onChangeTerm * Fix tests after merge * Add tabindex to suggestions so that Safari/WebKit fires focus events on it
1 parent 60a562e commit 2c84443

File tree

7 files changed

+145
-82
lines changed

7 files changed

+145
-82
lines changed

special-pages/pages/new-tab/app/omnibar/components/AiChatForm.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import { h } from 'preact';
2-
import { useContext, useRef } from 'preact/hooks';
2+
import { useRef } from 'preact/hooks';
33
import { eventToTarget } from '../../../../../shared/handlers';
44
import { ArrowRightIcon } from '../../components/Icons';
55
import { usePlatformName } from '../../settings.provider';
66
import { useTypedTranslationWith } from '../../types';
77
import styles from './AiChatForm.module.css';
8-
import { OmnibarContext } from './OmnibarProvider';
98

109
/**
1110
* @typedef {import('../strings.json')} Strings
11+
* @typedef {import('../../../types/new-tab.js').OpenTarget} OpenTarget
1212
*/
1313

1414
/**
1515
* @param {object} props
1616
* @param {string} props.chat
17-
* @param {(chat: string) => void} props.setChat
17+
* @param {(chat: string) => void} props.onChange
18+
* @param {(params: { chat: string, target: OpenTarget }) => void} props.onSubmit
1819
*/
19-
export function AiChatForm({ chat, setChat }) {
20-
const { submitChat } = useContext(OmnibarContext);
20+
export function AiChatForm({ chat, onChange, onSubmit }) {
2121
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
2222
const platformName = usePlatformName();
2323

@@ -27,21 +27,21 @@ export function AiChatForm({ chat, setChat }) {
2727
const disabled = chat.length === 0;
2828

2929
/** @type {(event: SubmitEvent) => void} */
30-
const onSubmit = (event) => {
30+
const handleSubmit = (event) => {
3131
event.preventDefault();
3232
if (disabled) return;
33-
submitChat({
33+
onSubmit({
3434
chat,
3535
target: 'same-tab',
3636
});
3737
};
3838

3939
/** @type {(event: KeyboardEvent) => void} */
40-
const onKeyDown = (event) => {
40+
const handleKeyDown = (event) => {
4141
if (event.key === 'Enter' && !event.shiftKey) {
4242
event.preventDefault();
4343
if (disabled) return;
44-
submitChat({
44+
onSubmit({
4545
chat,
4646
target: eventToTarget(event, platformName),
4747
});
@@ -53,14 +53,14 @@ export function AiChatForm({ chat, setChat }) {
5353
event.preventDefault();
5454
if (disabled) return;
5555
event.stopPropagation();
56-
submitChat({
56+
onSubmit({
5757
chat,
5858
target: eventToTarget(event, platformName),
5959
});
6060
};
6161

6262
/** @type {(event: import('preact').JSX.TargetedEvent<HTMLTextAreaElement>) => void} */
63-
const onChange = (event) => {
63+
const handleChange = (event) => {
6464
const form = formRef.current;
6565
const textArea = event.currentTarget;
6666

@@ -74,11 +74,11 @@ export function AiChatForm({ chat, setChat }) {
7474
form?.classList.remove(styles.hasScroll);
7575
}
7676

77-
setChat(textArea.value);
77+
onChange(textArea.value);
7878
};
7979

8080
return (
81-
<form ref={formRef} class={styles.form} onClick={() => textAreaRef.current?.focus()} onSubmit={onSubmit}>
81+
<form ref={formRef} class={styles.form} onClick={() => textAreaRef.current?.focus()} onSubmit={handleSubmit}>
8282
<textarea
8383
ref={textAreaRef}
8484
class={styles.textarea}
@@ -87,8 +87,8 @@ export function AiChatForm({ chat, setChat }) {
8787
aria-label={t('aiChatForm_placeholder')}
8888
autoComplete="off"
8989
rows={1}
90-
onKeyDown={onKeyDown}
91-
onChange={onChange}
90+
onKeyDown={handleKeyDown}
91+
onChange={handleChange}
9292
/>
9393
<div class={styles.buttons}>
9494
<button

special-pages/pages/new-tab/app/omnibar/components/Omnibar.js

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { h } from 'preact';
2-
import { useState } from 'preact/hooks';
2+
import { useContext, useState } from 'preact/hooks';
33
import { LogoStacked } from '../../components/Icons';
44
import { useTypedTranslationWith } from '../../types';
55
import { AiChatForm } from './AiChatForm';
66
import styles from './Omnibar.module.css';
77
import { SearchForm } from './SearchForm';
88
import { TabSwitcher } from './TabSwitcher';
99
import { Container } from './Container';
10+
import { OmnibarContext } from './OmnibarProvider';
1011

1112
/**
1213
* @typedef {import('../strings.json')} Strings
1314
* @typedef {import('../../../types/new-tab.js').OmnibarConfig} OmnibarConfig
15+
* @typedef {import('../../../types/new-tab.js').Suggestion} Suggestion
16+
* @typedef {import('../../../types/new-tab.js').OpenTarget} OpenTarget
1417
*/
1518

1619
/**
@@ -22,12 +25,49 @@ import { Container } from './Container';
2225
export function Omnibar({ mode, setMode, enableAi }) {
2326
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
2427
const [query, setQuery] = useState(/** @type {String} */ (''));
28+
const [resetKey, setResetKey] = useState(0);
29+
30+
const { openSuggestion, submitSearch, submitChat } = useContext(OmnibarContext);
31+
32+
const resetForm = () => {
33+
setQuery('');
34+
setResetKey((prev) => prev + 1);
35+
};
36+
37+
/** @type {(params: {suggestion: Suggestion, target: OpenTarget}) => void} */
38+
const handleOpenSuggestion = (params) => {
39+
openSuggestion(params);
40+
resetForm();
41+
};
42+
43+
/** @type {(params: {term: string, target: OpenTarget}) => void} */
44+
const handleSubmitSearch = (params) => {
45+
submitSearch(params);
46+
resetForm();
47+
};
48+
49+
/** @type {(params: {chat: string, target: OpenTarget}) => void} */
50+
const handleSubmitChat = (params) => {
51+
submitChat(params);
52+
resetForm();
53+
};
54+
2555
return (
2656
<div class={styles.root} data-mode={mode}>
2757
<LogoStacked class={styles.logo} aria-label={t('omnibar_logoAlt')} />
28-
{enableAi && <TabSwitcher mode={mode} setMode={setMode} />}
58+
{enableAi && <TabSwitcher mode={mode} onChange={setMode} />}
2959
<Container overflow={mode === 'search'}>
30-
{mode === 'search' ? <SearchForm term={query} setTerm={setQuery} /> : <AiChatForm chat={query} setChat={setQuery} />}
60+
{mode === 'search' ? (
61+
<SearchForm
62+
key={`search-${resetKey}`}
63+
term={query}
64+
onChangeTerm={setQuery}
65+
onOpenSuggestion={handleOpenSuggestion}
66+
onSubmitSearch={handleSubmitSearch}
67+
/>
68+
) : (
69+
<AiChatForm key={`chat-${resetKey}`} chat={query} onChange={setQuery} onSubmit={handleSubmitChat} />
70+
)}
3171
</Container>
3272
</div>
3373
);

special-pages/pages/new-tab/app/omnibar/components/SearchForm.js

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
import { h } from 'preact';
2-
import { useContext, useId } from 'preact/hooks';
2+
import { useId } from 'preact/hooks';
33
import { SearchIcon } from '../../components/Icons.js';
44
import { useTypedTranslationWith } from '../../types';
5-
import { OmnibarContext } from './OmnibarProvider';
65
import styles from './SearchForm.module.css';
76
import { SuggestionsList } from './SuggestionsList.js';
87
import { useSuggestionInput } from './useSuggestionInput.js';
98
import { useSuggestions } from './useSuggestions';
109

1110
/**
1211
* @typedef {import('../strings.json')} Strings
12+
* @typedef {import('../../../types/new-tab.js').Suggestion} Suggestion
13+
* @typedef {import('../../../types/new-tab.js').OpenTarget} OpenTarget
1314
*/
1415

1516
/**
1617
* @param {object} props
1718
* @param {string} props.term
18-
* @param {(term: string) => void} props.setTerm
19+
* @param {(term: string) => void} props.onChangeTerm
20+
* @param {(params: {suggestion: Suggestion, target: OpenTarget}) => void} props.onOpenSuggestion
21+
* @param {(params: {term: string, target: OpenTarget}) => void} props.onSubmitSearch
1922
*/
20-
export function SearchForm({ term, setTerm }) {
21-
const { submitSearch } = useContext(OmnibarContext);
22-
23+
export function SearchForm({ term, onChangeTerm, onOpenSuggestion, onSubmitSearch }) {
2324
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
2425
const suggestionsListId = useId();
2526

@@ -28,30 +29,32 @@ export function SearchForm({ term, setTerm }) {
2829
selectedSuggestion,
2930
setSelectedSuggestion,
3031
clearSelectedSuggestion,
31-
inputBase,
32-
inputSuggestion,
33-
onInputChange,
34-
onInputKeyDown,
35-
onInputClick,
36-
onFormBlur,
32+
termBase,
33+
termSuggestion,
34+
handleChange,
35+
handleKeyDown,
36+
handleClick,
37+
handleBlur,
3738
} = useSuggestions({
3839
term,
39-
setTerm,
40+
onChangeTerm,
41+
onOpenSuggestion,
42+
onSubmitSearch,
4043
});
4144

42-
const inputRef = useSuggestionInput(inputBase, inputSuggestion);
45+
const inputRef = useSuggestionInput(termBase, termSuggestion);
4346

4447
/** @type {(event: SubmitEvent) => void} */
45-
const onFormSubmit = (event) => {
48+
const handleSubmit = (event) => {
4649
event.preventDefault();
47-
submitSearch({
50+
onSubmitSearch({
4851
term,
4952
target: 'same-tab',
5053
});
5154
};
5255

5356
return (
54-
<form class={styles.form} onClick={() => inputRef.current?.focus()} onBlur={onFormBlur} onSubmit={onFormSubmit}>
57+
<form class={styles.form} onClick={() => inputRef.current?.focus()} onBlur={handleBlur} onSubmit={handleSubmit}>
5558
<div class={styles.inputContainer}>
5659
<SearchIcon inert />
5760
<input
@@ -69,18 +72,19 @@ export function SearchForm({ term, setTerm }) {
6972
autoComplete="off"
7073
autoCorrect="off"
7174
autoCapitalize="off"
72-
onChange={onInputChange}
73-
onKeyDown={onInputKeyDown}
74-
onClick={onInputClick}
75+
onChange={handleChange}
76+
onKeyDown={handleKeyDown}
77+
onClick={handleClick}
7578
/>
7679
</div>
7780
{suggestions.length > 0 && (
7881
<SuggestionsList
7982
id={suggestionsListId}
8083
suggestions={suggestions}
8184
selectedSuggestion={selectedSuggestion}
82-
setSelectedSuggestion={setSelectedSuggestion}
83-
clearSelectedSuggestion={clearSelectedSuggestion}
85+
onSelectSuggestion={setSelectedSuggestion}
86+
onClearSuggestion={clearSelectedSuggestion}
87+
onOpenSuggestion={onOpenSuggestion}
8488
/>
8589
)}
8690
</form>

special-pages/pages/new-tab/app/omnibar/components/SuggestionsList.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import { h } from 'preact';
2-
import { useContext } from 'preact/hooks';
32
import { eventToTarget } from '../../../../../shared/handlers';
43
import { BookmarkIcon, BrowserIcon, FavoriteIcon, GlobeIcon, HistoryIcon, SearchIcon } from '../../components/Icons';
54
import { usePlatformName } from '../../settings.provider';
6-
import { OmnibarContext } from './OmnibarProvider';
75
import styles from './SuggestionsList.module.css';
86

97
/**
108
* @typedef {import('./useSuggestions').SuggestionModel} SuggestionModel
9+
* @typedef {import('../../../types/new-tab.js').Suggestion} Suggestion
10+
* @typedef {import('../../../types/new-tab.js').OpenTarget} OpenTarget
1111
*/
1212

1313
/**
1414
* @param {object} props
1515
* @param {string} props.id
1616
* @param {SuggestionModel[]} props.suggestions
1717
* @param {SuggestionModel | null} props.selectedSuggestion
18-
* @param {(suggestion: SuggestionModel) => void} props.setSelectedSuggestion
19-
* @param {() => void} props.clearSelectedSuggestion
18+
* @param {(suggestion: SuggestionModel) => void} props.onSelectSuggestion
19+
* @param {() => void} props.onClearSuggestion
20+
* @param {(params: {suggestion: Suggestion, target: OpenTarget}) => void} props.onOpenSuggestion
2021
*/
21-
export function SuggestionsList({ id, suggestions, selectedSuggestion, setSelectedSuggestion, clearSelectedSuggestion }) {
22-
const { openSuggestion } = useContext(OmnibarContext);
22+
export function SuggestionsList({ id, suggestions, selectedSuggestion, onSelectSuggestion, onClearSuggestion, onOpenSuggestion }) {
2323
const platformName = usePlatformName();
2424
return (
2525
<div role="listbox" id={id} class={styles.list}>
@@ -30,12 +30,13 @@ export function SuggestionsList({ id, suggestions, selectedSuggestion, setSelect
3030
role="option"
3131
id={suggestion.id}
3232
class={styles.item}
33+
tabIndex={suggestion === selectedSuggestion ? 0 : -1}
3334
aria-selected={suggestion === selectedSuggestion}
34-
onMouseOver={() => setSelectedSuggestion(suggestion)}
35-
onMouseLeave={() => clearSelectedSuggestion()}
35+
onMouseOver={() => onSelectSuggestion(suggestion)}
36+
onMouseLeave={() => onClearSuggestion()}
3637
onClick={(event) => {
3738
event.preventDefault();
38-
openSuggestion({ suggestion, target: eventToTarget(event, platformName) });
39+
onOpenSuggestion({ suggestion, target: eventToTarget(event, platformName) });
3940
}}
4041
>
4142
<SuggestionIcon suggestion={suggestion} />

special-pages/pages/new-tab/app/omnibar/components/TabSwitcher.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ import styles from './TabSwitcher.module.css';
1313
/**
1414
* @param {object} props
1515
* @param {OmnibarConfig['mode']} props.mode
16-
* @param {(mode: OmnibarConfig['mode']) => void} props.setMode
16+
* @param {(mode: OmnibarConfig['mode']) => void} props.onChange
1717
*/
18-
export function TabSwitcher({ mode, setMode }) {
18+
export function TabSwitcher({ mode, onChange }) {
1919
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
2020
const { main } = useContext(CustomizerThemesContext);
2121
const Blob = main.value === 'light' ? BlobLight : BlobDark;
2222
return (
2323
<div class={styles.tabSwitcher} role="tablist" aria-label={t('omnibar_tabSwitcherLabel')}>
2424
<Blob class={styles.blob} style={{ translate: mode === 'search' ? 0 : 92 }} />
25-
<button class={styles.tab} role="tab" aria-selected={mode === 'search'} onClick={() => setMode('search')}>
25+
<button class={styles.tab} role="tab" aria-selected={mode === 'search'} onClick={() => onChange('search')}>
2626
{mode === 'search' ? <SearchColorIcon /> : <SearchIcon />}
2727
<span class={styles.tabLabel}>{t('omnibar_searchTabLabel')}</span>
2828
</button>
29-
<button class={styles.tab} role="tab" aria-selected={mode === 'ai'} onClick={() => setMode('ai')}>
29+
<button class={styles.tab} role="tab" aria-selected={mode === 'ai'} onClick={() => onChange('ai')}>
3030
{mode === 'ai' ? <AiChatColorIcon /> : <AiChatIcon />}
3131
<span class={styles.tabLabel}>{t('omnibar_aiTabLabel')}</span>
3232
</button>

0 commit comments

Comments
 (0)