import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; import { TransitionGroup, CSSTransition } from 'preact-transitioning' import cn from 'classnames'; import ColorHash from 'color-hash'; import { Note } from './ui/Note.js'; import css from './ui/css'; import './ui/theme'; const colors = new ColorHash({ lightness: 0.8 }); css` .panel-enter { opacity: 0; } .panel-enter-active { opacity: 1; transition: opacity 500ms; } .panel-exit { opacity: 1; } .panel-exit-active { opacity: 0; transition: opacity 500ms; } `; css` .page-enter { position: absolute; transform: translateX(100vw); } .page-enter-active { transform: none; transition: transform 500ms; } .page-exit { transform: none; } .page-exit-active { transform: translateX(100vw); transition: transform 500ms; } .panel-up-enter, .panel-down-enter, .panel-up-appear, .panel-down-appear { height: 0; opacity: 0; /* transform set by JS */; } .panel-up-enter-active, .panel-down-enter-active, .panel-up-appear-active, .panel-down-appear-active { /* height set by JS */; opacity: 1; transform: none !important; transition: all 500ms; } .panel-up-exit, .panel-down-exit { /* height set by JS */; opacity: 1; transform: none; } .panel-up-exit-active, .panel-down-exit-active { height: 0 !important; opacity: 0; /* transform set by JS */; transition: all 500ms; } `; const transitionHooks = { 'down': { onEnter: (node) => { node.base.style.transform = `translateY(-${node.base.scrollHeight}px)`; }, onEntering: (node) => { node.base.style.height = `${node.base.scrollHeight}px`; }, onEntered: (node) => { node.base.style.height = ''; node.base.style.transform = ''; }, onExit: (node) => { node.base.style.height = `${node.base.scrollHeight}px`; node.clientHeight }, onExiting: (node) => { node.base.style.transform = `translateY(-${node.base.scrollHeight}px)`; }, }, 'up': { onEnter: (node) => { node.base.style.transform = `translateY(${node.base.scrollHeight}px)`; }, onEntering: (node) => { node.base.style.height = `${node.base.scrollHeight}px`; }, onEntered: (node) => { node.base.style.height = ''; node.base.style.transform = ''; }, onExit: (node) => { node.base.style.height = `${node.base.scrollHeight}px`; node.clientHeight }, onExiting: (node) => { node.base.style.transform = `translateY(${node.base.scrollHeight}px)`; }, }, 'this': {}, }; css` .panel { display: flex; flex-direction: column; } `; const Panel = ({ id, focusDir, activePages, setActivePages, setFocus, groups, className }) => (
groups[pid])} active={activePages.up} focused={focusDir === 'this'} attached={focusDir === 'up'} setActivePage={(newId) => setActivePages({ ...activePages, up: newId })} /> groups[pid])} active={activePages.down} focused={focusDir === 'this'} attached={focusDir === 'down'} setActivePage={(newId) => setActivePages({ ...activePages, down: newId })} />
); css` .group { display: flex; flex-direction: column; position: relative; gap: 0.3rem; z-index: 0; } .group::before { position: absolute; content: ''; inset: -0.75rem; border-radius: 0.5rem; background: transparent; z-index: -1; box-shadow: transparent 0 0 4px; transition: all 500ms; } .panel-focused .group::before { box-shadow: rgba(0,0,0, 0.3) 0 0 4px; background: var(--theme-marker); } `; const Group = ({ group: { id, items }, onClick }) => (
{ e.preventDefault(); onClick(); }) : null} style={{ cursor: onClick && 'pointer' }} > {items.map(item => )}
); css` .page-indicator { --link-len: 0.75rem; --link-len-attached: 0.5rem; font-size: 0.6rem; font-weight: bold; z-index: 1; } .panel-focused .page-indicator { font-size: 0.75rem; } .page-indicator-up { padding-bottom: var(--link-len); } .page-indicator-down { padding-top: var(--link-len); } .page-indicator-up.page-indicator-attached { padding-top: var(--link-len-attached); } .page-indicator-down.page-indicator-attached { padding-bottom: var(--link-len-attached); } .page-indicator ul { position: relative; list-style: none; padding: 0; margin: 0; height: 2em; transition: all 500ms; } .page-indicator li { position: absolute; --offset: 0; width: 2em; height: 2em; line-height: 2em; z-index: 0; left: 50%; transform: translateX(calc(3em * var(--offset) - 50%)); transition: all 500ms; } .page-indicator button { position: absolute; width: 100%; height: 100%; box-sizing: border-box; border: 0.3em solid var(--theme-link); border-radius: 0.6em; background: var(--link-color); cursor: pointer; font-size: inherit; font-weight: inherit; z-index: 0; } .page-indicator li.active button { transition: transform 500ms, opacity 350ms 150ms; } .page-indicator-attached li.active button { opacity: 0; pointer-events: none; transform: translateY(calc(1.5em + var(--link-len-attached))); } .page-indicator-up.page-indicator-attached li.active button { transform: translateY(calc((1.5em + var(--link-len-attached)) * -1)); } .page-indicator-up li::after, .page-indicator-down li::before { position: absolute; content: ''; width: 2px; background: var(--theme-link); pointer-events: none; left: 0; right: 0; margin: auto; top: calc(var(--link-len) * -1); bottom: 0; z-index: -10; } .page-indicator-up li::after { bottom: calc(var(--link-len) * -1); top: 0; } .page-indicator-down.page-indicator-attached li.active::before { bottom: calc(var(--link-len-attached) * -1); } .page-indicator-up.page-indicator-attached li.active::after { top: calc(var(--link-len-attached) * -1); } .page-indicator li.active::after, .page-indicator li.active::before { width: 4px; } `; const PageIndicator = ({ pages, dir, focused, active, attached, setActivePage }) => { const activeIndex = pages.findIndex(g => g.id === active); return ( ); }; css` article.discussion { display: flex; flex-direction: column; max-width: 32rem; height: 100%; min-height: 100vh; margin: auto; padding: 1rem; gap: 1rem; box-sizing: border-box; background: var(--theme-backdrop); } article.discussion > header { display: flex; justify-content: space-between; align-items: baseline; } article.discussion > header h1 { margin: 0; } article.discussion > main { flex: 1; display: flex; flex-direction: column; justify-content: center; } ` export const Discussion = ({ name, groups, first }) => { // save/restore focus state const [focusId, setFocusId] = useState(() => { const id = window.location.hash.slice(1); if (id && groups[id]) return id; return first; }); useEffect(() => { window.focus = focusId; const loc = new URL(window.location); loc.hash = `#${focusId}`; if (loc.href === window.location.href) return; window.history.pushState(null, '', loc); }, [focusId]); useEffect(() => { const onpop = (e) => { console.log('popp'); const id = window.location.hash.slice(1); if (id && groups[id]) return setFocusId(id); }; window.addEventListener('popstate', onpop); window.addEventListener('hashchange', onpop); return () => { window.removeEventListener('popstate', onpop); window.removeEventListener('hashchange', onpop); }; }); const focus = groups[focusId]; window.groups = groups; const [pageStore, setPageStore] = useState(() => ( Object.fromEntries(Object.values(groups).map(g => { const up = g.next.up[0]; const down = g.next.down[0]; return [g.id, { up, down }]; })) )); const setActivePages = (id, activePages) => { const nextPageStore = Object.fromEntries(Object.entries(pageStore)); nextPageStore[id] = activePages; setPageStore(nextPageStore); }; const panels = []; // if (focus.next.up.length) { if (pageStore[focusId].up) { panels.push({ // ids: focus.next.up, id: pageStore[focusId].up, focusDir: 'down', }) } panels.push({ id: focusId, focusDir: 'this' }); // if (focus.next.down.length) { if (pageStore[focusId].down) { panels.push({ // ids: focus.next.down, id: pageStore[focusId].down, focusDir: 'up', }) } const getActivePages = (id, focusDir) => { const activePages = pageStore[id]; if (focusDir === 'this') return activePages; return { ...activePages, [focusDir]: focusId }; }; return (

{name}

{panels.map(({ id, activePages, focusDir }) => ( setActivePages(id, activePages)} setFocus={() => { setActivePages(id, getActivePages(id, focusDir)); setFocusId(id); }} groups={groups} /> ))}
); };