Skip to content

Commit

Permalink
ntp: customizer polish 💅 (#1336)
Browse files Browse the repository at this point in the history
* ntp: matching designs for customizer drawer

* re-enable tests
  • Loading branch information
shakyShane authored Dec 24, 2024
1 parent 4c6371f commit ac1518c
Show file tree
Hide file tree
Showing 36 changed files with 776 additions and 332 deletions.
29 changes: 29 additions & 0 deletions special-pages/pages/new-tab/app/InlineError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { h } from 'preact';
import { ErrorBoundary } from '../../../shared/components/ErrorBoundary.js';
import { useMessaging } from './types.js';

/**
* @param {object} props
* @param {import("preact").ComponentChild} props.children
* @param {string} props.named
* @param {(message: string) => import("preact").ComponentChild} [props.fallback]
*/
export function InlineError({ children, named, fallback }) {
const messaging = useMessaging();
/**
* @param {any} error
* @param {string} id
*/
const didCatch = (error, id) => {
const message = error?.message || error?.error || 'unknown';
const composed = `Customizer section '${id}' threw an exception: ` + message;
messaging.reportPageException({ message: composed });
};
const inlineMessage = 'A problem occurred with this feature. DuckDuckGo was notified';
const fallbackElement = fallback?.(inlineMessage) || <p>{inlineMessage}</p>;
return (
<ErrorBoundary didCatch={(error) => didCatch(error, named)} fallback={fallbackElement}>
{children}
</ErrorBoundary>
);
}
4 changes: 1 addition & 3 deletions special-pages/pages/new-tab/app/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,7 @@ export function App() {
data-browser-panel
>
<div class={styles.asideContent}>
<div class={styles.asideContentInner}>
<CustomizerDrawer displayChildren={displayChildren} />
</div>
<CustomizerDrawer displayChildren={displayChildren} />
</div>
</aside>
)}
Expand Down
10 changes: 5 additions & 5 deletions special-pages/pages/new-tab/app/components/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,18 @@ body:has([data-reset-layout="true"]) .tube {
.layout[data-animating="true"] & {
overflow: hidden;
}

.layout[data-animating="false"] &[aria-hidden=true] {
visibility: hidden;
opacity: 0;
}
}

.asideContent {
opacity: 1;
width: var(--ntp-drawer-width);
}

.asideContentInner {
padding: 1rem;
padding-right: calc(1rem - var(--ntp-drawer-scroll-width));
}

.asideScroller {
&::-webkit-scrollbar {
width: var(--ntp-drawer-scroll-width);
Expand Down
81 changes: 54 additions & 27 deletions special-pages/pages/new-tab/app/components/BackgroundProvider.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Fragment, h } from 'preact';
import cn from 'classnames';
import styles from './BackgroundReceiver.module.css';
import { values } from '../customizer/values.js';
import { useContext } from 'preact/hooks';
import { useContext, useState } from 'preact/hooks';
import { CustomizerContext } from '../customizer/CustomizerProvider.js';
import { detectThemeFromHex } from '../customizer/utils.js';

Expand Down Expand Up @@ -92,20 +93,9 @@ export function BackgroundConsumer({ browser }) {
const gradient = values.gradients[background.value];
return (
<Fragment key="gradient">
<ImageCrossFade src={gradient.path}></ImageCrossFade>
<div
class={styles.root}
data-animate="false"
data-testid="BackgroundConsumer"
style={{
backgroundColor: gradient.fallback,
backgroundImage: `url(${gradient.path})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
}}
/>
<div
class={styles.root}
data-animate="false"
className={styles.root}
style={{
backgroundImage: `url(gradients/grain.png)`,
backgroundRepeat: 'repeat',
Expand All @@ -118,23 +108,60 @@ export function BackgroundConsumer({ browser }) {
}
case 'userImage': {
const img = background.value;
return (
<div
class={styles.root}
data-animate="true"
data-testid="BackgroundConsumer"
style={{
backgroundImage: `url(${img.src})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center center',
}}
></div>
);
return <ImageCrossFade src={img.src} />;
}
default: {
console.warn('Unreachable!');
return <div className={styles.root}></div>;
}
}
}

/**
* @param {object} props
* @param {string} props.src
*/
function ImageCrossFade({ src }) {
/**
* Proxy the image source, so that we can keep the old
* image around whilst the new one is loading.
*/
const [stable, setStable] = useState(src);
/**
* Trigger the animation:
*
* NOTE: this animation is deliberately NOT done purely with CSS-triggered state.
* Whilst debugging in WebKit, I found the technique below to be 100% reliable
* in terms of fading a new image over the top of an existing one.
*
* If you find a better way, please test in webkit-based browsers
*/
return (
<Fragment>
<img src={stable} class={styles.root} style={{ display: src === stable ? 'none' : 'block' }} />
<img
src={src}
class={cn(styles.root, styles.over)}
onLoad={(e) => {
const elem = /** @type {HTMLImageElement} */ (e.target);

// HACK: This is what I needed to force, to get 100% predictability. 🤷
elem.style.opacity = '0';

const anim = elem.animate([{ opacity: '0' }, { opacity: '1' }], {
duration: 250,
iterations: 1,
easing: 'ease-in-out',
fill: 'both',
});

// when the fade completes, we want to reset the stable `src`.
// This allows the image underneath to be updated but also allows us to un-mount the fader on top.
anim.onfinish = () => {
setStable(src);
};
}}
/>
</Fragment>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@
inset: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
pointer-events: none;

&[data-animate="true"] {
transition: all .3s ease-in-out;
transition: background .25s ease-in-out;
}

&[data-background-kind="default"][data-theme=dark] {
background: var(--default-dark-bg);
}
&[data-background-kind="default"][data-theme=light] {
background: var(--default-light-bg);
}
}

.under {
opacity: 1;
}
.over {
opacity: 0;
}
25 changes: 14 additions & 11 deletions special-pages/pages/new-tab/app/components/Components.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mainExamples, otherExamples } from './Examples.jsx';
import { useThemes } from '../customizer/themes.js';
import { useSignal } from '@preact/signals';
import { BackgroundConsumer } from './BackgroundProvider.js';
import { CustomizerThemesContext } from '../customizer/CustomizerProvider.js';
const url = new URL(window.location.href);

