Skip to content

NTP: Add Duck.ai toggle to Customize drawer #1832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions special-pages/pages/new-tab/app/components/Icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export function BackChevron() {
*/
export function SearchIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#Find-Search-16_svg__a)">
<path
fill="currentColor"
Expand All @@ -194,7 +194,7 @@ export function SearchIcon(props) {
*/
export function SearchColorIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#Search-Find-Color-16_svg__a)">
<path fill="#ADC2FC" d="M12 7A5 5 0 1 1 2 7a5 5 0 0 1 10 0Z" />
<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" />
Expand All @@ -218,7 +218,7 @@ export function SearchColorIcon(props) {
*/
export function AiChatIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g fill="currentColor" clip-path="url(#Ai-Chat-16_svg__a)">
<path
fill-rule="evenodd"
Expand All @@ -242,7 +242,7 @@ export function AiChatIcon(props) {
*/
export function AiChatColorIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#Ai-Chat-Gradient-Color-16_svg__a)">
<path
fill="url(#Ai-Chat-Gradient-Color-16_svg__b)"
Expand Down Expand Up @@ -292,7 +292,7 @@ export function AiChatColorIcon(props) {
*/
export function ArrowRightIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fill="currentColor"
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"
Expand All @@ -307,7 +307,7 @@ export function ArrowRightIcon(props) {
*/
export function GlobeIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#Globe-16_svg__a)">
<path
fill="currentColor"
Expand All @@ -331,7 +331,7 @@ export function GlobeIcon(props) {
*/
export function HistoryIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g fill="currentColor" clip-path="url(#History-16_svg__a)">
<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" />
<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" />
Expand All @@ -351,7 +351,7 @@ export function HistoryIcon(props) {
*/
export function FavoriteIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#Favorite-16_svg__a)">
<path
fill="currentColor"
Expand All @@ -375,7 +375,7 @@ export function FavoriteIcon(props) {
*/
export function BookmarkIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#Bookmark-16_svg__a)">
<path
fill="currentColor"
Expand All @@ -399,7 +399,7 @@ export function BookmarkIcon(props) {
*/
export function BrowserIcon(props) {
return (
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fill="#000"
fill-rule="evenodd"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import { applyDefaultStyles } from './utils.js';
* @typedef {import('../service.hooks.js').Events<CustomizerData, undefined>} Events
*/

/**
* @typedef {{
* title: string,
* icon: import('preact').ComponentChild,
* onClick: () => void,
* }} SettingsLinkData
*/

/**
* These are the values exposed to consumers.
*/
Expand Down Expand Up @@ -47,6 +55,10 @@ export const CustomizerContext = createContext({
* @param {UserImageContextMenu} _params
*/
customizerContextMenu: (_params) => {},
/**
* @type {import('@preact/signals').Signal<Record<string, SettingsLinkData>>}
*/
settingsLinks: signal({}),
});

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

/** @type {import('@preact/signals').Signal<Record<string, SettingsLinkData>>} */
const settingsLinks = useSignal({});

return (
<CustomizerContext.Provider value={{ data, select, upload, setTheme, deleteImage, customizerContextMenu }}>
<CustomizerContext.Provider value={{ data, select, upload, setTheme, deleteImage, customizerContextMenu, settingsLinks }}>
<CustomizerThemesContext.Provider value={{ main, browser }}>{children}</CustomizerThemesContext.Provider>
</CustomizerContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function CustomizerDrawer({ displayChildren }) {
}

function CustomizerConsumer() {
const { data, select, upload, setTheme, deleteImage, customizerContextMenu } = useContext(CustomizerContext);
const { data, select, upload, setTheme, deleteImage, customizerContextMenu, settingsLinks } = useContext(CustomizerContext);
return (
<CustomizerDrawerInner
data={data}
Expand All @@ -22,6 +22,7 @@ function CustomizerConsumer() {
setTheme={setTheme}
deleteImage={deleteImage}
customizerContextMenu={customizerContextMenu}
settingsLinks={settingsLinks}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { BorderedSection, CustomizerSection } from './CustomizerSection.js';
import { SettingsLink } from './SettingsLink.js';
import { DismissButton } from '../../components/DismissButton.jsx';
import { InlineErrorBoundary } from '../../InlineErrorBoundary.js';
import { useTypedTranslationWith } from '../../types.js';
import { useMessaging, useTypedTranslationWith } from '../../types.js';
import { Open } from '../../components/icons/Open.js';

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

Expand All @@ -28,10 +30,12 @@ import { useTypedTranslationWith } from '../../types.js';
* @param {(theme: import('../../../types/new-tab').ThemeData) => void} props.setTheme
* @param {(id: string) => void} props.deleteImage
* @param {(p: UserImageContextMenu) => void} props.customizerContextMenu
* @param {import('@preact/signals').Signal<Record<string, SettingsLinkData>>} props.settingsLinks
*/
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu }) {
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu, settingsLinks }) {
const { close } = useDrawerControls();
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
const messaging = useMessaging();
return (
<div class={styles.root}>
<header class={cn(styles.header, styles.internal)}>
Expand Down Expand Up @@ -65,7 +69,14 @@ export function CustomizerDrawerInner({ data, select, onUpload, setTheme, delete
<VisibilityMenuSection />
</CustomizerSection>
<BorderedSection>
<SettingsLink />
{Object.entries(settingsLinks.value).map(([key, link]) => (
<SettingsLink key={key} title={link.title} icon={link.icon} onClick={() => link.onClick()} />
))}
<SettingsLink
title={t('customizer_settings_link')}
icon={<Open />}
onClick={() => messaging.open({ target: 'settings' })}
/>
</BorderedSection>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,12 @@
align-items: center;
text-decoration: none;
color: var(--ntp-color-primary);
margin-bottom: var(--sp-3);

&:focus {
outline: none;
}
&:focus-visible {
text-decoration: underline;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import cn from 'classnames';
import styles from './CustomizerDrawerInner.module.css';
import { h } from 'preact';
import { useMessaging, useTypedTranslationWith } from '../../types.js';
import { Open } from '../../components/icons/Open.js';

/**
* @import enStrings from '../strings.json';
* @param {object} props
* @param {string} props.title
* @param {import('preact').ComponentChild} props.icon
* @param {() => void} props.onClick
*/

/**
* Settings link
*/
export function SettingsLink() {
const messaging = useMessaging();
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
function onClick(e) {
e.preventDefault();
messaging.open({ target: 'settings' });
}
export function SettingsLink({ title, icon, onClick }) {
return (
<a href="duck://settings" class={cn(styles.settingsLink)} onClick={onClick}>
<span>{t('customizer_settings_link')}</span>
<Open />
<a
href="duck://settings"
class={cn(styles.settingsLink)}
onClick={(event) => {
event.preventDefault();
onClick();
}}
>
<span>{title}</span>
{icon}
</a>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@
padding: 0;
width: var(--sp-7);

svg {
height: var(--sp-4);
width: var(--sp-4);
}

&[disabled] {
background: none;
color: var(--color-black-at-30);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { useContext } from 'preact/hooks';
import { useContext, useEffect } from 'preact/hooks';
import { OmnibarContext } from './OmnibarProvider.js';
import { h } from 'preact';
import { Omnibar } from './Omnibar.js';
import { CustomizerContext } from '../../customizer/CustomizerProvider.js';
import { AiChatIcon } from '../../components/Icons.js';
import { useTypedTranslationWith } from '../../types.js';

/**
* @typedef {import('../strings.json')} Strings
* @typedef {import('../../../types/new-tab.js').OmnibarConfig} OmnibarConfig
*/

Expand Down Expand Up @@ -31,7 +35,26 @@ export function OmnibarConsumer() {
* @param {object} props
* @param {OmnibarConfig} props.config
*/
function OmnibarReadyState({ config }) {
const { setMode } = useContext(OmnibarContext);
return <Omnibar mode={config.mode} setMode={setMode} enableAi={config.enableAi ?? true} />;
function OmnibarReadyState({ config: { enableAi = true, mode } }) {
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));

const { settingsLinks } = useContext(CustomizerContext);
const { setMode, setEnableAi } = useContext(OmnibarContext);

useEffect(() => {
settingsLinks.value = {
...settingsLinks.value,
duckAi: {
title: enableAi ? t('omnibar_hideDuckAi') : t('omnibar_showDuckAi'),
icon: <AiChatIcon />,
onClick: () => setEnableAi(!enableAi),
},
};
return () => {
const { duckAi: _, ...rest } = settingsLinks.value;
settingsLinks.value = rest;
};
}, [enableAi]);

return <Omnibar mode={mode} setMode={setMode} enableAi={enableAi} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const OmnibarContext = createContext({
setMode: () => {
throw new Error('must implement');
},
/** @type {(enableAi: NonNullable<OmnibarConfig['enableAi']>) => void} */
setEnableAi: () => {
throw new Error('must implement');
},
/** @type {(term: string) => Promise<SuggestionsData>} */
getSuggestions: () => {
throw new Error('must implement');
Expand Down Expand Up @@ -78,6 +82,14 @@ export function OmnibarProvider(props) {
[service],
);

/** @type {(enableAi: NonNullable<OmnibarConfig['enableAi']>) => void} */
const setEnableAi = useCallback(
(enableAi) => {
service.current?.setEnableAi(enableAi);
},
[service],
);

/** @type {(term: string) => Promise<SuggestionsData>} */
const getSuggestions = useCallback(
(term) => {
Expand Down Expand Up @@ -125,6 +137,7 @@ export function OmnibarProvider(props) {
value={{
state,
setMode,
setEnableAi,
getSuggestions,
onSuggestions,
openSuggestion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
padding: var(--sp-1);

svg {
height: var(--sp-4);
margin: var(--sp-2);
width: var(--sp-4);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
justify-content: flex-start;

svg {
height: var(--sp-4);
margin: var(--sp-2);
width: var(--sp-4);
}

&[aria-selected="true"] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@
padding: 0;
width: 92px;
z-index: 1;

svg {
height: var(--sp-4);
width: var(--sp-4);
}
}

.tabLabel {
Expand Down
Loading
Loading