Skip to content

Commit c144529

Browse files
authored
NTP: Address omnibar design feedback (#1860)
* Fix alignment of search icon and SuggestionsList icons * Make AiChatForm buttons container have 12px padding consistently * Adjust omnibar container border radius and focus ring * Fix AI chat textarea height preservation on tab switch * Update SearchForm text measurement to accomodate new 500 font weight * Fix newline characters affecting spacing when switching from chat to search
1 parent 780ac6c commit c144529

File tree

8 files changed

+96
-43
lines changed

8 files changed

+96
-43
lines changed

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

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { h } from 'preact';
2-
import { useEffect, useRef } from 'preact/hooks';
2+
import { useEffect, useLayoutEffect, useRef } from 'preact/hooks';
33
import { eventToTarget } from '../../../../../shared/handlers';
44
import { ArrowRightIcon } from '../../components/Icons';
55
import { usePlatformName } from '../../settings.provider';
@@ -34,6 +34,23 @@ export function AiChatForm({ chat, autoFocus, onFocus, onBlur, onInput, onChange
3434
}
3535
}, [autoFocus]);
3636

37+
useLayoutEffect(() => {
38+
const textArea = textAreaRef.current;
39+
const form = formRef.current;
40+
41+
if (!textArea || !form) return;
42+
43+
const { paddingTop, paddingBottom } = window.getComputedStyle(textArea);
44+
textArea.style.height = 'auto'; // Reset height
45+
textArea.style.height = `calc(${textArea.scrollHeight}px - ${paddingTop} - ${paddingBottom})`;
46+
47+
if (textArea.scrollHeight > textArea.clientHeight) {
48+
form.classList.add(styles.hasScroll);
49+
} else {
50+
form.classList.remove(styles.hasScroll);
51+
}
52+
}, [chat]);
53+
3754
const disabled = chat.length === 0;
3855

3956
/** @type {(event: SubmitEvent) => void} */
@@ -69,24 +86,6 @@ export function AiChatForm({ chat, autoFocus, onFocus, onBlur, onInput, onChange
6986
});
7087
};
7188