const list = {
Expand Down Expand Up @@ -32,18 +33,20 @@ export function Components() {
const { main, browser } = useThemes(dataSignal);

return (
<div class={styles.main} data-main-scroller data-theme={main}>
<BackgroundConsumer browser={browser} />
<div data-content-tube class={styles.contentTube}>
{isolated && <Isolated entries={filtered} e2e={e2e} />}
{!isolated && (
<Fragment>
<DebugBar id={ids[0]} ids={ids} entries={entries} />
<Stage entries={/** @type {any} */ (filtered)} />
</Fragment>
)}
<CustomizerThemesContext.Provider value={{ main, browser }}>
<div class={styles.main} data-main-scroller data-theme={main}>
<BackgroundConsumer browser={browser} />
<div data-content-tube class={styles.contentTube}>
{isolated && <Isolated entries={filtered} e2e={e2e} />}
{!isolated && (
<Fragment>
<DebugBar id={ids[0]} ids={ids} entries={entries} />
<Stage entries={/** @type {any} */ (filtered)} />
</Fragment>
)}
</div>
</div>
</div>
</CustomizerThemesContext.Provider>
);
}

Expand Down
5 changes: 3 additions & 2 deletions special-pages/pages/new-tab/app/components/DismissButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import styles from './DismissButton.module.css';
* @param {object} props
* @param {string} [props.className]
* @param {() => void} [props.onClick]
* @param {import("preact").ComponentProps<"button"> & Record<string, string>} [props.buttonProps]
*/
export function DismissButton({ className, onClick }) {
export function DismissButton({ className, onClick, buttonProps = {} }) {
const { t } = useTypedTranslation();

return (
<button class={cn(styles.btn, className)} onClick={onClick} aria-label={t('ntp_dismiss')} data-testid="dismissBtn">
<button class={cn(styles.btn, className)} onClick={onClick} aria-label={t('ntp_dismiss')} data-testid="dismissBtn" {...buttonProps}>
<Cross />
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
border-radius: 50%;
transition: all .3s;

svg {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}

&:hover {
background-color: var(--color-black-at-9);
cursor: pointer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,40 +42,33 @@ export function BackgroundSection({ data, onNav, onUpload, select }) {
}

return (
<div class={styles.section}>
<h3 class={styles.sectionTitle}>Background</h3>
<ul class={cn(styles.sectionBody, styles.bgList)} role="radiogroup">
<li class={styles.bgListItem}>
<DefaultPanel
checked={data.value.background.kind === 'default'}
onClick={() => select({ background: { kind: 'default' } })}
/>
</li>
<li class={styles.bgListItem}>
<ColorPanel
checked={data.value.background.kind === 'color' || data.value.background.kind === 'hex'}
color={displayColor}
onClick={() => onNav('color')}
/>
</li>
<li class={styles.bgListItem}>
<GradientPanel
checked={data.value.background.kind === 'gradient'}
gradient={gradient}
onClick={() => onNav('gradient')}
/>
</li>
<li class={styles.bgListItem}>
<BackgroundImagePanel
checked={data.value.background.kind === 'userImage'}
onClick={() => onNav('image')}
data={data}
upload={onUpload}
browserTheme={browser}
/>
</li>
</ul>
</div>
<ul class={cn(styles.bgList)} role="radiogroup">
<li class={styles.bgListItem}>
<DefaultPanel
checked={data.value.background.kind === 'default'}
onClick={() => select({ background: { kind: 'default' } })}
/>
</li>
<li class={styles.bgListItem}>
<ColorPanel
checked={data.value.background.kind === 'color' || data.value.background.kind === 'hex'}
color={displayColor}
onClick={() => onNav('color')}
/>
</li>
<li class={styles.bgListItem}>
<GradientPanel checked={data.value.background.kind === 'gradient'} gradient={gradient} onClick={() => onNav('gradient')} />
</li>
<li class={styles.bgListItem}>
<BackgroundImagePanel
checked={data.value.background.kind === 'userImage'}
onClick={() => onNav('image')}
data={data}
upload={onUpload}
browserTheme={browser}
/>
</li>
</ul>
);
}

Expand Down
Loading

0 comments on commit ac1518c

Please sign in to comment.