Merge remote-tracking branch 'origin/develop' into chats

This commit is contained in:
Chewbacca
2022-10-14 14:27:53 -04:00
55 changed files with 1310 additions and 1447 deletions

View File

@@ -34,22 +34,23 @@ const Announcement: React.FC<IAnnouncement> = ({ announcement, addReaction, remo
<Text theme='muted'>
<FormattedDate
value={startsAt}
hour12={false}
hour12
year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'}
month='short'
day='2-digit'
hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'}
hour={skipTime ? undefined : 'numeric'}
minute={skipTime ? undefined : '2-digit'}
/>
{' '}
-
{' '}
<FormattedDate
value={endsAt}
hour12={false}
hour12
year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'}
month={skipEndDate ? undefined : 'short'}
day={skipEndDate ? undefined : '2-digit'}
hour={skipTime ? undefined : '2-digit'}
hour={skipTime ? undefined : 'numeric'}
minute={skipTime ? undefined : '2-digit'}
/>
</Text>

View File

@@ -6,9 +6,10 @@ import Bundle from 'soapbox/features/ui/components/bundle';
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import type { List as ImmutableList } from 'immutable';
import type { Attachment } from 'soapbox/types/entities';
interface IAttachmentThumbs {
media: ImmutableList<Immutable.Record<any>>
media: ImmutableList<Attachment>
onClick?(): void
sensitive?: boolean
}
@@ -18,7 +19,7 @@ const AttachmentThumbs = (props: IAttachmentThumbs) => {
const dispatch = useDispatch();
const renderLoading = () => <div className='media-gallery--compact' />;
const onOpenMedia = (media: Immutable.Record<any>, index: number) => dispatch(openModal('MEDIA', { media, index }));
const onOpenMedia = (media: ImmutableList<Attachment>, index: number) => dispatch(openModal('MEDIA', { media, index }));
return (
<div className='attachment-thumbs'>
@@ -30,6 +31,7 @@ const AttachmentThumbs = (props: IAttachmentThumbs) => {
height={50}
compact
sensitive={sensitive}
visible
/>
)}
</Bundle>

View File

@@ -263,14 +263,13 @@ const Item: React.FC<IItem> = ({
interface IMediaGallery {
sensitive?: boolean,
media: ImmutableList<Attachment>,
size: number,
height: number,
onOpenMedia: (media: ImmutableList<Attachment>, index: number) => void,
defaultWidth: number,
cacheWidth: (width: number) => void,
defaultWidth?: number,
cacheWidth?: (width: number) => void,
visible?: boolean,
onToggleVisibility?: () => void,
displayMedia: string,
displayMedia?: string,
compact: boolean,
}
@@ -278,7 +277,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
const {
media,
sensitive = false,
defaultWidth,
defaultWidth = 0,
onToggleVisibility,
onOpenMedia,
cacheWidth,

View File

@@ -1,6 +1,6 @@
import classNames from 'clsx';
import React, { useEffect, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useIntl, FormattedMessage } from 'react-intl';
import { usePopper } from 'react-popper';
import { useHistory } from 'react-router-dom';
@@ -15,9 +15,10 @@ import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
import { UserPanel } from 'soapbox/features/ui/util/async-components';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
import { isLocal } from 'soapbox/utils/accounts';
import { showProfileHoverCard } from './hover_ref_wrapper';
import { Card, CardBody, Stack, Text } from './ui';
import { Card, CardBody, HStack, Icon, Stack, Text } from './ui';
import type { AppDispatch } from 'soapbox/store';
import type { Account } from 'soapbox/types/entities';
@@ -60,6 +61,7 @@ interface IProfileHoverCard {
export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }) => {
const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
@@ -88,6 +90,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
if (!account) return null;
const accountBio = { __html: account.note_emojified };
const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' });
const followedBy = me !== account.id && account.relationship?.followed_by === true;
return (
@@ -116,6 +119,23 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
)}
</BundleContainer>
{isLocal(account) ? (
<HStack alignItems='center' space={0.5}>
<Icon
src={require('@tabler/icons/calendar.svg')}
className='w-4 h-4 text-gray-800 dark:text-gray-200'
/>
<Text size='sm'>
<FormattedMessage
id='account.member_since' defaultMessage='Joined {date}' values={{
date: memberSinceDate,
}}
/>
</Text>
</HStack>
) : null}
{account.source.get('note', '').length > 0 && (
<Text size='sm' dangerouslySetInnerHTML={accountBio} />
)}

View File

@@ -17,11 +17,11 @@ const messages = defineMessages({
});
const dateFormatOptions: FormatDateOptions = {
hour12: false,
hour12: true,
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
hour: 'numeric',
minute: '2-digit',
};
@@ -32,8 +32,8 @@ const shortDateFormatOptions: FormatDateOptions = {
const SECOND = 1000;
const MINUTE = 1000 * 60;
const HOUR = 1000 * 60 * 60;
const DAY = 1000 * 60 * 60 * 24;
const HOUR = 1000 * 60 * 60;
const DAY = 1000 * 60 * 60 * 24;
const MAX_DELAY = 2147483647;
@@ -170,12 +170,12 @@ class RelativeTimestamp extends React.Component<RelativeTimestampProps, Relative
clearTimeout(this._timer);
}
const { timestamp } = this.props;
const delta = (new Date(timestamp)).getTime() - this.state.now;
const unitDelay = getUnitDelay(selectUnits(delta));
const unitRemainder = Math.abs(delta % unitDelay);
const { timestamp } = this.props;
const delta = (new Date(timestamp)).getTime() - this.state.now;
const unitDelay = getUnitDelay(selectUnits(delta));
const unitRemainder = Math.abs(delta % unitDelay);
const updateInterval = 1000 * 10;
const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder);
const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder);
this._timer = setTimeout(() => {
this.setState({ now: Date.now() });

View File

@@ -374,7 +374,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
menu.push({
text: intl.formatMessage(status.pinned ? messages.unpin : messages.pin),
action: handlePinClick,
icon: mutingConversation ? require('@tabler/icons/pinned-off.svg') : require('@tabler/icons/pin.svg'),
icon: status.pinned ? require('@tabler/icons/pinned-off.svg') : require('@tabler/icons/pin.svg'),
});
} else {
if (status.visibility === 'private') {

View File

@@ -1,12 +1,32 @@
import classNames from 'clsx';
type ButtonThemes = 'primary' | 'secondary' | 'tertiary' | 'accent' | 'danger' | 'transparent' | 'outline'
type ButtonSizes = 'sm' | 'md' | 'lg'
const themes = {
primary:
'bg-primary-500 hover:bg-primary-400 dark:hover:bg-primary-600 border-transparent focus:bg-primary-500 text-gray-100 focus:ring-primary-300',
secondary:
'border-transparent bg-primary-100 dark:bg-primary-800 hover:bg-primary-50 dark:hover:bg-primary-700 focus:bg-primary-100 dark:focus:bg-primary-800 text-primary-500 dark:text-primary-200',
tertiary:
'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500',
accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300',
danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:bg-danger-800 dark:focus:bg-danger-600',
transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80',
outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10',
};
const sizes = {
xs: 'px-3 py-1 text-xs',
sm: 'px-3 py-1.5 text-xs leading-4',
md: 'px-4 py-2 text-sm',
lg: 'px-6 py-3 text-base',
};
type ButtonSizes = keyof typeof sizes
type ButtonThemes = keyof typeof themes
type IButtonStyles = {
theme: ButtonThemes,
block: boolean,
disabled: boolean,
theme: ButtonThemes
block: boolean
disabled: boolean
size: ButtonSizes
}
@@ -17,26 +37,6 @@ const useButtonStyles = ({
disabled,
size,
}: IButtonStyles) => {
const themes = {
primary:
'bg-primary-500 hover:bg-primary-400 dark:hover:bg-primary-600 border-transparent focus:bg-primary-500 text-gray-100 focus:ring-primary-300',
secondary:
'border-transparent bg-primary-100 dark:bg-primary-800 hover:bg-primary-50 dark:hover:bg-primary-700 focus:bg-primary-100 dark:focus:bg-primary-800 text-primary-500 dark:text-primary-200',
tertiary:
'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500',
accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300',
danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:bg-danger-800 dark:focus:bg-danger-600',
transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80',
outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10',
};
const sizes = {
xs: 'px-3 py-1 text-xs',
sm: 'px-3 py-1.5 text-xs leading-4',
md: 'px-4 py-2 text-sm',
lg: 'px-6 py-3 text-base',
};
const buttonStyle = classNames({
'inline-flex items-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all': true,
'select-none disabled:opacity-75 disabled:cursor-default': disabled,

View File

@@ -18,13 +18,13 @@ const messages = defineMessages({
interface ICard {
/** The type of card. */
variant?: 'default' | 'rounded',
variant?: 'default' | 'rounded'
/** Card size preset. */
size?: 'md' | 'lg' | 'xl',
size?: keyof typeof sizes
/** Extra classnames for the <div> element. */
className?: string,
className?: string
/** Elements inside the card. */
children: React.ReactNode,
children: React.ReactNode
}
/** An opaque backdrop to hold a collection of related elements. */

View File

@@ -17,7 +17,7 @@ const alignItemsOptions = {
};
const spaces = {
'0.5': 'space-x-0.5',
[0.5]: 'space-x-0.5',
1: 'space-x-1',
1.5: 'space-x-1.5',
2: 'space-x-2',
@@ -29,21 +29,21 @@ const spaces = {
interface IHStack {
/** Vertical alignment of children. */
alignItems?: 'top' | 'bottom' | 'center' | 'start',
alignItems?: keyof typeof alignItemsOptions
/** Extra class names on the <div> element. */
className?: string,
className?: string
/** Children */
children?: React.ReactNode,
children?: React.ReactNode
/** Horizontal alignment of children. */
justifyContent?: 'between' | 'center' | 'start' | 'end' | 'around',
justifyContent?: keyof typeof justifyContentOptions
/** Size of the gap between elements. */
space?: 0.5 | 1 | 1.5 | 2 | 3 | 4 | 6 | 8,
space?: keyof typeof spaces
/** Whether to let the flexbox grow. */
grow?: boolean,
grow?: boolean
/** Extra CSS styles for the <div> */
style?: React.CSSProperties
/** Whether to let the flexbox wrap onto multiple lines. */
wrap?: boolean,
wrap?: boolean
}
/** Horizontal row of child elements. */

View File

@@ -11,8 +11,6 @@ const messages = defineMessages({
confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
});
type Widths = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
const widths = {
xs: 'max-w-xs',
sm: 'max-w-sm',
@@ -52,7 +50,7 @@ interface IModal {
skipFocus?: boolean,
/** Title text for the modal. */
title?: React.ReactNode,
width?: Widths,
width?: keyof typeof widths,
}
/** Displays a modal dialog box. */

View File

@@ -1,13 +1,11 @@
import classNames from 'clsx';
import React from 'react';
type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 10
const spaces = {
0: 'space-y-0',
'0.5': 'space-y-0.5',
[0.5]: 'space-y-0.5',
1: 'space-y-1',
'1.5': 'space-y-1.5',
[1.5]: 'space-y-1.5',
2: 'space-y-2',
3: 'space-y-3',
4: 'space-y-4',
@@ -27,15 +25,15 @@ const alignItemsOptions = {
interface IStack extends React.HTMLAttributes<HTMLDivElement> {
/** Size of the gap between elements. */
space?: SIZES,
space?: keyof typeof spaces
/** Horizontal alignment of children. */
alignItems?: 'center' | 'start',
/** Vertical alignment of children. */
justifyContent?: 'center',
justifyContent?: 'center'
/** Extra class names on the <div> element. */
className?: string,
className?: string
/** Whether to let the flexbox grow. */
grow?: boolean,
grow?: boolean
}
/** Vertical stack of child elements. */

View File

@@ -13,8 +13,7 @@
[data-reach-tab] {
@apply flex-1 flex justify-center items-center
py-4 px-1 text-center font-medium text-sm text-gray-700
dark:text-gray-600 hover:text-gray-800 dark:hover:text-gray-500
focus:ring-primary-300 focus:ring-2;
dark:text-gray-600 hover:text-gray-800 dark:hover:text-gray-500;
}
[data-reach-tab][data-selected] {

View File

@@ -1,16 +1,6 @@
import classNames from 'clsx';
import React from 'react';
type Themes = 'default' | 'danger' | 'primary' | 'muted' | 'subtle' | 'success' | 'inherit' | 'white'
type Weights = 'normal' | 'medium' | 'semibold' | 'bold'
export type Sizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
type Alignments = 'left' | 'center' | 'right'
type TrackingSizes = 'normal' | 'wide'
type TransformProperties = 'uppercase' | 'normal'
type Families = 'sans' | 'mono'
type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label'
type Directions = 'ltr' | 'rtl'
const themes = {
default: 'text-gray-900 dark:text-gray-100',
danger: 'text-danger-600',
@@ -60,15 +50,19 @@ const families = {
mono: 'font-mono',
};
export type Sizes = keyof typeof sizes
type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label'
type Directions = 'ltr' | 'rtl'
interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'dangerouslySetInnerHTML'> {
/** How to align the text. */
align?: Alignments,
align?: keyof typeof alignments,
/** Extra class names for the outer element. */
className?: string,
/** Text direction. */
direction?: Directions,
/** Typeface of the text. */
family?: Families,
family?: keyof typeof families,
/** The "for" attribute specifies which form element a label is bound to. */
htmlFor?: string,
/** Font size of the text. */
@@ -76,15 +70,15 @@ interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'danger
/** HTML element name of the outer element. */
tag?: Tags,
/** Theme for the text. */
theme?: Themes,
theme?: keyof typeof themes,
/** Letter-spacing of the text. */
tracking?: TrackingSizes,
tracking?: keyof typeof trackingSizes,
/** Transform (eg uppercase) for the text. */
transform?: TransformProperties,
transform?: keyof typeof transformProperties,
/** Whether to truncate the text if its container is too small. */
truncate?: boolean,
/** Font weight of the text. */
weight?: Weights,
weight?: keyof typeof weights,
/** Tooltip title. */
title?: string,
}