Skip to content

Commit

Permalink
pr feedback on animations
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane Osbourne committed Oct 11, 2024
1 parent ccb9d2c commit 5527bec
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 80 deletions.
168 changes: 107 additions & 61 deletions packages/special-pages/pages/new-tab/app/components/Components.jsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,20 @@
import { Fragment, h } from "preact";
import styles from "./Components.module.css";
import { Body, Heading, PrivacyStatsConsumer } from "../privacy-stats/PrivacyStats.js";

import { PrivacyStatsMockProvider } from "../privacy-stats/mocks/PrivacyStatsMockProvider.js";
import { stats } from "../privacy-stats/mocks/stats.js";

/** @type {Record<string, {factory: () => import("preact").ComponentChild}>} */
const examples = {
'stats.few': {
factory: () => <PrivacyStatsMockProvider ticker={true}><PrivacyStatsConsumer /></PrivacyStatsMockProvider>
},
'stats.few.collapsed': {
factory: () => <PrivacyStatsMockProvider config={{expansion: "collapsed"}}><PrivacyStatsConsumer /></PrivacyStatsMockProvider>
},
'stats.single': {
factory: () => <PrivacyStatsMockProvider data={stats.single}><PrivacyStatsConsumer /></PrivacyStatsMockProvider>
},
'stats.none': {
factory: () => <PrivacyStatsMockProvider data={stats.none}><PrivacyStatsConsumer /></PrivacyStatsMockProvider>
},
'stats.norecent': {
factory: () => <PrivacyStatsMockProvider data={stats.norecent}><PrivacyStatsConsumer /></PrivacyStatsMockProvider>
},
'stats.list': {
factory: () => <Body trackerCompanies={stats.few.trackerCompanies} />
},
'stats.heading': {
factory: () => <Heading trackerCompanies={stats.few.trackerCompanies} totalCount={stats.few.totalCount} />
},
'stats.heading.none': {
factory: () => <Heading trackerCompanies={stats.none.trackerCompanies} totalCount={stats.none.totalCount} />
},
}
import { mainExamples, otherExamples } from "./Examples.jsx";

const url = new URL(window.location.href);

