Skip to content

Commit c12a146

Browse files
authored
NTP: Scaffold Omnibar widget and message types (#1778)
* Add message schemas * Scaffold omnibox widget and add data layer * Add mock transport * Add rough UI from Hack Days prototype * Rename omnibox -> omnibar * Remove UI for now * Add basic docs * Fix unused arguments * Fix lint errors * Add title to suggestion object types
1 parent fdd188c commit c12a146

32 files changed

+1056
-24
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,28 @@ export function BackChevron() {
163163
</svg>
164164
);
165165
}
166+
167+
/**
168+
* From https://dub.duckduckgo.com/duckduckgo/Icons/blob/Main/Glyphs/16px/Find-Search-16.svg. Inline SVG so that can be styled with CSS.
169+
* @param {object} params
170+
* @param {string} [params.className]
171+
*/
172+
export function SearchIcon({ className }) {
173+
return (
174+
<svg className={className} fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
175+
<g clip-path="url(#Find-Search-16_svg__a)">
176+
<path
177+
fill="currentColor"
178+
fill-rule="evenodd"
179+
d="M7 0a7 7 0 1 0 4.488 12.372l3.445 3.445a.625.625 0 1 0 .884-.884l-3.445-3.445A7 7 0 0 0 7 0M1.25 7a5.75 5.75 0 1 1 11.5 0 5.75 5.75 0 0 1-11.5 0"
180+
clip-rule="evenodd"
181+
/>
182+
</g>
183+
<defs>
184+
<clipPath id="Find-Search-16_svg__a">
185+
<path fill="#fff" d="M0 0h16v16H0z" />
186+
</clipPath>
187+
</defs>
188+
</svg>
189+
);
190+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class VisibilityRowData {
9090
* @param {object} params
9191
* @param {string} params.id - a unique id
9292
* @param {string} params.title - the title as it should appear in the menu
93-
* @param {'shield' | 'star'} params.icon - known icon name, maps to an SVG
93+
* @param {'shield' | 'star' | 'search'} params.icon - known icon name, maps to an SVG
9494
* @param {(id: string) => void} params.toggle - toggle function for this item
9595
* @param {number} params.index - position in the menu
9696
* @param {WidgetVisibility} params.visibility - known icon name, maps to an SVG

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { h } from 'preact';
22
import cn from 'classnames';
33
import { useContext } from 'preact/hooks';
44

5-
import { DuckFoot, Shield } from '../../components/Icons.js';
5+
import { DuckFoot, SearchIcon, Shield } from '../../components/Icons.js';
66
import styles from './VisibilityMenu.module.css';
77
import { Switch } from '../../../../../shared/components/Switch/Switch.js';
88
import { usePlatformName } from '../../settings.provider.js';
@@ -29,6 +29,7 @@ export function EmbeddedVisibilityMenu({ rows }) {
2929
<span className={styles.svg}>
3030
{row.icon === 'shield' && <DuckFoot />}
3131
{row.icon === 'star' && <Shield />}
32+
{row.icon === 'search' && <SearchIcon />}
3233
</span>
3334
<span>{row.title ?? row.id}</span>
3435
<Switch
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { h } from 'preact';
2+
import { Centered } from '../components/Layout.js';
3+
import { OmnibarCustomized } from '../omnibar/components/OmnibarCustomized.js';
4+
5+
export function factory() {
6+
return (
7+
<Centered data-entry-point="omnibar">
8+
<OmnibarCustomized />
9+
</Centered>
10+
);
11+
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,13 @@ export function FavoritesProvider({ children }) {
8282
// subscribe to data updates
8383
useDataSubscription({ dispatch, service });
8484

85-
// subscribe to toggle + expose a fn for sync toggling
86-
const { toggle } = useConfigSubscription({ dispatch, service });
85+
// subscribe to config updates
86+
useConfigSubscription({ dispatch, service });
87+
88+
// expose a fn for sync toggling
89+
const toggle = useCallback(() => {
90+
service.current?.toggleExpansion();
91+
}, [service]);
8792

8893
/** @type {ReorderFn<Favorite>} */
8994
const favoritesDidReOrder = useCallback(

special-pages/pages/new-tab/app/mock-transport.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { customizerData, customizerMockTransport } from './customizer/mocks.js';
99
import { freemiumPIRDataExamples } from './freemium-pir-banner/mocks/freemiumPIRBanner.data.js';
1010
import { activityMockTransport } from './activity/mocks/activity.mock-transport.js';
1111
import { protectionsMockTransport } from './protections/mocks/protections.mock-transport.js';
12+
import { omnibarMockTransport } from './omnibar/mocks/omnibar.mock-transport.js';
1213

1314
/**
1415
* @typedef {import('../types/new-tab').Favorite} Favorite
@@ -117,6 +118,7 @@ export function mockTransport() {
117118
customizer: customizerMockTransport(),
118119
activity: activityMockTransport(),
119120
protections: protectionsMockTransport(),
121+
omnibar: omnibarMockTransport(),
120122
};
121123

122124
return new TestTransportConfig({
@@ -490,6 +492,7 @@ export function mockTransport() {
490492
return Promise.resolve(fromStorage);
491493
}
492494
case 'initialSetup': {
495+
/** @type {import('../types/new-tab.ts').Widgets} */
493496
const widgetsFromStorage = read('widgets') || [
494497
{ id: 'updateNotification' },
495498
{ id: 'rmf' },
@@ -498,6 +501,7 @@ export function mockTransport() {
498501
{ id: 'favorites' },
499502
];
500503

504+
/** @type {import('../types/new-tab.ts').WidgetConfigs} */
501505
const widgetConfigFromStorage = read('widget_config') || [{ id: 'favorites', visibility: 'visible' }];
502506

503507
/** @type {UpdateNotificationData} */
@@ -524,6 +528,13 @@ export function mockTransport() {
524528
widgetsFromStorage.push({ id: 'protections' });
525529
widgetConfigFromStorage.push({ id: 'protections', visibility: 'visible' });
526530

531+
if (url.searchParams.has('omnibar')) {
532+
const favoritesWidgetIndex = widgetsFromStorage.findIndex((widget) => widget.id === 'favorites') ?? 0;
533+
widgetsFromStorage.splice(favoritesWidgetIndex, 0, { id: 'omnibar' });
534+
const favoritesWidgetConfigIndex = widgetConfigFromStorage.findIndex((widget) => widget.id === 'favorites') ?? 0;
535+
widgetConfigFromStorage.splice(favoritesWidgetConfigIndex, 0, { id: 'omnibar', visibility: 'visible' });
536+
}
537+
527538
initial.customizer = customizerData();
528539

529540
/** @type {import('../types/new-tab').NewTabPageSettings} */

special-pages/pages/new-tab/app/next-steps/NextStepsProvider.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,13 @@ export function NextStepsProvider(props) {
5959
// subscribe to data updates
6060
useDataSubscription({ dispatch, service });
6161

62-
// subscribe to toggle + expose a fn for sync toggling
63-
const { toggle } = useConfigSubscription({ dispatch, service });
62+
// subscribe to config updates
63+
useConfigSubscription({ dispatch, service });
64+
65+
// expose a fn for sync toggling
66+
const toggle = useCallback(() => {
67+
service.current?.toggleExpansion();
68+
}, [service]);
6469

6570
/** @type {(id: string) => void} */
6671
const action = useCallback(
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { h } from 'preact';
2+
import styles from './Omnibar.module.css';
3+
4+
/**
5+
* @typedef {import('../strings.json')} Strings
6+
* @typedef {import('../../../types/new-tab.js').OmnibarConfig} OmnibarConfig
7+
* @typedef {import('../../../types/new-tab.js').SuggestionsData} SuggestionsData
8+
* @typedef {import('../../../types/new-tab.js').Suggestion} Suggestion
9+
* @typedef {import('../../../types/new-tab.js').OpenTarget} OpenTarget
10+
*/
11+
12+
/**
13+
* @param {object} props
14+
* @param {OmnibarConfig['mode']} props.mode
15+
* @param {(mode: OmnibarConfig['mode']) => void} props.setMode
16+
* @param {(term: string) => Promise<SuggestionsData>} props.getSuggestions
17+
* @param {(params: {suggestion: Suggestion, target: OpenTarget}) => void} props.openSuggestion
18+
* @param {(params: {term: string, target: OpenTarget}) => void} props.submitSearch
19+
* @param {(params: {chat: string, target: OpenTarget}) => void} props.submitChat
20+
*/
21+
export function Omnibar(props) {
22+
return <div class={styles.root}>Omnibar goes here. Mode = {props.mode}</div>;
23+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.root {
2+
align-items: center;
3+
border: 1px solid red;
4+
display: flex;
5+
height: 96px;
6+
justify-content: center;
7+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useContext } from 'preact/hooks';
2+
import { OmnibarContext } from './OmnibarProvider.js';
3+
import { h } from 'preact';
4+
import { Omnibar } from './Omnibar.js';
5+
6+
/**
7+
* @typedef {import('../../../types/new-tab.js').OmnibarConfig} OmnibarConfig
8+
*/
9+
10+
/**
11+
* Use this when you want to render the UI from a context where
12+
* the service is available.
13+
*
14+
* for example:
15+
*
16+
* ```jsx
17+
* <OmnibarProvider>
18+
* <OmnibarConsumer />
19+
* </OmnibarProvider>
20+
* ```
21+
*/
22+
export function OmnibarConsumer() {
23+
const { state } = useContext(OmnibarContext);
24+
if (state.status === 'ready') {
25+
return <OmnibarReadyState config={state.config} />;
26+
}
27+
return null;
28+
}
29+
30+
/**
31+
* @param {object} props
32+
* @param {OmnibarConfig} props.config
33+
*/
34+
function OmnibarReadyState({ config }) {
35+
const { setMode, getSuggestions, openSuggestion, submitSearch, submitChat } = useContext(OmnibarContext);
36+
37+
return (
38+
<Omnibar
39+
mode={config.mode}
40+
setMode={setMode}
41+
getSuggestions={getSuggestions}
42+
openSuggestion={openSuggestion}
43+
submitSearch={submitSearch}
44+
submitChat={submitChat}
45+
/>
46+
);
47+
}

0 commit comments

Comments
 (0)