It's time to bring back the tab bar
This commit is contained in:
parent
969cd2f42b
commit
0bc009140e
10 changed files with 240 additions and 105 deletions
42
src/app.css
42
src/app.css
|
@ -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 {
|
||||||
|
|
22
src/app.jsx
22
src/app.jsx
|
@ -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
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,6 +70,41 @@ function Shortcuts() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="shortcuts">
|
<div id="shortcuts">
|
||||||
|
{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
|
<Menu
|
||||||
instanceRef={menuRef}
|
instanceRef={menuRef}
|
||||||
overflow="auto"
|
overflow="auto"
|
||||||
|
@ -106,6 +146,7 @@ function Shortcuts() {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue