mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-10 01:03:36 -05:00
feat: provide and consume scroll offsets
This commit is contained in:
parent
9387575372
commit
bad7458560
17 changed files with 113 additions and 97 deletions
|
@ -10,7 +10,6 @@ import { Text } from "preact-i18n";
|
|||
|
||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||
|
||||
import Header from "../ui/Header";
|
||||
import IconButton from "../ui/IconButton";
|
||||
|
||||
import Tooltip from "./Tooltip";
|
||||
|
@ -20,7 +19,7 @@ interface Props {
|
|||
background?: boolean;
|
||||
}
|
||||
|
||||
const ServerBanner = styled.div<Props>`
|
||||
const ServerBanner = styled.div<Omit<Props, "server">>`
|
||||
background-color: var(--secondary-header);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
|
@ -44,7 +43,8 @@ const ServerBanner = styled.div<Props>`
|
|||
`}
|
||||
|
||||
.container {
|
||||
height: 48px;
|
||||
height: var(--header-height);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
|
@ -54,12 +54,6 @@ const ServerBanner = styled.div<Props>`
|
|||
text-overflow: ellipsis;
|
||||
gap: 8px;
|
||||
|
||||
${() =>
|
||||
isTouchscreenDevice &&
|
||||
css`
|
||||
height: 56px;
|
||||
`}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -16,12 +16,12 @@ import { Children } from "../../types/Preact";
|
|||
interface Props {
|
||||
borders?: boolean;
|
||||
background?: boolean;
|
||||
transparent?: boolean;
|
||||
placement: "primary" | "secondary";
|
||||
}
|
||||
|
||||
const Header = styled.div<Props>`
|
||||
gap: 10px;
|
||||
height: 48px;
|
||||
flex: 0 auto;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
@ -29,16 +29,11 @@ const Header = styled.div<Props>`
|
|||
font-weight: 600;
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
|
||||
height: var(--header-height);
|
||||
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
background-color: rgba(
|
||||
var(--primary-header-rgb),
|
||||
max(var(--min-opacity), 0.75)
|
||||
);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 20;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
|
@ -49,11 +44,21 @@ const Header = styled.div<Props>`
|
|||
color: var(--secondary-foreground);
|
||||
}
|
||||
|
||||
${() =>
|
||||
isTouchscreenDevice &&
|
||||
css`
|
||||
height: 56px;
|
||||
`}
|
||||
${(props) =>
|
||||
props.transparent
|
||||
? css`
|
||||
background-color: rgba(
|
||||
var(--primary-header-rgb),
|
||||
max(var(--min-opacity), 0.75)
|
||||
);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 20;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
`
|
||||
: css`
|
||||
background-color: var(--primary-header);
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.background &&
|
||||
|
@ -99,19 +104,19 @@ const IconContainer = styled.div`
|
|||
`}
|
||||
`;
|
||||
|
||||
interface PageHeaderProps {
|
||||
type PageHeaderProps = Omit<Props, "placement" | "borders"> & {
|
||||
noBurger?: boolean;
|
||||
children: Children;
|
||||
icon: Children;
|
||||
}
|
||||
};
|
||||
|
||||
export const PageHeader = observer(
|
||||
({ children, icon, noBurger }: PageHeaderProps) => {
|
||||
({ children, icon, noBurger, ...props }: PageHeaderProps) => {
|
||||
const layout = useApplicationState().layout;
|
||||
const visible = layout.getSectionState(SIDEBAR_CHANNELS, true);
|
||||
|
||||
return (
|
||||
<Header placement="primary" borders={!visible}>
|
||||
<Header {...props} placement="primary" borders={!visible}>
|
||||
{!noBurger && <HamburgerAction />}
|
||||
<IconContainer
|
||||
onClick={() =>
|
||||
|
@ -136,7 +141,7 @@ export function HamburgerAction() {
|
|||
|
||||
function openSidebar() {
|
||||
document
|
||||
.querySelector("#app > div > div")
|
||||
.querySelector("#app > div > div > div")
|
||||
?.scrollTo({ behavior: "smooth", left: 0 });
|
||||
}
|
||||
|
||||
|
|
|
@ -72,10 +72,14 @@ export type Theme = Overrides & {
|
|||
font?: Fonts;
|
||||
css?: string;
|
||||
monospaceFont?: MonospaceFonts;
|
||||
|
||||
"min-opacity"?: number;
|
||||
};
|
||||
|
||||
export type ComputedVariables = Theme & {
|
||||
"header-height"?: string;
|
||||
"effective-bottom-offset"?: string;
|
||||
};
|
||||
|
||||
export interface ThemeOptions {
|
||||
base?: string;
|
||||
ligatures?: boolean;
|
||||
|
|
|
@ -138,6 +138,16 @@ export default class Layout implements Store, Persistent<Data> {
|
|||
return this.lastHomePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last path the user had open.
|
||||
* @returns Last path
|
||||
*/
|
||||
@computed getLastPath() {
|
||||
return this.lastSection === "home"
|
||||
? this.lastHomePath
|
||||
: this.getLastOpened(this.lastSection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current path open in the home tab.
|
||||
* @param path Pathname
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import rgba from "color-rgba";
|
||||
import { makeAutoObservable, computed, action } from "mobx";
|
||||
|
||||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||
|
||||
import {
|
||||
Theme,
|
||||
PRESETS,
|
||||
|
@ -9,6 +11,7 @@ import {
|
|||
DEFAULT_MONO_FONT,
|
||||
Fonts,
|
||||
MonospaceFonts,
|
||||
ComputedVariables,
|
||||
} from "../../../context/Theme";
|
||||
|
||||
import Settings from "../Settings";
|
||||
|
@ -94,14 +97,10 @@ export default class STheme {
|
|||
...PRESETS[this.getBase()],
|
||||
...this.settings.get("appearance:theme:overrides"),
|
||||
light: this.isLight(),
|
||||
|
||||
"min-opacity": this.settings.get("appearance:transparency", true)
|
||||
? 0
|
||||
: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@computed computeVariables(): Theme {
|
||||
@computed computeVariables(): ComputedVariables {
|
||||
const variables = this.getVariables() as Record<
|
||||
string,
|
||||
string | boolean | number
|
||||
|
@ -114,7 +113,16 @@ export default class STheme {
|
|||
}
|
||||
}
|
||||
|
||||
return variables as unknown as Theme;
|
||||
return {
|
||||
...(variables as unknown as Theme),
|
||||
"min-opacity": this.settings.get("appearance:transparency", true)
|
||||
? 0
|
||||
: 1,
|
||||
"header-height": isTouchscreenDevice ? "56px" : "48px",
|
||||
"effective-bottom-offset": isTouchscreenDevice
|
||||
? "var(--bottom-navigation-height)"
|
||||
: "0px",
|
||||
};
|
||||
}
|
||||
|
||||
@action setVariable(key: Variables, value: string) {
|
||||
|
|
|
@ -45,7 +45,7 @@ const StatusBar = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const Routes = styled.div`
|
||||
const Routes = styled.div.attrs({ "data-component": "routes" })`
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -29,20 +29,17 @@ import ChannelHeader from "./ChannelHeader";
|
|||
import { MessageArea } from "./messaging/MessageArea";
|
||||
import VoiceHeader from "./voice/VoiceHeader";
|
||||
|
||||
const ChannelMain = styled.div`
|
||||
const ChannelMain = styled.div.attrs({ "data-component": "channel" })`
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
|
||||
> * > ::-webkit-scrollbar-thumb {
|
||||
background-clip: content-box;
|
||||
border-top: 48px solid transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
const ChannelContent = styled.div`
|
||||
const ChannelContent = styled.div.attrs({
|
||||
"data-component": "content",
|
||||
})`
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -78,7 +78,6 @@ const Info = styled.div`
|
|||
|
||||
export default observer(({ channel }: ChannelHeaderProps) => {
|
||||
const { openScreen } = useIntermediate();
|
||||
const layout = useApplicationState().layout;
|
||||
|
||||
const name = getChannelName(channel);
|
||||
let icon, recipient: User | undefined;
|
||||
|
@ -99,7 +98,7 @@ export default observer(({ channel }: ChannelHeaderProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<PageHeader icon={icon}>
|
||||
<PageHeader icon={icon} transparent>
|
||||
<Info>
|
||||
<span className="name">{name}</span>
|
||||
{isTouchscreenDevice &&
|
||||
|
|
|
@ -30,7 +30,7 @@ export default function HeaderActions({ channel }: ChannelHeaderProps) {
|
|||
const history = useHistory();
|
||||
|
||||
function openRightSidebar() {
|
||||
const panels = document.querySelector("#app > div > div");
|
||||
const panels = document.querySelector("#app > div > div > div");
|
||||
panels?.scrollTo({
|
||||
behavior: "smooth",
|
||||
left: panels.clientWidth * 3,
|
||||
|
@ -84,7 +84,11 @@ export default function HeaderActions({ channel }: ChannelHeaderProps) {
|
|||
)}
|
||||
{(channel.channel_type === "Group" ||
|
||||
channel.channel_type === "TextChannel") && (
|
||||
<IconButton onClick={openSidebar}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
internalEmit("RightSidebar", "open", undefined);
|
||||
openRightSidebar();
|
||||
}}>
|
||||
<Group size={25} />
|
||||
</IconButton>
|
||||
)}
|
||||
|
|
|
@ -33,14 +33,18 @@ import Preloader from "../../../components/ui/Preloader";
|
|||
import ConversationStart from "./ConversationStart";
|
||||
import MessageRenderer from "./MessageRenderer";
|
||||
|
||||
const Area = styled.div`
|
||||
const Area = styled.div.attrs({ "data-scroll-offset": "with-padding" })`
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
word-break: break-word;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding-top: 48px;
|
||||
word-break: break-word;
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
|
|
|
@ -29,7 +29,7 @@ export default function Developer() {
|
|||
return (
|
||||
<div>
|
||||
<PageHeader icon={<Wrench size="24" />}>Developer Tab</PageHeader>
|
||||
<div style={{ padding: "16px", paddingTop: "56px" }}>
|
||||
<div style={{ padding: "16px" }}>
|
||||
<PaintCounter always />
|
||||
</div>
|
||||
<div style={{ padding: "16px" }}>
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
|
||||
.list {
|
||||
padding: 0 10px 10px 10px;
|
||||
padding-top: 48px;
|
||||
user-select: none;
|
||||
/*overflow-y: scroll;*/
|
||||
|
||||
&[data-empty="true"] {
|
||||
img {
|
||||
|
@ -186,12 +184,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Hide the remove friend button on smaller screens.
|
||||
@media only screen and (max-width: 768px) {
|
||||
.list {
|
||||
padding: 56px 8px 8px 8px;
|
||||
|
||||
.remove {
|
||||
display: none;
|
||||
}
|
||||
.list .remove {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { User } from "revolt.js/dist/maps/Users";
|
|||
import styled, { css } from "styled-components";
|
||||
|
||||
import styles from "./Friend.module.scss";
|
||||
import classNames from "classnames";
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
import { TextReact } from "../../lib/i18n";
|
||||
|
@ -17,32 +18,12 @@ import { useClient } from "../../context/revoltjs/RevoltClient";
|
|||
import CollapsibleSection from "../../components/common/CollapsibleSection";
|
||||
import Tooltip from "../../components/common/Tooltip";
|
||||
import UserIcon from "../../components/common/user/UserIcon";
|
||||
import Header, { PageHeader } from "../../components/ui/Header";
|
||||
import { PageHeader } from "../../components/ui/Header";
|
||||
import IconButton from "../../components/ui/IconButton";
|
||||
|
||||
import { Children } from "../../types/Preact";
|
||||
import { Friend } from "./Friend";
|
||||
|
||||
const Container = styled.div`
|
||||
overflow-y: scroll;
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
min-height: 100px;
|
||||
width: 4px;
|
||||
background-clip: content-box;
|
||||
border-top: 48px solid transparent;
|
||||
}
|
||||
${() =>
|
||||
isTouchscreenDevice &&
|
||||
css`
|
||||
::-webkit-scrollbar-thumb {
|
||||
min-height: 150px;
|
||||
border-top: 56px solid transparent;
|
||||
border-bottom: var(--bottom-navigation-height) solid transparent;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export default observer(() => {
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
|
@ -93,7 +74,7 @@ export default observer(() => {
|
|||
const isEmpty = lists.reduce((p: number, n) => p + n.length, 0) === 0;
|
||||
return (
|
||||
<>
|
||||
<PageHeader icon={<UserDetail size={24} />} noBurger>
|
||||
<PageHeader icon={<UserDetail size={24} />} transparent noBurger>
|
||||
<div className={styles.title}>
|
||||
<Text id="app.navigation.tabs.friends" />
|
||||
</div>
|
||||
|
@ -136,9 +117,9 @@ export default observer(() => {
|
|||
*/}
|
||||
</div>
|
||||
</PageHeader>
|
||||
<Container>
|
||||
<div data-scroll-offset="true" data-avoids-navigation="true">
|
||||
<div
|
||||
className={styles.list}
|
||||
className={classNames(styles.list, "with-padding")}
|
||||
data-empty={isEmpty}
|
||||
data-mobile={isTouchscreenDevice}>
|
||||
{isEmpty && (
|
||||
|
@ -234,7 +215,7 @@ export default observer(() => {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -83,7 +83,7 @@ export default observer(() => {
|
|||
</div>
|
||||
)}
|
||||
<div className="content">
|
||||
<PageHeader icon={<HomeIcon size={24} />}>
|
||||
<PageHeader icon={<HomeIcon size={24} />} transparent>
|
||||
<Text id="app.navigation.tabs.home" />
|
||||
</PageHeader>
|
||||
<div className={styles.homeScreen}>
|
||||
|
|
|
@ -102,7 +102,7 @@ export function GenericSettings({
|
|||
/>
|
||||
</Helmet>
|
||||
{isTouchscreenDevice && (
|
||||
<Header placement="primary">
|
||||
<Header placement="primary" transparent>
|
||||
{typeof page === "undefined" ? (
|
||||
<>
|
||||
{showExitButton && (
|
||||
|
@ -168,6 +168,9 @@ export function GenericSettings({
|
|||
<div className={styles.content}>
|
||||
<div
|
||||
className={styles.scrollbox}
|
||||
data-scroll-offset={
|
||||
isTouchscreenDevice ? "with-padding" : undefined
|
||||
}
|
||||
ref={(ref) => {
|
||||
// Force scroll to top if page changes.
|
||||
if (ref) {
|
||||
|
|
|
@ -43,20 +43,13 @@
|
|||
background: var(--primary-background);
|
||||
}
|
||||
|
||||
.scrollbox {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
min-height: 150px;
|
||||
border-top: 56px solid transparent;
|
||||
//border-bottom: var(--bottom-navigation-height) solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
overflow-y: auto;
|
||||
|
||||
.container {
|
||||
padding: 76px 8px calc(var(--bottom-navigation-height) + 30px);
|
||||
padding: calc(var(--header-height) + 4px) 8px
|
||||
calc(var(--bottom-navigation-height) + 30px);
|
||||
min-width: 218px;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,13 +13,32 @@
|
|||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
min-height: 15px;
|
||||
min-width: 15px;
|
||||
min-height: 30px;
|
||||
min-width: 30px;
|
||||
|
||||
background-clip: content-box;
|
||||
background: var(--scrollbar-thumb);
|
||||
}
|
||||
|
||||
[data-scroll-offset] {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
[data-scroll-offset="with-padding"],
|
||||
[data-scroll-offset] .with-padding {
|
||||
padding-top: var(--header-height);
|
||||
}
|
||||
|
||||
[data-scroll-offset]::-webkit-scrollbar-thumb {
|
||||
background-clip: content-box;
|
||||
border-top: var(--header-height) solid transparent;
|
||||
}
|
||||
|
||||
[data-avoids-navigation]::-webkit-scrollbar-thumb {
|
||||
background-clip: content-box;
|
||||
border-bottom: var(--effective-bottom-offset) solid transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue