Skip to content

Commit c626203

Browse files
authored
NTP: Swap “Show Duck.ai” / “Hide Duck.ai” link for a Duck.ai toggle (#1870)
* Add Duck.ai toggle to visibility menu * Refactor Customizer Menu and Visibility Icon Rendering * Adjust Duck.ai toggle icon colour * Remove Duck.ai show/hide strings from localization files * Remove unused settingsLinks code from CustomizerProvider * Update Toggle Duck.ai integration tests * Add Duck.ai toggle translation for multiple locales
1 parent cd1f2ba commit c626203

File tree

24 files changed

+112
-162
lines changed

24 files changed

+112
-162
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,18 @@ export function LogoStacked(props) {
484484
</svg>
485485
);
486486
}
487+
488+
/**
489+
* From https://dub.duckduckgo.com/duckduckgo/Icons/blob/Main/Glyphs/16px/Arrow-Indent-Centerd-16.svg
490+
* @param {import('preact').JSX.SVGAttributes<SVGSVGElement>} props
491+
*/
492+
export function ArrowIndentCenteredIcon(props) {
493+
return (
494+
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
495+
<path
496+
fill="currentColor"
497+
d="M3.625 1c.345 0 .625.28.625.625V5c0 1.52 1.23 2.75 2.749 2.75h7.117l-2.683-2.683a.625.625 0 0 1 .86-.906l.024.022 2.69 2.69c.83.83.83 2.175 0 3.005l-2.69 2.689a.625.625 0 1 1-.884-.884L14.116 9H7a3.999 3.999 0 0 1-4-4V1.625C3 1.28 3.28 1 3.625 1Z"
498+
/>
499+
</svg>
500+
);
501+
}

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,6 @@ export const CustomizerContext = createContext({
5555
* @param {UserImageContextMenu} _params
5656
*/
5757
customizerContextMenu: (_params) => {},
58-
/**
59-
* @type {import('@preact/signals').Signal<Record<string, SettingsLinkData>>}
60-
*/
61-
settingsLinks: signal({}),
6258
});
6359

6460
/**
@@ -138,11 +134,8 @@ export function CustomizerProvider({ service, initialData, children }) {
138134
/** @type {(p: UserImageContextMenu) => void} */
139135
const customizerContextMenu = useCallback((params) => service.contextMenu(params), [service]);
140136

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

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

