Merge remote-tracking branch 'origin/develop' into edit-group-page
This commit is contained in:
commit
9c78a37844
4 changed files with 43 additions and 15 deletions
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Reactions: Support custom emoji reactions
|
- Reactions: Support custom emoji reactions
|
||||||
- Compatbility: Support Mastodon v2 timeline filters.
|
- Compatbility: Support Mastodon v2 timeline filters.
|
||||||
- Posts: Support dislikes on Friendica.
|
- Posts: Support dislikes on Friendica.
|
||||||
|
- UI: added a character counter to some textareas.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Posts: truncate Nostr pubkeys in reply mentions.
|
- Posts: truncate Nostr pubkeys in reply mentions.
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import Stack from '../stack/stack';
|
||||||
|
import Text from '../text/text';
|
||||||
|
|
||||||
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'onKeyDown' | 'onPaste' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
|
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'onKeyDown' | 'onPaste' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
|
||||||
/** Put the cursor into the input on mount. */
|
/** Put the cursor into the input on mount. */
|
||||||
|
@ -28,6 +32,8 @@ interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElemen
|
||||||
isResizeable?: boolean
|
isResizeable?: boolean
|
||||||
/** Textarea theme. */
|
/** Textarea theme. */
|
||||||
theme?: 'default' | 'transparent'
|
theme?: 'default' | 'transparent'
|
||||||
|
/** Whether to display a character counter below the textarea. */
|
||||||
|
withCounter?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Textarea with custom styles. */
|
/** Textarea with custom styles. */
|
||||||
|
@ -40,8 +46,11 @@ const Textarea = React.forwardRef(({
|
||||||
maxRows = 10,
|
maxRows = 10,
|
||||||
minRows = 1,
|
minRows = 1,
|
||||||
theme = 'default',
|
theme = 'default',
|
||||||
|
maxLength,
|
||||||
|
value,
|
||||||
...props
|
...props
|
||||||
}: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
|
}: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
|
||||||
|
const length = value?.length || 0;
|
||||||
const [rows, setRows] = useState<number>(autoGrow ? 1 : 4);
|
const [rows, setRows] = useState<number>(autoGrow ? 1 : 4);
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
@ -70,20 +79,35 @@ const Textarea = React.forwardRef(({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<textarea
|
<Stack space={1.5}>
|
||||||
{...props}
|
<textarea
|
||||||
ref={ref}
|
{...props}
|
||||||
rows={rows}
|
value={value}
|
||||||
onChange={handleChange}
|
ref={ref}
|
||||||
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', {
|
rows={rows}
|
||||||
'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':
|
onChange={handleChange}
|
||||||
theme === 'default',
|
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-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',
|
'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':
|
||||||
'font-mono': isCodeEditor,
|
theme === 'default',
|
||||||
'text-red-600 border-red-600': hasError,
|
'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',
|
||||||
'resize-none': !isResizeable,
|
'font-mono': isCodeEditor,
|
||||||
})}
|
'text-red-600 border-red-600': hasError,
|
||||||
/>
|
'resize-none': !isResizeable,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{maxLength && (
|
||||||
|
<div className='text-right rtl:text-left'>
|
||||||
|
<Text size='xs' theme={maxLength - length < 0 ? 'danger' : 'muted'}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='textarea.counter.label'
|
||||||
|
defaultMessage='{count} characters remaining'
|
||||||
|
values={{ count: maxLength - length }}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,6 +28,7 @@ const messages = defineMessages({
|
||||||
newsletter: { id: 'registration.newsletter', defaultMessage: 'Subscribe to newsletter.' },
|
newsletter: { id: 'registration.newsletter', defaultMessage: 'Subscribe to newsletter.' },
|
||||||
needsConfirmationHeader: { id: 'confirmations.register.needs_confirmation.header', defaultMessage: 'Confirmation needed' },
|
needsConfirmationHeader: { id: 'confirmations.register.needs_confirmation.header', defaultMessage: 'Confirmation needed' },
|
||||||
needsApprovalHeader: { id: 'confirmations.register.needs_approval.header', defaultMessage: 'Approval needed' },
|
needsApprovalHeader: { id: 'confirmations.register.needs_approval.header', defaultMessage: 'Approval needed' },
|
||||||
|
reasonHint: { id: 'registration.reason_hint', defaultMessage: 'This will help us review your application' },
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IRegistrationForm {
|
interface IRegistrationForm {
|
||||||
|
@ -296,13 +297,14 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
|
||||||
{needsApproval && (
|
{needsApproval && (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
labelText={<FormattedMessage id='registration.reason' defaultMessage='Why do you want to join?' />}
|
labelText={<FormattedMessage id='registration.reason' defaultMessage='Why do you want to join?' />}
|
||||||
hintText={<FormattedMessage id='registration.reason_hint' defaultMessage='This will help us review your application' />}
|
|
||||||
>
|
>
|
||||||
<Textarea
|
<Textarea
|
||||||
name='reason'
|
name='reason'
|
||||||
|
placeholder={intl.formatMessage(messages.reasonHint)}
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
value={params.get('reason', '')}
|
value={params.get('reason', '')}
|
||||||
|
autoGrow
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
|
@ -1481,6 +1481,7 @@
|
||||||
"tabs_bar.profile": "Profile",
|
"tabs_bar.profile": "Profile",
|
||||||
"tabs_bar.search": "Search",
|
"tabs_bar.search": "Search",
|
||||||
"tabs_bar.settings": "Settings",
|
"tabs_bar.settings": "Settings",
|
||||||
|
"textarea.counter.label": "{count} characters remaining",
|
||||||
"theme_editor.Reset": "Reset",
|
"theme_editor.Reset": "Reset",
|
||||||
"theme_editor.export": "Export theme",
|
"theme_editor.export": "Export theme",
|
||||||
"theme_editor.import": "Import theme",
|
"theme_editor.import": "Import theme",
|
||||||
|
|
Loading…
Reference in a new issue