Skip to content
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
7 changes: 4 additions & 3 deletions special-pages/pages/history/app/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { h } from 'preact';
import cn from 'classnames';
import styles from './App.module.css';
import { useEnv } from '../../../../shared/components/EnvironmentProvider.js';
import { Header } from './Header.js';
import { ResultsContainer } from './Results.js';
import { useEffect, useRef } from 'preact/hooks';
Expand All @@ -20,11 +19,12 @@ import { useRangesData } from '../global/Providers/HistoryServiceProvider.js';
import { usePlatformName } from '../types.js';
import { useLayoutMode } from '../global/hooks/useLayoutMode.js';
import { useClickAnywhereElse } from '../global/hooks/useClickAnywhereElse.jsx';
import { useTheme } from '../global/Providers/ThemeProvider.js';

export function App() {
const platformName = usePlatformName();
const mainRef = useRef(/** @type {HTMLElement|null} */ (null));
const { isDarkMode } = useEnv();
const { theme, themeVariant } = useTheme();
const ranges = useRangesData();
const query = useQueryContext();
const mode = useLayoutMode();
Expand Down Expand Up @@ -66,7 +66,8 @@ export function App() {
return (
<div
class={styles.layout}
data-theme={isDarkMode ? 'dark' : 'light'}
data-theme={theme}
data-theme-variant={themeVariant}
data-platform={platformName}
data-layout-mode={mode}
onClick={clickAnywhere}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createContext, h } from 'preact';
import { useContext, useEffect, useState } from 'preact/hooks';
import { useMessaging } from '../../types.js';
import { useEnv } from '../../../../../shared/components/EnvironmentProvider.js';

/**
* @typedef {import('../../../types/history').BrowserTheme} BrowserTheme
* @typedef {import('../../../types/history').ThemeVariant} ThemeVariant
*/

const ThemeContext = createContext({
/** @type {BrowserTheme} */
theme: 'light',
/** @type {ThemeVariant} */
themeVariant: 'default',
});

/**
* @param {object} props
* @param {import('preact').ComponentChild} props.children
* @param {BrowserTheme | undefined} props.initialTheme
* @param {ThemeVariant | undefined} props.initialThemeVariant
*/
export function ThemeProvider({ children, initialTheme, initialThemeVariant }) {
const { isDarkMode } = useEnv();
const history = useMessaging();

// Track explicit theme updates from onThemeUpdate subscription
const [explicitTheme, setExplicitTheme] = useState(/** @type {BrowserTheme | undefined} */ (undefined));
const [explicitThemeVariant, setExplicitThemeVariant] = useState(/** @type {ThemeVariant | undefined} */ (undefined));

useEffect(() => {
const unsubscribe = history.messaging.subscribe('onThemeUpdate', (data) => {
setExplicitTheme(data.theme);
setExplicitThemeVariant(data.themeVariant);
});
return unsubscribe;
}, [history]);

// Derive theme from explicit updates, initial theme, or system preference (in that order)
const theme = explicitTheme ?? initialTheme ?? (isDarkMode ? 'dark' : 'light');
const themeVariant = explicitThemeVariant ?? initialThemeVariant ?? 'default';

return <ThemeContext.Provider value={{ theme, themeVariant }}>{children}</ThemeContext.Provider>;
}

export function useTheme() {
return useContext(ThemeContext);
}
24 changes: 15 additions & 9 deletions special-pages/pages/history/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Settings } from './Settings.js';
import { SelectionProvider } from './global/Providers/SelectionProvider.js';
import { QueryProvider } from './global/Providers/QueryProvider.js';
import { InlineErrorBoundary } from '../../../shared/components/InlineErrorBoundary.js';
import { ThemeProvider } from './global/Providers/ThemeProvider.js';

/**
* @param {Element} root
Expand Down Expand Up @@ -84,15 +85,17 @@ export async function init(root, messaging, baseEnvironment) {
<UpdateEnvironment search={window.location.search} />
<TranslationProvider translationObject={strings} fallback={enStrings} textLength={environment.textLength}>
<MessagingContext.Provider value={messaging}>
<SettingsContext.Provider value={settings}>
<QueryProvider query={query.query}>
<HistoryServiceProvider service={service} initial={initial}>
<SelectionProvider>
<App />
</SelectionProvider>
</HistoryServiceProvider>
</QueryProvider>
</SettingsContext.Provider>
<ThemeProvider initialTheme={init.theme} initialThemeVariant={init.themeVariant}>
<SettingsContext.Provider value={settings}>
<QueryProvider query={query.query}>
<HistoryServiceProvider service={service} initial={initial}>
<SelectionProvider>
<App />
</SelectionProvider>
</HistoryServiceProvider>
</QueryProvider>
</SettingsContext.Provider>
</ThemeProvider>
</MessagingContext.Provider>
</TranslationProvider>
</EnvironmentProvider>
Expand All @@ -117,6 +120,9 @@ export async function init(root, messaging, baseEnvironment) {
* @param {import("../types/history.ts").DefaultStyles | null | undefined} defaultStyles
*/
function applyDefaultStyles(defaultStyles) {
if (defaultStyles?.lightBackgroundColor || defaultStyles?.darkBackgroundColor) {
console.warn('defaultStyles is deprecated. Use themeVariant instead. This will override theme variant colors.', defaultStyles);
}
if (defaultStyles?.lightBackgroundColor) {
document.body.style.setProperty('--default-light-background-color', defaultStyles.lightBackgroundColor);
}
Expand Down
17 changes: 17 additions & 0 deletions special-pages/pages/history/app/mocks/mock-transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,23 @@ export function mockTransport() {
},
};

// Allow theme override via URL params
if (url.searchParams.has('theme')) {
const value = url.searchParams.get('theme');
if (value === 'light' || value === 'dark') {
initial.theme = /** @type {import('../../types/history.ts').BrowserTheme} */ (value);
}
}

// Allow themeVariant override via URL params
if (url.searchParams.has('themeVariant')) {
const value = url.searchParams.get('themeVariant');
const validVariants = ['default', 'coolGray', 'slateBlue', 'green', 'violet', 'rose', 'orange', 'desert'];
if (value && validVariants.includes(value)) {
initial.themeVariant = /** @type {import('../../types/history.ts').ThemeVariant} */ (value);
}
}

return Promise.resolve(initial);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { test } from '@playwright/test';
import { HistoryTestPage } from './history.page.js';

test.describe('history theme and theme variants', () => {
test('setting theme = dark and themeVariant via initialSetup', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo);
await hp.openPage({ additional: { theme: 'dark', themeVariant: 'violet' } });
await hp.hasTheme('dark', 'violet');
await hp.hasBackgroundColor({ hex: '#2e2158' });
});

test('setting theme = light and themeVariant via initialSetup', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo);
await hp.openPage({ additional: { theme: 'light', themeVariant: 'coolGray' } });
await hp.hasTheme('light', 'coolGray');
await hp.hasBackgroundColor({ hex: '#d2d5e3' });
});

