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%);
pointer-events: none;
user-select: none;
opacity: 0;
}
#compose-button .icon {
transition: transform 0.3s ease-in-out;
@ -963,6 +964,10 @@ body:has(.status-deck) .media-post-link {
#compose-button .icon {
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 */
@ -1273,43 +1278,6 @@ meter.donut:is(.danger, .explode):after {
/* 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 */
#not-found-page {

View file

@ -286,24 +286,10 @@ function App() {
<Routes>
<Route path="/:instance?/s/:id" element={<Status />} />
</Routes>
<nav id="tab-bar" hidden>
<li>
<Link to="/">
<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.settings.shortcutsColumnsMode ||
snapStates.settings.shortcutsViewMode !== 'multi-column') && (
<Shortcuts />
)}
{!!snapStates.showCompose && (
<Modal>
<Compose

View file

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

View file

@ -44,3 +44,114 @@
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 Icon from './icon';
import Link from './Link';
import MenuLink from './MenuLink';
function Shortcuts() {
@ -29,8 +30,11 @@ function Shortcuts() {
.map((pin, i) => {
const { type, ...data } = pin;
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') {
path = path(data, i);
}
@ -42,6 +46,7 @@ function Shortcuts() {
}
return {
id,
path,
title,
icon,
@ -65,47 +70,83 @@ function Shortcuts() {
return (
<div id="shortcuts">
<Menu
instanceRef={menuRef}
overflow="auto"
viewScroll="close"
boundingBoxPadding="8 8 8 8"
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>
{snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? (
<nav class="tab-bar">
<ul>
{formattedShortcuts.map(({ id, path, title, icon }, i) => {
return (
<li key={i + title}>
<Link
to={path}
onClick={(e) => {
if (e.target.classList.contains('is-active')) {
e.preventDefault();
const page = document.getElementById(`${id}-page`);
console.log(id, page);
if (page) {
page.scrollTop = 0;
const updatesButton =
page.querySelector('.updates-button');
if (updatesButton) {
updatesButton.click();
}
}
}
}}
>
<Icon icon={icon} size="xl" alt={title} />
<span>
<AsyncText>{title}</AsyncText>
</span>
</Link>
</li>
);
})}
</ul>
</nav>
) : (
<Menu
instanceRef={menuRef}
overflow="auto"
viewScroll="close"
boundingBoxPadding="8 8 8 8"
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>
);
}

View file

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

View file

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

View file

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

View file

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

View file

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