mirror of
https://github.com/Athou/commafeed.git
synced 2026-09-24 21:15:13 +00:00
force the header in mobile mode if it is too large even if above the mobile breakpoint (fixed #2080)
This commit is contained in:
@@ -5,14 +5,15 @@ import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
|
|||||||
import { Constants } from "@/app/constants"
|
import { Constants } from "@/app/constants"
|
||||||
import { useActionButton } from "@/hooks/useActionButton"
|
import { useActionButton } from "@/hooks/useActionButton"
|
||||||
|
|
||||||
|
export type Mode = "auto" | "mobile" | "desktop"
|
||||||
|
|
||||||
interface ActionButtonProps {
|
interface ActionButtonProps {
|
||||||
icon: ReactNode
|
icon: ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
label?: string | MessageDescriptor
|
label?: string | MessageDescriptor
|
||||||
onClick?: MouseEventHandler
|
onClick?: MouseEventHandler
|
||||||
variant?: ActionIconVariant & ButtonVariant
|
variant?: ActionIconVariant & ButtonVariant
|
||||||
hideLabelOnDesktop?: boolean
|
mode?: Mode
|
||||||
showLabelOnMobile?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +26,9 @@ export const ActionButton = forwardRef<HTMLDivElement, ActionButtonProps>((props
|
|||||||
|
|
||||||
const label = typeof props.label === "string" ? props.label : props.label && _(props.label)
|
const label = typeof props.label === "string" ? props.label : props.label && _(props.label)
|
||||||
const variant = props.variant ?? "subtle"
|
const variant = props.variant ?? "subtle"
|
||||||
const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop)
|
|
||||||
|
const mode: Mode = props.mode ?? "auto"
|
||||||
|
const iconOnly = mode === "mobile" || (mode === "auto" && mobile)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={ref} className="cf-action-button">
|
<Box ref={ref} className="cf-action-button">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { msg } from "@lingui/core/macro"
|
|||||||
import { useLingui } from "@lingui/react"
|
import { useLingui } from "@lingui/react"
|
||||||
import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
|
import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { useEffect } from "react"
|
import { useElementSize, useViewportSize } from "@mantine/hooks"
|
||||||
|
import { type ReactNode, type RefCallback, useEffect } from "react"
|
||||||
import {
|
import {
|
||||||
TbArrowDown,
|
TbArrowDown,
|
||||||
TbArrowUp,
|
TbArrowUp,
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
import { markAllAsReadWithConfirmationIfRequired, reloadEntries, search, selectNextEntry, selectPreviousEntry } from "@/app/entries/thunks"
|
import { markAllAsReadWithConfirmationIfRequired, reloadEntries, search, selectNextEntry, selectPreviousEntry } from "@/app/entries/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||||
import { changeSettings } from "@/app/user/thunks"
|
import { changeSettings } from "@/app/user/thunks"
|
||||||
import { ActionButton } from "@/components/ActionButton"
|
import { ActionButton, type Mode } from "@/components/ActionButton"
|
||||||
import { Loader } from "@/components/Loader"
|
import { Loader } from "@/components/Loader"
|
||||||
import { useActionButton } from "@/hooks/useActionButton"
|
import { useActionButton } from "@/hooks/useActionButton"
|
||||||
import { useBrowserExtension } from "@/hooks/useBrowserExtension"
|
import { useBrowserExtension } from "@/hooks/useBrowserExtension"
|
||||||
@@ -31,7 +32,7 @@ function HeaderDivider() {
|
|||||||
return <Divider orientation="vertical" />
|
return <Divider orientation="vertical" />
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderToolbar(props: { children: React.ReactNode }) {
|
function HeaderToolbar(props: { children: ReactNode; ref?: RefCallback<typeof HeaderToolbar> }) {
|
||||||
const { spacing } = useActionButton()
|
const { spacing } = useActionButton()
|
||||||
const mobile = useMobile("480px")
|
const mobile = useMobile("480px")
|
||||||
return mobile ? (
|
return mobile ? (
|
||||||
@@ -47,7 +48,7 @@ function HeaderToolbar(props: { children: React.ReactNode }) {
|
|||||||
{props.children}
|
{props.children}
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Group gap={spacing} className="cf-toolbar">
|
<Group gap={spacing} wrap="nowrap" className="cf-toolbar">
|
||||||
{props.children}
|
{props.children}
|
||||||
</Group>
|
</Group>
|
||||||
)
|
)
|
||||||
@@ -61,6 +62,13 @@ export function Header() {
|
|||||||
const searchFromStore = useAppSelector(state => state.entries.search)
|
const searchFromStore = useAppSelector(state => state.entries.search)
|
||||||
const { isBrowserExtensionPopup, openSettingsPage, openAppInNewTab } = useBrowserExtension()
|
const { isBrowserExtensionPopup, openSettingsPage, openAppInNewTab } = useBrowserExtension()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const viewport = useViewportSize()
|
||||||
|
const { ref: headerRef, width: headerWidth } = useElementSize()
|
||||||
|
const sidebarWidth = useAppSelector(state => state.user.localSettings.sidebarWidth)
|
||||||
|
const headerTooLarge = headerWidth > viewport.width - sidebarWidth
|
||||||
|
const mode: Mode = headerTooLarge ? "mobile" : "auto"
|
||||||
|
|
||||||
const { _ } = useLingui()
|
const { _ } = useLingui()
|
||||||
|
|
||||||
const searchForm = useForm<{ search: string }>()
|
const searchForm = useForm<{ search: string }>()
|
||||||
@@ -94,10 +102,11 @@ export function Header() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Center className="cf-toolbar-wrapper">
|
<Center className="cf-toolbar-wrapper">
|
||||||
<HeaderToolbar>
|
<HeaderToolbar ref={headerRef}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbArrowUp size={iconSize} />}
|
icon={<TbArrowUp size={iconSize} />}
|
||||||
label={msg`Previous`}
|
label={msg`Previous`}
|
||||||
|
mode={mode}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
await dispatch(
|
await dispatch(
|
||||||
selectPreviousEntry({
|
selectPreviousEntry({
|
||||||
@@ -111,6 +120,7 @@ export function Header() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbArrowDown size={iconSize} />}
|
icon={<TbArrowDown size={iconSize} />}
|
||||||
label={msg`Next`}
|
label={msg`Next`}
|
||||||
|
mode={mode}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
await dispatch(
|
await dispatch(
|
||||||
selectNextEntry({
|
selectNextEntry({
|
||||||
@@ -127,11 +137,13 @@ export function Header() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbRefresh size={iconSize} />}
|
icon={<TbRefresh size={iconSize} />}
|
||||||
label={msg`Refresh`}
|
label={msg`Refresh`}
|
||||||
|
mode={mode}
|
||||||
onClick={async () => await dispatch(reloadEntries())}
|
onClick={async () => await dispatch(reloadEntries())}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbChecks size={iconSize} />}
|
icon={<TbChecks size={iconSize} />}
|
||||||
label={msg`Mark all as read`}
|
label={msg`Mark all as read`}
|
||||||
|
mode={mode}
|
||||||
onClick={() => dispatch(markAllAsReadWithConfirmationIfRequired())}
|
onClick={() => dispatch(markAllAsReadWithConfirmationIfRequired())}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -140,18 +152,20 @@ export function Header() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
|
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
|
||||||
label={settings.readingMode === "all" ? msg`All` : msg`Unread`}
|
label={settings.readingMode === "all" ? msg`All` : msg`Unread`}
|
||||||
|
mode={mode}
|
||||||
onClick={toggleReadingMode}
|
onClick={toggleReadingMode}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />}
|
icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />}
|
||||||
label={settings.readingOrder === "asc" ? msg`Asc` : msg`Desc`}
|
label={settings.readingOrder === "asc" ? msg`Asc` : msg`Desc`}
|
||||||
|
mode={mode}
|
||||||
onClick={toggleReadingOrder}
|
onClick={toggleReadingOrder}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Popover>
|
<Popover>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Indicator disabled={!searchFromStore}>
|
<Indicator disabled={!searchFromStore}>
|
||||||
<ActionButton icon={<TbSearch size={iconSize} />} label={msg`Search`} />
|
<ActionButton icon={<TbSearch size={iconSize} />} label={msg`Search`} mode={mode} />
|
||||||
</Indicator>
|
</Indicator>
|
||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown>
|
<Popover.Dropdown>
|
||||||
@@ -169,7 +183,7 @@ export function Header() {
|
|||||||
|
|
||||||
<HeaderDivider />
|
<HeaderDivider />
|
||||||
|
|
||||||
<ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} />
|
<ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} mode={mode} />} />
|
||||||
|
|
||||||
{isBrowserExtensionPopup && (
|
{isBrowserExtensionPopup && (
|
||||||
<>
|
<>
|
||||||
@@ -178,11 +192,13 @@ export function Header() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbSettings size={iconSize} />}
|
icon={<TbSettings size={iconSize} />}
|
||||||
label={msg`Extension options`}
|
label={msg`Extension options`}
|
||||||
|
mode={mode}
|
||||||
onClick={() => openSettingsPage()}
|
onClick={() => openSettingsPage()}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<TbExternalLink size={iconSize} />}
|
icon={<TbExternalLink size={iconSize} />}
|
||||||
label={msg`Open CommaFeed`}
|
label={msg`Open CommaFeed`}
|
||||||
|
mode={mode}
|
||||||
onClick={() => openAppInNewTab()}
|
onClick={() => openAppInNewTab()}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function WelcomePage() {
|
|||||||
icon={<TbClock size={iconSize} />}
|
icon={<TbClock size={iconSize} />}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={async () => await login.execute({ name: "demo", password: "demo" })}
|
onClick={async () => await login.execute({ name: "demo", password: "demo" })}
|
||||||
showLabelOnMobile
|
mode="desktop"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
@@ -100,7 +100,7 @@ function Buttons() {
|
|||||||
icon={<TbKey size={iconSize} />}
|
icon={<TbKey size={iconSize} />}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={async () => await dispatch(redirectToLogin())}
|
onClick={async () => await dispatch(redirectToLogin())}
|
||||||
showLabelOnMobile
|
mode="desktop"
|
||||||
/>
|
/>
|
||||||
{serverInfos?.allowRegistrations && (
|
{serverInfos?.allowRegistrations && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@@ -108,7 +108,7 @@ function Buttons() {
|
|||||||
icon={<TbUserPlus size={iconSize} />}
|
icon={<TbUserPlus size={iconSize} />}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onClick={async () => await dispatch(redirectToRegistration())}
|
onClick={async () => await dispatch(redirectToRegistration())}
|
||||||
showLabelOnMobile
|
mode="desktop"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ function Buttons() {
|
|||||||
label={dark ? msg`Switch to light theme` : msg`Switch to dark theme`}
|
label={dark ? msg`Switch to light theme` : msg`Switch to dark theme`}
|
||||||
icon={colorScheme === "dark" ? <TbSun size={18} /> : <TbMoon size={iconSize} />}
|
icon={colorScheme === "dark" ? <TbSun size={18} /> : <TbMoon size={iconSize} />}
|
||||||
onClick={() => toggleColorScheme()}
|
onClick={() => toggleColorScheme()}
|
||||||
hideLabelOnDesktop
|
mode="mobile"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isBrowserExtensionPopup && (
|
{isBrowserExtensionPopup && (
|
||||||
@@ -124,7 +124,7 @@ function Buttons() {
|
|||||||
label={msg`Extension options`}
|
label={msg`Extension options`}
|
||||||
icon={<TbSettings size={iconSize} />}
|
icon={<TbSettings size={iconSize} />}
|
||||||
onClick={() => openSettingsPage()}
|
onClick={() => openSettingsPage()}
|
||||||
hideLabelOnDesktop
|
mode="mobile"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ export default function Layout(props: Readonly<LayoutProps>) {
|
|||||||
)}
|
)}
|
||||||
</OnMobile>
|
</OnMobile>
|
||||||
<OnDesktop>
|
<OnDesktop>
|
||||||
<Group p="md">
|
<Group p="md" wrap="nowrap">
|
||||||
<Group justify="space-between" style={{ width: sidebarWidth - 16 }}>
|
<Group justify="space-between" style={{ width: sidebarWidth - 16 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<LogoAndTitle />
|
<LogoAndTitle />
|
||||||
|
|||||||
Reference in New Issue
Block a user