mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-03-14 12:18:30 +03:00
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%);
|
||||
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 {
|
||||
|
|
22
src/app.jsx
22
src/app.jsx
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue