Skip to content

Commit 110a4c9

Browse files
authored
NTP: Add Duck.ai toggle to Customize drawer (#1832)
* Add fixed width and height to SVG icons and remove redundant SVG size styles in CSS * Add Duck.ai toggle to Customizer * Add integration tests
1 parent 0249d4e commit 110a4c9

17 files changed

+202
-55
lines changed

special-pages/pages/new-tab/app/components/Icons.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export function BackChevron() {
170170
*/
171171
export function SearchIcon(props) {
172172
return (
173-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
173+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
174174
<g clip-path="url(#Find-Search-16_svg__a)">
175175
<path
176176
fill="currentColor"
@@ -194,7 +194,7 @@ export function SearchIcon(props) {
194194
*/
195195
export function SearchColorIcon(props) {
196196
return (
197-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
197+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
198198
<g clip-path="url(#Search-Find-Color-16_svg__a)">
199199
<path fill="#ADC2FC" d="M12 7A5 5 0 1 1 2 7a5 5 0 0 1 10 0Z" />
200200
<path fill="#fff" d="M7 2a4.98 4.98 0 0 1 3.403 1.338 5.5 5.5 0 0 0-7.065 7.065A5 5 0 0 1 7 2Z" opacity=".5" />
@@ -218,7 +218,7 @@ export function SearchColorIcon(props) {
218218
*/
219219
export function AiChatIcon(props) {
220220
return (
221-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
221+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
222222
<g fill="currentColor" clip-path="url(#Ai-Chat-16_svg__a)">
223223
<path
224224
fill-rule="evenodd"
@@ -242,7 +242,7 @@ export function AiChatIcon(props) {
242242
*/
243243
export function AiChatColorIcon(props) {
244244
return (
245-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
245+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
246246
<g clip-path="url(#Ai-Chat-Gradient-Color-16_svg__a)">
247247
<path
248248
fill="url(#Ai-Chat-Gradient-Color-16_svg__b)"
@@ -292,7 +292,7 @@ export function AiChatColorIcon(props) {
292292
*/
293293
export function ArrowRightIcon(props) {
294294
return (
295-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
295+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
296296
<path
297297
fill="currentColor"
298298
d="M8.187 1.689a.625.625 0 0 1 .885-.884l5.31 5.316c.83.83.83 2.174 0 3.004l-5.31 5.315a.625.625 0 0 1-.885-.884l5.305-5.308H.625a.625.625 0 1 1 0-1.25h12.867z"
@@ -307,7 +307,7 @@ export function ArrowRightIcon(props) {
307307
*/
308308
export function GlobeIcon(props) {
309309
return (
310-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
310+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
311311
<g clip-path="url(#Globe-16_svg__a)">
312312
<path
313313
fill="currentColor"
@@ -331,7 +331,7 @@ export function GlobeIcon(props) {
331331
*/
332332
export function HistoryIcon(props) {
333333
return (
334-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
334+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
335335
<g fill="currentColor" clip-path="url(#History-16_svg__a)">
336336
<path d="m2.072 4.918-.08-.004A6.753 6.753 0 1 1 1.246 8 .623.623 0 1 0 0 8a8 8 0 1 0 1.247-4.29V1.115a.623.623 0 0 0-1.247 0v2.977c0 1.145.928 2.072 2.072 2.072h2.486a.623.623 0 0 0 0-1.246z" />
337337
<path d="M8.625 3.625a.625.625 0 1 0-1.25 0V8c0 .166.066.325.183.442l2.375 2.375a.625.625 0 1 0 .884-.884L8.625 7.741z" />
@@ -351,7 +351,7 @@ export function HistoryIcon(props) {
351351
*/
352352
export function FavoriteIcon(props) {
353353
return (
354-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
354+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
355355
<g clip-path="url(#Favorite-16_svg__a)">
356356
<path
357357
fill="currentColor"
@@ -375,7 +375,7 @@ export function FavoriteIcon(props) {
375375
*/
376376
export function BookmarkIcon(props) {
377377
return (
378-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
378+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
379379
<g clip-path="url(#Bookmark-16_svg__a)">
380380
<path
381381
fill="currentColor"
@@ -399,7 +399,7 @@ export function BookmarkIcon(props) {
399399
*/
400400
export function BrowserIcon(props) {
401401
return (
402-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
402+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
403403
<path
404404
fill="#000"
405405
fill-rule="evenodd"

special-pages/pages/new-tab/app/customizer/CustomizerProvider.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import { applyDefaultStyles } from './utils.js';
1414
* @typedef {import('../service.hooks.js').Events<CustomizerData, undefined>} Events
1515
*/
1616

17+
/**
18+
* @typedef {{
19+
* title: string,
20+
* icon: import('preact').ComponentChild,
21+
* onClick: () => void,
22+
* }} SettingsLinkData
23+
*/
24+
1725
/**
1826
* These are the values exposed to consumers.
1927
*/
@@ -47,6 +55,10 @@ export const CustomizerContext = createContext({
4755
* @param {UserImageContextMenu} _params
4856
*/
4957
customizerContextMenu: (_params) => {},
58+
/**
59+
* @type {import('@preact/signals').Signal<Record<string, SettingsLinkData>>}
60+
*/
61+
settingsLinks: signal({}),
5062
});
5163

5264
/**
@@ -126,8 +138,11 @@ export function CustomizerProvider({ service, initialData, children }) {
126138
/** @type {(p: UserImageContextMenu) => void} */
127139
const customizerContextMenu = useCallback((params) => service.contextMenu(params), [service]);
128140

141+
/** @type {import('@preact/signals').Signal<Record<string, SettingsLinkData>>} */
142+
const settingsLinks = useSignal({});
143+
129144
return (
130-
<CustomizerContext.Provider value={{ data, select, upload, setTheme, deleteImage, customizerContextMenu }}>
145+
<CustomizerContext.Provider value={{ data, select, upload, setTheme, deleteImage, customizerContextMenu, settingsLinks }}>
131146
<CustomizerThemesContext.Provider value={{ main, browser }}>{children}</CustomizerThemesContext.Provider>
132147
</CustomizerContext.Provider>
133148
);

special-pages/pages/new-tab/app/customizer/components/CustomizerDrawer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function CustomizerDrawer({ displayChildren }) {
1313
}
1414

1515
function CustomizerConsumer() {
16-
const { data, select, upload, setTheme, deleteImage, customizerContextMenu } = useContext(CustomizerContext);
16+
const { data, select, upload, setTheme, deleteImage, customizerContextMenu, settingsLinks } = useContext(CustomizerContext);
1717
return (
1818
<CustomizerDrawerInner
1919
data={data}
@@ -22,6 +22,7 @@ function CustomizerConsumer() {
2222
setTheme={setTheme}
2323
deleteImage={deleteImage}
2424
customizerContextMenu={customizerContextMenu}
25+
settingsLinks={settingsLinks}
2526
/>
2627
);
2728
}

special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import { BorderedSection, CustomizerSection } from './CustomizerSection.js';
1313
import { SettingsLink } from './SettingsLink.js';
1414
import { DismissButton } from '../../components/DismissButton.jsx';
1515
import { InlineErrorBoundary } from '../../InlineErrorBoundary.js';
16-
import { useTypedTranslationWith } from '../../types.js';
16+
import { useMessaging, useTypedTranslationWith } from '../../types.js';
17+
import { Open } from '../../components/icons/Open.js';
1718

1819
/**
1920
* @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, BackgroundData, UserImageContextMenu } from '../../../types/new-tab.js'
21+
* @import { SettingsLinkData } from '../CustomizerProvider';
2022
* @import enStrings from '../strings.json';
2123
*/
2224

@@ -28,10 +30,12 @@ import { useTypedTranslationWith } from '../../types.js';
2830
* @param {(theme: import('../../../types/new-tab').ThemeData) => void} props.setTheme
2931
* @param {(id: string) => void} props.deleteImage
3032
* @param {(p: UserImageContextMenu) => void} props.customizerContextMenu
33+
* @param {import('@preact/signals').Signal<Record<string, SettingsLinkData>>} props.settingsLinks
3134
*/
32-
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu }) {
35+
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu, settingsLinks }) {
3336
const { close } = useDrawerControls();
3437
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
38+
const messaging = useMessaging();
3539
return (
3640
<div class={styles.root}>
3741
<header class={cn(styles.header, styles.internal)}>
@@ -65,7 +69,14 @@ export function CustomizerDrawerInner({ data, select, onUpload, setTheme, delete
6569
<VisibilityMenuSection />
6670
</CustomizerSection>
6771
<BorderedSection>
68-
<SettingsLink />
72+
{Object.entries(settingsLinks.value).map(([key, link]) => (
73+
<SettingsLink key={key} title={link.title} icon={link.icon} onClick={() => link.onClick()} />
74+
))}
75+
<SettingsLink
76+
title={t('customizer_settings_link')}
77+
icon={<Open />}
78+
onClick={() => messaging.open({ target: 'settings' })}
79+
/>
6980
</BorderedSection>
7081
</div>
7182
)}

special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.module.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,12 @@
257257
align-items: center;
258258
text-decoration: none;
259259
color: var(--ntp-color-primary);
260+
margin-bottom: var(--sp-3);
260261

261262
&:focus {
262263
outline: none;
263264
}
264265
&:focus-visible {
265266
text-decoration: underline;
266267
}
267-
}
268+
}
Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
import cn from 'classnames';
22
import styles from './CustomizerDrawerInner.module.css';
33
import { h } from 'preact';
4-
import { useMessaging, useTypedTranslationWith } from '../../types.js';
5-
import { Open } from '../../components/icons/Open.js';
64

75
/**
8-
* @import enStrings from '../strings.json';
6+
* @param {object} props
7+
* @param {string} props.title
8+
* @param {import('preact').ComponentChild} props.icon
9+
* @param {() => void} props.onClick
910
*/
10-
11-
/**
12-
* Settings link
13-
*/
14-
export function SettingsLink() {
15-
const messaging = useMessaging();
16-
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
17-
function onClick(e) {
18-
e.preventDefault();
19-
messaging.open({ target: 'settings' });
20-
}
11+
export function SettingsLink({ title, icon, onClick }) {
2112
return (
22-
<a href="duck://settings" class={cn(styles.settingsLink)} onClick={onClick}>
23-
<span>{t('customizer_settings_link')}</span>
24-
<Open />
13+
<a
14+
href="duck://settings"
15+
class={cn(styles.settingsLink)}
16+
onClick={(event) => {
17+
event.preventDefault();
18+
onClick();
19+
}}
20+
>
21+
<span>{title}</span>
22+
{icon}
2523
</a>
2624
);
2725
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@
5353
padding: 0;
5454
width: var(--sp-7);
5555

56-
svg {
57-
height: var(--sp-4);
58-
width: var(--sp-4);
59-
}
60-
6156
&[disabled] {
6257
background: none;
6358
color: var(--color-black-at-30);

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { useContext } from 'preact/hooks';
1+
import { useContext, useEffect } from 'preact/hooks';
22
import { OmnibarContext } from './OmnibarProvider.js';
33
import { h } from 'preact';
44
import { Omnibar } from './Omnibar.js';
5+
import { CustomizerContext } from '../../customizer/CustomizerProvider.js';
6+
import { AiChatIcon } from '../../components/Icons.js';
7+
import { useTypedTranslationWith } from '../../types.js';
58

69
/**
10+
* @typedef {import('../strings.json')} Strings
711
* @typedef {import('../../../types/new-tab.js').OmnibarConfig} OmnibarConfig
812
*/
913

@@ -31,7 +35,26 @@ export function OmnibarConsumer() {
3135
* @param {object} props
3236
* @param {OmnibarConfig} props.config
3337
*/
34-
function OmnibarReadyState({ config }) {
35-
const { setMode } = useContext(OmnibarContext);
36-
return <Omnibar mode={config.mode} setMode={setMode} enableAi={config.enableAi ?? true} />;
38+
function OmnibarReadyState({ config: { enableAi = true, mode } }) {
39+
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
40+
41+
const { settingsLinks } = useContext(CustomizerContext);
42+
const { setMode, setEnableAi } = useContext(OmnibarContext);
43+
44+
useEffect(() => {
45+
settingsLinks.value = {
46+
...settingsLinks.value,
47+
duckAi: {
48+
title: enableAi ? t('omnibar_hideDuckAi') : t('omnibar_showDuckAi'),
49+
icon: <AiChatIcon />,
50+
onClick: () => setEnableAi(!enableAi),
51+
},
52+
};
53+
return () => {
54+
const { duckAi: _, ...rest } = settingsLinks.value;
55+
settingsLinks.value = rest;
56+
};
57+
}, [enableAi]);
58+
59+
return <Omnibar mode={mode} setMode={setMode} enableAi={enableAi} />;
3760
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export const OmnibarContext = createContext({
2222
setMode: () => {
2323
throw new Error('must implement');
2424
},
25+
/** @type {(enableAi: NonNullable<OmnibarConfig['enableAi']>) => void} */
26+
setEnableAi: () => {
27+
throw new Error('must implement');
28+
},
2529
/** @type {(term: string) => Promise<SuggestionsData>} */
2630
getSuggestions: () => {
2731
throw new Error('must implement');
@@ -78,6 +82,14 @@ export function OmnibarProvider(props) {
7882
[service],
7983
);
8084

85+
/** @type {(enableAi: NonNullable<OmnibarConfig['enableAi']>) => void} */
86+
const setEnableAi = useCallback(
87+
(enableAi) => {
88+
service.current?.setEnableAi(enableAi);
89+
},
90+
[service],
91+
);
92+
8193
/** @type {(term: string) => Promise<SuggestionsData>} */
8294
const getSuggestions = useCallback(
8395
(term) => {
@@ -125,6 +137,7 @@ export function OmnibarProvider(props) {
125137
value={{
126138
state,
127139
setMode,
140+
setEnableAi,
128141
getSuggestions,
129142
onSuggestions,
130143
openSuggestion,

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
padding: var(--sp-1);
55

66
svg {
7-
height: var(--sp-4);
87
margin: var(--sp-2);
9-
width: var(--sp-4);
108
}
119
}
1210

0 commit comments

Comments
 (0)