Skip to content

Commit 659a231

Browse files
authored
NTP Omnibar: Adjust focus ring styling and remove it when typing (#1838)
* Remove focus ring when typing * Fix overflow due to typo * Adjust focus ring styling to match spec and always hide ring when suggestions list is open
1 parent 74e9a3c commit 659a231

File tree

4 files changed

+44
-21
lines changed

4 files changed

+44
-21
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ import styles from './AiChatForm.module.css';
1515
* @param {object} props
1616
* @param {string} props.chat
1717
* @param {boolean} [props.autoFocus]
18+
* @param {(event: FocusEvent) => void} props.onFocus
19+
* @param {(event: FocusEvent) => void} props.onBlur
20+
* @param {(event: InputEvent) => void} props.onInput
1821
* @param {(chat: string) => void} props.onChange
1922
* @param {(params: { chat: string, target: OpenTarget }) => void} props.onSubmit
2023
*/
21-
export function AiChatForm({ chat, autoFocus, onChange, onSubmit }) {
24+
export function AiChatForm({ chat, autoFocus, onFocus, onBlur, onInput, onChange, onSubmit }) {
2225
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
2326
const platformName = usePlatformName();
2427

@@ -94,6 +97,9 @@ export function AiChatForm({ chat, autoFocus, onChange, onSubmit }) {
9497
aria-label={t('omnibar_aiChatFormPlaceholder')}
9598
autoComplete="off"
9699
rows={1}
100+
onFocus={onFocus}
101+
onBlur={onBlur}
102+
onInput={onInput}
97103
onKeyDown={handleKeyDown}
98104
onChange={handleChange}
99105
/>
Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
1+
import cn from 'classnames';
12
import { h } from 'preact';
23
import styles from './Container.module.css';
34
import { useRef, useLayoutEffect, useState } from 'preact/hooks';
45

56
/**
67
* @param {object} props
78
* @param {boolean} props.overflow
9+
* @param {boolean} [props.focusRing]
810
* @param {import('preact').ComponentChildren} props.children
911
*/
10-
export function Container({ overflow, children }) {
12+
export function Container({ overflow, focusRing, children }) {
13+
const { contentRef, initialHeight, currentHeight } = useContentHeight();
14+
return (
15+
<div class={styles.outer} style={{ height: overflow && initialHeight ? initialHeight : 'auto' }}>
16+
<div
17+
class={cn(styles.inner, {
18+
[styles.focusRing]: focusRing === true,
19+
[styles.noFocusRing]: focusRing === false,
20+
})}
21+
style={{ height: currentHeight ?? 'auto' }}
22+
>
23+
<div ref={contentRef}>{children}</div>
24+
</div>
25+
</div>
26+
);
27+
}
28+
29+
function useContentHeight() {
1130
const contentRef = useRef(/** @type {HTMLDivElement|null} */ (null));
1231
const initialHeight = useRef(/** @type {number|null} */ (null));
13-
const [contentHeight, setContentHeight] = useState(/** @type {number|null} */ (null));
32+
const [currentHeight, setCurrentHeight] = useState(/** @type {number|null} */ (null));
1433

1534
useLayoutEffect(() => {
1635
const content = contentRef.current;
1736
if (!content) return;
1837

1938
initialHeight.current = content.scrollHeight;
20-
setContentHeight(content.scrollHeight);
39+
setCurrentHeight(content.scrollHeight);
2140

22-
const resizeObserver = new ResizeObserver(() => setContentHeight(content.scrollHeight));
41+
const resizeObserver = new ResizeObserver(() => setCurrentHeight(content.scrollHeight));
2342
resizeObserver.observe(content);
2443
return () => resizeObserver.disconnect();
2544
}, []);
2645

27-
return (
28-
<div class={styles.outer} style={{ height: overflow && initialHeight.current ? initialHeight.current : 'auto' }}>
29-
<div class={styles.inner} style={{ height: contentHeight ?? 'auto' }}>
30-
<div ref={contentRef}>{children}</div>
31-
</div>
32-
</div>
33-
);
46+
return { contentRef, initialHeight: initialHeight.current, currentHeight };
3447
}

special-pages/pages/new-tab/app/omnibar/components/Container.module.css

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
.inner {
77
background: var(--ntp-surface-tertiary);
8-
border-radius: 11px;
8+
border-radius: 12px;
99
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.08);
1010
margin: 0 calc(-1 * var(--sp-1));
1111
overflow: hidden;
@@ -16,15 +16,14 @@
1616
transition: none;
1717
}
1818

19-
&:focus-within {
20-
border-radius: 14px;
21-
box-shadow: 0 0 0 2px var(--ntp-color-primary), 0 0 0 4px rgba(57, 105, 239, 0.2);
22-
margin: 0 calc(-1 * var(--sp-1) - 3px);
23-
padding: 0 3px;
19+
&:not(:has([role="listbox"])).focusRing,
20+
&:not(:has([role="listbox"])):focus-within:not(.noFocusRing) {
21+
border-radius: 15px;
22+
box-shadow: 0 0 0 1px var(--ntp-surface-tertiary), 0 0 0 3px var(--ntp-color-primary), 0 0 0 7px rgba(57, 105, 239, 0.2);
2423
}
2524

2625
&:has([role="listbox"]) {
27-
border-radius: 16px;
28-
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px rgba(0, 0, 0, 0.16);
26+
border-radius: 15px;
27+
box-shadow: 0 0 0 3px var(--ntp-surface-tertiary), 0px 4px 12px 3px rgba(0, 0, 0, 0.10), 0px 20px 40px 3px rgba(0, 0, 0, 0.08);
2928
}
3029
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export function Omnibar({ mode, setMode, enableAi }) {
2727
const [query, setQuery] = useState(/** @type {String} */ (''));
2828
const [resetKey, setResetKey] = useState(0);
2929
const [autoFocus, setAutoFocus] = useState(false);
30+
const [focusRing, setFocusRing] = useState(/** @type {boolean|undefined} */ (undefined));
3031

3132
const { openSuggestion, submitSearch, submitChat } = useContext(OmnibarContext);
3233

@@ -56,14 +57,15 @@ export function Omnibar({ mode, setMode, enableAi }) {
5657
/** @type {(mode: OmnibarConfig['mode']) => void} */
5758
const handleChangeMode = (nextMode) => {
5859
setAutoFocus(true);
60+
setFocusRing(undefined);
5961
setMode(nextMode);
6062
};
6163

6264
return (
6365
<div class={styles.root} data-mode={mode}>
6466
<LogoStacked class={styles.logo} aria-label={t('omnibar_logoAlt')} />
6567
{enableAi && <TabSwitcher mode={mode} onChange={handleChangeMode} />}
66-
<Container overflow={mode === 'search'}>
68+
<Container overflow={mode === 'search'} focusRing={focusRing}>
6769
{mode === 'search' ? (
6870
<SearchForm
6971
key={`search-${resetKey}`}
@@ -78,6 +80,9 @@ export function Omnibar({ mode, setMode, enableAi }) {
7880
key={`chat-${resetKey}`}
7981
chat={query}
8082
autoFocus={autoFocus}
83+
onFocus={() => setFocusRing(true)}
84+
onBlur={() => setFocusRing(false)}
85+
onInput={() => setFocusRing(false)}
8186
onChange={setQuery}
8287
onSubmit={handleSubmitChat}
8388
/>

0 commit comments

Comments
 (0)