test('light theme and default themeVariant when unspecified', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo);
await hp.openPage();
await hp.hasTheme('light', 'default');
await hp.hasBackgroundColor({ hex: '#fafafa' });
});

test('dark theme and default themeVariant when unspecified', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo);
await hp.darkMode();
await hp.openPage();
await hp.hasTheme('dark', 'default');
await hp.hasBackgroundColor({ hex: '#333333' });
});

test('changing theme to dark and themeVariant using onThemeUpdate', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo);
await hp.openPage({ additional: { theme: 'light', themeVariant: 'desert' } });
await hp.hasTheme('light', 'desert');
await hp.hasBackgroundColor({ hex: '#eee9e1' });
await hp.acceptsThemeUpdate('dark', 'slateBlue');
await hp.hasTheme('dark', 'slateBlue');
await hp.hasBackgroundColor({ hex: '#1e3347' });
});

test('changing theme to light and themeVariant using onThemeUpdate', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo);
await hp.openPage({ additional: { theme: 'dark', themeVariant: 'rose' } });
await hp.hasTheme('dark', 'rose');
await hp.hasBackgroundColor({ hex: '#5b194b' });
await hp.acceptsThemeUpdate('light', 'green');
await hp.hasTheme('light', 'green');
await hp.hasBackgroundColor({ hex: '#e3eee1' });
});
});
17 changes: 17 additions & 0 deletions special-pages/pages/history/integration-tests/history.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,23 @@ export class HistoryTestPage {
});
expect(borderTopColor).toBe(rgb);
}

/**
* @param {import('../types/history.ts').BrowserTheme} theme
* @param {import('../types/history.ts').ThemeVariant} themeVariant
*/
async acceptsThemeUpdate(theme, themeVariant) {
await this.mocks.simulateSubscriptionMessage('onThemeUpdate', { theme, themeVariant });
}

