Skip to content

Commit 9a2d05d

Browse files
shakyShaneShane Osbourne
andauthored
ntp: making the customizer menu more dynamic (#1177)
Co-authored-by: Shane Osbourne <[email protected]>
1 parent 791f7a7 commit 9a2d05d

File tree

14 files changed

+118
-92
lines changed

14 files changed

+118
-92
lines changed

special-pages/pages/new-tab/app/components/Examples.jsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,24 @@ export const otherExamples = {
124124
<br/>
125125
<MaxContent>
126126
<VisibilityMenu
127-
toggle={noop('toggle!')}
128127
rows={[
129128
{
130129
id: 'favorites',
131130
title: 'Favorites',
132-
icon: 'star'
131+
icon: 'star',
132+
toggle: noop("toggle favorites"),
133+
visibility: "hidden",
134+
index:0
133135
},
134136
{
135137
id: 'privacyStats',
136138
title: 'Privacy Stats',
137-
icon: 'shield'
139+
icon: 'shield',
140+
toggle: noop("toggle favorites"),
141+
visibility: "visible",
142+
index: 1
138143
}
139144
]}
140-
state={[
141-
{ checked: true },
142-
{ checked: false }
143-
]}
144145
/>
145146
</MaxContent>
146147
</Fragment>

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

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,41 @@
11
import { h } from 'preact'
2-
import { useContext, useEffect, useRef, useState, useCallback, useId } from 'preact/hooks'
3-
import { WidgetConfigContext } from '../widget-list/widget-config.provider.js'
2+
import { useEffect, useRef, useState, useCallback, useId } from 'preact/hooks'
43
import styles from './Customizer.module.css'
54
import { VisibilityMenu } from './VisibilityMenu.js'
65
import { CustomizeIcon } from '../components/Icons.js'
76
import cn from 'classnames'
87

98
/**
10-
* @import { Widgets, WidgetConfigItem } from '../../../../types/new-tab.js'
9+
* @import { Widgets, WidgetConfigItem, WidgetVisibility } from '../../../../types/new-tab.js'
1110
*/
1211

1312
/**
1413
* Represents the NTP customizer. For now it's just the ability to toggle sections.
1514
*/
1615
export function Customizer () {
17-
const { widgetConfigItems, toggle } = useContext(WidgetConfigContext)
1816
const { setIsOpen, buttonRef, dropdownRef, isOpen } = useDropdown()
1917
const [rowData, setRowData] = useState(/** @type {VisibilityRowData[]} */([]))
2018

2119
/**
2220
* Dispatch an event every time the customizer is opened - this
23-
* allows widgets to register themselves and provide
21+
* allows widgets to register themselves and provide titles/icons etc.
2422
*/
2523
const toggleMenu = useCallback(() => {
2624
if (isOpen) return setIsOpen(false)
27-
/** @type {VisibilityRowData[]} */
28-
const next = []
29-
const detail = {
30-
register: (/** @type {VisibilityRowData} */incoming) => {
31-
next.push(structuredClone(incoming))
32-
}
33-
}
34-
const event = new CustomEvent(Customizer.OPEN_EVENT, { detail })
35-
window.dispatchEvent(event)
36-
setRowData(next)
25+
setRowData(getItems())
3726
setIsOpen(true)
3827
}, [isOpen])
3928

40-
/**
41-
* Compute the current state of each registered row
42-
*/
43-
const visibilityState = rowData.map(row => {
44-
const item = widgetConfigItems.find(w => w.id === row.id)
45-
if (!item) console.warn('could not find', row.id)
46-
return {
47-
checked: item?.visibility === 'visible'
29+
useEffect(() => {
30+
if (!isOpen) return
31+
function handler () {
32+
setRowData(getItems())
33+
}
34+
window.addEventListener(Customizer.UPDATE_EVENT, handler)
35+
return () => {
36+
window.removeEventListener(Customizer.UPDATE_EVENT, handler)
4837
}
49-
})
38+
}, [isOpen])
5039

5140
const MENU_ID = useId()
5241
const BUTTON_ID = useId()
@@ -65,17 +54,28 @@ export function Customizer () {
6554
class={cn(styles.dropdownMenu, { [styles.show]: isOpen })}
6655
aria-labelledby={BUTTON_ID}
6756
>
68-
<VisibilityMenu
69-
rows={rowData}
70-
state={visibilityState}
71-
toggle={toggle}
72-
/>
57+
<VisibilityMenu rows={rowData} />
7358
</div>
7459
</div>
7560
)
7661
}
7762

7863
Customizer.OPEN_EVENT = 'ntp-customizer-open'
64+
Customizer.UPDATE_EVENT = 'ntp-customizer-update'
65+
66+
function getItems () {
67+
/** @type {VisibilityRowData[]} */
68+
const next = []
69+
const detail = {
70+
register: (/** @type {VisibilityRowData} */incoming) => {
71+
next.push(incoming)
72+
}
73+
}
74+
const event = new CustomEvent(Customizer.OPEN_EVENT, { detail })
75+
window.dispatchEvent(event)
76+
next.sort((a, b) => a.index - b.index)
77+
return next
78+
}
7979

8080
/**
8181
* @param {object} props
@@ -154,7 +154,6 @@ function useDropdown () {
154154

155155
export class VisibilityRowState {
156156
checked
157-
158157
/**
159158
* @param {object} params
160159
* @param {boolean} params.checked - whether this item should appear 'checked'
@@ -165,33 +164,39 @@ export class VisibilityRowState {
165164
}
166165

167166
export class VisibilityRowData {
168-
id
169-
title
170-
icon
171-
172167
/**
173168
* @param {object} params
174169
* @param {string} params.id - a unique id
175170
* @param {string} params.title - the title as it should appear in the menu
176171
* @param {'shield' | 'star'} params.icon - known icon name, maps to an SVG
172+
* @param {(id: string) => void} params.toggle - toggle function for this item
173+
* @param {number} params.index - position in the menu
174+
* @param {WidgetVisibility} params.visibility - known icon name, maps to an SVG
177175
*/
178-
constructor ({ id, title, icon }) {
176+
constructor ({ id, title, icon, toggle, visibility, index }) {
179177
this.id = id
180178
this.title = title
181179
this.icon = icon
180+
this.toggle = toggle
181+
this.index = index
182+
this.visibility = visibility
182183
}
183184
}
184185

185186
/**
186187
* Call this to opt-in to the visibility menu
187188
* @param {VisibilityRowData} row
188189
*/
189-
export function useCustomizer ({ title, id, icon }) {
190+
export function useCustomizer ({ title, id, icon, toggle, visibility, index }) {
190191
useEffect(() => {
191192
const handler = (/** @type {CustomEvent<any>} */e) => {
192-
e.detail.register({ title, id, icon })
193+
e.detail.register({ title, id, icon, toggle, visibility, index })
193194
}
194195
window.addEventListener(Customizer.OPEN_EVENT, handler)
195196
return () => window.removeEventListener(Customizer.OPEN_EVENT, handler)
196-
}, [title, id, icon])
197+
}, [title, id, icon, toggle, visibility, index])
198+
199+
useEffect(() => {
200+
window.dispatchEvent(new Event(Customizer.UPDATE_EVENT))
201+
}, [visibility])
197202
}

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,29 @@ import { useTypedTranslation } from '../types.js'
1515
* meta data like translated titles
1616
*
1717
* @param {object} props
18-
* @param {(id: string) => void} props.toggle
1918
* @param {VisibilityRowData[]} props.rows
20-
* @param {VisibilityRowState[]} props.state
2119
*/
22-
export function VisibilityMenu ({ rows, state, toggle }) {
20+
export function VisibilityMenu ({ rows }) {
2321
const { t } = useTypedTranslation()
2422
const MENU_ID = useId()
2523

2624
return (
2725
<div className={styles.dropdownInner}>
2826
<h2 className="sr-only">{t('widgets_visibility_menu_title')}</h2>
2927
<ul className={styles.list}>
30-
{rows.map((row, index) => {
31-
const current = state[index]
28+
{rows.map((row) => {
3229
return (
3330
<li key={row.id}>
3431
<label className={styles.menuItemLabel} htmlFor={MENU_ID + row.id}>
3532
<input
3633
type="checkbox"
37-
checked={current.checked}
38-
onChange={() => toggle(row.id)}
34+
checked={row.visibility === 'visible'}
35+
onChange={() => row.toggle?.(row.id)}
3936
id={MENU_ID + row.id}
4037
class={styles.checkbox}
4138
/>
4239
<span aria-hidden={true} className={styles.checkboxIcon}>
43-
{current.checked && (
40+
{row.visibility === 'visible' && (
4441
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"
4542
xmlns="http://www.w3.org/2000/svg">
4643
<path d="M3.5 9L6 11.5L12.5 5"

special-pages/pages/new-tab/app/customizer/VisibilityMenu.module.css

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
.dropdownInner {
22
background: var(--ntp-surface-background-color);
3+
padding: var(--sp-1);
4+
border-radius: 8px;
5+
backdrop-filter: blur(48px);
6+
37
border: 1px solid var(--color-black-at-9);
8+
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.12), 0px 8px 16px 0px rgba(0, 0, 0, 0.20), 0px 2px 4px 0px rgba(0, 0, 0, 0.15);
9+
410
@media screen and (prefers-color-scheme: dark) {
511
border-color: var(--color-white-at-9);
12+
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.09) inset, 0px 0px 0px 1px rgba(0, 0, 0, 0.50), 0px 2px 4px 0px rgba(0, 0, 0, 0.15), 0px 8px 16px 0px rgba(0, 0, 0, 0.40);
613
}
7-
box-shadow: 0 2px 6px rgba(0, 0, 0, .1), 0 8px 16px rgba(0, 0, 0, .08);
8-
padding: var(--sp-1);
9-
border-radius: 8px;
1014
}
1115

1216
.list {

special-pages/pages/new-tab/app/favorites/Favorites.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { useCustomizer } from '../customizer/Customizer.js'
55

66
export function FavoritesCustomized () {
77
const { t } = useTypedTranslation()
8-
const { id, visibility } = useVisibility()
8+
const { id, visibility, toggle, index } = useVisibility()
99

1010
// register with the visibility menu
1111
const title = t('favorites_menu_title')
12-
useCustomizer({ title, id, icon: 'shield' })
12+
useCustomizer({ title, id, icon: 'star', toggle, visibility, index })
1313

1414
if (visibility === 'hidden') {
1515
return null

special-pages/pages/new-tab/app/index.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ export async function init (messaging, baseEnvironment) {
3737
.withTextLength(baseEnvironment.urlParams.get('textLength'))
3838
.withDisplay(baseEnvironment.urlParams.get('display'))
3939

40-
console.log('environment:', environment)
41-
console.log('locale:', environment.locale)
42-
4340
const strings = environment.locale === 'en'
4441
? enStrings
4542
: await fetch(`./locales/${environment.locale}/new-tab.json`)
@@ -54,6 +51,10 @@ export async function init (messaging, baseEnvironment) {
5451
.withPlatformName(init.platform?.name)
5552
.withPlatformName(baseEnvironment.urlParams.get('platform'))
5653

54+
console.log('environment:', environment)
55+
console.log('settings:', settings)
56+
console.log('locale:', environment.locale)
57+
5758
const didCatch = (error) => {
5859
const message = error?.message || error?.error || 'unknown'
5960
messaging.reportPageException({ message })
@@ -71,9 +72,11 @@ export async function init (messaging, baseEnvironment) {
7172
debugState={environment.debugState}
7273
injectName={environment.injectName}
7374
willThrow={environment.willThrow}>
74-
<TranslationProvider translationObject={strings} fallback={strings} textLength={environment.textLength}>
75-
<Components />
76-
</TranslationProvider>
75+
<SettingsProvider settings={settings}>
76+
<TranslationProvider translationObject={strings} fallback={strings} textLength={environment.textLength}>
77+
<Components />
78+
</TranslationProvider>
79+
</SettingsProvider>
7780
</EnvironmentProvider>
7881
, root)
7982
}
@@ -82,7 +85,9 @@ export async function init (messaging, baseEnvironment) {
8285
<EnvironmentProvider
8386
debugState={environment.debugState}
8487
injectName={environment.injectName}
85-
willThrow={environment.willThrow}>
88+
willThrow={environment.willThrow}
89+
env={environment.env}
90+
>
8691
<ErrorBoundary didCatch={didCatch} fallback={<Fallback showDetails={environment.env === 'development'}/>}>
8792
<UpdateEnvironment search={window.location.search}/>
8893
<MessagingContext.Provider value={messaging}>

special-pages/pages/new-tab/app/privacy-stats/PrivacyStats.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,10 @@ export function Body ({ trackerCompanies, listAttrs = {} }) {
179179
*/
180180
export function PrivacyStatsCustomized () {
181181
const { t } = useTypedTranslation()
182-
const { visibility, id } = useVisibility()
182+
const { visibility, id, toggle, index } = useVisibility()
183183

184184
const title = t('trackerStatsMenuTitle')
185-
useCustomizer({ title, id, icon: 'star' })
185+
useCustomizer({ title, id, icon: 'shield', toggle, visibility, index })
186186

187187
if (visibility === 'hidden') {
188188
return null

special-pages/pages/new-tab/app/service.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export class Service {
147147
// some services will not implement persistence
148148
if (!this.impl.persist) return
149149

150-
// if the data never read, there's nothing to persist
150+
// if the data was never set, there's nothing to persist
151151
if (this.data === null) return
152152

153153
// send the data

special-pages/pages/new-tab/app/widget-list/WidgetList.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function WidgetList () {
3030

3131
return (
3232
<Stack gap={'var(--sp-8)'}>
33-
{widgets.map((widget) => {
33+
{widgets.map((widget, index) => {
3434
const matchingConfig = widgetConfigItems.find(item => item.id === widget.id)
3535
if (!matchingConfig) {
3636
const matching = widgetMap[widget.id]
@@ -49,6 +49,7 @@ export function WidgetList () {
4949
<WidgetVisibilityProvider
5050
visibility={matchingConfig.visibility}
5151
id={matchingConfig.id}
52+
index={index}
5253
>
5354
{widgetMap[widget.id]?.()}
5455
</WidgetVisibilityProvider>

special-pages/pages/new-tab/app/widget-list/widget-config.provider.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ const WidgetVisibilityContext = createContext({
6969
id: /** @type {WidgetConfigItem['id']} */(''),
7070
/** @type {(id: string) => void} */
7171
// eslint-disable-next-line @typescript-eslint/no-unused-vars
72-
toggle: (_id) => {}
72+
toggle: (_id) => {},
73+
/** @type {number} */
74+
index: -1
7375
})
7476

7577
export function useVisibility () {
@@ -81,6 +83,7 @@ export function useVisibility () {
8183
* @param {object} props
8284
* @param {WidgetConfigItem['id']} props.id - the current id key used for storage
8385
* @param {WidgetConfigItem['visibility']} props.visibility - the current id key used for storage
86+
* @param {number} props.index - the current id key used for storage
8487
* @param {import("preact").ComponentChild} props.children
8588
*/
8689
export function WidgetVisibilityProvider (props) {
@@ -89,10 +92,9 @@ export function WidgetVisibilityProvider (props) {
8992
return <WidgetVisibilityContext.Provider value={{
9093
visibility: props.visibility,
9194
id: props.id,
92-
toggle
95+
toggle,
96+
index: props.index
9397
}}>
94-
<div style={{ viewTransitionName: `widget-${props.id}` }}>
95-
{props.children}
96-
</div>
98+
{props.children}
9799
</WidgetVisibilityContext.Provider>
98100
}

0 commit comments

Comments
 (0)