72-
/** @type {(event: import('preact').JSX.TargetedEvent<HTMLTextAreaElement>) => void} */
73-
const handleChange = (event) => {
74-
const form = formRef.current;
75-
const textArea = event.currentTarget;
76-
77-
const { paddingTop, paddingBottom } = window.getComputedStyle(textArea);
78-
textArea.style.height = 'auto'; // Reset height
79-
textArea.style.height = `calc(${textArea.scrollHeight}px - ${paddingTop} - ${paddingBottom})`;
80-
81-
if (textArea.scrollHeight > textArea.clientHeight) {
82-
form?.classList.add(styles.hasScroll);
83-
} else {
84-
form?.classList.remove(styles.hasScroll);
85-
}
86-
87-
onChange(textArea.value);
88-
};
89-
9089
return (
9190
<form ref={formRef} class={styles.form} onClick={() => textAreaRef.current?.focus()} onSubmit={handleSubmit}>
9291
<textarea
@@ -102,7 +101,7 @@ export function AiChatForm({ chat, autoFocus, onFocus, onBlur, onInput, onChange
102101
onBlurCapture={onBlur}
103102
onInput={onInput}
104103
onKeyDown={handleKeyDown}
105-
onChange={handleChange}
104+
onChange={(event) => onChange(event.currentTarget.value)}
106105
/>
107106
<div class={styles.buttons}>
108107
<button

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
align-items: center;
33
display: flex;
44
flex-direction: column;
5-
padding-bottom: calc(14px + 9px + var(--sp-7)); /* Extra space to accomodate absolute positioned .buttons */
5+
padding-bottom: calc(var(--sp-3) + var(--sp-7) + var(--sp-3)); /* Extra space to accomodate absolute positioned .buttons */
66
}
77

88
.textarea {
@@ -35,13 +35,9 @@
3535
bottom: 0;
3636
justify-content: space-between;
3737
left: 0;
38-
padding: 14px 9px 9px;
38+
padding: var(--sp-3);
3939
position: absolute; /* Fix .buttons to <Container /> so that it animates smoothly when container resizes */
4040
right: 0;
41-
42-
.hasScroll & {
43-
padding-top: 9px;
44-
}
4541
}
4642

4743
.submitButton {

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

Lines changed: 2 additions & 4 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: 12px;
8+
border-radius: 15px;
99
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.08);
1010
overflow: hidden;
1111
position: relative;
@@ -17,12 +17,10 @@
1717

1818
&:not(:has([role="listbox"])).focusRing,
1919
&:not(:has([role="listbox"])):focus-within:not(.noFocusRing) {
20-
border-radius: 15px;
21-
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);
20+
box-shadow: 0 0 0 1px var(--ntp-surface-tertiary), 0 0 0 3px var(--ntp-color-primary), 0 0 0 5px rgba(57, 105, 239, 0.2);
2221
}
2322

2423
&:has([role="listbox"]) {
25-
border-radius: 15px;
2624
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);
2725
}
2826
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ export function Omnibar({ mode, setMode, enableAi }) {
6969
{mode === 'search' ? (
7070
<SearchForm
7171
key={`search-${resetKey}`}
72-
term={query}
72+
// Remove any newlines that come from switching from chat to search
73+
term={query.replace(/\n/g, '')}
7374
autoFocus={autoFocus}
7475
onChangeTerm={setQuery}
7576
onOpenSuggestion={handleOpenSuggestion}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,6 @@ function measureText(text) {
127127
const canvas = document.createElement('canvas');
128128
const context = canvas.getContext('2d');
129129
if (!context) return 0;
130-
context.font = '13px / 16px system-ui';
130+
context.font = '500 13px / 16px system-ui';
131131
return context.measureText(text).width;
132132
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
.inputContainer {
22
align-items: center;
33
display: flex;
4-
padding: var(--sp-1);
4+
padding: var(--sp-1) 7px;
55

66
svg {
7-
flex-shrink: 0;
7+
flex: none;
88
margin: var(--sp-2);
99
}
1010
}
@@ -15,13 +15,13 @@
1515
color: var(--ntp-text-normal);
1616
font-weight: 500;
1717
height: var(--sp-8);
18-
left: var(--sp-1);
18+
left: 7px;
1919
padding-bottom: 0;
2020
padding-left: calc(var(--sp-2) + var(--sp-4) + var(--sp-2));
2121
padding-right: var(--suffix-text-width);
2222
padding-top: 0;
2323
position: absolute;
24-
right: var(--sp-1);
24+
right: 7px;
2525

2626
&::placeholder {
2727
color: var(--ntp-text-muted);
@@ -33,12 +33,13 @@
3333
}
3434

3535
.suffixSpacer {
36+
font-weight: 500;
3637
overflow: hidden;
3738
visibility: hidden;
38-
white-space: pre;
39+
white-space: nowrap;
3940
}
4041

4142
.suffix {
4243
color: var(--ntp-color-primary);
43-
flex-shrink: 0;
44+
flex: none;
4445
}

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,65 @@
88
.item {
99
align-items: center;
1010
background: none;
11+
border-radius: 4px;
1112
border: none;
1213
color: var(--ntp-text-normal);
1314
cursor: pointer;
1415
display: flex;
1516
height: var(--sp-8);
1617
justify-content: flex-start;
18+
padding: 0 var(--sp-2) 0 0;
1719

1820
svg {
19-
flex-shrink: 0;
21+
flex: none;
2022
margin: var(--sp-2);
2123
}
2224

23-
&[aria-selected="true"] {
24-
background: var(--ddg-color-primary);
25-
border-radius: 4px;
25+
&[aria-selected="true"],
26+
&:active {
2627
color: var(--color-white);
2728

2829
.suffix {
2930
color: var(--color-white);
3031
}
32+
33+
.badge {
34+
background: var(--color-white-at-30);
35+
}
36+
}
37+
38+
&[aria-selected="true"] {
39+
background: var(--ddg-color-primary);
40+
}
41+
42+
&:active {
43+
background: var(--color-blue-60);
3144
}
3245
}
3346

3447
.title {
35-
white-space: pre;
48+
flex: none;
49+
max-width: 60%;
50+
overflow: hidden;
51+
text-overflow: ellipsis;
52+
white-space: nowrap;
3653
}
3754

3855
.suffix {
3956
color: var(--ntp-text-muted);
57+
flex: initial;
4058
font-size: 12px;
59+
overflow: hidden;
60+
text-overflow: ellipsis;
61+
white-space: nowrap;
4162
}
4263

4364
.badge {
4465
align-items: center;
4566
background: var(--ntp-controls-raised-backdrop);
4667
border-radius: var(--border-radius-sm);
4768
display: flex;
69+
flex: none;
4870
gap: 6px;
4971
height: 22px;
5072
margin-left: auto;

special-pages/pages/new-tab/app/omnibar/integration-tests/omnibar.spec.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,4 +873,40 @@ test.describe('omnibar widget', () => {
873873
// Input should retain its value
874874
await omnibar.expectInputValue('pizza');
875875
});
876+
877+
test('AI chat textarea height is preserved when switching tabs', async ({ page }, workerInfo) => {
878+
const ntp = NewtabPage.create(page, workerInfo);
879+
const omnibar = new OmnibarPage(ntp);
880+
881+
await ntp.reducedMotion();
882+
await ntp.openPage({ additional: { omnibar: true, 'omnibar.enableAi': true } });
883+
await omnibar.ready();
884+
885+
// Switch to AI tab
886+
await omnibar.aiTab().click();
887+
await omnibar.expectMode('ai');
888+
889+
// Type multiline content to expand textarea
890+
const multilineText = 'This is line 1\nThis is line 2\nThis is line 3';
891+
await omnibar.chatInput().fill(multilineText);
892+
893+
// Get initial textarea height after expansion
894+
const originalHeight = await omnibar.chatInput().evaluate((el) => el.style.height);
895+
expect(originalHeight).not.toBe(''); // Should have some height set
896+
897+
// Switch to search tab
898+
await omnibar.searchTab().click();
899+
await omnibar.expectMode('search');
900+
901+
// Switch back to AI tab
902+
await omnibar.aiTab().click();
903+
await omnibar.expectMode('ai');
904+
905+
// Textarea should preserve its expanded height
906+
const newHeight = await omnibar.chatInput().evaluate((el) => el.style.height);
907+
expect(newHeight).toBe(originalHeight);
908+
909+
// Content should still be there
910+
await expect(omnibar.chatInput()).toHaveValue(multilineText);
911+
});
876912
});

0 commit comments

Comments
 (0)