/**
* @param {import('../types/history.ts').BrowserTheme} theme
* @param {import('../types/history.ts').ThemeVariant} themeVariant
*/
async hasTheme(theme, themeVariant) {
await expect(this.page.locator('[data-layout-mode]')).toHaveAttribute('data-theme', theme);
await expect(this.page.locator('[data-layout-mode]')).toHaveAttribute('data-theme-variant', themeVariant);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@
}
}
},
"theme": {
"$ref": "./types/browser-theme.json"
},
"themeVariant": {
"$ref": "./types/theme-variant.json"
},
"customizer": {
"type": "object",
"properties": {
"defaultStyles": {
"deprecated": true,
"oneOf": [
{
"type": "null"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["theme", "themeVariant"],
"properties": {
"theme": {
"$ref": "./types/browser-theme.json"
},
"themeVariant": {
"$ref": "./types/theme-variant.json"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Browser Theme",
"enum": [
"light",
"dark"
]
}
14 changes: 14 additions & 0 deletions special-pages/pages/history/messages/types/theme-variant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Theme Variant",
"enum": [
"default",
"coolGray",
"slateBlue",
"green",
"violet",
"rose",
"orange",
"desert"
]
}
37 changes: 37 additions & 0 deletions special-pages/pages/history/styles/history-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,40 @@ body {
--history-text-invert: var(--color-black-at-84);
--history-text-muted: var(--color-white-at-60);
}

/* TODO: Use colour variables from design-tokens */

[data-theme-variant="coolGray"] {
--default-light-background-color: #d2d5e3;
--default-dark-background-color: #2b2f45;
}

[data-theme-variant="slateBlue"] {
--default-light-background-color: #d2e5f3;
--default-dark-background-color: #1e3347;
}

[data-theme-variant="green"] {
--default-light-background-color: #e3eee1;
--default-dark-background-color: #203b30;
}

[data-theme-variant="violet"] {
--default-light-background-color: #e7e4f5;
--default-dark-background-color: #2e2158;
}

[data-theme-variant="rose"] {
--default-light-background-color: #f8ebf5;
--default-dark-background-color: #5b194b;
}

[data-theme-variant="orange"] {
--default-light-background-color: #fcedd8;
--default-dark-background-color: #54240c;
}

[data-theme-variant="desert"] {
--default-light-background-color: #eee9e1;
--default-dark-background-color: #3c3833;
}
22 changes: 21 additions & 1 deletion special-pages/pages/history/types/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export type RangeId =
| "sunday"
| "older"
| "sites";
export type BrowserTheme = "light" | "dark";
export type ThemeVariant = "default" | "coolGray" | "slateBlue" | "green" | "violet" | "rose" | "orange" | "desert";
export type QueryKind = SearchTerm | DomainFilter | RangeFilter;
/**
* Indicates the query was triggered before the UI was rendered
Expand Down Expand Up @@ -65,6 +67,7 @@ export interface HistoryMessages {
| GetRangesRequest
| InitialSetupRequest
| QueryRequest;
subscriptions: OnThemeUpdateSubscription;
}
/**
* Generated from @see "../messages/open.notify.json"
Expand Down Expand Up @@ -197,7 +200,12 @@ export interface InitialSetupResponse {
platform: {
name: "macos" | "windows" | "android" | "ios" | "integration";
};
theme?: BrowserTheme;
themeVariant?: ThemeVariant;
customizer?: {
/**
* @deprecated
*/
defaultStyles?: null | DefaultStyles;
};
}
Expand Down Expand Up @@ -286,10 +294,22 @@ export interface HistoryItem {
url: string;
favicon?: Favicon;
}
/**
* Generated from @see "../messages/onThemeUpdate.subscribe.json"
*/
export interface OnThemeUpdateSubscription {
subscriptionEvent: "onThemeUpdate";
params: OnThemeUpdateSubscribe;
}
export interface OnThemeUpdateSubscribe {
theme: BrowserTheme;
themeVariant: ThemeVariant;
}

declare module "../src/index.js" {
export interface HistoryPage {
notify: import("@duckduckgo/messaging/lib/shared-types").MessagingBase<HistoryMessages>['notify'],
request: import("@duckduckgo/messaging/lib/shared-types").MessagingBase<HistoryMessages>['request']
request: import("@duckduckgo/messaging/lib/shared-types").MessagingBase<HistoryMessages>['request'],
subscribe: import("@duckduckgo/messaging/lib/shared-types").MessagingBase<HistoryMessages>['subscribe']
}
}
1 change: 1 addition & 0 deletions special-pages/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default defineConfig({
'activity.spec.js',
'history.spec.js',
'history-selections.spec.js',
'history-theme.spec.js',
'history.screenshots.spec.js',
'protections.spec.js',
'protections.screenshots.spec.js',
Expand Down
Loading