Lines changed: 1 addition & 2 deletions
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, settingsLinks } = useContext(CustomizerContext);
16+
const { data, select, upload, setTheme, deleteImage, customizerContextMenu } = useContext(CustomizerContext);
1717
return (
1818
<CustomizerDrawerInner
1919
data={data}
@@ -22,7 +22,6 @@ function CustomizerConsumer() {
2222
setTheme={setTheme}
2323
deleteImage={deleteImage}
2424
customizerContextMenu={customizerContextMenu}
25-
settingsLinks={settingsLinks}
2625
/>
2726
);
2827
}

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ import { Open } from '../../components/icons/Open.js';
3030
* @param {(theme: import('../../../types/new-tab').ThemeData) => void} props.setTheme
3131
* @param {(id: string) => void} props.deleteImage
3232
* @param {(p: UserImageContextMenu) => void} props.customizerContextMenu
33-
* @param {import('@preact/signals').Signal<Record<string, SettingsLinkData>>} props.settingsLinks
3433
*/
35-
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu, settingsLinks }) {
34+
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu }) {
3635
const { close } = useDrawerControls();
3736
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
3837
const messaging = useMessaging();
@@ -69,9 +68,6 @@ export function CustomizerDrawerInner({ data, select, onUpload, setTheme, delete
6968
<VisibilityMenuSection />
7069
</CustomizerSection>
7170
<BorderedSection>
72-
{Object.entries(settingsLinks.value).map(([key, link]) => (
73-
<SettingsLink key={key} title={link.title} icon={link.icon} onClick={() => link.onClick()} />
74-
))}
7571
<SettingsLink
7672
title={t('customizer_settings_link')}
7773
icon={<Open />}

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

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@ import { CustomizeIcon } from '../../components/Icons.js';
55
import { useMessaging, useTypedTranslation } from '../../types.js';
66

77
/**
8-
* @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem } from '../../../types/new-tab.js'
8+
* @import { WidgetVisibility, VisibilityMenuItem } from '../../../types/new-tab.js'
9+
*/
10+
11+
/**
12+
* @typedef {object} VisibilityRowData
13+
* @property {string} id - a unique id
14+
* @property {string} title - the title as it should appear in the menu
15+
* @property {import('preact').ComponentChild} icon - icon to display in the menu
16+
* @property {(id: string) => void} toggle - toggle function for this item
17+
* @property {number} index - position in the menu
18+
* @property {WidgetVisibility} visibility - known icon name, maps to an SVG
919
*/
1020

1121
export const OPEN_EVENT = 'ntp-customizer-open';
@@ -85,26 +95,6 @@ export function CustomizerMenuPositionedFixed({ children }) {
8595
return <div class={styles.lowerRightFixed}>{children}</div>;
8696
}
8797

88-
export class VisibilityRowData {
89-
/**
90-
* @param {object} params
91-
* @param {string} params.id - a unique id
92-
* @param {string} params.title - the title as it should appear in the menu
93-
* @param {'shield' | 'star' | 'search'} params.icon - known icon name, maps to an SVG
94-
* @param {(id: string) => void} params.toggle - toggle function for this item
95-
* @param {number} params.index - position in the menu
96-
* @param {WidgetVisibility} params.visibility - known icon name, maps to an SVG
97-
*/
98-
constructor({ id, title, icon, toggle, visibility, index }) {
99-
this.id = id;
100-
this.title = title;
101-
this.icon = icon;
102-
this.toggle = toggle;
103-
this.index = index;
104-
this.visibility = visibility;
105-
}
106-
}
107-
10898
/**
10999
* Call this to opt-in to the visibility menu
110100
* @param {VisibilityRowData} row

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { h } from 'preact';
21
import cn from 'classnames';
2+
import { h } from 'preact';
33
import { useContext } from 'preact/hooks';
44

5-
import { DuckFoot, SearchIcon, Shield } from '../../components/Icons.js';
6-
import styles from './VisibilityMenu.module.css';
75
import { Switch } from '../../../../../shared/components/Switch/Switch.js';
86
import { usePlatformName } from '../../settings.provider.js';
97
import { CustomizerThemesContext } from '../CustomizerProvider.js';
8+
import styles from './VisibilityMenu.module.css';
109

1110
/**
12-
* @import { Widgets, WidgetConfigItem } from '../../../types/new-tab.js'
13-
* @import { VisibilityRowData } from './CustomizerMenu.js'
11+
* @import { VisibilityRowData } from './CustomizerMenu.js';
1412
*/
1513

1614
/**
@@ -26,11 +24,7 @@ export function EmbeddedVisibilityMenu({ rows }) {
2624
return (
2725
<li key={row.id}>
2826
<div class={cn(styles.menuItemLabel, styles.menuItemLabelEmbedded)}>
29-
<span className={styles.svg}>
30-
{row.icon === 'shield' && <DuckFoot />}
31-
{row.icon === 'star' && <Shield />}
32-
{row.icon === 'search' && <SearchIcon />}
33-
</span>
27+
<span className={styles.svg}>{row.icon}</span>
3428
<span>{row.title ?? row.id}</span>
3529
<Switch
3630
theme={browser.value}

special-pages/pages/new-tab/app/favorites/components/FavoritesCustomized.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PragmaticDND } from './PragmaticDND.js';
1010
import { FavoritesMemo } from './Favorites.js';
1111
import { viewTransition } from '../../utils.js';
1212
import { CustomizerContext } from '../../customizer/CustomizerProvider.js';
13+
import { Shield } from '../../components/Icons.js';
1314

1415
/**
1516
* @typedef {import('../../../types/new-tab.ts').Favorite} Favorite
@@ -66,7 +67,7 @@ export function FavoritesCustomized() {
6667

6768
// register with the visibility menu
6869
const title = t('favorites_menu_title');
69-
useCustomizer({ title, id, icon: 'star', toggle, visibility: visibility.value, index });
70+
useCustomizer({ title, id, icon: <Shield />, toggle, visibility: visibility.value, index });
7071

7172
if (visibility.value === 'hidden') {
7273
return null;
Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { useContext, useEffect } from 'preact/hooks';
2-
import { OmnibarContext } from './OmnibarProvider.js';
3-
import { h } from 'preact';
4-
import { Omnibar } from './Omnibar.js';
5-
import { CustomizerContext } from '../../customizer/CustomizerProvider.js';
6-
import { AiChatIcon } from '../../components/Icons.js';
1+
import { Fragment, h } from 'preact';
2+
import { useContext } from 'preact/hooks';
3+
import { useCustomizer } from '../../customizer/components/CustomizerMenu.js';
74
import { useTypedTranslationWith } from '../../types.js';
5+
import { useVisibility } from '../../widget-list/widget-config.provider.js';
6+
import { Omnibar } from './Omnibar.js';
7+
import { OmnibarContext } from './OmnibarProvider.js';
8+
import { ArrowIndentCenteredIcon } from '../../components/Icons.js';
89

910
/**
1011
* @typedef {import('../strings.json')} Strings
@@ -36,29 +37,30 @@ export function OmnibarConsumer() {
3637
* @param {OmnibarConfig} props.config
3738
*/
3839
function OmnibarReadyState({ config: { enableAi = true, showAiSetting = true, mode } }) {
39-
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
40-
41-
const { settingsLinks } = useContext(CustomizerContext);
42-
const { setMode, setEnableAi } = useContext(OmnibarContext);
43-
44-
useEffect(() => {
45-
if (!showAiSetting) {
46-
return;
47-
}
48-
49-
settingsLinks.value = {
50-
...settingsLinks.value,
51-
duckAi: {
52-
title: enableAi ? t('omnibar_hideDuckAi') : t('omnibar_showDuckAi'),
53-
icon: <AiChatIcon />,
54-
onClick: () => setEnableAi(!enableAi),
55-
},
56-
};
57-
return () => {
58-
const { duckAi: _, ...rest } = settingsLinks.value;
59-
settingsLinks.value = rest;
60-
};
61-
}, [enableAi, showAiSetting]);
40+
const { setEnableAi, setMode } = useContext(OmnibarContext);
41+
return (
42+
<>
43+
{showAiSetting && <AiSetting enableAi={enableAi} setEnableAi={setEnableAi} />}
44+
<Omnibar mode={mode} setMode={setMode} enableAi={enableAi} />
45+
</>
46+
);
47+
}
6248

63-
return <Omnibar mode={mode} setMode={setMode} enableAi={enableAi} />;
49+
/**
50+
* @param {object} props
51+
* @param {boolean} props.enableAi
52+
* @param {(enable: boolean) => void} props.setEnableAi
53+
*/
54+
function AiSetting({ enableAi, setEnableAi }) {
55+
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
56+
const { id, index } = useVisibility();
57+
useCustomizer({
58+
title: t('omnibar_toggleDuckAi'),
59+
id: `_${id}-toggleAi`,
60+
icon: <ArrowIndentCenteredIcon style={{ color: 'var(--ntp-icons-tertiary)' }} />,
61+
toggle: () => setEnableAi(!enableAi),
62+
visibility: enableAi ? 'visible' : 'hidden',
63+
index: index + 0.1,
64+
});
65+
return null;
6466
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { OmnibarProvider } from './OmnibarProvider.js';
55
import { h } from 'preact';
66

77
import { OmnibarConsumer } from './OmnibarConsumer.js';
8+
import { SearchIcon } from '../../components/Icons.js';
89

910
/**
1011
* @import enStrings from "../strings.json"
@@ -27,7 +28,7 @@ export function OmnibarCustomized() {
2728

2829
const { visibility, id, toggle, index } = useVisibility();
2930

30-
useCustomizer({ title: sectionTitle, id, icon: 'search', toggle, visibility: visibility.value, index });
31+
useCustomizer({ title: sectionTitle, id, icon: <SearchIcon />, toggle, visibility: visibility.value, index });
3132

3233
if (visibility.value === 'hidden') {
3334
return null;

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,8 @@ export class OmnibarPage {
6161
return this.page.getByRole('switch', { name: 'Toggle Search' });
6262
}
6363

64-
showDuckAiButton() {
65-
return this.page.getByRole('link', { name: 'Show Duck.ai' });
66-
}
67-
68-
hideDuckAiButton() {
69-
return this.page.getByRole('link', { name: 'Hide Duck.ai' });
64+
toggleDuckAiButton() {
65+
return this.page.getByRole('switch', { name: 'Toggle Duck.ai' });
7066
}
7167

7268
/**

0 commit comments

Comments
 (0)