Merge branch 'fix-classnames' into 'main'

Fix classnames warnings

Closes #1761

See merge request soapbox-pub/soapbox!3210
This commit is contained in:
Alex Gleason 2024-11-15 03:47:39 +00:00
commit 5725cc4a80
98 changed files with 381 additions and 1341 deletions

View File

@ -21,6 +21,6 @@
</div>
</div>
</div>
<noscript>To use this website, please enable JavaScript.</noscript>
<noscript class="text-center">To use this website, please enable JavaScript.</noscript>
</body>
</html>

View File

@ -25,13 +25,12 @@ const AutosuggestEmoji: React.FC<IAutosuggestEmoji> = ({ emoji }) => {
}
return (
<div className='autosuggest-emoji' data-testid='emoji'>
<div className='flex flex-row items-center justify-start text-[14px] leading-[18px]' data-testid='emoji'>
<img
className='emojione'
className='emojione mr-2 block size-4'
src={url}
alt={alt}
/>
{emoji.colons}
</div>
);

View File

@ -235,7 +235,7 @@ export default class AutosuggestInput extends PureComponent<IAutosuggestInput> {
return menu.map((item, i) => (
<a // eslint-disable-line jsx-a11y/anchor-is-valid
className={clsx('flex cursor-pointer items-center space-x-2 px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800 dark:focus:bg-primary-800', { selected: suggestions.size - selectedSuggestion === i })}
href='#'
href='/'
role='button'
tabIndex={0}
onMouseDown={this.handleMenuItemClick(item)}

View File

@ -32,7 +32,7 @@ const DisplayNameInline: React.FC<IDisplayName> = ({ account, withSuffix = true
);
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
const suffix = (<span className='display-name'>@{getAcct(account, displayFqn)}</span>);
const suffix = (<span className='relative block max-w-full truncate'>@{getAcct(account, displayFqn)}</span>);
return (
<div className='flex max-w-80 flex-col items-center justify-center text-center sm:flex-row sm:gap-2'>

View File

@ -33,10 +33,10 @@ const DisplayName: React.FC<IDisplayName> = ({ account, children, withSuffix = t
</HStack>
);
const suffix = (<span className='display-name__account'>@{getAcct(account, displayFqn)}</span>); // eslint-disable-line formatjs/no-literal-string-in-jsx
const suffix = (<span className='relative text-[14px] font-semibold'>@{getAcct(account, displayFqn)}</span>); // eslint-disable-line formatjs/no-literal-string-in-jsx
return (
<span className='display-name' data-testid='display-name'>
<span className='relative block max-w-full truncate' data-testid='display-name'>
<HoverRefWrapper accountId={account.id} inline>
{displayName}
</HoverRefWrapper>

View File

@ -42,10 +42,11 @@ const ExtendedVideoPlayer: React.FC<IExtendedVideoPlayer> = ({ src, alt, time, c
}
return (
<div className='extended-video-player'>
<div className='flex size-full items-center justify-center'>
<video
ref={video}
src={src}
className='max-h-[80%] max-w-full'
autoPlay
role='button'
tabIndex={0}

View File

@ -24,7 +24,7 @@ const ForkAwesomeIcon: React.FC<IForkAwesomeIcon> = ({ id, className, fixedWidth
<i
role='img'
// alt={alt}
className={clsx('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
className={clsx('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} // eslint-disable-line tailwindcss/no-custom-classname
{...rest}
/>
);

View File

@ -47,7 +47,7 @@ export const HoverRefWrapper: React.FC<IHoverRefWrapper> = ({ accountId, childre
return (
<Elem
ref={ref}
className={clsx('hover-ref-wrapper', className)}
className={clsx(className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}

View File

@ -1,4 +1,3 @@
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import { useRef } from 'react';
import { useDispatch } from 'react-redux';
@ -45,7 +44,7 @@ export const HoverStatusWrapper: React.FC<IHoverStatusWrapper> = ({ statusId, ch
return (
<Elem
ref={ref}
className={clsx('hover-status-wrapper', className)}
className={className}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}

View File

@ -65,9 +65,9 @@ const IconButton: React.FC<IIconButton> = ({
}
};
const classes = clsx(className, 'icon-button', {
active,
disabled,
const classes = clsx(className, 'inline-flex cursor-pointer items-center border-0 bg-transparent p-0 text-black opacity-40 transition-opacity duration-100 ease-in hover:opacity-60 hover:transition-colors hover:duration-200 focus:opacity-60 focus:outline-none focus:transition-colors focus:duration-200 dark:text-white', {
'opacity-60 outline-none transition-colors duration-200': active,
'opacity-20 cursor-default': disabled,
});
return (
@ -88,10 +88,10 @@ const IconButton: React.FC<IIconButton> = ({
disabled={disabled}
type='button'
>
<div>
<div className='flex items-center justify-center'>
<Icon className={iconClassName} src={src} aria-hidden='true' />
</div>
{text && <span className='icon-button__text'>{text}</span>}
{text && <span className='pl-0.5'>{text}</span>}
</button>
);
};

View File

@ -1,7 +1,7 @@
import dotsIcon from '@tabler/icons/outline/dots.svg';
import { defineMessages, useIntl } from 'react-intl';
import Icon from 'soapbox/components/icon.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
@ -19,8 +19,8 @@ const LoadGap: React.FC<ILoadGap> = ({ disabled, maxId, onClick }) => {
const handleClick = () => onClick(maxId);
return (
<button className='load-more load-gap' disabled={disabled} onClick={handleClick} aria-label={intl.formatMessage(messages.load_more)}>
<Icon src={dotsIcon} />
<button className='m-0 box-border block w-full border-0 bg-transparent p-4 text-gray-900' disabled={disabled} onClick={handleClick} aria-label={intl.formatMessage(messages.load_more)}>
<SvgIcon className='mx-auto' src={dotsIcon} />
</button>
);
};

View File

@ -8,7 +8,7 @@ import { defineMessages, useIntl } from 'react-intl';
import { locationSearch } from 'soapbox/actions/events.ts';
import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest-input.tsx';
import Icon from 'soapbox/components/icon.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import AutosuggestLocation from './autosuggest-location.tsx';
@ -87,7 +87,7 @@ const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
}, [value]);
return (
<div className='search'>
<div className='relative'>
<AutosuggestInput
className='rounded-full'
placeholder={intl.formatMessage(messages.placeholder)}
@ -101,9 +101,9 @@ const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
onKeyDown={handleKeyDown}
renderSuggestion={AutosuggestLocation}
/>
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
<Icon src={searchIcon} className={clsx('svg-icon--search', { active: isEmpty() })} />
<Icon src={backspaceIcon} className={clsx('svg-icon--backspace', { active: !isEmpty() })} aria-label={intl.formatMessage(messages.placeholder)} />
<div role='button' tabIndex={0} className='focus:!outline-0' onClick={handleClear}>
<SvgIcon src={searchIcon} className={clsx('pointer-events-none absolute right-4 top-1/2 z-[2] inline-block size-[18px] -translate-y-1/2 cursor-default text-gray-400 opacity-0 rtl:left-4 rtl:right-auto', { 'opacity-100': isEmpty() })} />
<SvgIcon src={backspaceIcon} className={clsx('pointer-events-none absolute right-4 top-1/2 z-[2] inline-block size-[22px] -translate-y-1/2 cursor-pointer text-gray-400 opacity-0 rtl:left-4 rtl:right-auto', { 'pointer-events-auto opacity-100': !isEmpty() })} aria-label={intl.formatMessage(messages.placeholder)} />
</div>
</div>
);

View File

@ -8,8 +8,8 @@ import { useState, useEffect } from 'react';
import Blurhash from 'soapbox/components/blurhash.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Icon from 'soapbox/components/ui/icon.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { normalizeAttachment } from 'soapbox/normalizers/index.ts';
import { addAutoPlay } from 'soapbox/utils/media.ts';
@ -96,7 +96,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
return (
<div
ref={setRef}
className='status-card__image status-card-video'
className='relative w-full flex-none overflow-hidden'
dangerouslySetInnerHTML={content}
style={{ height }}
/>
@ -113,7 +113,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
const interactive = card.type !== 'link';
horizontal = typeof horizontal === 'boolean' ? horizontal : interactive || embedded;
const className = clsx('status-card', { horizontal, compact, interactive }, `status-card--${card.type}`);
const className = clsx('flex overflow-hidden rounded-lg border border-solid border-gray-200 text-sm text-gray-800 no-underline dark:border-gray-800 dark:text-gray-200', { '!block': horizontal, 'border-gray-200 dark:border-gray-800': compact, interactive, 'flex flex-col md:flex-row': card.type === 'link' });
const ratio = getRatio(card);
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
@ -142,7 +142,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
)}
<HStack space={1} alignItems='center'>
<Text tag='span' theme='muted'>
<Icon src={linkIcon} />
<SvgIcon src={linkIcon} />
</Text>
<Text tag='span' theme='muted' size='sm' direction={direction}>
{card.provider_name}
@ -167,7 +167,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
width: horizontal ? width : undefined,
height: horizontal ? height : undefined,
}}
className='status-card__image-image'
className='block size-full bg-cover bg-center object-cover'
/>
);
@ -182,7 +182,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
}
embed = (
<div className='status-card__image'>
<div className='relative w-full flex-none overflow-hidden' style={{ flex: '0 0 40%' }}>
{canvas}
{thumbnail}
@ -190,7 +190,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
<div className='flex items-center justify-center rounded-full bg-gray-500/90 px-4 py-3 shadow-md dark:bg-gray-700/90'>
<HStack space={3} alignItems='center'>
<button onClick={handleEmbedClick} className='appearance-none text-gray-700 hover:text-gray-900 dark:text-gray-500 dark:hover:text-gray-100'>
<Icon
<SvgIcon
src={iconVariant}
className=' size-6 text-inherit'
/>
@ -204,7 +204,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
rel='noopener'
className='text-gray-700 hover:text-gray-900 dark:text-gray-500 dark:hover:text-gray-100'
>
<Icon
<SvgIcon
src={externalLinkIcon}
className='size-6 text-inherit'
/>
@ -226,7 +226,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
} else if (card.image) {
embed = (
<div className={clsx(
'status-card__image',
'relative overflow-hidden',
'w-full flex-none rounded-l md:size-auto md:flex-auto',
{
'h-auto': horizontal,
@ -243,7 +243,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
return (
<a
href={card.url}
className={className}
className={clsx(className, 'cursor-pointer hover:bg-gray-100 hover:no-underline dark:hover:bg-primary-800/30')}
target='_blank'
rel='noopener'
ref={setRef}

View File

@ -172,9 +172,8 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
})
}
>
<div
<button
className='fixed inset-0 bg-gray-500/90 black:bg-gray-900/90 dark:bg-gray-700/90'
role='button'
onClick={handleClose}
/>

View File

@ -212,10 +212,12 @@ const StatusList: React.FC<IStatusList> = ({
if (isPartial) {
return (
<div className='regeneration-indicator'>
<div className='flex flex-1 cursor-default items-center justify-center rounded-lg p-5 text-center text-[16px] font-medium text-gray-900 sm:rounded-none'>
<div className='w-full bg-transparent pt-0'>
<div>
<div className='regeneration-indicator__label'>
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
<strong className='mb-2.5 block text-gray-900'>
<FormattedMessage id='regeneration_indicator.label' defaultMessage='Loading…' />
</strong>
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
</div>
</div>

View File

@ -41,15 +41,15 @@ const StatusMedia: React.FC<IStatusMedia> = ({
let media: JSX.Element | null = null;
const renderLoadingMediaGallery = (): JSX.Element => {
return <div className='media_gallery' style={{ height: '285px' }} />;
return <div className='relative isolate box-border h-auto w-full overflow-hidden rounded-lg' style={{ height: '285px' }} />;
};
const renderLoadingVideoPlayer = (): JSX.Element => {
return <div className='media-spoiler-video' style={{ height: '285px' }} />;
return <div className='relative mt-2 block cursor-pointer border-0 bg-cover bg-center bg-no-repeat' style={{ height: '285px' }} />;
};
const renderLoadingAudioPlayer = (): JSX.Element => {
return <div className='media-spoiler-audio' style={{ height: '285px' }} />;
return <div className='relative mt-2 block cursor-pointer border-0 bg-cover bg-center bg-no-repeat' style={{ height: '285px' }} />;
};
const openMedia = (media: ImmutableList<Attachment>, index: number) => {

View File

@ -38,7 +38,7 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
// Rare, but it can happen.
if (to.size === 0) {
return (
<div className='reply-mentions'>
<div className='mb-1 text-sm text-gray-700 dark:text-gray-600'>
<FormattedMessage
id='reply_mentions.reply_empty'
defaultMessage='Replying to post'
@ -53,7 +53,7 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
<Link
key={account.id}
to={`/@${account.acct}`}
className='reply-mentions__account max-w-[200px] truncate align-bottom'
className='inline-block max-w-[200px] truncate align-bottom text-primary-600 no-underline hover:text-primary-700 hover:underline dark:text-accent-blue dark:hover:text-accent-blue' style={{ direction: 'ltr' }}
onClick={(e) => e.stopPropagation()}
> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
@{shortenNostr(account.username)}
@ -80,7 +80,7 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
}
return (
<div className='reply-mentions'>
<div className='mb-1 text-sm text-gray-700 dark:text-gray-600'>
<FormattedMessage
id='reply_mentions.reply.hoverable'
defaultMessage='<hover>Replying to</hover> {accounts}'

View File

@ -340,7 +340,7 @@ const Status: React.FC<IStatus> = (props) => {
return (
<HotKeys handlers={minHandlers}>
<div className={clsx('status__wrapper text-center', { focusable })} tabIndex={focusable ? 0 : undefined} ref={node}>
<div className={clsx('status--wrapper text-center', { focusable })} tabIndex={focusable ? 0 : undefined} ref={node}>
{/* eslint-disable formatjs/no-literal-string-in-jsx */}
<Text theme='muted'>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {status.filtered.join(', ')}.
@ -368,7 +368,7 @@ const Status: React.FC<IStatus> = (props) => {
if (actualStatus.quote) {
if (actualStatus.pleroma.get('quote_visible', true) === false) {
quote = (
<div className='quoted-status-tombstone'>
<div>
<p><FormattedMessage id='statuses.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
</div>
);
@ -408,6 +408,7 @@ const Status: React.FC<IStatus> = (props) => {
return (
<HotKeys handlers={handlers} data-testid='status'>
{/* eslint-disable-next-line jsx-a11y/interactive-supports-focus */}
<div
className={clsx('status cursor-pointer', { focusable })}
tabIndex={focusable && !muted ? 0 : undefined}
@ -419,11 +420,8 @@ const Status: React.FC<IStatus> = (props) => {
>
<Card
variant={variant}
className={clsx('status__wrapper space-y-4', `status-${actualStatus.visibility}`, {
'py-6 sm:p-5': variant === 'rounded',
'status-reply': !!status.in_reply_to_id,
muted,
read: unread === false,
className={clsx('status--wrapper space-y-4', {
'py-6 sm:p-5': variant === 'rounded', muted, read: unread === false,
})}
data-id={status.id}
>
@ -443,7 +441,7 @@ const Status: React.FC<IStatus> = (props) => {
avatarSize={avatarSize}
/>
<div className='status__content-wrapper'>
<div className='status--content-wrapper'>
<StatusReplyMentions status={actualStatus} hoverable={hoverable} />
<Stack

View File

@ -32,7 +32,7 @@ const ThumbNavigationLink: React.FC<IThumbNavigationLink> = ({ count, countMax,
const icon = (active && activeSrc) || src;
return (
<NavLink to={to} exact={exact} className='thumb-navigation__link'>
<NavLink to={to} exact={exact} className='flex flex-1 flex-col items-center space-y-1 px-2 py-2.5 text-lg text-gray-600'>
{count !== undefined ? (
<IconWithCounter
src={icon}

View File

@ -57,7 +57,14 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
};
return (
<div className='thumb-navigation'>
<div
className='hide-scrollbar fixed inset-x-0 bottom-0 z-50 flex w-full border-t border-solid border-gray-200 bg-white/90 shadow-2xl backdrop-blur-md black:bg-black/90 dark:border-gray-800 dark:bg-primary-900/90 lg:hidden' style={{
paddingBottom: 'env(safe-area-inset-bottom)', // iOS PWA
overflowX: 'auto',
scrollbarWidth: 'thin',
scrollbarColor: '#fff transparent',
}}
>
<ThumbNavigationLink
src={homeIcon}
activeSrc={homeFilledIcon}

View File

@ -20,7 +20,7 @@ const Tombstone: React.FC<ITombstone> = ({ id, onMoveUp, onMoveDown }) => {
<HotKeys handlers={handlers}>
<div className='h-16'>
<div
className='focusable flex h-[42px] items-center justify-center rounded-lg border-2 border-gray-200 text-center dark:border-gray-800'
className='flex h-[42px] items-center justify-center rounded-lg border-2 border-gray-200 text-center focus-within:outline-none focus-within:ring-2 focus-within:ring-primary-300 focus:outline-none focus:ring-2 focus:ring-primary-300 dark:border-gray-800'
>
<Text theme='muted'>
<FormattedMessage

View File

@ -54,13 +54,17 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
)}
{hasError && (
<div>
<div className='relative'>
<div className='pointer-events-none absolute bottom-full left-2.5 -ml-px size-0 border-[6px] border-solid border-transparent' style={{ borderBottomColor: 'rgba(254, 202, 202, var(--tw-bg-opacity))', '--tw-bg-opacity': '1' } as React.CSSProperties} />
<p
data-testid='form-group-error'
className='form-error relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
className='relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
>
{errors.join(', ')}
</p>
<div className='pointer-events-none absolute bottom-full left-2.5 size-0 border-[6px] border-transparent' />
</div>
)}
@ -98,12 +102,18 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
{inputChildren.filter((_, i) => i !== 0)}
{hasError && (
<div className='relative'>
<div className='pointer-events-none absolute bottom-full left-2.5 -ml-px size-0 border-[6px] border-solid border-transparent' style={{ borderBottomColor: 'rgba(254, 202, 202, var(--tw-bg-opacity))', '--tw-bg-opacity': '1' } as React.CSSProperties} />
<p
data-testid='form-group-error'
className='form-error relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
className='relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
>
{errors.join(', ')}
</p>
<div className='pointer-events-none absolute bottom-full left-2.5 size-0 border-[6px] border-transparent' />
</div>
)}
</div>
</div>

View File

@ -27,7 +27,7 @@ const MenuList: React.FC<IMenuList> = (props) => {
<MenuItems
onKeyDown={(event) => event.nativeEvent.stopImmediatePropagation()}
className={
clsx(className, 'shadow-menu rounded-lg bg-white py-1 black:bg-black dark:bg-primary-900')
clsx(className, 'rounded-lg bg-white py-1 black:bg-black dark:bg-primary-900')
}
{...filteredProps}
/>

View File

@ -24,7 +24,7 @@ const VerificationBadge: React.FC<IVerificationBadge> = ({ className }) => {
const Element = icon.endsWith('.svg') ? Icon : 'img';
return (
<span className='verified-icon' data-testid='verified-badge'>
<span data-testid='verified-badge'>
<Element className={clsx('w-4 text-accent-500', className)} src={icon} alt={intl.formatMessage(messages.verified)} />
</span>
);

View File

@ -107,7 +107,7 @@ const AccountGallery = () => {
if (isUnavailable) {
return (
<Column>
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
</Column>
@ -128,7 +128,7 @@ const AccountGallery = () => {
))}
{!isLoading && attachments.size === 0 && (
<div className='empty-column-indicator col-span-2 sm:col-span-3'>
<div className='col-span-2 flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300 sm:col-span-3'>
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
</div>
)}
@ -137,7 +137,7 @@ const AccountGallery = () => {
</div>
{isLoading && attachments.size === 0 && (
<div className='slist__append'>
<div className='relative flex flex-1 p-[30px_15px]'>
<Spinner />
</div>
)}

View File

@ -3,8 +3,8 @@ import clsx from 'clsx';
import { defineMessages, useIntl } from 'react-intl';
import { fetchAliasesSuggestions, clearAliasesSuggestions, changeAliasesSuggestions } from 'soapbox/actions/aliases.ts';
import Icon from 'soapbox/components/icon.tsx';
import Button from 'soapbox/components/ui/button.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
@ -54,7 +54,7 @@ const Search: React.FC = () => {
/>
<div role='button' tabIndex={hasValue ? 0 : -1} className='search__icon' onClick={handleClear}>
<Icon src={backspaceIcon} aria-label={intl.formatMessage(messages.search)} className={clsx('svg-icon--backspace', { active: hasValue })} />
<SvgIcon src={backspaceIcon} aria-label={intl.formatMessage(messages.search)} className={clsx('pointer-events-none absolute right-4 top-1/2 z-20 inline-block size-4.5 -translate-y-1/2 cursor-default text-[16px] text-gray-400 opacity-0 rtl:left-4 rtl:right-auto', { 'pointer-events-auto opacity-100': hasValue })} />
</div>
</label>
<Button onClick={handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>

View File

@ -54,18 +54,20 @@ const Aliases = () => {
const emptyMessage = <FormattedMessage id='empty_column.aliases' defaultMessage="You haven't created any account alias yet." />;
return (
<Column className='aliases-settings-panel' label={intl.formatMessage(messages.heading)}>
<Column className='flex-1' label={intl.formatMessage(messages.heading)}>
<CardHeader>
<CardTitle title={intl.formatMessage(messages.subheading_add_new)} />
</CardHeader>
<Search />
{
loaded && searchAccountIds.size === 0 ? (
<div className='aliases__accounts empty-column-indicator'>
<div
className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'
>
<FormattedMessage id='empty_column.aliases.suggestions' defaultMessage='There are no account suggestions available for the provided term.' />
</div>
) : (
<div className='aliases__accounts mb-4'>
<div className='mb-4 overflow-y-auto'>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} aliases={aliases} />)}
</div>
)
@ -73,7 +75,7 @@ const Aliases = () => {
<CardHeader>
<CardTitle title={intl.formatMessage(messages.subheading_aliases)} />
</CardHeader>
<div className='aliases-settings-panel'>
<div className='flex-1'>
<ScrollableList
scrollKey='aliases'
emptyMessage={emptyMessage}

View File

@ -113,7 +113,9 @@ const NativeCaptchaField: React.FC<INativeCaptchaField> = ({ captcha, onChange,
return (
<Stack space={2}>
<div className='flex w-full items-center justify-center rounded-md border border-solid border-gray-300 bg-white dark:border-gray-600'>
<img alt={intl.formatMessage(messages.captcha)} src={captcha.get('url')} onClick={onClick} />
<button className='!block space-x-2 !border-none !p-0 !py-2 !text-primary-600 hover:!underline focus:!ring-transparent focus:!ring-offset-0 dark:!text-accent-blue rtl:space-x-reverse' onClick={onClick}>
<img alt={intl.formatMessage(messages.captcha)} src={captcha.get('url')} />
</button>
</div>
<Input

View File

@ -49,7 +49,7 @@ const START_INDEX = 10000;
const List: Components['List'] = forwardRef((props, ref) => {
const { context, ...rest } = props;
return <div ref={ref} {...rest} className='mb-2' />;
return <div ref={ref} {...rest} />;
});
const Scroller: Components['Scroller'] = forwardRef((props, ref) => {

View File

@ -68,7 +68,7 @@ const ChatPage: React.FC<IChatPage> = ({ chatId }) => {
data-testid='chat-page'
>
<Stack
className={clsx('dark:inset col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 black:bg-black dark:bg-gray-900 dark:bg-none sm:col-span-3', {
className={clsx('col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 black:bg-black dark:inset-0 dark:bg-gray-900 dark:bg-none sm:col-span-3', {
'hidden sm:block': isSidebarHidden,
})}
>

View File

@ -127,7 +127,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
// List of elements that shouldn't collapse the composer when clicked
// FIXME: Make this less brittle
getClickableArea(),
document.querySelector('.privacy-dropdown__dropdown'),
document.getElementById('privacy-dropdown'),
document.querySelector('em-emoji-picker'),
document.getElementById('modal-overlay'),
].some(element => element?.contains(e.target as any));
@ -217,7 +217,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
), [features, id]);
const composeModifiers = !condensed && (
<Stack space={4} className='compose-form__modifiers'>
<Stack space={4} className='text-sm text-gray-900'>
<UploadForm composeId={id} onSubmit={handleSubmit} />
<PollForm composeId={id} />

View File

@ -42,9 +42,10 @@ interface IPrivacyDropdownMenu {
onClose: () => void;
onChange: (value: string | null) => void;
unavailable?: boolean;
active: boolean;
}
const PrivacyDropdownMenu: React.FC<IPrivacyDropdownMenu> = ({ style, items, placement, value, onClose, onChange }) => {
const PrivacyDropdownMenu: React.FC<IPrivacyDropdownMenu> = ({ style, items, placement, value, onClose, onChange, active }) => {
const node = useRef<HTMLDivElement>(null);
const focusedItem = useRef<HTMLDivElement>(null);
@ -125,15 +126,15 @@ const PrivacyDropdownMenu: React.FC<IPrivacyDropdownMenu> = ({ style, items, pla
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={clsx('privacy-dropdown__dropdown', placement)} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : undefined }} role='listbox' ref={node}>
<div id={'privacy-dropdown'} className={clsx('absolute z-[1000] ml-10 overflow-hidden rounded-md bg-white text-sm shadow-lg black:border black:border-gray-800 black:bg-black dark:bg-gray-900', { 'block shadow-md': active })} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : undefined, transformOrigin: placement === 'top' ? '50% 100%' : '50% 0' }} role='listbox' ref={node}>
{items.map(item => (
<div role='option' tabIndex={0} key={item.value} data-index={item.value} onKeyDown={handleKeyDown} onClick={handleClick} className={clsx('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? focusedItem : null}>
<div className='privacy-dropdown__option__icon'>
<div role='option' tabIndex={0} key={item.value} data-index={item.value} onKeyDown={handleKeyDown} onClick={handleClick} className={clsx('group flex cursor-pointer p-2.5 text-sm text-gray-700 hover:bg-gray-100 black:hover:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800', { 'bg-gray-100 dark:bg-gray-800 black:bg-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700': item.value === value })} aria-selected={item.value === value} ref={item.value === value ? focusedItem : null}>
<div className='mr-2.5 flex items-center justify-center rtl:ml-2.5 rtl:mr-0'>
<Icon src={item.icon} />
</div>
<div className='privacy-dropdown__option__content'>
<strong>{item.text}</strong>
<div className={clsx('flex-auto text-primary-600 group-hover:text-black dark:text-primary-400 group-hover:dark:text-white', { 'group-active:text-black group-active:dark:text-white': item.value === value })}>
<strong className='block font-medium text-black dark:text-white'>{item.text}</strong>
{item.meta}
</div>
</div>
@ -244,8 +245,8 @@ const PrivacyDropdown: React.FC<IPrivacyDropdown> = ({
const valueOption = options.find(item => item.value === value);
return (
<div className={clsx('privacy-dropdown', placement, { active: open })} onKeyDown={handleKeyDown} ref={node}>
<div className={clsx('privacy-dropdown__value', { active: valueOption && options.indexOf(valueOption) === 0 })}>
<div onKeyDown={handleKeyDown} ref={node}>
<div className={clsx({ 'rouded-t-md': placement === 'top' && open })}>
<IconButton
className={clsx({
'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500': !open,
@ -266,6 +267,7 @@ const PrivacyDropdown: React.FC<IPrivacyDropdown> = ({
onClose={handleClose}
onChange={onChange}
placement={placement}
active={open}
/>
</Overlay>
</div>

View File

@ -137,7 +137,7 @@ const SearchResults = () => {
searchResults = suggestions.map(suggestion => <AccountContainer key={suggestion.account} id={suggestion.account} />);
} else if (loaded) {
noResultsMessage = (
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage
id='empty_column.search.accounts'
defaultMessage='There are no people results for "{term}"'
@ -179,7 +179,7 @@ const SearchResults = () => {
resultsIds = trendingStatuses;
} else if (loaded) {
noResultsMessage = (
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage
id='empty_column.search.statuses'
defaultMessage='There are no posts results for "{term}"'
@ -203,7 +203,7 @@ const SearchResults = () => {
searchResults = trends.map(hashtag => <Hashtag key={hashtag.name} hashtag={hashtag} />);
} else if (loaded) {
noResultsMessage = (
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage
id='empty_column.search.hashtags'
defaultMessage='There are no hashtags results for "{term}"'

View File

@ -10,7 +10,7 @@ interface IWarning {
const Warning: React.FC<IWarning> = ({ message }) => (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
<div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
<div className='compose-form-warning mb-2.5 rounded bg-accent-300 px-2.5 py-2 text-xs text-white shadow-md' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
{message}
</div>
)}

View File

@ -532,7 +532,7 @@ const AutosuggestPlugin = ({
? ReactDOM.createPortal(
<div
className={clsx({
'scroll-smooth snap-y snap-always will-change-scroll mt-6 overflow-y-auto max-h-56 relative w-max z-1000 shadow bg-white dark:bg-gray-900 rounded-lg py-1 space-y-0 dark:ring-2 dark:ring-primary-700 focus:outline-none': true,
'scroll-smooth snap-y snap-always will-change-scroll mt-6 overflow-y-auto max-h-56 relative w-max z-[1000] shadow bg-white dark:bg-gray-900 rounded-lg py-1 space-y-0 dark:ring-2 dark:ring-primary-700 focus:outline-none': true,
hidden: suggestionsHidden || suggestions.isEmpty(),
block: !suggestionsHidden && !suggestions.isEmpty(),
})}

View File

@ -18,6 +18,7 @@ const CryptoIcon: React.FC<ICryptoIcon> = ({ ticker, title, className }): JSX.El
return (
<div className={className}>
<img
className='w-full'
src={getIcon(ticker)}
alt={title || ticker}
/>

View File

@ -2,7 +2,7 @@ import externalLinkIcon from '@tabler/icons/outline/external-link.svg';
import { QRCodeCanvas as QRCode } from 'qrcode.react';
import CopyableInput from 'soapbox/components/copyable-input.tsx';
import Icon from 'soapbox/components/icon.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import { getExplorerUrl } from '../utils/block-explorer.ts';
import { getTitle } from '../utils/coin-db.ts';
@ -20,22 +20,22 @@ const DetailedCryptoAddress: React.FC<IDetailedCryptoAddress> = ({ address, tick
const explorerUrl = getExplorerUrl(ticker, address);
return (
<div className='crypto-address'>
<div className='crypto-address__head'>
<div className='flex flex-col p-0'>
<div className='mb-1.5 flex items-center'>
<CryptoIcon
className='crypto-address__icon'
className='mr-2.5 flex w-6 items-start justify-center'
ticker={ticker}
title={title}
/>
<div className='crypto-address__title'>{title || ticker.toUpperCase()}</div>
<div className='crypto-address__actions'>
{explorerUrl && <a href={explorerUrl} target='_blank'>
<Icon src={externalLinkIcon} />
<div className='font-bold'>{title || ticker.toUpperCase()}</div>
<div className='ml-auto flex'>
{explorerUrl && <a className='ml-2 text-gray-400' href={explorerUrl} target='_blank'>
<SvgIcon size={20} src={externalLinkIcon} />
</a>}
</div>
</div>
{note && <div className='crypto-address__note'>{note}</div>}
<div className='crypto-address__qrcode'>
{note && <div className='mb-2.5'>{note}</div>}
<div className='mb-3 flex items-center justify-center p-2.5'>
<QRCode className='rounded-lg' value={address} includeMargin />
</div>

View File

@ -179,7 +179,7 @@ const EventDiscussion: React.FC<IEventDiscussion> = (props) => {
{me && <div className='border-b border-solid border-gray-200 p-2 pt-0 dark:border-gray-800'>
<ComposeForm id={`reply:${status.id}`} autoFocus={false} event={status.id} />
</div>}
<div ref={node} className='thread p-0 shadow-none sm:p-2'>
<div ref={node} className='thread p-0 shadow-none black:bg-black dark:bg-primary-900 sm:p-2'>
<ScrollableList
id='thread'
ref={scroller}

View File

@ -69,7 +69,7 @@ const Favourites: React.FC<IFavourites> = ({ params }) => {
if (isUnavailable) {
return (
<Column>
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
</Column>

View File

@ -188,7 +188,7 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
if (notFound) return <MissingIndicator />;
return (
<Column className='filter-settings-panel' label={intl.formatMessage(messages.subheading_add_new)}>
<Column label={intl.formatMessage(messages.subheading_add_new)}>
<Form onSubmit={handleAddNew}>
<FormGroup labelText={intl.formatMessage(messages.title)}>
<Input

View File

@ -60,7 +60,7 @@ const Filters = () => {
const emptyMessage = <FormattedMessage id='empty_column.filters' defaultMessage="You haven't created any muted words yet." />;
return (
<Column className='filter-settings-panel' label={intl.formatMessage(messages.heading)}>
<Column label={intl.formatMessage(messages.heading)}>
<HStack className='mb-4' space={2} justifyContent='end'>
<Button
to='/filters/new'

View File

@ -44,7 +44,7 @@ const Followers: React.FC<IFollowers> = ({ params }) => {
if (isUnavailable) {
return (
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
);

View File

@ -44,7 +44,7 @@ const Following: React.FC<IFollowing> = ({ params }) => {
if (isUnavailable) {
return (
<div className='empty-column-indicator'>
<div className='flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
);

View File

@ -24,7 +24,7 @@ export const InputContainer: React.FC<IInputContainer> = (props) => {
return (
<div className={containerClass}>
{props.children}
{props.hint && <span className='hint'>{props.hint}</span>}
{props.hint && <span>{props.hint}</span>}
</div>
);
};
@ -45,10 +45,10 @@ export const LabelInputContainer: React.FC<ILabelInputContainer> = ({ label, hin
return (
<div className='label_input'>
<label htmlFor={id}>{label}</label>
<div className='label_input__wrapper'>
<div>
{childrenWithProps}
</div>
{hint && <span className='hint'>{hint}</span>}
{hint && <span>{hint}</span>}
</div>
);
};

View File

@ -77,7 +77,7 @@ const GroupGallery: React.FC<IGroupGallery> = (props) => {
))}
{(!isLoading && attachments.length === 0) && (
<div className='empty-column-indicator col-span-2 sm:col-span-3'>
<div className='col-span-2 flex min-h-[160px] flex-1 items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300 sm:col-span-3'>
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
</div>
)}

View File

@ -3,11 +3,11 @@ import clsx from 'clsx';
import { defineMessages, useIntl } from 'react-intl';
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from 'soapbox/actions/lists.ts';
import Icon from 'soapbox/components/icon.tsx';
import Button from 'soapbox/components/ui/button.tsx';
import Form from 'soapbox/components/ui/form.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Input from 'soapbox/components/ui/input.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
@ -49,7 +49,7 @@ const Search = () => {
placeholder={intl.formatMessage(messages.search)}
/>
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
<Icon src={backspaceIcon} aria-label={intl.formatMessage(messages.search)} className={clsx('svg-icon--backspace', { active: hasValue })} />
<SvgIcon src={backspaceIcon} aria-label={intl.formatMessage(messages.search)} className={clsx('pointer-events-none absolute right-4 top-1/2 z-20 inline-block size-4.5 -translate-y-1/2 cursor-pointer text-[16px] text-gray-400 opacity-0 rtl:left-4 rtl:right-auto', { 'pointer-events-auto opacity-100': hasValue })} />
</div>
</label>

View File

@ -5,11 +5,11 @@ import { randomIntFromInterval, generateText } from '../utils.ts';
/** Fake link preview to display while data is loading. */
const PlaceholderCard: React.FC = () => (
<div className={clsx('status-card', {
<div className={clsx('flex overflow-hidden rounded-lg border border-solid border-gray-200 text-sm text-gray-800 no-underline dark:border-gray-800 dark:text-gray-200', {
'animate-pulse': true,
})}
>
<div className='primary-500 w-2/5 rounded-l'>&nbsp;</div>
<div className='w-2/5 rounded-l'>&nbsp;</div>
<div className='flex w-3/5 flex-col justify-between break-words p-4 text-primary-50'>
<p>{generateText(randomIntFromInterval(5, 25))}</p>

View File

@ -3,8 +3,8 @@ import PlaceholderStatus from './placeholder-status.tsx';
/** Fake material status to display while data is loading. */
const PlaceholderMaterialStatus: React.FC = () => {
return (
<div className='material-status' tabIndex={-1} aria-hidden>
<div className='material-status__status' tabIndex={0}>
<div className='pb-2.5' tabIndex={-1} aria-hidden>
<div className='rounded-[10px] py-[15px] pb-[10px] shadow-[0_0_6px_0_rgba(0,0,0,0.1)]' tabIndex={0}>
<PlaceholderStatus />
</div>
</div>

View File

@ -33,7 +33,7 @@ const PlaceholderStatus: React.FC<IPlaceholderStatus> = ({ variant }) => (
</HStack>
</div>
<div className='status__content-wrapper mt-4'>
<div className='status--content-wrapper mt-4'>
<PlaceholderStatusContent minLength={5} maxLength={120} />
</div>
</div>

View File

@ -1,14 +1,14 @@
import clsx from 'clsx';
import noop from 'lodash/noop';
import { Suspense } from 'react';
import { toggleStatusReport } from 'soapbox/actions/reports.ts';
import StatusContent from 'soapbox/components/status-content.tsx';
import Toggle from 'soapbox/components/ui/toggle.tsx';
import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { MediaGallery, Video, Audio } from '../../ui/util/async-components.ts';
interface IStatusCheckBox {
id: string;
disabled?: boolean;
@ -21,6 +21,8 @@ const StatusCheckBox: React.FC<IStatusCheckBox> = ({ id, disabled }) => {
const onToggle: React.ChangeEventHandler<HTMLInputElement> = (e) => dispatch(toggleStatusReport(id, e.target.checked));
const mediaType = status?.media_attachments.get(0)?.type;
if (!status || status.reblog) {
return null;
}
@ -71,13 +73,17 @@ const StatusCheckBox: React.FC<IStatusCheckBox> = ({ id, disabled }) => {
}
return (
<div className='status-check-box'>
<div className='status-check-box__status'>
<div className='flex items-center justify-between'>
<div className='py-2'>
<StatusContent status={status} />
<Suspense>{media}</Suspense>
<Suspense>
<div className={clsx('max-w-[250px]', { 'mt-2': mediaType === 'audio' || mediaType === 'video' })}>
{media}
</div>
</Suspense>
</div>
<div className='status-check-box-toggle'>
<div className='flex flex-[0_0_auto] items-center justify-center p-2.5'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} />
</div>
</div>

View File

@ -31,8 +31,8 @@ const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) =>
const account = status.account;
return (
<div className={clsx('status__wrapper', `status__wrapper-${status.visibility}`, { 'status__wrapper-reply': !!status.in_reply_to_id })} tabIndex={0}>
<div className={clsx('status', `status-${status.visibility}`, { 'status-reply': !!status.in_reply_to_id })} data-id={status.id}>
<div className={clsx('status--wrapper')} tabIndex={0}>
<div className={clsx('status', { 'status-reply': !!status.in_reply_to_id })} data-id={status.id}>
<div className='mb-4'>
<HStack justifyContent='between' alignItems='start'>
<Account

View File

@ -24,6 +24,7 @@ const SitePreview: React.FC<ISitePreview> = ({ soapbox }) => {
const dark = ['dark', 'black'].includes(userTheme as string) || (userTheme === 'system' && systemTheme === 'dark');
// eslint-disable-next-line tailwindcss/no-custom-classname
const bodyClass = clsx(
'site-preview',
'align-center relative flex justify-center text-base',

View File

@ -105,7 +105,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
if (actualStatus.quote) {
if (actualStatus.pleroma.get('quote_visible', true) === false) {
quote = (
<div className='quoted-actualStatus-tombstone'>
<div>
<p><FormattedMessage id='status.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
</div>
);
@ -121,8 +121,8 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
}
return (
<div className='border-box'>
<div ref={node} className='detailed-actualStatus' tabIndex={-1}>
<div className='box-border'>
<div ref={node} tabIndex={-1}>
{renderStatusInfo()}
<div className='mb-4'>

View File

@ -12,7 +12,7 @@ const ColumnForbidden = () => {
return (
<Column label={intl.formatMessage(messages.title)}>
<div className='error-column'>
<div className='error-column flex min-h-[160px] flex-1 flex-col items-center justify-center rounded-lg bg-primary-50 p-10 text-center text-gray-900 dark:bg-gray-700 dark:text-gray-300'>
{intl.formatMessage(messages.body)}
</div>
</Column>

View File

@ -135,16 +135,16 @@ class ImageLoader extends PureComponent<IImageLoader> {
const { alt, src, width, height, onClick } = this.props;
const { loading } = this.state;
const className = clsx('image-loader', {
'image-loader--loading': loading,
'image-loader--amorphous': !this.hasSize(),
});
const className = 'relative size-full flex items-center justify-center flex-col';
return (
<div className={className}>
{loading ? (
<canvas
className='image-loader__preview-canvas'
className={clsx('max-h-[80%] max-w-full object-contain', { 'hidden': !this.hasSize() })}
style={{
background: 'url(\'../assets/images/void.png\') repeat',
}}
ref={this.setCanvasRef}
width={width}
height={height}

View File

@ -118,9 +118,9 @@ export default class ModalRoot extends PureComponent<IModalRoot> {
componentDidUpdate(prevProps: IModalRoot, prevState: any, { visible }: any) {
if (visible) {
document.body.classList.add('with-modals');
document.body.classList.add('overflow-hidden');
} else {
document.body.classList.remove('with-modals');
document.body.classList.remove('overflow-hidden');
}
}

View File

@ -2,8 +2,8 @@ import clsx from 'clsx';
import { FormattedMessage } from 'react-intl';
import { spring } from 'react-motion';
import Icon from 'soapbox/components/icon.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import ReplyIndicator from 'soapbox/features/compose/components/reply-indicator.tsx';
import Motion from '../../util/optional-motion.tsx';
@ -21,7 +21,7 @@ interface IActionsModal {
const ActionsModal: React.FC<IActionsModal> = ({ status, actions, onClick, onClose }) => {
const renderAction = (action: MenuItem | null, i: number) => {
if (action === null) {
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
return <li key={`sep-${i}`} className='m-2 block h-px bg-gray-200 black:bg-gray-800 dark:bg-gray-600' />;
}
const { icon = null, text, meta = null, active = false, href = '#', destructive } = action;
@ -35,12 +35,12 @@ const ActionsModal: React.FC<IActionsModal> = ({ status, actions, onClick, onClo
{...compProps}
space={2.5}
data-index={i}
className={clsx('w-full', { active, destructive })}
className={clsx('flex w-full items-center px-4 py-3 text-left text-gray-700 no-underline hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800 dark:focus:bg-primary-800', { active, 'text-danger-600 dark:text-danger-400': destructive })}
element={Comp}
>
{icon && <Icon title={text} src={icon} role='presentation' tabIndex={-1} />}
{icon && <SvgIcon title={text} className='size-6 min-w-5 stroke-[1.5]' src={icon} role='presentation' tabIndex={-1} />}
<div>
<div className={clsx({ 'actions-modal__item-label': !!meta })}>{text}</div>
<div className={clsx({ 'font-medium': !!meta })}>{text}</div>
<div>{meta}</div>
</div>
</HStack>
@ -51,18 +51,22 @@ const ActionsModal: React.FC<IActionsModal> = ({ status, actions, onClick, onClo
return (
<Motion defaultStyle={{ top: 100 }} style={{ top: spring(0) }}>
{({ top }) => (
<div className='modal-root__modal actions-modal' style={{ top: `${top}%` }}>
<div className='pointer-events-auto relative z-[9999] m-auto flex max-h-[calc(100vh-3rem)] w-full max-w-lg flex-col overflow-hidden rounded-2xl bg-white text-gray-400 shadow-xl black:bg-black dark:bg-gray-900' style={{ top: `${top}%` }}>
{status && (
<ReplyIndicator className='actions-modal__status rounded-b-none' status={status} hideActions />
<ReplyIndicator className='max-h-[300px] overflow-y-auto rounded-b-none' status={status} hideActions />
)}
<ul className={clsx({ 'with-status': !!status })}>
<ul className={clsx({ ' max-h-[calc(80vh-75px)]': !!status }, 'my-2 max-h-[calc(100vh-147px)] shrink-0 overflow-y-auto')}>
{actions && actions.map(renderAction)}
<li className='dropdown-menu__separator' />
<li className='m-2 block h-px bg-gray-200 black:bg-gray-800 dark:bg-gray-600' />
<li>
<button type='button' onClick={onClose}>
<button
type='button'
className='flex w-full items-center justify-center px-4 py-3 text-left text-gray-700 no-underline hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800 dark:focus:bg-primary-800'
onClick={onClose}
>
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
</button>
</li>

View File

@ -54,7 +54,7 @@ export const PuzzleCaptcha: React.FC<IPuzzleCaptcha> = ({ bg, puzzle, position,
return (
<div id='drop-area' ref={ref} className='relative'>
<img
className='drop-shadow-black absolute z-[101] w-[61px] drop-shadow-2xl hover:cursor-grab'
className='absolute z-[101] w-[61px] drop-shadow-2xl hover:cursor-grab'
src={puzzle}
alt=''
onPointerDown={(e) => e.currentTarget.setPointerCapture(e.pointerId)}

View File

@ -57,10 +57,10 @@ const CompareHistoryModal: React.FC<ICompareHistoryModal> = ({ onClose, statusId
</>
)}
<div className='status__content' dangerouslySetInnerHTML={content} />
<div className='whitespace-normal p-0 pt-2.5 text-sm text-gray-700 dark:text-gray-500' dangerouslySetInnerHTML={content} />
{poll && (
<div className='poll'>
<div>
<Stack>
{version.poll.options.map((option: any) => (
<HStack alignItems='center' className='p-1 text-gray-900 dark:text-gray-300'>

View File

@ -7,7 +7,7 @@ const CryptoDonateModal: React.FC<ICryptoAddress & { onClose: () => void }> = ({
return (
<Modal onClose={onClose} width='xs'>
<div className='crypto-donate-modal'>
<div>
<DetailedCryptoAddress {...props} />
</div>
</Modal>

View File

@ -243,7 +243,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
};
return (
<div className='media-modal pointer-events-auto fixed inset-0 z-[9999] h-full bg-gray-900/90'>
<div className='pointer-events-auto fixed inset-0 z-[9999] h-full bg-gray-900/90'>
<div
className='absolute inset-0'
role='presentation'

View File

@ -38,7 +38,7 @@ const ReplyMentionsModal: React.FC<IReplyMentionsModal> = ({ composeId, onClose
closeIcon={arrowLeftIcon}
closePosition='left'
>
<div className='reply-mentions-modal__accounts'>
<div className='block min-h-[300px] flex-1 flex-row overflow-y-auto'>
{mentions.map(accountId => <Account composeId={composeId} key={accountId} accountId={accountId} author={author === accountId} />)}
</div>
</Modal>

View File

@ -115,8 +115,8 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
secondaryAction={isOpen ? onRegister : undefined}
secondaryText={isOpen ? <FormattedMessage id='account.register' defaultMessage='Sign up' /> : undefined}
>
<div className='remote-interaction-modal__content'>
<Form className='remote-interaction-modal__fields' onSubmit={onSubmit}>
<div className='flex flex-col gap-y-[10px]'>
<Form className='flex w-full flex-col gap-2.5' onSubmit={onSubmit}>
<Input
placeholder={intl.formatMessage(messages.accountPlaceholder)}
name='remote_follow[acct]'
@ -126,12 +126,14 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
onChange={onAccountChange}
required
/>
<Button type='submit' theme='primary'>{button}</Button>
<Button className='self-end' type='submit' theme='primary'>{button}</Button>
</Form>
<div className='remote-interaction-modal__divider'>
<div className='m-0 -mx-2.5 flex items-center gap-2.5'>
<div className='flex-1 border-b border-gray-300 dark:border-gray-600' />
<Text align='center'>
<FormattedMessage id='remote_interaction.divider' defaultMessage='or' />
</Text>
<div className='flex-1 border-b border-gray-300 dark:border-gray-600' />
</div>
{isOpen && (
<Text size='lg' weight='medium'>

View File

@ -1,52 +0,0 @@
import HStack from 'soapbox/components/ui/hstack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import VerificationBadge from 'soapbox/components/verification-badge.tsx';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { getAcct } from 'soapbox/utils/accounts.ts';
import type { Account } from 'soapbox/schemas/index.ts';
interface IDisplayName {
account: Pick<Account, 'id' | 'acct' | 'fqn' | 'verified' | 'display_name_html'>;
withSuffix?: boolean;
}
/**
* This component is different from other display name components because it displays the name inline.
*
* @param {IDisplayName} props - The properties for this component.
* @param {Pick<Account, 'id' | 'acct' | 'fqn' | 'verified' | 'display_name_html'>} props.account - The account object contains all the metadata for an account, such as the display name, ID, and more.
* @param {boolean} [props.withSuffix=true] - Determines whether to show the account suffix (eg, @danidfra).
*
* @returns {JSX.Element} The DisplayNameRow component.
*/
const DisplayNameRow: React.FC<IDisplayName> = ({ account, withSuffix = true }) => {
const { displayFqn = false } = useSoapboxConfig();
const { verified } = account;
const displayName = (
<HStack space={1} alignItems='center' justifyContent='center' grow>
<Text
size='sm'
weight='normal'
truncate
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
/>
{verified && <VerificationBadge />}
</HStack>
);
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
const suffix = (<span className='display-name'>@{getAcct(account, displayFqn)}</span>);
return (
<div className='flex max-w-80 flex-col items-center justify-center text-center sm:flex-row sm:gap-2'>
{displayName}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span className='hidden text-2xl font-bold sm:block'>-</span>
{withSuffix && suffix}
</div>
);
};
export default DisplayNameRow;

View File

@ -47,7 +47,7 @@ const ZapSplit = ({ zapData, zapAmount, invoice, onNext, isLastStep, onFinish }:
<Account account={account} showProfileHoverCard={false} />
</div>
</Stack>
<div className='bg-grey-500 dark:border-grey-800 -mx-4 w-full border-b border-solid sm:-mx-10' />
<div className='-mx-4 w-full border-b border-solid bg-gray-500 dark:border-gray-800 sm:-mx-10' />
<Stack justifyContent='center' alignItems='center' className='min-w-72 text-center' space={4}>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
@ -67,7 +67,7 @@ const ZapSplit = ({ zapData, zapAmount, invoice, onNext, isLastStep, onFinish }:
</Stack>
<div className='flex justify-center'>
<div className='box-shadow:none rounded-none border-0 border-b-2 p-0.5 text-center !ring-0 dark:bg-transparent'>
<div className='rounded-none border-0 border-b-2 p-0.5 text-center shadow-none !ring-0 dark:bg-transparent'>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span className='!text-5xl font-bold'>{zapAmount}</span> sats
</div>
@ -81,7 +81,7 @@ const ZapSplit = ({ zapData, zapAmount, invoice, onNext, isLastStep, onFinish }:
</a>
</Stack>
{invoice && <div className='border-grey-500 mt-4 flex w-full border-t pt-4 sm:ml-4 sm:w-4/5 sm:border-l sm:border-t-0 sm:pl-4'>
{invoice && <div className='mt-4 flex w-full border-t border-gray-500 pt-4 sm:ml-4 sm:w-4/5 sm:border-l sm:border-t-0 sm:pl-4'>
<Stack space={6} className='relative m-auto' alignItems='center'>
<h3 className='text-xl font-bold'>
{renderTitleQr()}

View File

@ -113,7 +113,7 @@ const Navbar = () => {
<HStack
space={4}
alignItems='center'
className={clsx('enter flex-1 lg:items-stretch', {
className={clsx('flex-1 lg:items-stretch', {
'justify-center lg:justify-start': account,
'justify-start': !account,
})}

View File

@ -61,7 +61,7 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
<div className={clsx('opacity-50', className)}>
<div className={clsx('status', { 'status-reply': !!status.in_reply_to_id, muted })} data-id={status.id}>
<Card
className={clsx(`status-${status.visibility}`, {
className={clsx({
'py-6 sm:p-5': !thread,
'status-reply': !!status.in_reply_to_id,
})}
@ -79,7 +79,7 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
</HStack>
</div>
<div className='status__content-wrapper'>
<div className='status--content-wrapper'>
<StatusReplyMentions status={status} />
<Stack space={4}>

View File

@ -48,7 +48,7 @@ const ProfileFamiliarFollowers: React.FC<IProfileFamiliarFollowers> = ({ account
const accounts: Array<React.ReactNode> = familiarFollowers.map(account => !!account && (
<HoverRefWrapper accountId={account.id} key={account.id} inline>
<Link className='mention inline-block' to={`/@${account.acct}`}>
<Link className='inline-block text-primary-600 hover:underline dark:text-accent-blue' to={`/@${account.acct}`}>
<HStack space={1} alignItems='center' grow>
<Text
size='sm'

View File

@ -124,7 +124,7 @@ class ZoomableImage extends PureComponent<IZoomableImage> {
return (
<div
className='zoomable-image'
className='relative flex size-full items-center justify-center'
ref={this.setContainerRef}
style={{ overflow }}
>
@ -132,6 +132,7 @@ class ZoomableImage extends PureComponent<IZoomableImage> {
role='presentation'
ref={this.setImageRef}
alt={alt}
className='size-auto max-h-[80%] max-w-full object-contain shadow-2xl'
title={alt}
src={src}
style={{

View File

@ -135,7 +135,7 @@ const ZapPayRequestForm = ({ account, status, onClose }: IZapPayRequestForm) =>
<div className='relative flex items-end justify-center gap-4'>
<Input
type='text' onChange={handleCustomAmount} value={zapAmount}
className='box-shadow:none max-w-20 rounded-none border-0 border-b-4 p-0 text-center !text-2xl font-bold !ring-0 dark:bg-transparent sm:max-w-28 sm:p-0.5 sm:!text-4xl'
className='max-w-20 rounded-none border-0 border-b-4 p-0 text-center !text-2xl font-bold shadow-none !ring-0 dark:bg-transparent sm:max-w-28 sm:p-0.5 sm:!text-4xl'
/>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{hasZapSplit && <p className='absolute right-0 font-bold sm:-right-6 sm:text-xl'>sats</p>}

View File

@ -1,30 +1,9 @@
@use 'variables';
@use 'fonts';
@use 'basics';
@use 'loading';
@use 'ui';
@use 'emoji-picker';
@use 'rtl';
@use 'accessibility';
@use 'navigation';
@use 'autosuggest';
// COMPONENTS
@use 'components/buttons';
@use 'components/modal';
@use 'components/compose-form';
@use 'components/status';
@use 'components/reply-mentions';
@use 'components/detailed-status';
@use 'components/media-gallery';
@use 'components/notification';
@use 'components/display-name';
@use 'components/columns';
@use 'components/search';
@use 'components/video-player';
@use 'components/audio-player';
@use 'components/crypto-donate';
@use 'components/aliases';
@use 'components/icon';
@use 'forms';
@use 'utilities';

View File

@ -1,38 +0,0 @@
.react-datepicker__input-container input {
// display: block;
// box-sizing: border-box;
// width: 100%;
// margin: 0;
// background: transparent;
// color: var(--primary-text-color);
// padding: 10px;
// font-family: inherit;
// font-size: 16px;
// resize: vertical;
// border: 0;
// outline: 0;
// &:focus {
// outline: 0;
// }
// @media screen and (max-width: 600px) {
// font-size: 16px;
// }
}
.autosuggest-emoji {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
line-height: 18px;
font-size: 14px;
}
.autosuggest-emoji img {
display: block;
margin-right: 8px;
width: 16px;
height: 16px;
}

View File

@ -1,33 +0,0 @@
body {
@apply antialiased;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
body.with-modals {
@apply overflow-hidden;
}
// Note: this is needed for React HotKeys performance. Removing this
// will cause severe performance degradation on Safari.
div[tabindex='-1']:focus {
outline: 0;
}
::selection {
@apply bg-primary-600 text-white;
}
noscript {
text-align: center;
}
.emojione {
@apply w-4 h-4 -mt-[0.2ex] mb-[0.2ex] inline-block align-middle object-contain;
}
// Virtuoso empty placeholder fix.
// https://gitlab.com/petyosi/soapbox-fe/-/commit/1e22c39934b60e5e186de804060ecfdf1955b506
div[data-viewport-type='window'] {
position: static !important;
}

View File

@ -1,14 +0,0 @@
.aliases {
&__accounts {
overflow-y: auto;
&.empty-column-indicator {
min-height: unset;
overflow-y: unset;
}
}
}
.aliases-settings-panel {
flex: 1;
}

View File

@ -1,51 +0,0 @@
.audio-player {
@apply relative box-border overflow-hidden rounded-[10px] bg-black pb-11;
direction: ltr;
&.editable {
@apply rounded-none h-full;
}
.video-player__volume::before,
.video-player__seek::before {
@apply bg-white/10;
}
.video-player__seek__buffer {
@apply bg-white/20;
}
.video-player__buttons button {
@apply text-current opacity-[75];
&:active,
&:hover,
&:focus {
@apply text-current opacity-100;
}
}
.video-player__time-sep,
.video-player__time-total,
.video-player__time-current {
@apply text-current;
}
.video-player__seek::before,
.video-player__seek__buffer,
.video-player__seek__progress {
@apply top-0;
}
.video-player__seek__handle {
@apply -top-1;
}
.video-player__controls {
@apply pt-2.5 bg-transparent;
}
}
.media-spoiler-audio {
@apply relative mt-2 block cursor-pointer border-0 bg-cover bg-center bg-no-repeat;
}

View File

@ -1,8 +0,0 @@
button {
font-family: inherit;
cursor: pointer;
&:focus {
outline: none;
}
}

View File

@ -1,26 +0,0 @@
.empty-column-indicator,
.error-column {
@apply bg-primary-50 dark:bg-gray-700 text-gray-900 dark:text-gray-300 text-center p-10 flex flex-1 items-center justify-center min-h-[160px] rounded-lg;
@supports (display: grid) { // hack to fix Chrome <57
contain: strict;
}
& > span {
@apply max-w-[400px];
}
a {
@apply text-primary-600 dark:text-primary-400 no-underline hover:underline;
}
}
.error-column {
flex-direction: column;
.svg-icon {
width: 70px;
height: 70px;
margin-bottom: 30px;
}
}

View File

@ -1,17 +1,9 @@
@use "../variables";
.compose-form {
&__warning {
@apply text-xs mb-2.5 px-2.5 py-2 shadow-md rounded bg-accent-300 text-white;
strong {
@apply font-medium;
@each $lang in variables.$cjk-langs {
&:lang(#{$lang}) {
@apply font-bold;
}
}
}
a {
@ -169,12 +161,6 @@
strong {
@apply block font-medium text-black dark:text-white;
@each $lang in variables.$cjk-langs {
&:lang(#{$lang}) {
@apply font-bold;
}
}
}
}
}

View File

@ -1,47 +0,0 @@
.crypto-address {
@apply flex flex-col p-5;
&__head {
@apply flex items-center mb-1.5;
}
&__title {
@apply font-bold;
}
&__icon {
@apply flex items-start justify-center w-6 mr-2.5;
img {
@apply w-full;
}
}
&__actions {
@apply flex ml-auto;
a {
@apply text-gray-400 ml-2;
}
.svg-icon {
@apply h-4.5 w-4.5;
}
}
&__note {
@apply mb-2.5;
}
&__qrcode {
@apply flex items-center justify-center mb-3 p-2.5;
}
&__address {
@apply mt-auto;
}
}
.crypto-donate-modal .crypto-address {
@apply p-0;
}

View File

@ -1,15 +0,0 @@
.thread {
@apply bg-white black:bg-black dark:bg-primary-900;
&__status {
@apply relative pb-4;
.status__wrapper {
@apply shadow-none p-0;
}
}
.status__content-wrapper {
@apply pl-[calc(42px+12px)] rtl:pl-0 rtl:pr-[calc(42px+12px)];
}
}

View File

@ -1,18 +0,0 @@
.display-name {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: relative;
bdi {
min-width: 0;
}
&__account {
position: relative;
font-weight: 600;
font-size: 14px;
}
}

View File

@ -1,152 +0,0 @@
$media-compact-size: 50px;
.media-gallery {
@apply rounded-lg;
box-sizing: border-box;
overflow: hidden;
isolation: isolate;
position: relative;
width: 100%;
height: auto;
&__item {
@apply rounded-sm;
border: 0;
box-sizing: border-box;
display: block;
float: left;
position: relative;
overflow: hidden;
&__icons {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.svg-icon {
@apply h-24 w-24;
}
}
&-overflow {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.75);
z-index: 2;
color: #333;
text-align: center;
font-weight: bold;
font-size: 50px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
&-thumbnail {
@apply text-gray-400;
cursor: zoom-in;
display: block;
text-decoration: none;
line-height: 0;
position: relative;
z-index: 1;
height: 100%;
width: 100%;
video {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
&__preview {
@apply bg-gray-200 dark:bg-gray-900 rounded-lg;
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
z-index: 0;
&--hidden {
display: none;
}
}
&__gifv {
height: 100%;
overflow: hidden;
position: relative;
width: 100%;
}
&__item-gifv-thumbnail {
@apply rounded-md;
cursor: zoom-in;
height: 100%;
object-fit: cover;
position: relative;
width: 100%;
z-index: 1;
transform: none;
top: 0;
}
&__gifv__label,
&__filename__label,
&__file-extension__label {
@apply pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 py-0.5 px-1.5 font-semibold text-white opacity-90;
font-size: 11px;
transition: opacity 0.1s ease;
line-height: 18px;
}
&__gifv {
&.autoplay {
.media-gallery__gifv__label {
display: none;
}
}
&:hover {
.media-gallery__gifv__label {
opacity: 1;
}
}
}
&--compact {
height: $media-compact-size !important;
background: transparent;
.media-gallery__item {
width: $media-compact-size !important;
height: $media-compact-size !important;
inset: auto !important;
float: left !important;
margin-right: 5px;
&-overflow {
font-size: 20px;
}
&__icons .svg-icon {
@apply h-8 w-8;
}
}
.media-gallery__file-extension__label {
display: none;
}
}
}

View File

@ -1,177 +0,0 @@
.modal-root__modal {
pointer-events: auto;
display: flex;
z-index: 9999;
max-height: 100%;
overflow-y: hidden;
}
.media-modal {
.audio-player.detailed,
.extended-video-player {
display: flex;
align-items: center;
justify-content: center;
}
.audio-player {
max-width: 800px;
max-height: 600px;
}
.extended-video-player {
width: 100%;
height: 100%;
video {
@apply max-w-full max-h-[80%];
}
}
}
.error-modal {
@apply text-gray-900;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
&__body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 80vh;
width: 80vw;
max-width: 520px;
max-height: 420px;
position: relative;
text-align: center;
& > div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
opacity: 0;
user-select: text;
}
}
&__footer {
flex: 0 0 auto;
display: flex;
justify-content: center;
padding: 25px;
& > div {
min-width: 33px;
}
}
}
.actions-modal {
@apply flex-col relative text-gray-400 overflow-hidden w-full max-w-lg m-auto bg-white black:bg-black dark:bg-gray-900 shadow-xl rounded-2xl;
max-height: calc(100vh - 3rem);
&__item-label {
font-weight: 500;
}
.dropdown-menu__separator {
@apply block m-2 h-[1px] bg-gray-200 dark:bg-gray-600 black:bg-gray-800;
}
&__status {
@apply overflow-y-auto max-h-[300px];
}
ul {
@apply my-2 flex-shrink-0 overflow-y-auto;
max-height: calc(100vh - 147px);
&.with-status { max-height: calc(80vh - 75px); }
li:not(:empty) {
a,
button {
@apply flex items-center px-4 py-3 text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gray-100 dark:focus:bg-primary-800 no-underline text-left;
&.destructive {
@apply text-danger-600 dark:text-danger-400;
}
.svg-icon:first-child {
@apply min-w-[1.25rem] w-5 h-5;
svg {
stroke-width: 1.5;
}
}
}
button[type='button'] {
@apply w-full justify-center text-center;
}
}
}
}
.reply-mentions-modal__accounts {
display: block;
flex-direction: row;
flex: 1;
overflow-y: auto;
min-height: 300px;
}
.remote-interaction-modal {
&__content {
display: flex;
flex-direction: column;
row-gap: 10px;
}
&__fields {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
button {
align-self: flex-end;
}
}
&__divider {
display: flex;
align-items: center;
gap: 10px;
margin: 0 -10px;
&::before,
&::after {
@apply border-b border-gray-300 dark:border-gray-600;
content: '';
flex: 1;
}
}
@media screen and (width <= 895px) {
margin: 0;
border-radius: 6px;
height: unset !important;
width: 440px !important;
}
@media screen and (width <= 480px) {
width: 330px !important;
}
}

View File

@ -1,3 +0,0 @@
.notification .status__wrapper {
@apply p-0 shadow-none rounded-none;
}

View File

@ -1,18 +0,0 @@
.reply-mentions {
@apply text-gray-700 dark:text-gray-600 mb-1 text-sm;
&__account {
@apply text-primary-600 dark:text-accent-blue hover:text-primary-700 dark:hover:text-accent-blue no-underline hover:underline inline-block;
direction: ltr;
}
}
.status__wrapper {
.reply-mentions {
display: block;
span {
cursor: pointer;
}
}
}

View File

@ -1,46 +0,0 @@
@use "../fonts";
.search {
position: relative;
}
.search__icon {
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus {
outline: 0 !important;
}
.svg-icon {
@apply right-4 rtl:left-4 rtl:right-auto text-gray-400;
@include fonts.font-size(16);
cursor: default;
display: inline-block;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
width: 18px;
height: 18px;
opacity: 0;
pointer-events: none;
&.active {
pointer-events: auto;
opacity: 1;
}
}
.svg-icon--search.active {
pointer-events: none;
}
.svg-icon--backspace {
cursor: pointer;
width: 22px;
height: 22px;
}
}

View File

@ -15,7 +15,7 @@
}
}
[column-type='filled'] .status__wrapper,
[column-type='filled'] .status--wrapper,
[column-type='filled'] .status-placeholder {
@apply bg-transparent dark:bg-transparent rounded-none shadow-none;
}

View File

@ -1,307 +0,0 @@
.detailed,
.fullscreen {
.video-player__volume__current,
.video-player__volume::before {
bottom: 27px;
}
.video-player__volume__handle {
bottom: 23px;
}
}
.video-player {
@apply relative box-border max-w-full overflow-hidden rounded-[10px] bg-black text-white;
direction: ltr;
&.editable {
@apply rounded-none;
height: 100% !important;
}
&:focus {
outline: 0;
}
video {
display: block;
z-index: 1;
position: relative;
}
&.fullscreen {
width: 100% !important;
height: 100% !important;
margin: 0;
video {
max-width: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
outline: 0;
}
}
&--inline {
video {
object-fit: contain;
max-height: 100%;
}
}
&__controls {
position: absolute;
z-index: 2;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
background: linear-gradient(0deg, #000000d9 0, #00000073 60%, transparent);
padding: 0 15px;
opacity: 0;
transition: opacity 0.1s ease;
&.active {
opacity: 1;
}
}
&__buttons-bar {
display: flex;
justify-content: space-between;
padding-bottom: 8px;
margin: 0 -5px;
.video-player__download__icon {
color: inherit;
}
}
&__buttons {
display: flex;
flex: 0 1 auto;
min-width: 30px;
align-items: center;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.player-button {
display: inline-block;
outline: 0;
flex: 0 0 auto;
background: transparent;
padding: 5px 6px;
font-size: 16px;
border: 0;
color: rgba(#fff, 0.75);
.svg-icon {
width: 20px;
height: 20px;
}
&:active,
&:hover,
&:focus {
color: #fff;
}
}
}
&__time {
display: inline;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 5px;
}
&__time-sep,
&__time-total,
&__time-current {
font-size: 14px;
font-weight: 500;
}
&__time-current {
color: #fff;
}
&__time-sep {
display: inline-block;
margin: 0 6px;
}
&__time-sep,
&__time-total {
color: #fff;
}
&__volume {
flex: 0 0 auto;
display: inline-flex;
cursor: pointer;
height: 24px;
position: relative;
overflow: hidden;
.no-reduce-motion & {
transition: all 100ms linear;
}
&.active {
overflow: visible;
width: 50px;
margin-right: 16px;
}
&::before {
content: '';
width: 50px;
background: rgba(#fff, 0.35);
border-radius: 4px;
display: block;
position: absolute;
height: 4px;
left: 0;
top: 50%;
transform: translate(0, -50%);
}
&__current {
@apply bg-accent-500;
display: block;
position: absolute;
height: 4px;
border-radius: 4px;
left: 0;
top: 50%;
transform: translate(0, -50%);
}
&__handle {
@apply bg-accent-500;
position: absolute;
z-index: 3;
border-radius: 50%;
width: 12px;
height: 12px;
top: 50%;
left: 0;
margin-left: -6px;
transform: translate(0, -50%);
box-shadow: 1px 2px 6px #0003;
opacity: 0;
.no-reduce-motion & {
transition: opacity 100ms linear;
}
}
&.active &__handle {
opacity: 1;
}
}
&__link {
padding: 2px 10px;
a {
text-decoration: none;
font-size: 14px;
font-weight: 500;
color: #fff;
&:hover,
&:active,
&:focus {
text-decoration: underline;
}
}
}
&__seek {
cursor: pointer;
height: 24px;
position: relative;
&::before {
content: '';
width: 100%;
background: rgba(#fff, 0.35);
border-radius: 4px;
display: block;
position: absolute;
height: 4px;
top: 14px;
}
&__progress,
&__buffer {
display: block;
position: absolute;
height: 4px;
border-radius: 4px;
top: 14px;
}
&__progress {
@apply bg-accent-500;
}
&__buffer {
background: rgba(#fff, 0.2);
}
&__handle {
@apply bg-accent-500;
position: absolute;
z-index: 3;
opacity: 0;
border-radius: 50%;
width: 12px;
height: 12px;
top: 10px;
margin-left: -6px;
box-shadow: 1px 2px 6px #0003;
.no-reduce-motion & {
transition: opacity 0.1s ease;
}
&.active {
opacity: 1;
}
}
&:hover {
.video-player__seek__handle {
opacity: 1;
}
}
}
&.detailed,
&.fullscreen {
.video-player__buttons {
.player-button {
padding-top: 10px;
padding-bottom: 10px;
}
}
}
}
.media-spoiler-video {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
margin-top: 8px;
position: relative;
border: 0;
display: block;
}

View File

@ -1,13 +0,0 @@
@use 'sass:math';
// TYPEOGRAPHY MIXINS
// Use these mixins to define font-size and line-height
// html and body declaration allows developer to pass px value as argument
// Rendered css will default to "rem" and fall back to "px" for unsupported browsers
@mixin font-size($size) {
$rem: math.div($size, 10);
$px: $size;
font-size: #{$px + 'px'};
font-size: #{$rem + 'rem'};
}

View File

@ -1,29 +0,0 @@
select {
@apply pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
}
.form-error::before,
.form-error::after {
border: solid transparent;
bottom: 100%;
content: '';
height: 0;
left: 10px;
pointer-events: none;
width: 0;
position: absolute;
}
.form-error::before {
--tw-bg-opacity: 1;
border-bottom-color: rgba(254, 202, 202, var(--tw-bg-opacity));
border-width: 6px;
margin-left: -1px;
}
.input.with_label.toggle .label_input {
display: flex;
font-size: 14px;
align-items: center;
}

View File

@ -1,40 +0,0 @@
.thumb-navigation {
@apply fixed lg:hidden bottom-0 bg-white/90 black:bg-black/90 dark:bg-primary-900/90 backdrop-blur-md border-t border-solid border-gray-200 dark:border-gray-800 left-0 right-0 shadow-2xl w-full flex z-50;
padding-bottom: env(safe-area-inset-bottom); /* iOS PWA */
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: #fff;
&::-webkit-scrollbar {
display: none;
}
&__link {
@apply px-2 py-2.5 space-y-1 flex flex-col flex-1 items-center text-gray-600 text-lg;
// padding: 8px 10px;
// display: flex;
// flex-direction: column;
// align-items: center;
// justify-content: end;
// color: var(--primary-text-color);
// text-decoration: none;
// font-size: 20px;
// width: 55px;
// span {
// margin-top: 1px;
// text-align: center;
// font-size: 1.2rem;
// }
// .svg-icon {
// width: 24px;
// height: 24px;
// svg {
// stroke-width: 1px;
// }
// }
}
}

View File

@ -1,13 +0,0 @@
body.rtl {
direction: rtl;
.status {
padding-left: 10px;
padding-right: 68px;
}
.table th,
.table td {
text-align: right;
}
}

View File

@ -2,7 +2,168 @@
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply antialiased;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
body.rtl {
direction: rtl;
.status {
padding-left: 10px;
padding-right: 68px;
}
.table th,
.table td {
text-align: right;
}
}
::selection {
@apply bg-primary-600 text-white;
}
div[data-viewport-type='window'] {
position: static !important;
}
div[tabindex='-1']:focus {
outline: 0;
}
}
@layer utilities {
.status {
@apply min-h-[54px] cursor-default animate-fade opacity-100;
}
.thread {
@apply bg-white black:bg-black dark:bg-primary-900;
.status--content-wrapper {
@apply pl-[54px] rtl:pl-0 rtl:pr-[54px];
}
}
.thread__status {
@apply relative pb-4;
.status--wrapper {
@apply shadow-none p-0;
}
}
.notification .status--wrapper {
@apply p-0 shadow-none rounded-none;
}
.status--wrapper {
.reply-mentions {
display: block;
span {
cursor: pointer;
}
}
}
[column-type='filled'] .status--wrapper,
[column-type='filled'] .status-placeholder {
@apply bg-transparent dark:bg-transparent rounded-none shadow-none;
}
.search__icon {
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus {
outline: 0 !important;
}
}
.focusable:focus,
.focusable-within:focus-within {
outline: 0;
@apply ring-2 ring-primary-300;
}
.error-column > span {
@apply max-w-[400px];
}
.error-column {
.svg-icon {
width: 70px;
height: 70px;
margin-bottom: 30px;
}
@supports (display: grid) {
contain: strict;
}
a {
@apply text-primary-600 dark:text-primary-400 no-underline hover:underline;
}
}
.mention {
@apply text-primary-600 dark:text-accent-blue hover:underline;
}
.input.with_label.toggle .label_input {
display: flex;
font-size: 14px;
align-items: center;
}
.divide-x-dot > *:not(:last-child)::after {
content: '·';
padding-right: 4px;
padding-left: 4px;
}
.emoji-lg img.emojione {
width: 36px !important;
height: 36px !important;
}
.emojione {
@apply w-4 h-4 -mt-[0.2ex] mb-[0.2ex] inline-block align-middle object-contain;
}
.compose-form-warning {
strong {
@apply font-medium;
}
a {
font-weight: 500;
text-decoration: underline;
&:hover,
&:active,
&:focus {
text-decoration: none;
}
}
}
.hide-scrollbar {
scrollbar-width: none; /* Firefox */
}
.hide-scrollbar::-webkit-scrollbar {
display: none; /* iOS PWA, Chrome */
}
.break-word-nested > p {
word-break: break-word;
}

View File

@ -86,7 +86,7 @@
.ui {
display: block;
width: 100%;
padding: 0 0 calc(var(--thumb-navigation-height) + 86px);
padding: 0 0 calc(60px + env(safe-area-inset-bottom) + 86px);
.page {
display: flex;

View File

@ -1,22 +0,0 @@
.w-10i {
width: 2.5rem !important;
}
.z-1000 {
z-index: 1000;
}
.divide-x-dot > *:not(:last-child)::after {
content: '·';
padding-right: 4px;
padding-left: 4px;
}
.mention {
@apply text-primary-600 dark:text-accent-blue hover:underline;
}
.emoji-lg img.emojione {
width: 36px !important;
height: 36px !important;
}

View File

@ -1,10 +0,0 @@
// Language codes that uses CJK fonts
/* stylelint-disable-next-line value-keyword-case -- locale filenames */
$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
// CSS variables
// NOTE: Prefer CSS variables whenever possible.
// They're future-proof and more flexible.
:root {
--thumb-navigation-height: calc(60px + env(safe-area-inset-bottom));
}

View File

@ -63,6 +63,11 @@ const addAutoPlay = (html: string): string => {
const document = domParser.parseFromString(html, 'text/html').documentElement;
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.style.width = '100%';
iframe.style.height = '100%';
}
if (iframe) {
const url = new URL(iframe.src);
const provider = new URL(iframe.src).host;

View File

@ -69,6 +69,7 @@ const config: Config = {
'greentext': true,
}),
animation: {
fade: 'fade 150ms linear',
'sonar-scale-4': 'sonar-scale-4 3s linear infinite',
'sonar-scale-3': 'sonar-scale-3 3s 0.5s linear infinite',
'sonar-scale-2': 'sonar-scale-2 3s 1s linear infinite',
@ -77,6 +78,10 @@ const config: Config = {
'leave': 'leave 150ms ease-in forwards',
},
keyframes: {
fade: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
'sonar-scale-4': {
from: { opacity: '0.4', transform: 'scale(1)' },
to: { opacity: '0', transform: 'scale(4)' },