Merge branch 'thread' into 'develop'
Add sticky column header, improve design of threads See merge request soapbox-pub/soapbox!2423
This commit is contained in:
@ -16,7 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
- Posts: truncate Nostr pubkeys in reply mentions.
|
||||
- Posts: upgraded emoji picker component.
|
||||
- Posts: improved design of threads.
|
||||
- UI: unified design of "approve" and "reject" buttons in follow requests and waitlist.
|
||||
- UI: added sticky column header.
|
||||
|
||||
### Fixed
|
||||
- Posts: fixed emojis being cut off in reactions modal.
|
||||
|
||||
@ -16,11 +16,13 @@ const messages = defineMessages({
|
||||
back: { id: 'card.back.label', defaultMessage: 'Back' },
|
||||
});
|
||||
|
||||
export type CardSizes = keyof typeof sizes
|
||||
|
||||
interface ICard {
|
||||
/** The type of card. */
|
||||
variant?: 'default' | 'rounded'
|
||||
/** Card size preset. */
|
||||
size?: keyof typeof sizes
|
||||
size?: CardSizes
|
||||
/** Extra classnames for the <div> element. */
|
||||
className?: string
|
||||
/** Elements inside the card. */
|
||||
@ -33,7 +35,7 @@ const Card = React.forwardRef<HTMLDivElement, ICard>(({ children, variant = 'def
|
||||
ref={ref}
|
||||
{...filteredProps}
|
||||
className={clsx({
|
||||
'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none overflow-hidden': variant === 'rounded',
|
||||
'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none': variant === 'rounded',
|
||||
[sizes[size]]: variant === 'rounded',
|
||||
}, className)}
|
||||
>
|
||||
@ -72,7 +74,7 @@ const CardHeader: React.FC<ICardHeader> = ({ className, children, backHref, onBa
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack alignItems='center' space={2} className={clsx('mb-4', className)}>
|
||||
<HStack alignItems='center' space={2} className={className}>
|
||||
{renderBackButton()}
|
||||
|
||||
{children}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import Helmet from 'soapbox/components/helmet';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
import { Card, CardBody, CardHeader, CardTitle } from '../card/card';
|
||||
import { Card, CardBody, CardHeader, CardTitle, type CardSizes } from '../card/card';
|
||||
|
||||
type IColumnHeader = Pick<IColumn, 'label' | 'backHref' | 'className' | 'action'>;
|
||||
|
||||
@ -54,13 +55,29 @@ export interface IColumn {
|
||||
ref?: React.Ref<HTMLDivElement>
|
||||
/** Children to display in the column. */
|
||||
children?: React.ReactNode
|
||||
/** Action for the ColumnHeader, displayed at the end. */
|
||||
action?: React.ReactNode
|
||||
/** Column size, inherited from Card. */
|
||||
size?: CardSizes
|
||||
}
|
||||
|
||||
/** A backdrop for the main section of the UI. */
|
||||
const Column: React.FC<IColumn> = React.forwardRef((props, ref: React.ForwardedRef<HTMLDivElement>): JSX.Element => {
|
||||
const { backHref, children, label, transparent = false, withHeader = true, className, action } = props;
|
||||
const { backHref, children, label, transparent = false, withHeader = true, className, action, size } = props;
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
const handleScroll = useCallback(throttle(() => {
|
||||
setIsScrolled(window.pageYOffset > 32);
|
||||
}, 50), []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div role='region' className='relative' ref={ref} aria-label={label} column-type={transparent ? 'transparent' : 'filled'}>
|
||||
@ -76,12 +93,18 @@ const Column: React.FC<IColumn> = React.forwardRef((props, ref: React.ForwardedR
|
||||
)}
|
||||
</Helmet>
|
||||
|
||||
<Card variant={transparent ? undefined : 'rounded'} className={className}>
|
||||
<Card size={size} variant={transparent ? undefined : 'rounded'} className={className}>
|
||||
{withHeader && (
|
||||
<ColumnHeader
|
||||
label={label}
|
||||
backHref={backHref}
|
||||
className={clsx({ 'px-4 pt-4 sm:p-0': transparent })}
|
||||
className={clsx({
|
||||
'rounded-t-3xl': !isScrolled && !transparent,
|
||||
'sticky top-12 z-10 bg-white/90 dark:bg-primary-900/90 backdrop-blur lg:top-16': !transparent,
|
||||
'p-4 sm:p-0 sm:pb-4': transparent,
|
||||
'-mt-4 -mx-4 p-4': size !== 'lg' && !transparent,
|
||||
'-mt-4 -mx-4 p-4 sm:-mt-6 sm:-mx-6 sm:p-6': size === 'lg' && !transparent,
|
||||
})}
|
||||
action={action}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -50,8 +50,9 @@ import type {
|
||||
} from 'soapbox/types/entities';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'status.title', defaultMessage: '@{username}\'s Post' },
|
||||
title: { id: 'status.title', defaultMessage: 'Post Details' },
|
||||
titleDirect: { id: 'status.title_direct', defaultMessage: 'Direct message' },
|
||||
titleGroup: { id: 'status.title_group', defaultMessage: 'Group Post Details' },
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete post' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' },
|
||||
@ -462,9 +463,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||
react: handleHotkeyReact,
|
||||
};
|
||||
|
||||
const username = String(status.getIn(['account', 'acct']));
|
||||
const titleMessage = status.visibility === 'direct' ? messages.titleDirect : messages.title;
|
||||
|
||||
const focusedStatus = (
|
||||
<div className={clsx({ 'pb-4': hasDescendants })} key={status.id}>
|
||||
<HotKeys handlers={handlers}>
|
||||
@ -488,7 +486,7 @@ const Thread: React.FC<IThread> = (props) => {
|
||||
|
||||
{!isUnderReview ? (
|
||||
<>
|
||||
<hr className='mb-2 border-t-2 dark:border-primary-800' />
|
||||
<hr className='-mx-4 mb-2 max-w-[100vw] border-t-2 dark:border-primary-800' />
|
||||
|
||||
<StatusActionBar
|
||||
status={status}
|
||||
@ -502,7 +500,7 @@ const Thread: React.FC<IThread> = (props) => {
|
||||
</HotKeys>
|
||||
|
||||
{hasDescendants && (
|
||||
<hr className='mt-2 border-t-2 dark:border-primary-800' />
|
||||
<hr className='-mx-4 mt-2 max-w-[100vw] border-t-2 dark:border-primary-800' />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@ -523,10 +521,15 @@ const Thread: React.FC<IThread> = (props) => {
|
||||
return <Redirect to={`/groups/${status.group.id}/posts/${props.params.statusId}`} />;
|
||||
}
|
||||
|
||||
const titleMessage = () => {
|
||||
if (status.visibility === 'direct') return messages.titleDirect;
|
||||
return status.group ? messages.titleGroup : messages.title;
|
||||
};
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(titleMessage, { username })} transparent>
|
||||
<Column label={intl.formatMessage(titleMessage())}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Stack space={2}>
|
||||
<Stack space={2} className='mt-2'>
|
||||
<div ref={node} className='thread'>
|
||||
<ScrollableList
|
||||
id='thread'
|
||||
|
||||
@ -141,7 +141,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<HStack space={1} alignItems='center'>
|
||||
<Text size='lg' weight='bold' dangerouslySetInnerHTML={displayNameHtml} />
|
||||
<Text size='lg' weight='bold' dangerouslySetInnerHTML={displayNameHtml} truncate />
|
||||
|
||||
{account.bot && <Badge slug='bot' title={intl.formatMessage(messages.bot)} />}
|
||||
|
||||
@ -153,7 +153,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
|
||||
</HStack>
|
||||
|
||||
<HStack alignItems='center' space={0.5}>
|
||||
<Text size='sm' theme='muted' direction='ltr'>
|
||||
<Text size='sm' theme='muted' direction='ltr' truncate>
|
||||
@{displayFqn ? account.fqn : account.acct}
|
||||
</Text>
|
||||
|
||||
|
||||
@ -1462,8 +1462,9 @@
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more_all": "Show more for all",
|
||||
"status.show_original": "Show original",
|
||||
"status.title": "@{username}'s post",
|
||||
"status.title": "Post Details",
|
||||
"status.title_direct": "Direct message",
|
||||
"status.title_group": "Group Post Details",
|
||||
"status.translate": "Translate",
|
||||
"status.translated_from_with": "Translated from {lang} using {provider}",
|
||||
"status.unbookmark": "Remove bookmark",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
.thread {
|
||||
@apply bg-white dark:bg-primary-900 p-4 shadow-xl dark:shadow-none sm:p-6 sm:rounded-xl;
|
||||
@apply bg-white dark:bg-primary-900 sm:rounded-xl;
|
||||
|
||||
&__status {
|
||||
@apply relative pb-4;
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
[column-type='filled'] .status__wrapper,
|
||||
[column-type='filled'] .status-placeholder {
|
||||
@apply bg-transparent dark:bg-transparent rounded-none shadow-none p-4;
|
||||
@apply bg-transparent dark:bg-transparent rounded-none shadow-none;
|
||||
}
|
||||
|
||||
.status-check-box {
|
||||
|
||||
Reference in New Issue
Block a user