It's time to bring back the tab bar

This commit is contained in:
Lim Chee Aun 2023-02-27 23:59:41 +08:00
parent 969cd2f42b
commit 0bc009140e
10 changed files with 240 additions and 105 deletions

View file

@ -945,6 +945,7 @@ body:has(.status-deck) .media-post-link {
transform: translateY(200%); transform: translateY(200%);
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
opacity: 0;
} }
#compose-button .icon { #compose-button .icon {
transition: transform 0.3s ease-in-out; transition: transform 0.3s ease-in-out;
@ -963,6 +964,10 @@ body:has(.status-deck) .media-post-link {
#compose-button .icon { #compose-button .icon {
filter: drop-shadow(0 1px 2px var(--button-bg-color)); filter: drop-shadow(0 1px 2px var(--button-bg-color));
} }
#app:has(#shortcuts .tab-bar) #compose-button {
bottom: calc(16px + 52px);
bottom: calc(max(16px, env(safe-area-inset-bottom)) + 52px);
}
/* SHEET */ /* SHEET */
@ -1273,43 +1278,6 @@ meter.donut:is(.danger, .explode):after {
/* content-visibility: hidden; */ /* content-visibility: hidden; */
} }
/* TAB BAR */
#tab-bar:not([hidden]) {
position: fixed;
bottom: 16px;
bottom: max(16px, env(safe-area-inset-bottom));
width: calc(100% - 32px);
max-width: calc(var(--main-width) - 32px);
z-index: 100;
display: flex;
background-color: var(--bg-blur-color);
backdrop-filter: blur(16px) saturate(3);
border: var(--hairline-width) solid var(--outline-color);
border-radius: 16px;
box-shadow: 0 8px 32px var(--outline-color);
}
#tab-bar li {
flex-grow: 1;
margin: 0;
padding: 0;
list-style: none;
}
#tab-bar li a {
text-align: center;
padding: 16px 0;
display: block;
color: var(--text-insignificant-color);
}
#tab-bar li a.is-active {
color: var(--link-color);
background-image: radial-gradient(
closest-side at 50% 50%,
var(--bg-blur-color),
transparent 75%
);
}
/* 404 */ /* 404 */
#not-found-page { #not-found-page {

View file

@ -286,24 +286,10 @@ function App() {
<Routes> <Routes>
<Route path="/:instance?/s/:id" element={<Status />} /> <Route path="/:instance?/s/:id" element={<Status />} />
</Routes> </Routes>
<nav id="tab-bar" hidden> {(!snapStates.settings.shortcutsColumnsMode ||
<li> snapStates.settings.shortcutsViewMode !== 'multi-column') && (
<Link to="/"> <Shortcuts />
<Icon icon="home" alt="Home" size="xl" /> )}
</Link>
</li>
<li>
<Link to="/notifications">
<Icon icon="notification" alt="Notifications" size="xl" />
</Link>
</li>
<li>
<Link to="/bookmarks">
<Icon icon="bookmark" alt="Bookmarks" size="xl" />
</Link>
</li>
</nav>
{!snapStates.settings.shortcutsColumnsMode && <Shortcuts />}
{!!snapStates.showCompose && ( {!!snapStates.showCompose && (
<Modal> <Modal>
<Compose <Compose

View file

@ -82,16 +82,19 @@ const TYPE_PARAMS = {
}; };
export const SHORTCUTS_META = { export const SHORTCUTS_META = {
following: { following: {
title: 'Home / Following', id: (_, index) => (index === 0 ? 'home' : 'following'),
path: (_, index) => (index === 0 ? '/' : '/following'), title: (_, index) => (index === 0 ? 'Home' : 'Following'),
path: '/',
icon: 'home', icon: 'home',
}, },
notifications: { notifications: {
id: 'notifications',
title: 'Notifications', title: 'Notifications',
path: '/notifications', path: '/notifications',
icon: 'notification', icon: 'notification',
}, },
list: { list: {
id: 'list',
title: mem( title: mem(
async ({ id }) => { async ({ id }) => {
const list = await api().masto.v1.lists.fetch(id); const list = await api().masto.v1.lists.fetch(id);
@ -105,17 +108,20 @@ export const SHORTCUTS_META = {
icon: 'list', icon: 'list',
}, },
public: { public: {
id: 'public',
title: ({ local, instance }) => title: ({ local, instance }) =>
`${local ? 'Local' : 'Federated'} (${instance})`, `${local ? 'Local' : 'Federated'} (${instance})`,
path: ({ local, instance }) => `/${instance}/p${local ? '/l' : ''}`, path: ({ local, instance }) => `/${instance}/p${local ? '/l' : ''}`,
icon: ({ local }) => (local ? 'group' : 'earth'), icon: ({ local }) => (local ? 'group' : 'earth'),
}, },
search: { search: {
id: 'search',
title: ({ query }) => query, title: ({ query }) => query,
path: ({ query }) => `/search?q=${query}`, path: ({ query }) => `/search?q=${query}`,
icon: 'search', icon: 'search',
}, },
'account-statuses': { 'account-statuses': {
id: 'account-statuses',
title: mem( title: mem(
async ({ id }) => { async ({ id }) => {
const account = await api().masto.v1.accounts.fetch(id); const account = await api().masto.v1.accounts.fetch(id);
@ -129,16 +135,19 @@ export const SHORTCUTS_META = {
icon: 'user', icon: 'user',
}, },
bookmarks: { bookmarks: {
id: 'bookmarks',
title: 'Bookmarks', title: 'Bookmarks',
path: '/b', path: '/b',
icon: 'bookmark', icon: 'bookmark',
}, },
favourites: { favourites: {
id: 'favourites',
title: 'Favourites', title: 'Favourites',
path: '/f', path: '/f',
icon: 'heart', icon: 'heart',
}, },
hashtag: { hashtag: {
id: 'hashtag',
title: ({ hashtag }) => hashtag, title: ({ hashtag }) => hashtag,
path: ({ hashtag }) => `/t/${hashtag.split(/\s+/).join('+')}`, path: ({ hashtag }) => `/t/${hashtag.split(/\s+/).join('+')}`,
icon: 'hashtag', icon: 'hashtag',
@ -201,6 +210,21 @@ function ShortcutsSettings() {
button. button.
</p> </p>
<p> <p>
<label>
View mode{' '}
<select
value={snapStates.settings.shortcutsViewMode || 'float-button'}
onChange={(e) => {
states.settings.shortcutsViewMode = e.target.value;
}}
>
<option value="float-button">Floating button</option>
<option value="multi-column">Multi-column</option>
<option value="tab-menu-bar">Tab/Menu bar </option>
</select>
</label>
</p>
{/* <p>
<details> <details>
<summary class="insignificant"> <summary class="insignificant">
Experimental Multi-column mode Experimental Multi-column mode
@ -216,7 +240,7 @@ function ShortcutsSettings() {
Show shortcuts in multiple columns instead of the floating button. Show shortcuts in multiple columns instead of the floating button.
</label> </label>
</details> </details>
</p> </p> */}
{shortcuts.length > 0 ? ( {shortcuts.length > 0 ? (
<ol class="shortcuts-list"> <ol class="shortcuts-list">
{shortcuts.map((shortcut, i) => { {shortcuts.map((shortcut, i) => {

View file

@ -44,3 +44,114 @@
transform: translateY(-200%); transform: translateY(-200%);
} }
} }
/* TAB BAR */
#shortcuts .tab-bar:not([hidden]) {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
background-color: var(--bg-blur-color);
backdrop-filter: blur(16px) saturate(3);
border-top: var(--hairline-width) solid var(--outline-color);
box-shadow: 0 -8px 16px -8px var(--drop-shadow-color);
overflow: auto;
transition: all 0.3s ease-in-out;
padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom)
env(safe-area-inset-left);
}
#shortcuts .tab-bar ul {
display: flex;
margin: 0;
padding: 0;
}
#shortcuts .tab-bar li {
flex-grow: 1;
margin: 0;
padding: 0;
list-style: none;
display: flex;
}
#shortcuts .tab-bar li a {
padding: 16px 0;
display: block;
color: var(--text-insignificant-color);
font-size: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 8px;
text-decoration: none;
text-shadow: 0 var(--hairline-width) var(--bg-color);
max-width: max(44px, 20vw);
}
#shortcuts .tab-bar li a:active {
transform: scale(0.95);
transition: none;
}
#shortcuts .tab-bar li a * {
pointer-events: none;
}
#shortcuts .tab-bar li a span {
width: 100%;
text-align: center;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
#shortcuts .tab-bar li a.is-active {
color: var(--link-color);
background-image: radial-gradient(
closest-side at 50% 50%,
var(--bg-color),
transparent
);
}
#app:has(header[hidden]) #shortcuts .tab-bar,
shortcuts .tab-bar[hidden] {
transform: translateY(200%);
pointer-events: none;
user-select: none;
}
@media (min-width: calc(40em)) {
.timeline-deck {
margin-top: 44px;
}
.timeline-deck > header {
--margin-top: calc(44px + 8px);
}
#shortcuts .tab-bar:not([hidden]) {
top: 0;
bottom: auto;
padding: env(safe-area-inset-top) env(safe-area-inset-right) 0
env(safe-area-inset-left);
background-color: var(--bg-faded-blur-color);
border: 0;
box-shadow: none;
border-bottom: var(--hairline-width) solid var(--bg-faded-color);
}
#shortcuts .tab-bar ul {
justify-content: center;
}
#shortcuts .tab-bar li {
flex-grow: 0;
}
#shortcuts .tab-bar li a {
padding: 0 16px;
flex-direction: row;
font-size: 14px;
height: 44px;
gap: 4px;
}
#app:has(header[hidden]) #shortcuts .tab-bar,
shortcuts .tab-bar[hidden] {
transform: translateY(-150%);
pointer-events: none;
user-select: none;
}
}

View file

@ -11,6 +11,7 @@ import states from '../utils/states';
import AsyncText from './AsyncText'; import AsyncText from './AsyncText';
import Icon from './icon'; import Icon from './icon';
import Link from './Link';
import MenuLink from './MenuLink'; import MenuLink from './MenuLink';
function Shortcuts() { function Shortcuts() {
@ -29,8 +30,11 @@ function Shortcuts() {
.map((pin, i) => { .map((pin, i) => {
const { type, ...data } = pin; const { type, ...data } = pin;
if (!SHORTCUTS_META[type]) return null; if (!SHORTCUTS_META[type]) return null;
let { path, title, icon } = SHORTCUTS_META[type]; let { id, path, title, icon } = SHORTCUTS_META[type];
if (typeof id === 'function') {
id = id(data);
}
if (typeof path === 'function') { if (typeof path === 'function') {
path = path(data, i); path = path(data, i);
} }
@ -42,6 +46,7 @@ function Shortcuts() {
} }
return { return {
id,
path, path,
title, title,
icon, icon,
@ -65,47 +70,83 @@ function Shortcuts() {
return ( return (
<div id="shortcuts"> <div id="shortcuts">
<Menu {snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? (
instanceRef={menuRef} <nav class="tab-bar">
overflow="auto" <ul>
viewScroll="close" {formattedShortcuts.map(({ id, path, title, icon }, i) => {
boundingBoxPadding="8 8 8 8" return (
menuClassName="glass-menu shortcuts-menu" <li key={i + title}>
offsetY={8} <Link
position="anchor" to={path}
menuButton={ onClick={(e) => {
<button if (e.target.classList.contains('is-active')) {
type="button" e.preventDefault();
id="shortcuts-button" const page = document.getElementById(`${id}-page`);
class="plain" console.log(id, page);
onTransitionStart={(e) => { if (page) {
// Close menu if the button disappears page.scrollTop = 0;
try { const updatesButton =
const { target } = e; page.querySelector('.updates-button');
if (getComputedStyle(target).pointerEvents === 'none') { if (updatesButton) {
menuRef.current?.closeMenu?.(); updatesButton.click();
} }
} catch (e) {} }
}} }
> }}
<Icon icon="shortcut" size="xl" alt="Shortcuts" /> >
</button> <Icon icon={icon} size="xl" alt={title} />
} <span>
> <AsyncText>{title}</AsyncText>
{formattedShortcuts.map(({ path, title, icon }, i) => { </span>
return ( </Link>
<MenuLink to={path} key={i + title} class="glass-menu-item"> </li>
<Icon icon={icon} size="l" />{' '} );
<span class="menu-grow"> })}
<AsyncText>{title}</AsyncText> </ul>
</span> </nav>
<span class="menu-shortcut hide-until-focus-visible"> ) : (
{i + 1} <Menu
</span> instanceRef={menuRef}
</MenuLink> overflow="auto"
); viewScroll="close"
})} boundingBoxPadding="8 8 8 8"
</Menu> menuClassName="glass-menu shortcuts-menu"
offsetY={8}
position="anchor"
menuButton={
<button
type="button"
id="shortcuts-button"
class="plain"
onTransitionStart={(e) => {
// Close menu if the button disappears
try {
const { target } = e;
if (getComputedStyle(target).pointerEvents === 'none') {
menuRef.current?.closeMenu?.();
}
} catch (e) {}
}}
>
<Icon icon="shortcut" size="xl" alt="Shortcuts" />
</button>
}
>
{formattedShortcuts.map(({ path, title, icon }, i) => {
return (
<MenuLink to={path} key={i + title} class="glass-menu-item">
<Icon icon={icon} size="l" />{' '}
<span class="menu-grow">
<AsyncText>{title}</AsyncText>
</span>
<span class="menu-shortcut hide-until-focus-visible">
{i + 1}
</span>
</MenuLink>
);
})}
</Menu>
)}
</div> </div>
); );
} }

View file

@ -262,7 +262,7 @@ function Timeline({
{headerStart !== null && headerStart !== undefined ? ( {headerStart !== null && headerStart !== undefined ? (
headerStart headerStart
) : ( ) : (
<Link to="/" class="button plain"> <Link to="/" class="button plain home-button">
<Icon icon="home" size="l" /> <Icon icon="home" size="l" />
</Link> </Link>
)} )}

View file

@ -97,7 +97,7 @@ function AccountStatuses() {
</div> </div>
</h1> </h1>
} }
id="account_statuses" id="account-statuses"
instance={instance} instance={instance}
emptyText="Nothing to see here yet." emptyText="Nothing to see here yet."
errorText="Unable to load statuses" errorText="Unable to load statuses"

View file

@ -104,7 +104,7 @@ function Hashtags(props) {
</h1> </h1>
) )
} }
id="hashtags" id="hashtag"
instance={instance} instance={instance}
emptyText="No one has posted anything with this tag yet." emptyText="No one has posted anything with this tag yet."
errorText="Unable to load posts with this tag" errorText="Unable to load posts with this tag"

View file

@ -28,7 +28,8 @@ function Home() {
return ( return (
<> <>
{snapStates.settings.shortcutsColumnsMode && {(snapStates.settings.shortcutsColumnsMode ||
snapStates.settings.shortcutsViewMode === 'multi-column') &&
!!snapStates.shortcuts?.length ? ( !!snapStates.shortcuts?.length ? (
<Columns /> <Columns />
) : ( ) : (
@ -40,7 +41,7 @@ function Home() {
headerEnd={ headerEnd={
<Link <Link
to="/notifications" to="/notifications"
class={`button plain ${ class={`button plain notifications-button ${
snapStates.notificationsShowNew ? 'has-badge' : '' snapStates.notificationsShowNew ? 'has-badge' : ''
}`} }`}
onClick={(e) => { onClick={(e) => {

View file

@ -37,6 +37,7 @@ const states = proxy({
shortcuts: store.account.get('shortcuts') ?? [], shortcuts: store.account.get('shortcuts') ?? [],
// Settings // Settings
settings: { settings: {
shortcutsViewMode: store.account.get('settings-shortcutsViewMode') ?? null,
shortcutsColumnsMode: shortcutsColumnsMode:
store.account.get('settings-shortcutsColumnsMode') ?? false, store.account.get('settings-shortcutsColumnsMode') ?? false,
boostsCarousel: store.account.get('settings-boostsCarousel') ?? true, boostsCarousel: store.account.get('settings-boostsCarousel') ?? true,
@ -58,6 +59,9 @@ subscribe(states, (v) => {
if (path.join('.') === 'settings.shortcutsColumnsMode') { if (path.join('.') === 'settings.shortcutsColumnsMode') {
store.account.set('settings-shortcutsColumnsMode', !!value); store.account.set('settings-shortcutsColumnsMode', !!value);
} }
if (path.join('.') === 'settings.shortcutsViewMode') {
store.account.set('settings-shortcutsViewMode', value);
}
if (path?.[0] === 'shortcuts') { if (path?.[0] === 'shortcuts') {
store.account.set('shortcuts', states.shortcuts); store.account.set('shortcuts', states.shortcuts);
} }