export function Components() {
const ids = url.searchParams.getAll("id");
const isolated = url.searchParams.has("isolate");
const e2e = url.searchParams.has("e2e");
const entries = Object.entries(examples);
const entries = Object.entries(mainExamples).concat(Object.entries(otherExamples));
const entryIds = entries.map(([id]) => id);

const validIds = ids.filter(id => entryIds.includes(id));

const validIds = ids.filter(id => (id || '') in examples)
const filtered = validIds.length
? validIds.map((id) => /** @type {const} */([id, examples[id]]))
? validIds.map((id) => /** @type {const} */([id, mainExamples[id] || otherExamples[id]]))
: entries

if (isolated) {
Expand All @@ -52,7 +23,7 @@ export function Components() {

return (
<div>
<DebugBar id={ids[0]} entries={entries}/>
<DebugBar id={ids[0]} ids={ids} entries={entries}/>
<Stage entries={/** @type {any} */(filtered)} />
</div>
)
Expand All @@ -72,15 +43,36 @@ function Stage({ entries }) {
next.searchParams.set('isolate', 'true');
next.searchParams.set('id', id);

const selected = new URL(url)
selected.searchParams.set('id', id);

const e2e = new URL(url)
e2e.searchParams.set('isolate', 'true');
e2e.searchParams.set('id', id);
e2e.searchParams.set('e2e', 'true');

const without = new URL(url);
const current = without.searchParams.getAll('id');
const others = current.filter(x => x !== id);
const matching = current.filter(x => x === id);
const matchingMinus1 = matching.length === 1
? []
: matching.slice(0, -1)

without.searchParams.delete('id');
for (let string of [...others, ...matchingMinus1]) {
without.searchParams.append('id', string)
}

return (
<Fragment>
<div class={styles.itemInfo}>
<code>{id}</code>{" "}
<a href={without.toString()} hidden={current.length === 0}>Remove</a>
<div>
<a href={selected.toString()}
class={styles.itemLink}
title="show this component only">select</a>{" "}
<a href={next.toString()}
target="_blank"
class={styles.itemLink}
Expand All @@ -102,7 +94,6 @@ function Stage({ entries }) {
}

function Isolated({ entries, e2e }) {
console.log(entries.length);
if (e2e) {
return (
<div>
Expand All @@ -129,6 +120,19 @@ function Isolated({ entries, e2e }) {
)
}

function DebugBar({ entries, id, ids }) {
return (
<div class={styles.debugBar} data-testid="selector">
<ExampleSelector entries={entries} id={id} />
{ids.length > 0 && (
<Append entries={entries} id={id} />
)}
<TextLength />
<Isolate />
</div>
)
}

function TextLength() {
function onClick() {
url.searchParams.set('textLength', '1.5')
Expand All @@ -151,23 +155,19 @@ function Isolate() {
next.searchParams.set('isolate', 'true');
return (
<div class={styles.buttonRow}>
<a href={next.toString()} target={"_blank"}>Isolate</a>
</div>
)
}

function DebugBar({ entries, id }) {
return (
<div class={styles.debugBar} data-testid="selector">
<ExampleSelector entries={entries} id={id} />
<TextLength />
<Isolate />
<a href={next.toString()} target={"_blank"}>Isolate (open in a new tab)</a>
</div>
)
}

/**
* Allows users to select an example from a list and update the URL accordingly.
*
* @param {Object} options - The options object.
* @param {Array} options.entries - The list of examples to choose from, each represented as an array with an id.
* @param {string} options.id - The current selected example id.
*/
function ExampleSelector({ entries, id }) {

function onReset() {
const url = new URL(window.location.href);
url.searchParams.delete("id");
Expand All @@ -186,17 +186,63 @@ function ExampleSelector({ entries, id }) {
}
}
return (
<div class={styles.buttonRow}>
<label>
Example:{" "}
<select value={id || 'none'} onChange={onChange}>
<option value='none'>Select an example</option>
{entries.map(([id]) => (
<option key={id} value={id}>{id}</option>
))}
</select>
</label>
<button hidden={!id} onClick={onReset}>Reset</button>
</div>
<Fragment>
<div class={styles.buttonRow}>
<label>
Single:{" "}
<select value={id || 'none'} onChange={onChange}>
<option value='none'>Select an example</option>
{entries.map(([id]) => (
<option key={id} value={id}>{id}</option>
))}
</select>
</label>
<button onClick={onReset}>RESET 🔁</button>
</div>
</Fragment>
)
}

/**
* Allows users to select an example from a list and update the URL accordingly.
*
* @param {Object} options - The options object.
* @param {Array} options.entries - The list of examples to choose from, each represented as an array with an id.
* @param {string} options.id - The current selected example id.
*/
function Append({ entries, id }) {
function onReset() {
const url = new URL(window.location.href);
url.searchParams.delete("id");
window.location.href = url.toString();
}

function onSubmit(event) {
if (!event.target) return;
event.preventDefault();
const form = event.target;
const data = new FormData(form);
const value = data.get('add-id');
if (typeof value !== "string") return;
const url = new URL(window.location.href);
url.searchParams.append("id", value);
window.location.href = url.toString();
}

return (
<Fragment>
<form class={styles.buttonRow} onSubmit={onSubmit}>
<label>
Append:{" "}
<select value='none' name="add-id">
<option value='none'>Select an example</option>
{entries.map(([id]) => (
<option key={id} value={id}>{id}</option>
))}
</select>
</label>
<button>Confirm</button>
</form>
</Fragment>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ body[data-display="components"] {
grid-row-gap: .5rem;
align-items: center;
grid-column-gap: 1rem;
grid-template-columns: max-content max-content;
grid-template-columns: max-content;
}

.buttonRow {
Expand Down
Empty file.
40 changes: 40 additions & 0 deletions packages/special-pages/pages/new-tab/app/components/Examples.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { h } from "preact";
import { PrivacyStatsMockProvider } from "../privacy-stats/mocks/PrivacyStatsMockProvider.js";
import { Body, Heading, PrivacyStatsConsumer } from "../privacy-stats/PrivacyStats.js";
import { stats } from "../privacy-stats/mocks/stats.js";

/** @type {Record<string, {factory: () => import("preact").ComponentChild}>} */
export const mainExamples = {
'stats.few': {
factory: () => <PrivacyStatsMockProvider ticker={true}><PrivacyStatsConsumer/></PrivacyStatsMockProvider>
},
'stats.few.collapsed': {
factory: () => <PrivacyStatsMockProvider
config={{expansion: "collapsed"}}><PrivacyStatsConsumer/></PrivacyStatsMockProvider>
},
'stats.single': {
factory: () => <PrivacyStatsMockProvider data={stats.single}><PrivacyStatsConsumer/></PrivacyStatsMockProvider>
},
'stats.none': {
factory: () => <PrivacyStatsMockProvider data={stats.none}><PrivacyStatsConsumer/></PrivacyStatsMockProvider>
},
'stats.norecent': {
factory: () => <PrivacyStatsMockProvider
data={stats.norecent}><PrivacyStatsConsumer/></PrivacyStatsMockProvider>
},
'stats.list': {
factory: () => <Body trackerCompanies={stats.few.trackerCompanies}/>
},
'stats.heading': {
factory: () => <Heading trackerCompanies={stats.few.trackerCompanies} totalCount={stats.few.totalCount}/>
},
'stats.heading.none': {
factory: () => <Heading trackerCompanies={stats.none.trackerCompanies} totalCount={stats.none.totalCount}/>
},
}

export const otherExamples = {
'stats.with-animation': {
factory: () => <PrivacyStatsMockProvider ticker={true} animate={true}><PrivacyStatsConsumer/></PrivacyStatsMockProvider>
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
border: none;
border-radius: 4px;
cursor: pointer;
transition: transform ease-in-out .3s, background-color ease-in-out .2s;
transition: background-color ease-in-out .2s;

color: var(--color-black-at-48);
@media screen and (prefers-color-scheme: dark) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ import { reducer } from '../../service.hooks.js'
* @param {import("preact").ComponentChild} [props.children] - The children elements to be rendered.
* @param {StatsConfig} [props.config]
* @param {PrivacyStatsData} [props.data]
* @param {boolean} [props.animate=false]
* @param {boolean} [props.ticker] - if true, gradually increment the count of the first company, for testing
*
*/
export function PrivacyStatsMockProvider ({ data = stats.few, config = { expansion: 'expanded' }, ticker = false, children }) {
export function PrivacyStatsMockProvider ({
data = stats.few,
config = { expansion: 'expanded' },
animate = false,
ticker = false,
children
}) {
const { isReducedMotion } = useEnv()
const initial = /** @type {import('../PrivacyStatsProvider.js').State} */({
status: 'ready',
Expand Down Expand Up @@ -54,12 +61,13 @@ export function PrivacyStatsMockProvider ({ data = stats.few, config = { expansi

const providerData = /** @type {any} */(state)
const toggle = useCallback(() => {
const willAnimate = !isReducedMotion && animate === true
if (state.config?.expansion === 'expanded') {
send({ kind: 'config', config: { expansion: 'collapsed' }, animate: !isReducedMotion })
send({ kind: 'config', config: { expansion: 'collapsed' }, animate: willAnimate })
} else {
send({ kind: 'config', config: { expansion: 'expanded' }, animate: !isReducedMotion })
send({ kind: 'config', config: { expansion: 'expanded' }, animate: willAnimate })
}
}, [state.config?.expansion, isReducedMotion])
}, [state.config?.expansion, isReducedMotion, animate])

return (
<PrivacyStatsContext.Provider value={{ state: providerData, toggle }}>
Expand Down
16 changes: 3 additions & 13 deletions packages/special-pages/pages/new-tab/app/service.hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
*/

import { useCallback, useEffect } from 'preact/hooks'
import { useEnv } from '../../../shared/components/EnvironmentProvider.js'

/**
* @template D
Expand Down Expand Up @@ -166,13 +165,11 @@ export function useDataSubscription ({ dispatch, service }) {
* @param {object} params
* @param {import("preact/hooks").Dispatch<any>} params.dispatch
* @param {import("preact").RefObject<{
* onConfig: (cb: (d: {data:Config, source: 'manual' | 'subscription'})=>void) => () => void;
* onConfig: (cb: (event: { data:Config, source: 'manual' | 'subscription'}) => void) => () => void;
* toggleExpansion: () => void;
* }>} params.service
*/
export function useConfigSubscription ({ dispatch, service }) {
const { isReducedMotion } = useEnv()

/**
* Consumers can call 'toggle' and the in-memory data will be updated in the service
* The result of that toggle will be broadcasts immediately, allowing real-time (optimistic) UI updates
Expand All @@ -184,19 +181,12 @@ export function useConfigSubscription ({ dispatch, service }) {
useEffect(() => {
if (!service.current) return console.warn('could not access service')
const unsub2 = service.current.onConfig((data) => {
const animate = !isReducedMotion && data.source === 'manual'
if ('startViewTransition' in document && typeof document.startViewTransition === 'function') {
document.startViewTransition(() => {
dispatch({ kind: 'config', config: data.data, animate: false })
})
} else {
dispatch({ kind: 'config', config: data.data, animate })
}
dispatch({ kind: 'config', config: data.data, animate: false })
})
return () => {
unsub2()
}
}, [service, isReducedMotion])
}, [service])

return { toggle }
}

0 comments on commit 5527bec

Please sign in to comment.