Merge remote-tracking branch 'soapbox/develop' into instancev2

instancev2
marcin mikołajczak 2023-02-14 14:59:32 +01:00
commit 200b863e0e
331 zmienionych plików z 10561 dodań i 2710 usunięć

Wyświetl plik

@ -5,6 +5,7 @@ module.exports = {
'eslint:recommended',
'plugin:import/typescript',
'plugin:compat/recommended',
'plugin:tailwindcss/recommended',
],
env: {
@ -61,6 +62,9 @@ module.exports = {
'URL', // core-js
'URLSearchParams', // core-js
],
tailwindcss: {
config: 'tailwind.config.cjs',
},
},
rules: {
@ -235,18 +239,7 @@ module.exports = {
},
],
'import/newline-after-import': 'error',
'import/no-extraneous-dependencies': [
'error',
// {
// devDependencies: [
// 'webpack/**',
// 'app/soapbox/test_setup.js',
// 'app/soapbox/test_helpers.js',
// 'app/**/__tests__/**',
// 'app/**/__mocks__/**',
// ],
// },
],
'import/no-extraneous-dependencies': 'error',
'import/no-unresolved': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/order': [
@ -271,6 +264,9 @@ module.exports = {
'promise/catch-or-return': 'error',
'react-hooks/rules-of-hooks': 'error',
'tailwindcss/classnames-order': 'error',
'tailwindcss/migration-from-tailwind-2': 'error',
},
overrides: [
{

Wyświetl plik

@ -149,9 +149,9 @@ pages:
docker:
stage: deploy
image: docker:20.10.23
image: docker:23.0.0
services:
- docker:20.10.23-dind
- docker:23.0.0-dind
tags:
- dind
# https://medium.com/devops-with-valentine/how-to-build-a-docker-image-and-push-it-to-the-gitlab-container-registry-from-a-gitlab-ci-pipeline-acac0d1f26df

43
.storybook/main.ts 100644
Wyświetl plik

@ -0,0 +1,43 @@
import sharedConfig from '../webpack/shared';
import type { StorybookConfig } from '@storybook/core-common';
const config: StorybookConfig = {
stories: [
'../stories/**/*.stories.mdx',
'../stories/**/*.stories.@(js|jsx|ts|tsx)'
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-react-intl',
{
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
webpackFinal: async (config) => {
config.resolve!.alias = {
...sharedConfig.resolve!.alias,
...config.resolve!.alias,
};
config.resolve!.modules = [
...sharedConfig.resolve!.modules!,
...config.resolve!.modules!,
];
return config;
},
};
export default config;

Wyświetl plik

@ -0,0 +1,22 @@
import '../app/styles/tailwind.css';
import '../stories/theme.css';
import { addDecorator, Story } from '@storybook/react';
import { IntlProvider } from 'react-intl';
import React from 'react';
const withProvider = (Story: Story) => (
<IntlProvider locale='en'><Story /></IntlProvider>
);
addDecorator(withProvider);
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};

Wyświetl plik

@ -1 +1 @@
nodejs 18.13.0
nodejs 18.14.0

Wyświetl plik

@ -13,11 +13,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Compatibility: improved browser support for older browsers.
- Events: allow to repost events in event menu.
- Groups: Initial support for groups.
- Profile: Add RSS link to user profiles.
- Reactions: adds support for reacting to chat messages.
- Groups: initial support for groups.
- Profile: add RSS link to user profiles.
- Posts: fix posts filtering.
### Changed
- Chats: improved display of media attachments.
- ServiceWorker: switch to a network-first strategy. The "An update is available!" prompt goes away.
- Posts: increased font size of focused status in threads.
- Posts: let "mute conversation" be clicked from any feed, not just noficiations.
- Posts: display all emoji reactions.
- Reactions: improved UI of reactions on statuses.
### Fixed
- Chats: media attachments rendering at the wrong size and/or causing the chat to scroll on load.

Wyświetl plik

@ -32,8 +32,8 @@ const getSoapboxConfig = createSelector([
}
// If RGI reacts aren't supported, strip VS16s
// // https://git.pleroma.social/pleroma/pleroma/-/issues/2355
if (!features.emojiReactsRGI) {
// https://git.pleroma.social/pleroma/pleroma/-/issues/2355
if (features.emojiReactsNonRGI) {
soapboxConfig.set('allowedEmoji', soapboxConfig.allowedEmoji.map(removeVS16s));
}
});

Wyświetl plik

@ -2,7 +2,7 @@ import { getSettings } from 'soapbox/actions/settings';
import messages from 'soapbox/locales/messages';
import { ChatKeys, IChat, isLastMessage } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { getUnreadChatsCount, updateChatListItem } from 'soapbox/utils/chats';
import { getUnreadChatsCount, updateChatListItem, updateChatMessage } from 'soapbox/utils/chats';
import { removePageItem } from 'soapbox/utils/queries';
import { play, soundCache } from 'soapbox/utils/sounds';
@ -170,6 +170,9 @@ const connectTimelineStream = (
}
});
break;
case 'chat_message.reaction': // TruthSocial
updateChatMessage(JSON.parse(data.payload));
break;
case 'pleroma:follow_relationships_update':
dispatch(updateFollowRelationships(JSON.parse(data.payload)));
break;

Wyświetl plik

@ -1,16 +0,0 @@
import React from 'react';
import { render, screen } from '../../jest/test-helpers';
import EmojiSelector from '../emoji-selector';
describe('<EmojiSelector />', () => {
it('renders correctly', () => {
const children = <EmojiSelector />;
// @ts-ignore
children.__proto__.addEventListener = () => {};
render(children);
expect(screen.queryAllByRole('button')).toHaveLength(6);
});
});

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -72,17 +72,17 @@ const AccountSearch: React.FC<IAccountSearch> = ({ onSelected, ...rest }) => {
<div
role='button'
tabIndex={0}
className='absolute inset-y-0 right-0 px-3 flex items-center cursor-pointer'
className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3'
onClick={handleClear}
>
<SvgIcon
src={require('@tabler/icons/search.svg')}
className={classNames('h-4 w-4 text-gray-400', { hidden: !isEmpty() })}
className={clsx('h-4 w-4 text-gray-400', { hidden: !isEmpty() })}
/>
<SvgIcon
src={require('@tabler/icons/x.svg')}
className={classNames('h-4 w-4 text-gray-400', { hidden: isEmpty() })}
className={clsx('h-4 w-4 text-gray-400', { hidden: isEmpty() })}
aria-label={intl.formatMessage(messages.placeholder)}
/>
</div>

Wyświetl plik

@ -43,11 +43,11 @@ const InstanceFavicon: React.FC<IInstanceFavicon> = ({ account, disabled }) => {
return (
<button
className='w-4 h-4 flex-none focus:ring-primary-500 focus:ring-2 focus:ring-offset-2'
className='h-4 w-4 flex-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2'
onClick={handleClick}
disabled={disabled}
>
<img src={account.favicon} alt='' title={account.domain} className='w-full max-h-full' />
<img src={account.favicon} alt='' title={account.domain} className='max-h-full w-full' />
</button>
);
};
@ -147,7 +147,7 @@ const Account = ({
src={actionIcon}
title={actionTitle}
onClick={handleAction}
className='bg-transparent text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500'
className='bg-transparent text-gray-600 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-500'
iconClassName='w-4 h-4'
/>
);
@ -193,7 +193,7 @@ const Account = ({
const LinkEl: any = withLinkToProfile ? Link : 'div';
return (
<div data-testid='account' className='flex-shrink-0 group block w-full' ref={overflowRef}>
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
<HStack alignItems={actionAlignment} justifyContent='between'>
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3}>
<ProfilePopper
@ -208,14 +208,14 @@ const Account = ({
<Avatar src={account.avatar} size={avatarSize} />
{emoji && (
<Emoji
className='w-5 h-5 absolute -bottom-1.5 -right-1.5'
className='absolute -bottom-1.5 -right-1.5 h-5 w-5'
emoji={emoji}
/>
)}
</LinkEl>
</ProfilePopper>
<div className='flex-grow'>
<div className='grow'>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper accountId={account.id} inline>{children}</HoverRefWrapper>}

Wyświetl plik

@ -50,7 +50,7 @@ const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
return (
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
{items => (
<span className='inline-flex flex-col items-stretch relative overflow-hidden'>
<span className='relative inline-flex flex-col items-stretch overflow-hidden'>
{items.map(({ key, data, style }) => (
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <FormattedNumber value={data} />}</span>
))}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -52,7 +52,7 @@ const AnnouncementsPanel = () => {
key={i}
tabIndex={0}
onClick={() => setIndex(i)}
className={classNames({
className={clsx({
'w-2 h-2 rounded-full focus:ring-primary-600 focus:ring-2 focus:ring-offset-2': true,
'bg-gray-200 hover:bg-gray-300': i !== index,
'bg-primary-600': i === index,

Wyświetl plik

@ -24,7 +24,7 @@ const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
return (
<img
draggable='false'
className='emojione block m-0'
className='emojione m-0 block'
alt={emoji}
title={title}
src={joinPublicPath(`packs/emoji/${filename}.svg`)}
@ -37,7 +37,7 @@ const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
return (
<img
draggable='false'
className='emojione block m-0'
className='emojione m-0 block'
alt={shortCode}
title={shortCode}
src={filename as string}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
import AnimatedNumber from 'soapbox/components/animated-number';
@ -43,7 +43,7 @@ const Reaction: React.FC<IReaction> = ({ announcementId, reaction, addReaction,
return (
<button
className={classNames('flex shrink-0 items-center gap-1.5 bg-gray-100 dark:bg-primary-900 rounded-sm px-1.5 py-1 transition-colors', {
className={clsx('flex shrink-0 items-center gap-1.5 rounded-sm bg-gray-100 px-1.5 py-1 transition-colors dark:bg-primary-900', {
'bg-gray-200 dark:bg-primary-800': hovered,
'bg-primary-200 dark:bg-primary-500': reaction.me,
})}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { TransitionMotion, spring } from 'react-motion';
@ -42,7 +42,7 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addR
return (
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
{items => (
<div className={classNames('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
<div className={clsx('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
{items.map(({ key, data, style }) => (
<Reaction
key={key}

Wyświetl plik

@ -1,5 +1,5 @@
import { Portal } from '@reach/portal';
import classNames from 'clsx';
import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -199,7 +199,7 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
tabIndex={0}
key={key}
data-index={i}
className={classNames({
className={clsx({
'px-4 py-2.5 text-sm 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 group': true,
'bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800': i === selectedSuggestion,
})}
@ -235,7 +235,7 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
return menu.map((item, i) => (
<a
className={classNames('flex items-center space-x-2 px-4 py-2.5 text-sm cursor-pointer 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', { selected: suggestions.size - selectedSuggestion === i })}
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='#'
role='button'
tabIndex={0}
@ -302,7 +302,7 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
<Portal key='portal'>
<div
style={this.setPortalPosition()}
className={classNames({
className={clsx({
'fixed w-full z-[1001] shadow bg-white dark:bg-gray-900 rounded-lg py-1 dark:ring-2 dark:ring-primary-700 focus:outline-none': true,
hidden: !visible,
block: visible,

Wyświetl plik

@ -1,5 +1,5 @@
import { Portal } from '@reach/portal';
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
@ -201,7 +201,7 @@ class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea>
tabIndex={0}
key={key}
data-index={i}
className={classNames({
className={clsx({
'px-4 py-2.5 text-sm 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 group': true,
'bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800': i === selectedSuggestion,
})}
@ -244,7 +244,7 @@ class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea>
<Textarea
ref={this.setTextarea}
className={classNames('transition-[min-height] motion-reduce:transition-none dark:bg-transparent px-0 border-0 text-gray-800 dark:text-white placeholder:text-gray-600 dark:placeholder:text-gray-600 resize-none w-full focus:shadow-none focus:border-0 focus:ring-0', {
className={clsx('w-full resize-none border-0 px-0 text-gray-800 transition-[min-height] placeholder:text-gray-600 focus:border-0 focus:shadow-none focus:ring-0 motion-reduce:transition-none dark:bg-transparent dark:text-white dark:placeholder:text-gray-600', {
'min-h-[40px]': condensed,
'min-h-[100px]': !condensed,
})}
@ -271,7 +271,7 @@ class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea>
<Portal key='portal'>
<div
style={this.setPortalPosition()}
className={classNames({
className={clsx({
'fixed 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(),

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IBadge {
@ -12,13 +12,13 @@ const Badge: React.FC<IBadge> = ({ title, slug }) => {
return (
<span
data-testid='badge'
className={classNames('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', {
className={clsx('inline-flex items-center rounded px-2 py-0.5 text-xs font-medium', {
'bg-fuchsia-700 text-white': slug === 'patron',
'bg-emerald-800 text-white': slug === 'badge:donor',
'bg-black text-white': slug === 'admin',
'bg-cyan-600 text-white': slug === 'moderator',
'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100': fallback,
'bg-white bg-opacity-75 text-gray-900': slug === 'opaque',
'bg-white/75 text-gray-900': slug === 'opaque',
})}
>
{title}

Wyświetl plik

@ -113,7 +113,7 @@ const BirthdayInput: React.FC<IBirthdayInput> = ({ value, onChange, required })
const handleChange = (date: Date) => onChange(date ? new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) : '');
return (
<div className='mt-1 relative rounded-md shadow-sm'>
<div className='relative mt-1 rounded-md shadow-sm'>
<BundleContainer fetchComponent={DatePicker}>
{Component => (<Component
selected={selected}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { supportsPassiveEvents } from 'detect-passive-events';
import React from 'react';
import { spring } from 'react-motion';
@ -182,7 +182,7 @@ class DropdownMenu extends React.PureComponent<IDropdownMenu, IDropdownMenuState
const { text, href, to, newTab, isLogout, icon, count, destructive } = option;
return (
<li className={classNames('dropdown-menu__item truncate', { destructive })} key={`${text}-${i}`}>
<li className={clsx('dropdown-menu__item truncate', { destructive })} key={`${text}-${i}`}>
<a
href={href || to || '#'}
role='button'
@ -196,7 +196,7 @@ class DropdownMenu extends React.PureComponent<IDropdownMenu, IDropdownMenuState
data-method={isLogout ? 'delete' : undefined}
title={text}
>
{icon && <SvgIcon src={icon} className='mr-3 rtl:ml-3 rtl:mr-0 h-5 w-5 flex-none' />}
{icon && <SvgIcon src={icon} className='mr-3 h-5 w-5 flex-none rtl:ml-3 rtl:mr-0' />}
<span className='truncate'>{text}</span>
@ -392,7 +392,7 @@ class Dropdown extends React.PureComponent<IDropdown, IDropdownState> {
) : (
<IconButton
disabled={disabled}
className={classNames({
className={clsx({
'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500': true,
'text-gray-700 dark:text-gray-500': open,
})}

Wyświetl plik

@ -1,142 +0,0 @@
// import classNames from 'clsx';
import React from 'react';
import { HotKeys } from 'react-hotkeys';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import { EmojiSelector as RealEmojiSelector } from 'soapbox/components/ui';
import type { List as ImmutableList } from 'immutable';
import type { RootState } from 'soapbox/store';
const mapStateToProps = (state: RootState) => ({
allowedEmoji: getSoapboxConfig(state).allowedEmoji,
});
interface IEmojiSelector {
allowedEmoji: ImmutableList<string>,
onReact: (emoji: string) => void,
onUnfocus: () => void,
visible: boolean,
focused?: boolean,
}
class EmojiSelector extends ImmutablePureComponent<IEmojiSelector> {
static defaultProps: Partial<IEmojiSelector> = {
onReact: () => { },
onUnfocus: () => { },
visible: false,
};
node?: HTMLDivElement = undefined;
handleBlur: React.FocusEventHandler<HTMLDivElement> = e => {
const { focused, onUnfocus } = this.props;
if (focused && (!e.currentTarget || !e.currentTarget.classList.contains('emoji-react-selector__emoji'))) {
onUnfocus();
}
};
_selectPreviousEmoji = (i: number): void => {
if (!this.node) return;
if (i !== 0) {
const button: HTMLButtonElement | null = this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i})`);
button?.focus();
} else {
const button: HTMLButtonElement | null = this.node.querySelector('.emoji-react-selector__emoji:last-child');
button?.focus();
}
};
_selectNextEmoji = (i: number) => {
if (!this.node) return;
if (i !== this.props.allowedEmoji.size - 1) {
const button: HTMLButtonElement | null = this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i + 2})`);
button?.focus();
} else {
const button: HTMLButtonElement | null = this.node.querySelector('.emoji-react-selector__emoji:first-child');
button?.focus();
}
};
handleKeyDown = (i: number): React.KeyboardEventHandler => e => {
const { onUnfocus } = this.props;
switch (e.key) {
case 'Tab':
e.preventDefault();
if (e.shiftKey) this._selectPreviousEmoji(i);
else this._selectNextEmoji(i);
break;
case 'Left':
case 'ArrowLeft':
this._selectPreviousEmoji(i);
break;
case 'Right':
case 'ArrowRight':
this._selectNextEmoji(i);
break;
case 'Escape':
onUnfocus();
break;
}
};
handleReact = (emoji: string) => (): void => {
const { onReact, focused, onUnfocus } = this.props;
onReact(emoji);
if (focused) {
onUnfocus();
}
};
handlers = {
open: () => { },
};
setRef = (c: HTMLDivElement): void => {
this.node = c;
};
render() {
const { visible, focused, allowedEmoji, onReact } = this.props;
return (
<HotKeys handlers={this.handlers}>
{/*<div
className={classNames('flex absolute bg-white dark:bg-gray-500 px-2 py-3 rounded-full shadow-md opacity-0 pointer-events-none duration-100 w-max', { 'opacity-100 pointer-events-auto z-[999]': visible || focused })}
onBlur={this.handleBlur}
ref={this.setRef}
>
{allowedEmoji.map((emoji, i) => (
<button
key={i}
className='emoji-react-selector__emoji'
onClick={this.handleReact(emoji)}
onKeyDown={this.handleKeyDown(i)}
tabIndex={(visible || focused) ? 0 : -1}
>
<Emoji emoji={emoji} />
</button>
))}
</div>*/}
<RealEmojiSelector
emojis={allowedEmoji.toArray()}
onReact={onReact}
visible={visible}
focused={focused}
/>
</HotKeys>
);
}
}
export default connect(mapStateToProps)(EmojiSelector);

Wyświetl plik

@ -113,17 +113,17 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
const errorText = this.getErrorText();
return (
<div className='h-screen pt-16 pb-12 flex flex-col bg-white dark:bg-primary-900'>
<main className='flex-grow flex flex-col justify-center max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8'>
<div className='flex-shrink-0 flex justify-center'>
<div className='flex h-screen flex-col bg-white pt-16 pb-12 dark:bg-primary-900'>
<main className='mx-auto flex w-full max-w-7xl grow flex-col justify-center px-4 sm:px-6 lg:px-8'>
<div className='flex shrink-0 justify-center'>
<a href='/' className='inline-flex'>
<SiteLogo alt='Logo' className='h-12 w-auto cursor-pointer' />
</a>
</div>
<div className='py-8'>
<div className='text-center max-w-xl mx-auto space-y-2'>
<h1 className='text-3xl font-extrabold text-gray-900 dark:text-gray-500 tracking-tight sm:text-4xl'>
<div className='mx-auto max-w-xl space-y-2 text-center'>
<h1 className='text-3xl font-extrabold tracking-tight text-gray-900 dark:text-gray-500 sm:text-4xl'>
<FormattedMessage id='alert.unexpected.message' defaultMessage='Something went wrong.' />
</h1>
<p className='text-lg text-gray-700 dark:text-gray-600'>
@ -132,7 +132,7 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
defaultMessage="We're sorry for the interruption. If the problem persists, please reach out to our support team. You may also try to {clearCookies} (this will log you out)."
values={{
clearCookies: (
<a href='/' onClick={this.clearCookies} className='text-primary-600 dark:text-accent-blue hover:underline'>
<a href='/' onClick={this.clearCookies} className='text-primary-600 hover:underline dark:text-accent-blue'>
<FormattedMessage
id='alert.unexpected.clear_cookies'
defaultMessage='clear cookies and browser data'
@ -150,7 +150,7 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
</Text>
<div className='mt-10'>
<a href='/' className='text-base font-medium text-primary-600 dark:text-accent-blue hover:underline'>
<a href='/' className='text-base font-medium text-primary-600 hover:underline dark:text-accent-blue'>
<FormattedMessage id='alert.unexpected.return_home' defaultMessage='Return Home' />
<span aria-hidden='true'> &rarr;</span>
</a>
@ -158,11 +158,11 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
</div>
{!isProduction && (
<div className='py-16 max-w-lg mx-auto space-y-4'>
<div className='mx-auto max-w-lg space-y-4 py-16'>
{errorText && (
<textarea
ref={this.setTextareaRef}
className='h-48 p-4 shadow-sm bg-gray-100 text-gray-900 dark:text-gray-100 dark:bg-gray-800 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 dark:border-gray-700 rounded-md font-mono'
className='block h-48 w-full rounded-md border-gray-300 bg-gray-100 p-4 font-mono text-gray-900 shadow-sm focus:border-primary-500 focus:ring-2 focus:ring-primary-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 sm:text-sm'
value={errorText}
onClick={this.handleCopy}
readOnly
@ -180,11 +180,11 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
</div>
</main>
<footer className='flex-shrink-0 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8'>
<footer className='mx-auto w-full max-w-7xl shrink-0 px-4 sm:px-6 lg:px-8'>
<HStack justifyContent='center' space={4} element='nav'>
{links.get('status') && (
<>
<a href={links.get('status')} className='text-sm font-medium text-gray-700 dark:text-gray-600 hover:underline'>
<a href={links.get('status')} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
<FormattedMessage id='alert.unexpected.links.status' defaultMessage='Status' />
</a>
</>
@ -193,7 +193,7 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
{links.get('help') && (
<>
<span className='inline-block border-l border-gray-300' aria-hidden='true' />
<a href={links.get('help')} className='text-sm font-medium text-gray-700 dark:text-gray-600 hover:underline'>
<a href={links.get('help')} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
<FormattedMessage id='alert.unexpected.links.help' defaultMessage='Help Center' />
</a>
</>
@ -202,7 +202,7 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
{links.get('support') && (
<>
<span className='inline-block border-l border-gray-300' aria-hidden='true' />
<a href={links.get('support')} className='text-sm font-medium text-gray-700 dark:text-gray-600 hover:underline'>
<a href={links.get('support')} className='text-sm font-medium text-gray-700 hover:underline dark:text-gray-600'>
<FormattedMessage id='alert.unexpected.links.support' defaultMessage='Support' />
</a>
</>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -51,11 +51,11 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
));
return (
<div className={classNames('w-full rounded-lg bg-gray-100 dark:bg-primary-800 relative overflow-hidden', className)}>
<div className={clsx('relative w-full overflow-hidden rounded-lg bg-gray-100 dark:bg-primary-800', className)}>
<div className='absolute top-28 right-3'>
{floatingAction && action}
</div>
<div className='bg-primary-200 dark:bg-gray-600 h-40'>
<div className='h-40 bg-primary-200 dark:bg-gray-600'>
{banner && <img className='h-full w-full object-cover' src={banner.url} alt={intl.formatMessage(messages.eventBanner)} />}
</div>
<Stack className='p-2.5' space={2}>
@ -65,7 +65,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
{!floatingAction && action}
</HStack>
<div className='flex gap-y-1 gap-x-2 flex-wrap text-gray-700 dark:text-gray-600'>
<div className='flex flex-wrap gap-y-1 gap-x-2 text-gray-700 dark:text-gray-600'>
<HStack alignItems='center' space={2}>
<Icon src={require('@tabler/icons/user.svg')} />
<HStack space={1} alignItems='center' grow>

Wyświetl plik

@ -5,7 +5,7 @@
* @see soapbox/components/icon
*/
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
export interface IForkAwesomeIcon extends React.HTMLAttributes<HTMLLIElement> {
@ -25,7 +25,7 @@ const ForkAwesomeIcon: React.FC<IForkAwesomeIcon> = ({ id, className, fixedWidth
<i
role='img'
// alt={alt}
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
className={clsx('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
{...rest}
/>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -30,8 +30,8 @@ const GdprBanner: React.FC = () => {
}
return (
<Banner theme='opaque' className={classNames('transition-transform', { 'translate-y-full': slideout })}>
<div className='flex flex-col space-y-4 lg:space-y-0 lg:space-x-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between'>
<Banner theme='opaque' className={clsx('transition-transform', { 'translate-y-full': slideout })}>
<div className='flex flex-col space-y-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4'>
<Stack space={2}>
<Text size='xl' weight='bold'>
<FormattedMessage id='gdpr.title' defaultMessage='{siteTitle} uses cookies' values={{ siteTitle: instance.title }} />

Wyświetl plik

@ -18,9 +18,9 @@ const GroupCard: React.FC<IGroupCard> = ({ group }) => {
return (
<div className='overflow-hidden'>
<Stack className='bg-white dark:bg-primary-900 border border-solid border-gray-300 dark:border-primary-800 rounded-lg sm:rounded-xl'>
<div className='bg-primary-100 dark:bg-gray-800 h-[120px] relative -m-[1px] mb-0 rounded-t-lg sm:rounded-t-xl'>
{group.header && <img className='h-full w-full object-cover rounded-t-lg sm:rounded-t-xl' src={group.header} alt={intl.formatMessage(messages.groupHeader)} />}
<Stack className='rounded-lg border border-solid border-gray-300 bg-white dark:border-primary-800 dark:bg-primary-900 sm:rounded-xl'>
<div className='relative -m-[1px] mb-0 h-[120px] rounded-t-lg bg-primary-100 dark:bg-gray-800 sm:rounded-t-xl'>
{group.header && <img className='h-full w-full rounded-t-lg object-cover sm:rounded-t-xl' src={group.header} alt={intl.formatMessage(messages.groupHeader)} />}
<div className='absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2'>
<Avatar className='ring-2 ring-white dark:ring-primary-900' src={group.avatar} size={64} />
</div>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import React, { useRef } from 'react';
@ -47,7 +47,7 @@ export const HoverRefWrapper: React.FC<IHoverRefWrapper> = ({ accountId, childre
return (
<Elem
ref={ref}
className={classNames('hover-ref-wrapper', className)}
className={clsx('hover-ref-wrapper', className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}

Wyświetl plik

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

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import Icon from 'soapbox/components/icon';
@ -66,7 +66,7 @@ const IconButton: React.FC<IIconButton> = ({
}
};
const classes = classNames(className, 'icon-button', {
const classes = clsx(className, 'icon-button', {
active,
disabled,
});

Wyświetl plik

@ -3,7 +3,7 @@
* @module soapbox/components/icon
*/
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
@ -17,7 +17,7 @@ export interface IIcon extends React.HTMLAttributes<HTMLDivElement> {
const Icon: React.FC<IIcon> = ({ src, alt, className, ...rest }) => {
return (
<div
className={classNames('svg-icon', className)}
className={clsx('svg-icon', className)}
{...rest}
>
<InlineSVG src={src} title={alt} loader={<></>} />

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
/** Fullscreen gradient used as a backdrop to public pages. */
const LandingGradient: React.FC = () => (
<div className='fixed h-screen w-full bg-gradient-to-tr from-primary-50 dark:from-primary-900/50 via-white dark:via-primary-900 to-gradient-end/10 dark:to-primary-800/50' />
<div className='fixed h-screen w-full bg-gradient-to-tr from-primary-50 via-white to-gradient-end/10 dark:from-primary-900/50 dark:via-primary-900 dark:to-primary-800/50' />
);
export default LandingGradient;

Wyświetl plik

@ -4,7 +4,7 @@ import { Link as Comp, LinkProps } from 'react-router-dom';
const Link = (props: LinkProps) => (
<Comp
{...props}
className='text-primary-600 dark:text-accent-blue hover:underline'
className='text-primary-600 hover:underline dark:text-accent-blue'
/>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
@ -45,7 +45,7 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
return React.cloneElement(child, {
id: domId,
className: classNames({
className: clsx({
'w-auto': isSelect,
}, child.props.className),
});
@ -57,7 +57,7 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
return (
<Comp
className={classNames({
className={clsx({
'flex items-center justify-between px-3 py-2 first:rounded-t-lg last:rounded-b-lg bg-gradient-to-r from-gradient-start/10 to-gradient-end/10': true,
'cursor-pointer hover:from-gradient-start/20 hover:to-gradient-end/20 dark:hover:from-gradient-start/5 dark:hover:to-gradient-end/5': typeof onClick !== 'undefined' || typeof onSelect !== 'undefined',
})}

Wyświetl plik

@ -9,7 +9,7 @@ const LoadingScreen: React.FC = () => {
<div className='fixed h-screen w-screen'>
<LandingGradient />
<div className='fixed d-screen w-screen flex items-center justify-center z-10'>
<div className='d-screen fixed z-10 flex w-screen items-center justify-center'>
<div className='p-4'>
<Spinner size={40} withText={false} />
</div>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import throttle from 'lodash/throttle';
import React, { useCallback, useEffect, useRef, useState } from 'react';
@ -100,8 +100,8 @@ const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
renderSuggestion={AutosuggestLocation}
/>
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
<Icon src={require('@tabler/icons/search.svg')} className={classNames('svg-icon--search', { active: isEmpty() })} />
<Icon src={require('@tabler/icons/backspace.svg')} className={classNames('svg-icon--backspace', { active: !isEmpty() })} aria-label={intl.formatMessage(messages.placeholder)} />
<Icon src={require('@tabler/icons/search.svg')} className={clsx('svg-icon--search', { active: isEmpty() })} />
<Icon src={require('@tabler/icons/backspace.svg')} className={clsx('svg-icon--backspace', { active: !isEmpty() })} aria-label={intl.formatMessage(messages.placeholder)} />
</div>
</div>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState, useRef, useLayoutEffect } from 'react';
import Blurhash from 'soapbox/components/blurhash';
@ -152,7 +152,7 @@ const Item: React.FC<IItem> = ({
);
return (
<div className={classNames('media-gallery__item', { standalone })} key={attachment.id} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
<div className={clsx('media-gallery__item', { standalone })} key={attachment.id} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
<a className='media-gallery__item-thumbnail' href={attachment.url} target='_blank' style={{ cursor: 'pointer' }}>
<Blurhash hash={attachment.blurhash} className='media-gallery__preview' />
<span className='media-gallery__item__icons'>{attachmentIcon}</span>
@ -171,7 +171,7 @@ const Item: React.FC<IItem> = ({
target='_blank'
>
<StillImage
className='w-full h-full'
className='h-full w-full'
src={mediaPreview ? attachment.preview_url : attachment.url}
alt={attachment.description}
letterboxed={letterboxed}
@ -189,7 +189,7 @@ const Item: React.FC<IItem> = ({
}
thumbnail = (
<div className={classNames('media-gallery__gifv', { autoplay: autoPlayGif })}>
<div className={clsx('media-gallery__gifv', { autoplay: autoPlayGif })}>
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.description}
@ -211,7 +211,7 @@ const Item: React.FC<IItem> = ({
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={classNames('media-gallery__item-thumbnail')}
className={clsx('media-gallery__item-thumbnail')}
href={attachment.url}
onClick={handleClick}
target='_blank'
@ -225,7 +225,7 @@ const Item: React.FC<IItem> = ({
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={classNames('media-gallery__item-thumbnail')}
className={clsx('media-gallery__item-thumbnail')}
href={attachment.url}
onClick={handleClick}
target='_blank'
@ -245,7 +245,7 @@ const Item: React.FC<IItem> = ({
}
return (
<div className={classNames('media-gallery__item', `media-gallery__item--${attachment.type}`, { standalone })} key={attachment.id} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
<div className={clsx('media-gallery__item', `media-gallery__item--${attachment.type}`, { standalone })} key={attachment.id} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
{last && total > ATTACHMENT_LIMIT && (
<div className='media-gallery__item-overflow'>
+{total - ATTACHMENT_LIMIT + 1}
@ -546,7 +546,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
}, [node.current]);
return (
<div className={classNames('media-gallery', { 'media-gallery--compact': compact })} style={sizeData.style} ref={node}>
<div className={clsx('media-gallery', { 'media-gallery--compact': compact })} style={sizeData.style} ref={node}>
{children}
</div>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import 'wicg-inert';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
@ -232,7 +232,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
return (
<div
ref={ref}
className={classNames({
className={clsx({
'fixed top-0 left-0 z-[100] w-full h-full overflow-x-hidden overflow-y-auto': true,
'pointer-events-none': !visible,
})}
@ -241,13 +241,13 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
<div
role='presentation'
id='modal-overlay'
className='fixed inset-0 bg-gray-500/90 dark:bg-gray-700/90 backdrop-blur'
className='fixed inset-0 bg-gray-500/90 backdrop-blur dark:bg-gray-700/90'
onClick={handleOnClose}
/>
<div
role='dialog'
className={classNames({
className={clsx({
'my-2 mx-auto relative pointer-events-none flex items-center min-h-[calc(100%-3.5rem)]': true,
'p-4 md:p-0': type !== 'MEDIA',
})}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IOutlineBox extends React.HTMLAttributes<HTMLDivElement> {
@ -10,7 +10,7 @@ interface IOutlineBox extends React.HTMLAttributes<HTMLDivElement> {
const OutlineBox: React.FC<IOutlineBox> = ({ children, className, ...rest }) => {
return (
<div
className={classNames('p-4 rounded-lg border border-solid border-gray-300 dark:border-gray-800', className)}
className={clsx('rounded-lg border border-solid border-gray-300 p-4 dark:border-gray-800', className)}
{...rest}
>
{children}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Motion, presets, spring } from 'react-motion';
@ -20,7 +20,7 @@ const PollPercentageBar: React.FC<{ percent: number, leading: boolean }> = ({ pe
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { ...presets.gentle, precision: 0.1 }) }}>
{({ width }) => (
<span
className='absolute inset-0 h-full inline-block bg-primary-100 dark:bg-primary-500 rounded-l-md'
className='absolute inset-0 inline-block h-full rounded-l-md bg-primary-100 dark:bg-primary-500'
style={{ width: `${width}%` }}
/>
)}
@ -46,7 +46,7 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
return (
<label
className={
classNames('flex relative p-2 bg-white dark:bg-primary-900 cursor-pointer rounded-3xl border border-solid hover:bg-primary-50 dark:hover:bg-primary-800/50', {
clsx('relative flex cursor-pointer rounded-3xl border border-solid bg-white p-2 hover:bg-primary-50 dark:bg-primary-900 dark:hover:bg-primary-800/50', {
'border-primary-600 ring-1 ring-primary-600 bg-primary-50 dark:bg-primary-800/50 dark:border-primary-300 dark:ring-primary-300': active,
'border-primary-300 dark:border-primary-500': !active,
})
@ -61,8 +61,8 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
onChange={handleOptionChange}
/>
<div className='grid items-center w-full'>
<div className='col-start-1 row-start-1 justify-self-center ml-4 mr-6'>
<div className='grid w-full items-center'>
<div className='col-start-1 row-start-1 ml-4 mr-6 justify-self-center'>
<div className='text-primary-600 dark:text-white'>
<Text
theme='inherit'
@ -72,9 +72,9 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
</div>
</div>
<div className='col-start-1 row-start-1 justify-self-end flex items-center'>
<div className='col-start-1 row-start-1 flex items-center justify-self-end'>
<span
className={classNames('flex items-center justify-center w-6 h-6 flex-none border border-solid rounded-full', {
className={clsx('flex h-6 w-6 flex-none items-center justify-center rounded-full border border-solid', {
'bg-primary-600 border-primary-600 dark:bg-primary-300 dark:border-primary-300': active,
'border-primary-300 bg-white dark:bg-primary-900 dark:border-primary-500': !active,
})}
@ -85,7 +85,7 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
aria-label={option.title}
>
{active && (
<Icon src={require('@tabler/icons/check.svg')} className='text-white dark:text-primary-900 w-4 h-4' />
<Icon src={require('@tabler/icons/check.svg')} className='h-4 w-4 text-white dark:text-primary-900' />
)}
</span>
</div>
@ -123,7 +123,7 @@ const PollOption: React.FC<IPollOption> = (props): JSX.Element | null => {
<HStack
justifyContent='between'
alignItems='center'
className='relative p-2 w-full bg-white dark:bg-primary-800 rounded-md overflow-hidden'
className='relative w-full overflow-hidden rounded-md bg-white p-2 dark:bg-primary-800'
>
<PollPercentageBar percent={percent} leading={leading} />
@ -141,7 +141,7 @@ const PollOption: React.FC<IPollOption> = (props): JSX.Element | null => {
<Icon
src={require('@tabler/icons/circle-check.svg')}
alt={intl.formatMessage(messages.voted)}
className='text-primary-600 dark:text-primary-800 dark:fill-white w-4 h-4'
className='h-4 w-4 text-primary-600 dark:fill-white dark:text-primary-800'
/>
) : (
<div className='svg-icon' />

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
import { usePopper } from 'react-popper';
@ -95,7 +95,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
return (
<div
className={classNames({
className={clsx({
'absolute transition-opacity w-[320px] z-[101] top-0 left-0': true,
'opacity-100': visible,
'opacity-0 pointer-events-none': !visible,
@ -123,7 +123,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
<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'
className='h-4 w-4 text-gray-800 dark:text-gray-200'
/>
<Text size='sm'>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IProgressCircle {
@ -30,7 +30,7 @@ const ProgressCircle: React.FC<IProgressCircle> = ({ progress, radius = 12, stro
strokeWidth={stroke}
/>
<circle
className={classNames('stroke-primary-500', {
className={clsx('stroke-primary-500', {
'stroke-secondary-500': progress > 1,
})}
style={{

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { MouseEventHandler, useEffect, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
@ -94,7 +94,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
return (
<OutlineBox
data-testid='quoted-status'
className={classNames('cursor-pointer', {
className={clsx('cursor-pointer', {
'hover:bg-gray-100 dark:hover:bg-gray-800': !compose,
})}
>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import throttle from 'lodash/throttle';
import React, { useState, useEffect, useCallback } from 'react';
import { useIntl, MessageDescriptor } from 'react-intl';
@ -36,7 +36,7 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
const visible = count > 0 && scrolled;
const classes = classNames('left-1/2 -translate-x-1/2 fixed top-20 z-50', {
const classes = clsx('fixed left-1/2 top-20 z-50 -translate-x-1/2', {
'hidden': !visible,
});
@ -83,7 +83,7 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
return (
<div className={classes}>
<a className='flex items-center bg-primary-600 hover:bg-primary-700 hover:scale-105 active:scale-100 transition-transform text-white rounded-full px-4 py-2 space-x-1.5 cursor-pointer whitespace-nowrap' onClick={handleClick}>
<a className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100' onClick={handleClick}>
<Icon src={require('@tabler/icons/arrow-bar-to-up.svg')} />
{(count > 0) && (

Wyświetl plik

@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/interactive-supports-focus */
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Link, NavLink } from 'react-router-dom';
@ -53,8 +53,8 @@ interface ISidebarLink {
const SidebarLink: React.FC<ISidebarLink> = ({ href, to, icon, text, onClick }) => {
const body = (
<HStack space={2} alignItems='center'>
<div className='bg-primary-50 dark:bg-gray-800 relative rounded-full inline-flex p-2'>
<Icon src={icon} className='text-primary-500 h-5 w-5' />
<div className='relative inline-flex rounded-full bg-primary-50 p-2 dark:bg-gray-800'>
<Icon src={icon} className='h-5 w-5 text-primary-500' />
</div>
<Text tag='span' weight='medium' theme='inherit'>{text}</Text>
@ -63,14 +63,14 @@ const SidebarLink: React.FC<ISidebarLink> = ({ href, to, icon, text, onClick })
if (to) {
return (
<NavLink className='group rounded-full text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800' to={to} onClick={onClick}>
<NavLink className='group rounded-full text-gray-900 hover:bg-gray-50 dark:text-gray-100 dark:hover:bg-gray-800' to={to} onClick={onClick}>
{body}
</NavLink>
);
}
return (
<a className='group rounded-full text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800' href={href} target='_blank' onClick={onClick}>
<a className='group rounded-full text-gray-900 hover:bg-gray-50 dark:text-gray-100 dark:hover:bg-gray-800' href={href} target='_blank' onClick={onClick}>
{body}
</a>
);
@ -138,7 +138,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<div
aria-expanded={sidebarOpen}
className={
classNames({
clsx({
'z-[1000]': sidebarOpen,
hidden: !sidebarOpen,
})
@ -153,7 +153,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<div className='fixed inset-0 z-[1000] flex'>
<div
className={
classNames({
clsx({
'flex flex-col flex-1 bg-white dark:bg-primary-900 -translate-x-full rtl:translate-x-full w-full max-w-xs': true,
'!translate-x-0': sidebarOpen,
})
@ -165,10 +165,10 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
src={require('@tabler/icons/x.svg')}
ref={closeButtonRef}
iconClassName='h-6 w-6'
className='absolute top-0 right-0 -mr-11 mt-2 text-gray-600 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'
className='absolute top-0 right-0 -mr-11 mt-2 text-gray-600 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300'
/>
<div className='relative overflow-y-scroll overflow-auto h-full w-full'>
<div className='relative h-full w-full overflow-auto overflow-y-scroll'>
<div className='p-4'>
<Stack space={4}>
<Link to={`/@${account.acct}`} onClick={onClose}>
@ -334,7 +334,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<Icon
src={require('@tabler/icons/chevron-down.svg')}
className={classNames('w-4 h-4 text-gray-900 dark:text-gray-100 transition-transform', {
className={clsx('h-4 w-4 text-gray-900 transition-transform dark:text-gray-100', {
'rotate-180': switcher,
})}
/>
@ -342,11 +342,11 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
</button>
{switcher && (
<div className='border-t-2 border-gray-100 dark:border-gray-800 border-solid'>
<div className='border-t-2 border-solid border-gray-100 dark:border-gray-800'>
{otherAccounts.map(account => renderAccount(account))}
<NavLink className='flex items-center py-2 space-x-1' to='/login/add' onClick={handleClose}>
<Icon className='text-primary-500 w-4 h-4' src={require('@tabler/icons/plus.svg')} />
<NavLink className='flex items-center space-x-1 py-2' to='/login/add' onClick={handleClose}>
<Icon className='h-4 w-4 text-primary-500' src={require('@tabler/icons/plus.svg')} />
<Text size='sm' weight='medium'>{intl.formatMessage(messages.addAccount)}</Text>
</NavLink>
</div>
@ -361,7 +361,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
{/* Dummy element to keep Close Icon visible */}
<div
aria-hidden
className='w-14 flex-shrink-0'
className='w-14 shrink-0'
onClick={handleClose}
/>
</div>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { NavLink } from 'react-router-dom';
@ -38,7 +38,7 @@ const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, r
to={to}
ref={ref}
onClick={handleClick}
className={classNames({
className={clsx({
'flex items-center px-4 py-3.5 text-base font-semibold space-x-4 rtl:space-x-reverse rounded-full group text-gray-600 hover:text-gray-900 dark:text-gray-500 dark:hover:text-gray-100 hover:bg-primary-200 dark:hover:bg-primary-900': true,
'dark:text-gray-100 text-gray-900': isActive,
})}
@ -48,7 +48,7 @@ const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, r
src={icon}
count={count}
countMax={countMax}
className={classNames('h-5 w-5', {
className={clsx('h-5 w-5', {
'text-gray-600 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400': !isActive,
'text-primary-500 dark:text-primary-400': isActive,
})}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { useSoapboxConfig, useSettings, useTheme } from 'soapbox/hooks';
@ -36,7 +36,7 @@ const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
return (
// eslint-disable-next-line jsx-a11y/alt-text
<img
className={classNames('object-contain', className)}
className={clsx('object-contain', className)}
src={getSrc()}
{...rest}
/>

Wyświetl plik

@ -14,8 +14,8 @@ import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions
import { initMuteModal } from 'soapbox/actions/mutes';
import { initReport } from 'soapbox/actions/reports';
import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses';
import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper';
import StatusActionButton from 'soapbox/components/status-action-button';
import StatusReactionWrapper from 'soapbox/components/status-reaction-wrapper';
import { HStack } from 'soapbox/components/ui';
import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container';
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
@ -98,7 +98,6 @@ const messages = defineMessages({
interface IStatusActionBar {
status: Status,
withDismiss?: boolean,
withLabels?: boolean,
expandable?: boolean,
space?: 'expand' | 'compact',
@ -106,7 +105,6 @@ interface IStatusActionBar {
const StatusActionBar: React.FC<IStatusActionBar> = ({
status,
withDismiss = false,
withLabels = false,
expandable = true,
space = 'compact',
@ -387,14 +385,13 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
menu.push(null);
if (ownAccount || withDismiss) {
menu.push({
text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
action: handleConversationMuteClick,
icon: mutingConversation ? require('@tabler/icons/bell.svg') : require('@tabler/icons/bell-off.svg'),
});
menu.push(null);
}
menu.push({
text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
action: handleConversationMuteClick,
icon: mutingConversation ? require('@tabler/icons/bell.svg') : require('@tabler/icons/bell-off.svg'),
});
menu.push(null);
if (ownAccount) {
if (publicStatus) {
@ -632,7 +629,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
)}
{features.emojiReacts ? (
<EmojiButtonWrapper statusId={status.id}>
<StatusReactionWrapper statusId={status.id}>
<StatusActionButton
title={meEmojiTitle}
icon={require('@tabler/icons/heart.svg')}
@ -643,7 +640,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
emoji={meEmojiReact}
text={withLabels ? meEmojiTitle : undefined}
/>
</EmojiButtonWrapper>
</StatusReactionWrapper>
) : (
<StatusActionButton
title={intl.formatMessage(messages.favourite)}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { Text, Icon, Emoji } from 'soapbox/components/ui';
@ -41,15 +41,15 @@ const StatusActionButton = React.forwardRef<HTMLButtonElement, IStatusActionButt
const renderIcon = () => {
if (emoji) {
return (
<span className='flex w-6 h-6 items-center justify-center'>
<Emoji className='w-full h-full p-0.5' emoji={emoji} />
<span className='flex h-6 w-6 items-center justify-center'>
<Emoji className='h-full w-full p-0.5' emoji={emoji} />
</span>
);
} else {
return (
<Icon
src={icon}
className={classNames(
className={clsx(
{
'fill-accent-300 hover:fill-accent-300': active && filled && color === COLORS.accent,
},
@ -78,11 +78,11 @@ const StatusActionButton = React.forwardRef<HTMLButtonElement, IStatusActionButt
<button
ref={ref}
type='button'
className={classNames(
'flex items-center p-1 rounded-full rtl:space-x-reverse',
className={clsx(
'flex items-center rounded-full p-1 rtl:space-x-reverse',
'text-gray-600 hover:text-gray-600 dark:hover:text-white',
'bg-white dark:bg-transparent',
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:ring-offset-0',
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-offset-0',
{
'text-black dark:text-white': active && emoji,
'hover:text-gray-600 dark:hover:text-white': !filteredProps.disabled,

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import { useHistory } from 'react-router-dom';
@ -25,7 +25,7 @@ interface IReadMoreButton {
/** Button to expand a truncated status (due to too much content) */
const ReadMoreButton: React.FC<IReadMoreButton> = ({ onClick }) => (
<button className='flex items-center text-gray-900 dark:text-gray-300 border-0 bg-transparent p-0 pt-2 hover:underline active:underline' onClick={onClick}>
<button className='flex items-center border-0 bg-transparent p-0 pt-2 text-gray-900 hover:underline active:underline dark:text-gray-300' onClick={onClick}>
<FormattedMessage id='status.read_more' defaultMessage='Read more' />
<Icon className='inline-block h-5 w-5' src={require('@tabler/icons/chevron-right.svg')} />
</button>
@ -153,7 +153,7 @@ const StatusContent: React.FC<IStatusContent> = ({
const content = { __html: parsedHtml };
const direction = isRtl(status.search_index) ? 'rtl' : 'ltr';
const className = classNames(baseClassName, {
const className = clsx(baseClassName, {
'cursor-pointer': onClick,
'whitespace-normal': withSpoiler,
'max-h-[300px]': collapsed,
@ -183,14 +183,14 @@ const StatusContent: React.FC<IStatusContent> = ({
output.push(<Poll id={status.poll} key='poll' status={status.url} />);
}
return <div className={classNames({ 'bg-gray-100 dark:bg-primary-800 rounded-md p-4': hasPoll })}>{output}</div>;
return <div className={clsx({ 'bg-gray-100 dark:bg-primary-800 rounded-md p-4': hasPoll })}>{output}</div>;
} else {
const output = [
<Markup
ref={node}
tabIndex={0}
key='content'
className={classNames(baseClassName, {
className={clsx(baseClassName, {
'leading-normal big-emoji': onlyEmoji,
})}
direction={direction}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useEffect, useState, useCallback } from 'react';
import { usePopper } from 'react-popper';
import { useHistory } from 'react-router-dom';
@ -79,7 +79,7 @@ export const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true })
return (
<div
className={classNames({
className={clsx({
'absolute transition-opacity w-[500px] z-50 top-0 left-0': true,
'opacity-100': visible,
'opacity-0 pointer-events-none': !visible,

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { Map as ImmutableMap } from 'immutable';
import debounce from 'lodash/debounce';
import React, { useRef, useCallback } from 'react';
@ -244,10 +244,10 @@ const StatusList: React.FC<IStatusList> = ({
placeholderComponent={PlaceholderStatus}
placeholderCount={20}
ref={node}
className={classNames('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
className={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
'divide-none': divideType !== 'border',
})}
itemClassName={classNames({
itemClassName={clsx({
'pb-3': divideType !== 'border',
})}
{...other}

Wyświetl plik

@ -1,6 +1,4 @@
import classNames from 'clsx';
import React, { useState, useEffect, useRef } from 'react';
import { usePopper } from 'react-popper';
import { simpleEmojiReact } from 'soapbox/actions/emoji-reacts';
import { openModal } from 'soapbox/actions/modals';
@ -9,13 +7,13 @@ import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig } from
import { isUserTouching } from 'soapbox/is-mobile';
import { getReactForStatus } from 'soapbox/utils/emoji-reacts';
interface IEmojiButtonWrapper {
interface IStatusReactionWrapper {
statusId: string,
children: JSX.Element,
}
/** Provides emoji reaction functionality to the underlying button component */
const EmojiButtonWrapper: React.FC<IEmojiButtonWrapper> = ({ statusId, children }): JSX.Element | null => {
const StatusReactionWrapper: React.FC<IStatusReactionWrapper> = ({ statusId, children }): JSX.Element | null => {
const dispatch = useAppDispatch();
const ownAccount = useOwnAccount();
const status = useAppSelector(state => state.statuses.get(statusId));
@ -23,24 +21,8 @@ const EmojiButtonWrapper: React.FC<IEmojiButtonWrapper> = ({ statusId, children
const timeout = useRef<NodeJS.Timeout>();
const [visible, setVisible] = useState(false);
// const [focused, setFocused] = useState(false);
// `useRef` won't trigger a re-render, while `useState` does.
// https://popper.js.org/react-popper/v2/
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'top-start',
modifiers: [
{
name: 'offset',
options: {
offset: [-10, 0],
},
},
],
});
useEffect(() => {
return () => {
@ -116,28 +98,6 @@ const EmojiButtonWrapper: React.FC<IEmojiButtonWrapper> = ({ statusId, children
}));
};
// const handleUnfocus: React.EventHandler<React.KeyboardEvent> = () => {
// setFocused(false);
// };
const selector = (
<div
className={classNames('z-50 transition-opacity duration-100', {
'opacity-0 pointer-events-none': !visible,
})}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<EmojiSelector
emojis={soapboxConfig.allowedEmoji}
onReact={handleReact}
// focused={focused}
// onUnfocus={handleUnfocus}
/>
</div>
);
return (
<div className='relative' onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
{React.cloneElement(children, {
@ -145,9 +105,14 @@ const EmojiButtonWrapper: React.FC<IEmojiButtonWrapper> = ({ statusId, children
ref: setReferenceElement,
})}
{selector}
<EmojiSelector
placement='top-start'
referenceElement={referenceElement}
onReact={handleReact}
visible={visible}
/>
</div>
);
};
export default EmojiButtonWrapper;
export default StatusReactionWrapper;

Wyświetl plik

@ -73,7 +73,7 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
if (to.size > 2) {
accounts.push(
<span key='more' className='hover:underline cursor-pointer' role='button' onClick={handleOpenMentionsModal} tabIndex={0}>
<span key='more' className='cursor-pointer hover:underline' role='button' onClick={handleOpenMentionsModal} tabIndex={0}>
<FormattedMessage id='reply_mentions.more' defaultMessage='{count} more' values={{ count: to.size - 2 }} />
</span>,
);
@ -93,7 +93,7 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
<HoverStatusWrapper statusId={status.in_reply_to_id} inline>
<span
key='hoverstatus'
className='hover:underline cursor-pointer'
className='cursor-pointer hover:underline'
role='presentation'
>
{children}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import { HotKeys } from 'react-hotkeys';
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
@ -53,7 +53,6 @@ export interface IStatus {
hoverable?: boolean,
variant?: 'default' | 'rounded',
showGroup?: boolean,
withDismiss?: boolean,
accountAction?: React.ReactElement,
}
@ -74,7 +73,6 @@ const Status: React.FC<IStatus> = (props) => {
hideActionBar,
variant = 'rounded',
showGroup = true,
withDismiss,
} = props;
const intl = useIntl();
@ -291,8 +289,10 @@ const Status: React.FC<IStatus> = (props) => {
return (
<HotKeys handlers={minHandlers}>
<div className={classNames('status__wrapper', 'status__wrapper--filtered', { focusable })} tabIndex={focusable ? 0 : undefined} ref={node}>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
<div className={clsx('status__wrapper text-center', { focusable })} tabIndex={focusable ? 0 : undefined} ref={node}>
<Text theme='muted'>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
</Text>
</div>
</HotKeys>
);
@ -341,7 +341,7 @@ const Status: React.FC<IStatus> = (props) => {
return (
<HotKeys handlers={handlers} data-testid='status'>
<div
className={classNames('status cursor-pointer', { focusable })}
className={clsx('status cursor-pointer', { focusable })}
tabIndex={focusable && !muted ? 0 : undefined}
data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, actualStatus, rebloggedByText)}
@ -351,7 +351,7 @@ const Status: React.FC<IStatus> = (props) => {
>
<Card
variant={variant}
className={classNames('status__wrapper space-y-4', `status-${actualStatus.visibility}`, {
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,
@ -421,7 +421,7 @@ const Status: React.FC<IStatus> = (props) => {
{(!hideActionBar && !isUnderReview) && (
<div className='pt-4'>
<StatusActionBar status={actualStatus} withDismiss={withDismiss} />
<StatusActionBar status={actualStatus} />
</div>
)}
</div>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useEffect, useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -91,7 +91,7 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
return (
<div
className={classNames('absolute z-40', {
className={clsx('absolute z-40', {
'cursor-default backdrop-blur-lg rounded-lg w-full h-full border-0 flex justify-center': !visible,
'bg-gray-800/75 inset-0': !visible,
'bottom-1 right-1': visible,
@ -107,8 +107,8 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
size='sm'
/>
) : (
<div className='flex justify-center items-center max-h-screen'>
<div className='text-center w-3/4 mx-auto space-y-4' ref={ref}>
<div className='flex max-h-screen items-center justify-center'>
<div className='mx-auto w-3/4 space-y-4 text-center' ref={ref}>
<div className='space-y-1'>
<Text theme='white' weight='semibold'>
{intl.formatMessage(isUnderReview ? messages.underReviewTitle : messages.sensitiveTitle)}

Wyświetl plik

@ -21,7 +21,7 @@ const StatusInfo = (props: IStatusInfo) => {
return (
<Container
{...containerProps}
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-3 rtl:space-x-reverse hover:underline'
className='flex items-center space-x-3 text-xs font-medium text-gray-700 hover:underline rtl:space-x-reverse dark:text-gray-600'
>
<div
className='flex justify-end'

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useRef } from 'react';
import { useSettings } from 'soapbox/hooks';
@ -39,7 +39,7 @@ const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterb
};
/** ClassNames shared between the `<img>` and `<canvas>` elements. */
const baseClassName = classNames('w-full h-full block', {
const baseClassName = clsx('block h-full w-full', {
'object-contain': letterboxed,
'object-cover': !letterboxed,
});
@ -47,7 +47,7 @@ const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterb
return (
<div
data-testid='still-image-container'
className={classNames(className, 'relative group overflow-hidden isolate')}
className={clsx(className, 'group relative isolate overflow-hidden')}
style={style}
>
<img
@ -55,7 +55,7 @@ const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterb
alt={alt}
ref={img}
onLoad={handleImageLoad}
className={classNames(baseClassName, {
className={clsx(baseClassName, {
'invisible group-hover:visible': hoverToPlay,
})}
/>
@ -63,14 +63,14 @@ const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterb
{hoverToPlay && (
<canvas
ref={canvas}
className={classNames(baseClassName, {
className={clsx(baseClassName, {
'group-hover:invisible': hoverToPlay,
})}
/>
)}
{(hoverToPlay && showExt) && (
<div className='group-hover:hidden absolute opacity-90 left-2 bottom-2 pointer-events-none'>
<div className='pointer-events-none absolute left-2 bottom-2 opacity-90 group-hover:hidden'>
<ExtensionBadge ext='GIF' />
</div>
)}
@ -86,7 +86,7 @@ interface IExtensionBadge {
/** Badge displaying a file extension. */
const ExtensionBadge: React.FC<IExtensionBadge> = ({ ext }) => {
return (
<div className='inline-flex items-center px-2 py-0.5 rounded text-sm font-medium bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100'>
<div className='inline-flex items-center rounded bg-gray-100 px-2 py-0.5 text-sm font-medium text-gray-900 dark:bg-gray-800 dark:text-gray-100'>
{ext}
</div>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { NavLink, useLocation } from 'react-router-dom';
@ -33,7 +33,7 @@ const ThumbNavigationLink: React.FC<IThumbNavigationLink> = ({ count, countMax,
{count !== undefined ? (
<IconWithCounter
src={src}
className={classNames({
className={clsx({
'h-5 w-5': true,
'text-gray-600': !active,
'text-primary-500': active,
@ -44,7 +44,7 @@ const ThumbNavigationLink: React.FC<IThumbNavigationLink> = ({ count, countMax,
) : (
<Icon
src={src}
className={classNames({
className={clsx({
'h-5 w-5': true,
'text-gray-600': !active,
'text-primary-500': active,
@ -56,7 +56,7 @@ const ThumbNavigationLink: React.FC<IThumbNavigationLink> = ({ count, countMax,
tag='span'
size='xs'
weight='medium'
className={classNames({
className={clsx({
'text-gray-600': !active,
'text-primary-500': active,
})}

Wyświetl plik

@ -19,7 +19,7 @@ const Tombstone: React.FC<ITombstone> = ({ id, onMoveUp, onMoveDown }) => {
return (
<HotKeys handlers={handlers}>
<div className='p-9 flex items-center justify-center sm:rounded-xl bg-gray-100 border border-solid border-gray-200 dark:bg-gray-900 dark:border-gray-800 focusable' tabIndex={0}>
<div className='focusable flex items-center justify-center border border-solid border-gray-200 bg-gray-100 p-9 dark:border-gray-800 dark:bg-gray-900 sm:rounded-xl' tabIndex={0}>
<Text>
<FormattedMessage id='statuses.tombstone' defaultMessage='One or more posts are unavailable.' />
</Text>

Wyświetl plik

@ -6,7 +6,7 @@ import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses
import { useAppDispatch, useAppSelector, useFeatures, useInstance } from 'soapbox/hooks';
import { isLocal } from 'soapbox/utils/accounts';
import { Icon, Stack } from './ui';
import { Stack, Button, Text } from './ui';
import type { Account, Status } from 'soapbox/types/entities';
@ -44,32 +44,36 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
if (!features.translations || !renderTranslate || !supportsLanguages) return null;
const buttonClassName = 'flex items-center gap-0.5 w-fit px-2 py-1 border-gray-600 hover:border-gray-700 dark:hover:border-gray-500 border-solid border text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 text-start text-sm rounded-full';
if (status.translation) {
const languageNames = new Intl.DisplayNames([intl.locale], { type: 'language' });
const languageName = languageNames.of(status.language!);
const provider = status.translation.get('provider');
return (
<Stack className='text-gray-700 dark:text-gray-600 text-sm' space={1} alignItems='start'>
<span>
<Stack space={3} alignItems='start'>
<Button
theme='muted'
text={<FormattedMessage id='status.show_original' defaultMessage='Show original' />}
icon={require('@tabler/icons/language.svg')}
onClick={handleTranslate}
/>
<Text theme='muted'>
<FormattedMessage id='status.translated_from_with' defaultMessage='Translated from {lang} using {provider}' values={{ lang: languageName, provider }} />
</span>
<button className={buttonClassName} onClick={handleTranslate}>
<Icon className='h-5 w-5 stroke-[1.25]' src={require('@tabler/icons/language.svg')} strokeWidth={1.25} />
<FormattedMessage id='status.show_original' defaultMessage='Show original' />
</button>
</Text>
</Stack>
);
}
return (
<button className={buttonClassName} onClick={handleTranslate}>
<Icon className='h-5 w-5' src={require('@tabler/icons/language.svg')} strokeWidth={1.25} />
<FormattedMessage id='status.translate' defaultMessage='Translate' />
</button>
<div>
<Button
theme='muted'
text={<FormattedMessage id='status.translate' defaultMessage='Translate' />}
icon={require('@tabler/icons/language.svg')}
onClick={handleTranslate}
/>
</div>
);
};

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -36,13 +36,13 @@ const Accordion: React.FC<IAccordion> = ({ headline, children, menu, expanded =
};
return (
<div className='bg-white dark:bg-primary-800 text-gray-900 dark:text-gray-100 rounded-lg shadow dark:shadow-none'>
<div className='rounded-lg bg-white text-gray-900 shadow dark:bg-primary-800 dark:text-gray-100 dark:shadow-none'>
<button
type='button'
onClick={handleToggle}
title={intl.formatMessage(expanded ? messages.collapse : messages.expand)}
aria-expanded={expanded}
className='px-4 py-3 font-semibold flex items-center justify-between w-full'
className='flex w-full items-center justify-between px-4 py-3 font-semibold'
>
<span>{headline}</span>
@ -55,14 +55,14 @@ const Accordion: React.FC<IAccordion> = ({ headline, children, menu, expanded =
)}
<Icon
src={expanded ? require('@tabler/icons/chevron-up.svg') : require('@tabler/icons/chevron-down.svg')}
className='text-gray-700 dark:text-gray-600 h-5 w-5'
className='h-5 w-5 text-gray-700 dark:text-gray-600'
/>
</HStack>
</button>
<div
className={
classNames({
clsx({
'p-4 rounded-b-lg border-t border-solid border-gray-100 dark:border-primary-900': true,
'h-0 hidden': !expanded,
})

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import StillImage from 'soapbox/components/still-image';
@ -25,7 +25,7 @@ const Avatar = (props: IAvatar) => {
return (
<StillImage
className={classNames('rounded-full', className)}
className={clsx('rounded-full', className)}
style={style}
src={src}
alt='Avatar'

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IBanner {
@ -12,12 +12,12 @@ const Banner: React.FC<IBanner> = ({ theme, children, className }) => {
return (
<div
data-testid='banner'
className={classNames('fixed bottom-0 left-0 right-0 py-8 z-50', {
className={clsx('fixed inset-x-0 bottom-0 z-50 py-8', {
'backdrop-blur bg-primary-800/80 dark:bg-primary-900/80': theme === 'frosted',
'bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-3xl dark:shadow-inset': theme === 'opaque',
}, className)}
>
<div className='max-w-4xl mx-auto px-4'>
<div className='mx-auto max-w-4xl px-4'>
{children}
</div>
</div>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { Link } from 'react-router-dom';
@ -63,7 +63,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.El
return null;
}
return <Icon src={icon} className='w-4 h-4' />;
return <Icon src={icon} className='h-4 w-4' />;
};
const handleClick: React.MouseEventHandler<HTMLButtonElement> = React.useCallback((event) => {
@ -74,7 +74,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.El
const renderButton = () => (
<button
className={classNames('space-x-2 rtl:space-x-reverse', themeClass, className)}
className={clsx('space-x-2 rtl:space-x-reverse', themeClass, className)}
disabled={disabled}
onClick={handleClick}
ref={ref}
@ -100,4 +100,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.El
return renderButton();
});
export default Button;
export {
Button as default,
Button,
};

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
const themes = {
primary:
@ -11,6 +11,7 @@ const themes = {
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',
muted: 'border border-solid 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',
};
const sizes = {
@ -37,7 +38,7 @@ const useButtonStyles = ({
disabled,
size,
}: IButtonStyles) => {
const buttonStyle = classNames({
const buttonStyle = clsx({
'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,
[`${themes[theme]}`]: true,

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
@ -32,7 +32,7 @@ const Card = React.forwardRef<HTMLDivElement, ICard>(({ children, variant = 'def
<div
ref={ref}
{...filteredProps}
className={classNames({
className={clsx({
'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none overflow-hidden': variant === 'rounded',
[sizes[size]]: variant === 'rounded',
}, className)}
@ -64,7 +64,7 @@ const CardHeader: React.FC<ICardHeader> = ({ className, children, backHref, onBa
const backAttributes = backHref ? { to: backHref } : { onClick: onBackClick };
return (
<Comp {...backAttributes} className='text-gray-900 dark:text-gray-100 focus:ring-primary-500 focus:ring-2' aria-label={intl.formatMessage(messages.back)}>
<Comp {...backAttributes} className='text-gray-900 focus:ring-2 focus:ring-primary-500 dark:text-gray-100' aria-label={intl.formatMessage(messages.back)}>
<SvgIcon src={require('@tabler/icons/arrow-left.svg')} className='h-6 w-6 rtl:rotate-180' />
<span className='sr-only' data-testid='back-button'>{intl.formatMessage(messages.back)}</span>
</Comp>
@ -72,7 +72,7 @@ const CardHeader: React.FC<ICardHeader> = ({ className, children, backHref, onBa
};
return (
<HStack alignItems='center' space={2} className={classNames('mb-4', className)}>
<HStack alignItems='center' space={2} className={clsx('mb-4', className)}>
{renderBackButton()}
{children}

Wyświetl plik

@ -9,7 +9,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, ICheckbox>((props, ref) => {
{...props}
ref={ref}
type='checkbox'
className='border-2 dark:bg-gray-900 dark:border-gray-800 focus:ring-primary-500 h-4 w-4 text-primary-600 border-gray-300 rounded'
className='h-4 w-4 rounded border-2 border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-gray-800 dark:bg-gray-900'
/>
);
});

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { useHistory } from 'react-router-dom';
@ -74,7 +74,7 @@ const Column: React.FC<IColumn> = React.forwardRef((props, ref: React.ForwardedR
<ColumnHeader
label={label}
backHref={backHref}
className={classNames({ 'px-4 pt-4 sm:p-0': transparent })}
className={clsx({ 'px-4 pt-4 sm:p-0': transparent })}
/>
)}

Wyświetl plik

@ -12,7 +12,7 @@ interface ICounter {
/** A simple counter for notifications, etc. */
const Counter: React.FC<ICounter> = ({ count, countMax }) => {
return (
<span className='h-5 min-w-[20px] max-w-[26px] flex items-center justify-center bg-secondary-500 text-xs font-medium text-white rounded-full ring-2 ring-white dark:ring-gray-800'>
<span className='flex h-5 min-w-[20px] max-w-[26px] items-center justify-center rounded-full bg-secondary-500 text-xs font-medium text-white ring-2 ring-white dark:ring-gray-800'>
{shortNumberFormat(count, countMax)}
</span>
);

Wyświetl plik

@ -31,7 +31,7 @@ const Datepicker = ({ onChange }: IDatepicker) => {
}, [month, day, year]);
return (
<div className='grid grid-cols-1 gap-y-2 gap-x-2 sm:grid-cols-3'>
<div className='grid grid-cols-1 gap-2 sm:grid-cols-3'>
<div className='sm:col-span-1'>
<Stack>
<Text size='sm' weight='medium' theme='muted'>

Wyświetl plik

@ -13,12 +13,12 @@ interface IDivider {
const Divider = ({ text, textSize = 'md' }: IDivider) => (
<div className='relative' data-testid='divider'>
<div className='absolute inset-0 flex items-center' aria-hidden='true'>
<div className='w-full border-t-2 border-gray-100 dark:border-gray-800 border-solid' />
<div className='w-full border-t-2 border-solid border-gray-100 dark:border-gray-800' />
</div>
{text && (
<div className='relative flex justify-center'>
<span className='px-2 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-600' data-testid='divider-text'>
<span className='bg-white px-2 text-gray-700 dark:bg-gray-900 dark:text-gray-600' data-testid='divider-text'>
<Text size={textSize} tag='span' theme='inherit'>{text}</Text>
</span>
</div>

Wyświetl plik

@ -1,13 +1,16 @@
import classNames from 'clsx';
import React from 'react';
import { Placement } from '@popperjs/core';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { usePopper } from 'react-popper';
import { Emoji, HStack } from 'soapbox/components/ui';
import { useSoapboxConfig } from 'soapbox/hooks';
interface IEmojiButton {
/** Unicode emoji character. */
emoji: string,
/** Event handler when the emoji is clicked. */
onClick: React.EventHandler<React.MouseEvent>,
onClick(emoji: string): void
/** Extra class name on the <button> element. */
className?: string,
/** Tab order of the button. */
@ -16,48 +19,103 @@ interface IEmojiButton {
/** Clickable emoji button that scales when hovered. */
const EmojiButton: React.FC<IEmojiButton> = ({ emoji, className, onClick, tabIndex }): JSX.Element => {
const handleClick: React.EventHandler<React.MouseEvent> = (event) => {
event.preventDefault();
event.stopPropagation();
onClick(emoji);
};
return (
<button className={classNames(className)} onClick={onClick} tabIndex={tabIndex}>
<Emoji className='w-8 h-8 duration-100 hover:scale-125' emoji={emoji} />
<button className={clsx(className)} onClick={handleClick} tabIndex={tabIndex}>
<Emoji className='h-6 w-6 duration-100 hover:scale-110' emoji={emoji} />
</button>
);
};
interface IEmojiSelector {
/** List of Unicode emoji characters. */
emojis: Iterable<string>,
onClose?(): void
/** Event handler when an emoji is clicked. */
onReact: (emoji: string) => void,
onReact(emoji: string): void
/** Element that triggers the EmojiSelector Popper */
referenceElement: HTMLElement | null
placement?: Placement
/** Whether the selector should be visible. */
visible?: boolean,
/** Whether the selector should be focused. */
focused?: boolean,
visible?: boolean
}
/** Panel with a row of emoji buttons. */
const EmojiSelector: React.FC<IEmojiSelector> = ({ emojis, onReact, visible = false, focused = false }): JSX.Element => {
const EmojiSelector: React.FC<IEmojiSelector> = ({
referenceElement,
onClose,
onReact,
placement = 'top',
visible = false,
}): JSX.Element => {
const soapboxConfig = useSoapboxConfig();
const handleReact = (emoji: string): React.EventHandler<React.MouseEvent> => {
return (e) => {
onReact(emoji);
e.preventDefault();
e.stopPropagation();
};
// `useRef` won't trigger a re-render, while `useState` does.
// https://popper.js.org/react-popper/v2/
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const handleClickOutside = (event: MouseEvent) => {
if (referenceElement?.contains(event.target as Node) || popperElement?.contains(event.target as Node)) {
return;
}
if (onClose) {
onClose();
}
};
const { styles, attributes, update } = usePopper(referenceElement, popperElement, {
placement,
modifiers: [
{
name: 'offset',
options: {
offset: [-10, 0],
},
},
],
});
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [referenceElement]);
useEffect(() => {
if (visible && update) {
update();
}
}, [visible, update]);
return (
<HStack
className={classNames('gap-2 bg-white dark:bg-gray-900 p-3 rounded-full shadow-md z-[999] w-max max-w-[100vw] flex-wrap')}
<div
className={clsx('z-50 transition-opacity duration-100', {
'opacity-0 pointer-events-none': !visible,
})}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
{Array.from(emojis).map((emoji, i) => (
<EmojiButton
key={i}
emoji={emoji}
onClick={handleReact(emoji)}
tabIndex={(visible || focused) ? 0 : -1}
/>
))}
</HStack>
<HStack
className={clsx('z-[999] flex w-max max-w-[100vw] flex-wrap space-x-3 rounded-full bg-white px-3 py-2.5 shadow-lg focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700')}
>
{Array.from(soapboxConfig.allowedEmoji).map((emoji, i) => (
<EmojiButton
key={i}
emoji={emoji}
onClick={onReact}
tabIndex={visible ? 0 : -1}
/>
))}
</HStack>
</div>
);
};

Wyświetl plik

@ -8,7 +8,7 @@ const FileInput = forwardRef<HTMLInputElement, IFileInput>((props, ref) => {
{...props}
ref={ref}
type='file'
className='block w-full text-sm text-gray-800 dark:text-gray-200 file:cursor-pointer file:mr-2 file:py-1.5 file:px-3 file:rounded-full file:text-xs file:leading-4 file:font-medium file:border-gray-200 file:border file:border-solid file:bg-white file:text-gray-700 hover:file:bg-gray-100 dark:file:border-gray-800 dark:file:bg-gray-900 dark:file:hover:bg-gray-800 dark:file:text-gray-500'
className='block w-full text-sm text-gray-800 file:mr-2 file:cursor-pointer file:rounded-full file:border file:border-solid file:border-gray-200 file:bg-white file:py-1.5 file:px-3 file:text-xs file:font-medium file:leading-4 file:text-gray-700 hover:file:bg-gray-100 dark:text-gray-200 dark:file:border-gray-800 dark:file:bg-gray-900 dark:file:text-gray-500 dark:file:hover:bg-gray-800'
/>
);
});

Wyświetl plik

@ -55,7 +55,7 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
<div>
<p
data-testid='form-group-error'
className='mt-0.5 text-xs text-danger-900 bg-danger-200 rounded-md inline-block px-2 py-1 relative form-error'
className='form-error relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
>
{errors.join(', ')}
</p>
@ -92,7 +92,7 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
{hasError && (
<p
data-testid='form-group-error'
className='mt-0.5 text-xs text-danger-900 bg-danger-200 rounded-md inline-block px-2 py-1 relative form-error'
className='form-error relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
>
{errors.join(', ')}
</p>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { forwardRef } from 'react';
const justifyContentOptions = {
@ -61,14 +61,14 @@ const HStack = forwardRef<HTMLDivElement, IHStack>((props, ref) => {
<Elem
{...filteredProps}
ref={ref}
className={classNames('flex rtl:space-x-reverse', {
className={clsx('flex rtl:space-x-reverse', {
// @ts-ignore
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
// @ts-ignore
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
// @ts-ignore
[spaces[space]]: typeof space !== 'undefined',
'flex-grow': grow,
'grow': grow,
'flex-wrap': wrap,
}, className)}
/>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import SvgIcon from '../icon/svg-icon';
@ -15,6 +15,8 @@ interface IIconButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
transparent?: boolean,
/** Predefined styles to display for the button. */
theme?: 'seamless' | 'outlined',
/** Override the data-testid */
'data-testid'?: string
}
/** A clickable icon. */
@ -25,13 +27,13 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef
<button
ref={ref}
type='button'
className={classNames('flex items-center space-x-2 p-1 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 dark:ring-offset-0 focus:ring-primary-500', {
className={clsx('flex items-center space-x-2 rounded-full p-1 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-offset-0', {
'bg-white dark:bg-transparent': !transparent,
'border border-solid 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': theme === 'outlined',
'opacity-50': filteredProps.disabled,
}, className)}
{...filteredProps}
data-testid='icon-button'
data-testid={filteredProps['data-testid'] || 'icon-button'}
>
<SvgIcon src={src} className={iconClassName} />

Wyświetl plik

@ -21,9 +21,9 @@ interface IIcon extends Pick<React.SVGAttributes<SVGAElement>, 'strokeWidth'> {
/** Renders and SVG icon with optional counter. */
const Icon: React.FC<IIcon> = ({ src, alt, count, size, countMax, ...filteredProps }): JSX.Element => (
<div className='flex flex-col flex-shrink-0 relative' data-testid='icon'>
<div className='relative flex shrink-0 flex-col' data-testid='icon'>
{count ? (
<span className='absolute -top-2 -right-3 min-w-[20px] h-5 flex-shrink-0 whitespace-nowrap flex items-center justify-center break-words'>
<span className='absolute -top-2 -right-3 flex h-5 min-w-[20px] shrink-0 items-center justify-center whitespace-nowrap break-words'>
<Counter count={count} countMax={countMax} />
</span>
) : null}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -59,7 +59,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
return (
<div
className={
classNames('relative', {
clsx('relative', {
'rounded-md': theme !== 'search',
'rounded-full': theme === 'search',
'mt-1': !String(outerClassName).includes('mt-'),
@ -68,7 +68,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
}
>
{icon ? (
<div className='absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none'>
<div className='pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3'>
<Icon src={icon} className='h-4 w-4 text-gray-700 dark:text-gray-600' aria-hidden='true' />
</div>
) : null}
@ -83,7 +83,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
{...filteredProps}
type={revealed ? 'text' : type}
ref={ref}
className={classNames('text-base placeholder:text-gray-600 dark:placeholder:text-gray-600', {
className={clsx('text-base placeholder:text-gray-600 dark:placeholder:text-gray-600', {
'text-gray-900 dark:text-gray-100 block w-full sm:text-sm dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
['normal', 'search'].includes(theme),
'rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800': theme === 'normal',
@ -95,7 +95,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
/>
{append ? (
<div className='absolute inset-y-0 right-0 rtl:left-0 rtl:right-auto flex items-center pr-3'>
<div className='absolute inset-y-0 right-0 flex items-center pr-3 rtl:left-0 rtl:right-auto'>
{append}
</div>
) : null}
@ -108,12 +108,12 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
intl.formatMessage(messages.showPassword)
}
>
<div className='absolute inset-y-0 right-0 rtl:left-0 rtl:right-auto flex items-center'>
<div className='absolute inset-y-0 right-0 flex items-center rtl:left-0 rtl:right-auto'>
<button
type='button'
onClick={togglePassword}
tabIndex={-1}
className='text-gray-700 dark:text-gray-600 hover:text-gray-500 dark:hover:text-gray-400 h-full px-2 focus:ring-primary-500 focus:ring-2'
className='h-full px-2 text-gray-700 hover:text-gray-500 focus:ring-2 focus:ring-primary-500 dark:text-gray-600 dark:hover:text-gray-400'
>
<SvgIcon
src={revealed ? require('@tabler/icons/eye-off.svg') : require('@tabler/icons/eye.svg')}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import StickyBox from 'react-sticky-box';
@ -21,8 +21,8 @@ interface LayoutComponent extends React.FC<ILayout> {
/** Layout container, to hold Sidebar, Main, and Aside. */
const Layout: LayoutComponent = ({ children }) => (
<div className='sm:pt-4 relative'>
<div className='max-w-3xl mx-auto sm:px-6 md:max-w-7xl md:px-8 md:grid md:grid-cols-12 md:gap-8'>
<div className='relative sm:pt-4'>
<div className='mx-auto max-w-3xl sm:px-6 md:grid md:max-w-7xl md:grid-cols-12 md:gap-8 md:px-8'>
{children}
</div>
</div>
@ -30,7 +30,7 @@ const Layout: LayoutComponent = ({ children }) => (
/** Left sidebar container in the UI. */
const Sidebar: React.FC<ISidebar> = ({ children }) => (
<div className='hidden lg:block lg:col-span-3'>
<div className='hidden lg:col-span-3 lg:block'>
<StickyBox offsetTop={80} className='pb-4'>
{children}
</StickyBox>
@ -40,7 +40,7 @@ const Sidebar: React.FC<ISidebar> = ({ children }) => (
/** Center column container in the UI. */
const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, className }) => (
<main
className={classNames({
className={clsx({
'md:col-span-12 lg:col-span-9 xl:col-span-6 pb-36': true,
}, className)}
>
@ -50,7 +50,7 @@ const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, classN
/** Right sidebar container in the UI. */
const Aside: React.FC<IAside> = ({ children }) => (
<aside className='hidden xl:block xl:col-span-3'>
<aside className='hidden xl:col-span-3 xl:block'>
<StickyBox offsetTop={80} className='space-y-6 pb-12'>
{children}
</StickyBox>

Wyświetl plik

@ -8,7 +8,7 @@ import {
MenuListProps,
} from '@reach/menu-button';
import { positionDefault, positionRight } from '@reach/popover';
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import './menu.css';
@ -28,7 +28,7 @@ const MenuList: React.FC<IMenuList> = (props) => {
<MenuItems
onKeyDown={(event) => event.nativeEvent.stopImmediatePropagation()}
className={
classNames(className, 'py-1 bg-white dark:bg-primary-900 rounded-lg shadow-menu')
clsx(className, 'shadow-menu rounded-lg bg-white py-1 dark:bg-primary-900')
}
{...filteredProps}
/>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -87,16 +87,16 @@ const Modal: React.FC<IModal> = ({
}, [skipFocus, buttonRef]);
return (
<div data-testid='modal' className={classNames('block w-full p-6 mx-auto text-start align-middle transition-all transform bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-xl rounded-2xl pointer-events-auto', widths[width])}>
<div className='sm:flex sm:items-start w-full justify-between'>
<div data-testid='modal' className={clsx('pointer-events-auto mx-auto block w-full rounded-2xl bg-white p-6 text-start align-middle text-gray-900 shadow-xl transition-all dark:bg-primary-900 dark:text-gray-100', widths[width])}>
<div className='w-full justify-between sm:flex sm:items-start'>
<div className='w-full'>
{title && (
<div
className={classNames('w-full flex items-center gap-2', {
className={clsx('flex w-full items-center gap-2', {
'flex-row-reverse': closePosition === 'left',
})}
>
<h3 className='flex-grow text-lg leading-6 font-bold text-gray-900 dark:text-white'>
<h3 className='grow text-lg font-bold leading-6 text-gray-900 dark:text-white'>
{title}
</h3>
@ -105,14 +105,14 @@ const Modal: React.FC<IModal> = ({
src={closeIcon}
title={intl.formatMessage(messages.close)}
onClick={onClose}
className='text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-200 rtl:rotate-180'
className='text-gray-500 hover:text-gray-700 rtl:rotate-180 dark:text-gray-300 dark:hover:text-gray-200'
/>
)}
</div>
)}
{title ? (
<div className='w-full mt-2'>
<div className='mt-2 w-full'>
{children}
</div>
) : children}
@ -121,7 +121,7 @@ const Modal: React.FC<IModal> = ({
{confirmationAction && (
<HStack className='mt-5' justifyContent='between' data-testid='modal-actions'>
<div className={classNames({ 'flex-grow': !confirmationFullWidth })}>
<div className={clsx({ 'grow': !confirmationFullWidth })}>
{cancelAction && (
<Button
theme='tertiary'
@ -132,7 +132,7 @@ const Modal: React.FC<IModal> = ({
)}
</div>
<HStack space={2} className={classNames({ 'flex-grow': confirmationFullWidth })}>
<HStack space={2} className={clsx({ 'grow': confirmationFullWidth })}>
{secondaryAction && (
<Button
theme='secondary'

Wyświetl plik

@ -12,7 +12,7 @@ const CountryCodeDropdown: React.FC<ICountryCodeDropdown> = ({ countryCode, onCh
return (
<select
value={countryCode}
className='h-full py-0 pl-3 pr-7 text-base bg-transparent border-transparent focus:outline-none focus:ring-primary-500 dark:text-white sm:text-sm rounded-md'
className='h-full rounded-md border-transparent bg-transparent py-0 pl-3 pr-7 text-base focus:outline-none focus:ring-primary-500 dark:text-white sm:text-sm'
onChange={(event) => onChange(event.target.value as any)}
>
{COUNTRY_CODES.map((code) => (

Wyświetl plik

@ -1,13 +1,32 @@
import clsx from 'clsx';
import React from 'react';
import { spring } from 'react-motion';
import Motion from 'soapbox/features/ui/util/optional-motion';
interface IProgressBar {
progress: number,
/** Number between 0 and 1 to represent the percentage complete. */
progress: number
/** Height of the progress bar. */
size?: 'sm' | 'md'
}
/** A horizontal meter filled to the given percentage. */
const ProgressBar: React.FC<IProgressBar> = ({ progress }) => (
<div className='h-2.5 w-full rounded-full bg-gray-300 dark:bg-primary-800 overflow-hidden'>
<div className='h-full bg-secondary-500' style={{ width: `${Math.floor(progress * 100)}%` }} />
const ProgressBar: React.FC<IProgressBar> = ({ progress, size = 'md' }) => (
<div
className={clsx('h-2.5 w-full overflow-hidden rounded-lg bg-gray-300 dark:bg-primary-800', {
'h-2.5': size === 'md',
'h-[6px]': size === 'sm',
})}
>
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress * 100) }}>
{({ width }) => (
<div
className='h-full bg-secondary-500'
style={{ width: `${width}%` }}
/>
)}
</Motion>
</div>
);

Wyświetl plik

@ -11,7 +11,7 @@ const Select = React.forwardRef<HTMLSelectElement, ISelect>((props, ref) => {
return (
<select
ref={ref}
className={`w-full pl-3 pr-10 py-2 text-base truncate border-gray-300 dark:border-gray-800 focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:focus:ring-primary-500 dark:focus:border-primary-500 sm:text-sm rounded-md disabled:opacity-50 ${className}`}
className={`w-full truncate rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-primary-500 focus:outline-none focus:ring-primary-500 disabled:opacity-50 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:focus:border-primary-500 dark:focus:ring-primary-500 sm:text-sm ${className}`}
{...filteredProps}
>
{children}

Wyświetl plik

@ -53,14 +53,14 @@ const Slider: React.FC<ISlider> = ({ value, onChange }) => {
return (
<div
className='inline-flex cursor-pointer h-6 relative transition'
className='relative inline-flex h-6 cursor-pointer transition'
onMouseDown={handleMouseDown}
ref={node}
>
<div className='w-full h-1 bg-primary-200 dark:bg-primary-700 absolute top-1/2 -translate-y-1/2 rounded-full' />
<div className='h-1 bg-accent-500 absolute top-1/2 -translate-y-1/2 rounded-full' style={{ width: `${value * 100}%` }} />
<div className='absolute top-1/2 h-1 w-full -translate-y-1/2 rounded-full bg-primary-200 dark:bg-primary-700' />
<div className='absolute top-1/2 h-1 -translate-y-1/2 rounded-full bg-accent-500' style={{ width: `${value * 100}%` }} />
<span
className='bg-accent-500 absolute rounded-full w-3 h-3 -ml-1.5 top-1/2 -translate-y-1/2 z-10 shadow'
className='absolute top-1/2 z-10 -ml-1.5 h-3 w-3 -translate-y-1/2 rounded-full bg-accent-500 shadow'
tabIndex={0}
style={{ left: `${value * 100}%` }}
/>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
const spaces = {
@ -52,14 +52,14 @@ const Stack = React.forwardRef<HTMLDivElement, IStack>((props, ref: React.Legacy
<Elem
{...filteredProps}
ref={ref}
className={classNames('flex flex-col', {
className={clsx('flex flex-col', {
// @ts-ignore
[spaces[space]]: typeof space !== 'undefined',
// @ts-ignore
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
// @ts-ignore
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
'flex-grow': grow,
'grow': grow,
}, className)}
/>
);

Wyświetl plik

@ -5,7 +5,7 @@ import {
Tab as ReachTab,
useTabsContext,
} from '@reach/tabs';
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { useHistory } from 'react-router-dom';
@ -46,11 +46,11 @@ const AnimatedTabs: React.FC<IAnimatedInterface> = ({ children, ...rest }) => {
ref={ref}
>
<div
className='w-full h-[3px] bg-primary-200 dark:bg-primary-700 absolute'
className='absolute h-[3px] w-full bg-primary-200 dark:bg-gray-800'
style={{ top }}
/>
<div
className={classNames('absolute h-[3px] bg-primary-500 transition-all duration-200', {
className={clsx('absolute h-[3px] bg-primary-500 transition-all duration-200', {
'hidden': top <= 0,
})}
style={{ left, top, width }}

Wyświetl plik

@ -43,9 +43,9 @@ const TagInput: React.FC<ITagInput> = ({ tags, onChange, placeholder }) => {
};
return (
<div className='mt-1 relative shadow-sm flex-grow'>
<div className='relative mt-1 grow shadow-sm'>
<HStack
className='p-2 pb-0 text-gray-900 dark:text-gray-100 placeholder:text-gray-600 dark:placeholder:text-gray-600 block w-full sm:text-sm dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500 rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800'
className='block w-full rounded-md border-gray-400 bg-white p-2 pb-0 text-gray-900 placeholder:text-gray-600 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus:border-primary-500 dark:focus:ring-primary-500 sm:text-sm'
space={2}
wrap
>
@ -56,7 +56,7 @@ const TagInput: React.FC<ITagInput> = ({ tags, onChange, placeholder }) => {
))}
<input
className='p-1 mb-2 w-32 h-8 flex-grow bg-transparent outline-none'
className='mb-2 h-8 w-32 grow bg-transparent p-1 outline-none'
value={input}
placeholder={placeholder}
onChange={e => setInput(e.target.value)}

Wyświetl plik

@ -13,7 +13,7 @@ interface ITag {
/** A single editable Tag (used by TagInput). */
const Tag: React.FC<ITag> = ({ tag, onDelete }) => {
return (
<div className='inline-flex p-1 rounded bg-primary-500 items-center whitespace-nowrap'>
<div className='inline-flex items-center whitespace-nowrap rounded bg-primary-500 p-1'>
<Text theme='white'>{tag}</Text>
<IconButton

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
const themes = {
@ -115,7 +115,7 @@ const Text = React.forwardRef<any, IText>(
textDecoration: tag === 'abbr' ? 'underline dotted' : undefined,
direction,
}}
className={classNames({
className={clsx({
'cursor-default': tag === 'abbr',
truncate: truncate,
[sizes[size]]: true,

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'onKeyDown' | 'onPaste' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
@ -26,6 +26,8 @@ interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElemen
hasError?: boolean,
/** Whether or not you can resize the teztarea */
isResizeable?: boolean,
/** Textarea theme. */
theme?: 'default' | 'transparent',
}
/** Textarea with custom styles. */
@ -37,6 +39,7 @@ const Textarea = React.forwardRef(({
autoGrow = false,
maxRows = 10,
minRows = 1,
theme = 'default',
...props
}: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
const [rows, setRows] = useState<number>(autoGrow ? 1 : 4);
@ -72,9 +75,10 @@ const Textarea = React.forwardRef(({
ref={ref}
rows={rows}
onChange={handleChange}
className={classNames({
'bg-white dark:bg-transparent shadow-sm block w-full sm:text-sm rounded-md text-gray-900 dark:text-gray-100 placeholder:text-gray-600 dark:placeholder:text-gray-600 border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
true,
className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 dark:text-gray-100 dark:placeholder:text-gray-600 sm:text-sm', {
'bg-white dark:bg-transparent shadow-sm border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
theme === 'default',
'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',
'font-mono': isCodeEditor,
'text-red-600 border-red-600': hasError,
'resize-none': !isResizeable,

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import toast, { Toast as RHToast } from 'react-hot-toast';
import { FormattedMessage } from 'react-intl';
@ -40,7 +40,7 @@ const Toast = (props: IToast) => {
return (
<Icon
src={require('@tabler/icons/circle-check.svg')}
className='w-6 h-6 text-success-500 dark:text-success-400'
className='h-6 w-6 text-success-500 dark:text-success-400'
aria-hidden
/>
);
@ -48,7 +48,7 @@ const Toast = (props: IToast) => {
return (
<Icon
src={require('@tabler/icons/info-circle.svg')}
className='w-6 h-6 text-primary-600 dark:text-accent-blue'
className='h-6 w-6 text-primary-600 dark:text-accent-blue'
aria-hidden
/>
);
@ -56,7 +56,7 @@ const Toast = (props: IToast) => {
return (
<Icon
src={require('@tabler/icons/alert-circle.svg')}
className='w-6 h-6 text-danger-600'
className='h-6 w-6 text-danger-600'
aria-hidden
/>
);
@ -102,7 +102,7 @@ const Toast = (props: IToast) => {
<div
data-testid='toast'
className={
classNames({
clsx({
'p-4 pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white dark:bg-gray-900 shadow-lg dark:ring-2 dark:ring-gray-800': true,
'animate-enter': t.visible,
'animate-leave': !t.visible,
@ -112,7 +112,7 @@ const Toast = (props: IToast) => {
<HStack space={4} alignItems='start'>
<HStack space={3} justifyContent='between' alignItems='start' className='w-0 flex-1'>
<HStack space={3} alignItems='start' className='w-0 flex-1'>
<div className='flex-shrink-0'>
<div className='shrink-0'>
{renderIcon()}
</div>
@ -126,15 +126,15 @@ const Toast = (props: IToast) => {
</HStack>
{/* Dismiss Button */}
<div className='flex flex-shrink-0 pt-0.5'>
<div className='flex shrink-0 pt-0.5'>
<button
type='button'
className='inline-flex rounded-md text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2'
className='inline-flex rounded-md text-gray-600 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:text-gray-600 dark:hover:text-gray-500'
onClick={dismissToast}
data-testid='toast-dismiss'
>
<span className='sr-only'>Close</span>
<Icon src={require('@tabler/icons/x.svg')} className='w-5 h-5' />
<Icon src={require('@tabler/icons/x.svg')} className='h-5 w-5' />
</button>
</div>
</HStack>

Wyświetl plik

@ -49,7 +49,7 @@ const Widget: React.FC<IWidget> = ({
<WidgetTitle title={title} />
{action || (onActionClick && (
<IconButton
className='w-6 h-6 ml-2 text-black dark:text-white rtl:rotate-180'
className='ml-2 h-6 w-6 text-black rtl:rotate-180 dark:text-white'
src={actionIcon}
onClick={onActionClick}
title={actionTitle}

Wyświetl plik

@ -1,13 +1,11 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { spring } from 'react-motion';
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
import Motion from 'soapbox/features/ui/util/optional-motion';
import { HStack, Icon, ProgressBar, Stack, Text } from 'soapbox/components/ui';
interface IUploadProgress {
/** Number between 0 and 1 to represent the percentage complete. */
progress: number,
/** Number between 0 and 100 to represent the percentage complete. */
progress: number
}
/** Displays a progress bar for uploading files. */
@ -16,7 +14,7 @@ const UploadProgress: React.FC<IUploadProgress> = ({ progress }) => {
<HStack alignItems='center' space={2}>
<Icon
src={require('@tabler/icons/cloud-upload.svg')}
className='w-7 h-7 text-gray-500'
className='h-7 w-7 text-gray-500'
/>
<Stack space={1}>
@ -24,16 +22,7 @@ const UploadProgress: React.FC<IUploadProgress> = ({ progress }) => {
<FormattedMessage id='upload_progress.label' defaultMessage='Uploading…' />
</Text>
<div className='w-full h-1.5 rounded-lg bg-gray-200 relative'>
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
{({ width }) =>
(<div
className='absolute left-0 top-0 h-1.5 bg-primary-600 rounded-lg'
style={{ width: `${width}%` }}
/>)
}
</Motion>
</div>
<ProgressBar progress={progress / 100} size='sm' />
</Stack>
</HStack>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import React, { useState } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
@ -141,7 +141,7 @@ const Upload: React.FC<IUpload> = ({
const uploadIcon = mediaType === 'unknown' && (
<Icon
className='h-16 w-16 mx-auto my-12 text-gray-800 dark:text-gray-200'
className='mx-auto my-12 h-16 w-16 text-gray-800 dark:text-gray-200'
src={MIMETYPE_ICONS[mimeType || ''] || defaultIcon}
/>
);
@ -152,13 +152,13 @@ const Upload: React.FC<IUpload> = ({
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) => (
<div
className={classNames('compose-form__upload-thumbnail', mediaType)}
className={clsx('compose-form__upload-thumbnail', mediaType)}
style={{
transform: `scale(${scale})`,
backgroundImage: mediaType === 'image' ? `url(${media.preview_url})` : undefined,
backgroundPosition: typeof x === 'number' && typeof y === 'number' ? `${x}% ${y}%` : undefined }}
>
<div className={classNames('compose-form__upload__actions', { active })}>
<div className={clsx('compose-form__upload__actions', { active })}>
{onDelete && (
<IconButton
onClick={handleUndoClick}
@ -178,7 +178,7 @@ const Upload: React.FC<IUpload> = ({
</div>
{onDescriptionChange && (
<div className={classNames('compose-form__upload-description', { active })}>
<div className={clsx('compose-form__upload-description', { active })}>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { HStack, Icon, Text } from 'soapbox/components/ui';
@ -13,7 +13,7 @@ const ValidationCheckmark = ({ isValid, text }: IValidationCheckmark) => {
<HStack alignItems='center' space={2} data-testid='validation-checkmark'>
<Icon
src={isValid ? require('@tabler/icons/check.svg') : require('@tabler/icons/point.svg')}
className={classNames({
className={clsx({
'w-4 h-4': true,
'text-gray-400 dark:text-gray-600 dark:fill-gray-600 fill-gray-400': !isValid,
'text-success-500': isValid,

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { useIntl, defineMessages } from 'react-intl';
@ -25,7 +25,7 @@ const VerificationBadge: React.FC<IVerificationBadge> = ({ className }) => {
return (
<span className='verified-icon' data-testid='verified-badge'>
<Element className={classNames('w-4 text-accent-500', className)} src={icon} alt={intl.formatMessage(messages.verified)} />
<Element className={clsx('w-4 text-accent-500', className)} src={icon} alt={intl.formatMessage(messages.verified)} />
</span>
);
};

Wyświetl plik

@ -16,7 +16,7 @@ const mapStateToProps = (state: RootState) => ({
openedViaKeyboard: state.dropdown_menu.keyboard,
});
const mapDispatchToProps = (dispatch: Dispatch, { status, items }: Partial<IDropdown>) => ({
const mapDispatchToProps = (dispatch: Dispatch, { status, items, ...filteredProps }: Partial<IDropdown>) => ({
onOpen(
id: number,
onItemClick: React.EventHandler<React.MouseEvent | React.KeyboardEvent>,
@ -28,10 +28,18 @@ const mapDispatchToProps = (dispatch: Dispatch, { status, items }: Partial<IDrop
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
if (filteredProps.onOpen) {
filteredProps.onOpen(id, onItemClick, dropdownPlacement, keyboard);
}
},
onClose(id: number) {
dispatch(closeModal('ACTIONS'));
dispatch(closeDropdownMenu(id));
if (filteredProps.onClose) {
filteredProps.onClose(id);
}
},
});

Wyświetl plik

@ -1,7 +1,7 @@
'use strict';
import { QueryClientProvider } from '@tanstack/react-query';
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState, useEffect } from 'react';
import { Toaster } from 'react-hot-toast';
import { IntlProvider } from 'react-intl';
@ -271,7 +271,7 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
const darkMode = useTheme() === 'dark';
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
const bodyClass = classNames('bg-white dark:bg-gray-800 text-base h-full', {
const bodyClass = clsx('h-full bg-white text-base dark:bg-gray-800', {
'no-reduce-motion': !settings.get('reduceMotion'),
'underline-links': settings.get('underlineLinks'),
'demetricator': settings.get('demetricator'),
@ -280,7 +280,7 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
return (
<>
<Helmet>
<html lang={locale} className={classNames('h-full', { dark: darkMode })} />
<html lang={locale} className={clsx('h-full', { dark: darkMode })} />
<body className={bodyClass} dir={direction} />
{themeCss && <style id='theme' type='text/css'>{`:root{${themeCss}}`}</style>}
{darkMode && <style type='text/css'>{':root { color-scheme: dark; }'}</style>}

Some files were not shown because too many files have changed in this diff Show More