Skip to content

Commit

Permalink
show-hide for favorites
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane Osbourne committed Oct 10, 2024
1 parent 62d9428 commit 41a6cff
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"$ref": "./types/favorites-config.json"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"$ref": "./types/favorites-config.json"
}
]
}
11 changes: 11 additions & 0 deletions packages/special-pages/pages/new-tab/app/components/Chevron.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { h } from 'preact'

export function Chevron () {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 10L8 4.5L2.5 10" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
)
}
88 changes: 71 additions & 17 deletions packages/special-pages/pages/new-tab/app/components/Components.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,30 @@ const examples = {
<MockFavoritesProvider data={favorites.many}><FavoritesConsumer /></MockFavoritesProvider>
)
},
'favorites.few': {
'favorites.few.7': {
factory: () => (
<MockFavoritesProvider data={{favorites: favorites.many.favorites.slice(0, 7)}}><FavoritesConsumer /></MockFavoritesProvider>
)
},
'favorites.few.6': {
factory: () => (
<MockFavoritesProvider data={{favorites: favorites.many.favorites.slice(0, 6)}}><FavoritesConsumer /></MockFavoritesProvider>
)
},
'favorites.few.5': {
factory: () => (
<MockFavoritesProvider data={{favorites: favorites.many.favorites.slice(0, 5)}}><FavoritesConsumer /></MockFavoritesProvider>
)
},
'favorites.multi': {
factory: () => (
<div>
<MockFavoritesProvider data={favorites.many}><FavoritesConsumer /></MockFavoritesProvider>
<br/>
<MockFavoritesProvider data={favorites.two}><FavoritesConsumer /></MockFavoritesProvider>
<br/>
<MockFavoritesProvider data={favorites.single}><FavoritesConsumer /></MockFavoritesProvider>
<br/>
<MockFavoritesProvider data={favorites.none}><FavoritesConsumer /></MockFavoritesProvider>
</div>
)
Expand All @@ -69,22 +82,24 @@ const examples = {
const url = new URL(window.location.href);

export function Components() {
const id = url.searchParams.get("id");
const ids = url.searchParams.getAll("id");
const isolated = url.searchParams.has("isolate");
const valid = (id||'') in examples;
const e2e = url.searchParams.has("e2e");
const entries = Object.entries(examples);
const filtered = id && valid
? entries.filter(([_id]) => _id === id)

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

if (isolated) {
return <Isolated entries={filtered} />
return <Isolated entries={filtered} e2e={e2e} />
}

return (
<div>
<DebugBar id={id} entries={entries}/>
<Stage entries={filtered} />
<DebugBar id={ids[0]} entries={entries}/>
<Stage entries={/** @type {any} */(filtered)} />
</div>
)
}
Expand All @@ -99,23 +114,62 @@ function Stage({ entries }) {
return (
<div class={styles.componentList} data-testid="stage">
{entries.map(([id, item]) => {
const next = new URL(url)
next.searchParams.set('isolate', 'true');
next.searchParams.set('id', id);

const e2e = new URL(url)
e2e.searchParams.set('isolate', 'true');
e2e.searchParams.set('id', id);
e2e.searchParams.set('e2e', 'true');
return (
<div className={styles.item} key={id}>
{item.factory()}
</div>
<Fragment>
<div class={styles.itemInfo}>
<code>{id}</code>{" "}
<div>
<a href={next.toString()}
target="_blank"
class={styles.itemLink}
title="isolate this component">isolate</a>{" "}
<a href={e2e.toString()}
target="_blank"
class={styles.itemLink}
title="isolate this component">edge-to-edge</a>
</div>
</div>
<div className={styles.item} key={id}>
{item.factory()}
</div>
</Fragment>
)
})}
</div>
)
}

function Isolated({ entries }) {
function Isolated({ entries, e2e }) {
console.log(entries.length);
if (e2e) {
return (
<div>
{entries.map(([id, item]) => {
return (
<Fragment key={id}>
{item.factory()}
</Fragment>
)
})}
</div>
)
}
return (
<div>
{entries.map(([id, item]) => {
return <Fragment key={id}>
{item.factory()}
</Fragment>
<div class={styles.componentList} data-testid="stage">
{entries.map(([id, item], index) => {
return (
<div key={id + index}>
{item.factory()}
</div>
)
})}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@ body[data-display="components"] {
display: grid;
grid-template-columns: auto;
grid-row-gap: 2rem;


}

.itemInfo {
display: flex;
align-items: center;
gap: 1em;
}
.itemLink {
padding: 0.2em 0.3em;
border: 1px solid var(--color-gray-60);
border-radius: 4px;
display: inline-block;
line-height: 1;
text-decoration: none;

&:hover {
background: var(--color-gray-20);
}

@media screen and (prefers-color-scheme: dark) {
&:hover {
background: var(--color-gray-90);
}
}
}

.debugBar {
Expand Down
128 changes: 104 additions & 24 deletions packages/special-pages/pages/new-tab/app/favorites/Favorites.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { h } from 'preact'
import cn from 'classnames'
import { useVisibility } from '../widget-list/widget-config.provider.js'
import styles from './Favorites.module.css'
import { useContext } from 'preact/hooks'
import { useCallback, useContext } from 'preact/hooks'
import { TileMemo } from './Tile.js'
import { FavoritesContext, FavoritesDispatchContext, FavoritesProvider } from './FavoritesProvider.js'
import { useGridState } from './FavouritesGrid.js'
import { memo } from 'preact/compat'
import { Chevron } from '../components/Chevron.js'
import { useTypedTranslation } from '../types.js'
import { useEnv } from '../../../../shared/components/EnvironmentProvider.js'

/**
* @typedef {import('../../../../types/new-tab').Expansion} Expansion
Expand All @@ -15,39 +18,114 @@ import { memo } from 'preact/compat'
* @typedef {import('../../../../types/new-tab').FavoritesConfig} FavoritesConfig
*/

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const states = /** @type {const} */([
// in this instance, there's more to see if the user wishes to do so
// for example, capacity is 6. user has 6 favorites, and the additional `+` icon makes us over limit
'collapsed_over_capacity',

// in this instance, there's nothing more to show
// for example; the user has 5 favorites; the 6th item is the '+' button, meaning we have nothing to show
'collapsed_within_capacity',

// if expanded + over capacity (eg: anything over 6)
// for example, the user has 10 favorites - everything will be shown, but we can allow it to become collapsed
'expanded_over_capacity',

// we can be 'expanded', but not have enough elements to allow it to become collapsed,
// for example, we could technically be 'expanded' but only have a single favorite
'expanded_within_capacity'
])

/**
* @typedef {states[number]} ShowHideState
*/

/**
* @param {object} props
* @param {Favorite[]} props.favorites
* @param {(list: Favorite[]) => void} props.listDidReOrder
* @param {Expansion} props.expansion
* @param {() => void} props.toggle
*/
export function Favorites ({ favorites, listDidReOrder }) {
export function Favorites ({ favorites, listDidReOrder, expansion, toggle }) {
useGridState(favorites, listDidReOrder)
const { isReducedMotion } = useEnv()
const placeholders = calculatePlaceholders(favorites.length, 6)
const total = favorites.length + placeholders

const items = favorites.map((item) => (
<TileMemo
data={item.data}
favicon={item.favicon}
title={item.title}
key={item.id}
id={item.id}
/>
)).concat(Array.from({ length: placeholders }).map((_, index) => {
if (index === 0) {
return <PlusIcon key={`placeholder-${index}`}/>
}
return (
<div key={`placeholder-${index}`} class={cn(styles.icon, styles.placeholder)}/>
)
}))

const onToggle = useCallback(() => {
if (isReducedMotion) {
return toggle()
}

if ('startViewTransition' in document && typeof document.startViewTransition === 'function') {
return document.startViewTransition(toggle)
}

toggle()
}, [isReducedMotion])

return (
<div class={styles.root}>
<div class={styles.grid}>
{favorites.map((item) => (
<TileMemo
data={item.data}
favicon={item.favicon}
title={item.title}
key={item.id}
id={item.id}
/>
))}
{
Array.from({ length: placeholders }).map((_, index) => {
if (index === 0) {
return <PlusIcon key={`placeholder-${index}`} />
}
return (
<div key={`placeholder-${index}`} class={cn(styles.icon, styles.placeholder)} />
)
})
}
{items.slice(0, expansion === 'expanded' ? undefined : 6)}
</div>
<ShowHide total={total} expansion={expansion} toggle={onToggle} />
</div>
)
}

function ShowHide ({ total, expansion, toggle }) {
const { t } = useTypedTranslation()
/** @type {ShowHideState} */
let state
if (expansion === 'expanded') {
if (total > 6) {
state = 'expanded_over_capacity'
} else {
state = 'expanded_within_capacity'
}
} else {
if (total > 6) {
state = 'collapsed_over_capacity'
} else {
state = 'collapsed_within_capacity'
}
}
return (
<div className={cn({
[styles.showhide]: true,
[styles.showhideVisible]: state === 'collapsed_over_capacity' || state === 'expanded_over_capacity'
})}>
<div className={styles.showhideInner}>
<hr className={styles.hr}/>
<button
className={styles.showhideButton}
aria-pressed={expansion === 'expanded'}
onClick={toggle}>
{expansion === 'expanded' && t('favorites_show_less')}
{expansion === 'collapsed' && t('favorites_show_more')}
<Chevron/>
</button>
</div>
<div class={styles.showhide}></div>
</div>
)
}
Expand Down Expand Up @@ -82,6 +160,7 @@ const PlusIcon = memo(function PlusIcon () {
*/
function calculatePlaceholders (totalItems, itemsPerRow) {
if (totalItems === 0) return itemsPerRow
if (totalItems === itemsPerRow) return 1
// Calculate how many items are left over in the last row
const itemsInLastRow = totalItems % itemsPerRow

Expand All @@ -104,15 +183,16 @@ export function FavoritesCustomized () {
}

export function FavoritesConsumer () {
const { state, listDidReOrder } = useContext(FavoritesContext)
const { state, toggle, listDidReOrder } = useContext(FavoritesContext)
if (state.status === 'ready') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const send = useContext(FavoritesDispatchContext)
return (
<Favorites
favorites={state.data.favorites}
// expansion={state.config.expansion}
expansion={state.config.expansion}
listDidReOrder={listDidReOrder}
toggle={toggle}
/>
)
}
Expand Down
Loading

0 comments on commit 41a6cff

Please sign in to comment.