Merge branch 'ui-improvements' into 'develop'
Ui improvements See merge request soapbox-pub/soapbox-fe!1394
This commit is contained in:
commit
008b51eef1
5 changed files with 30 additions and 13 deletions
|
@ -14,7 +14,7 @@ import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||||
let word;
|
let word;
|
||||||
|
|
||||||
const left = str.slice(0, caretPosition).search(/\S+$/);
|
const left = str.slice(0, caretPosition).search(/\S+$/);
|
||||||
const right = str.slice(caretPosition).search(/\s/);
|
const right = str.slice(caretPosition).search(/\s/);
|
||||||
|
|
||||||
if (right < 0) {
|
if (right < 0) {
|
||||||
|
@ -69,7 +69,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
onChange = (e) => {
|
onChange = (e) => {
|
||||||
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
|
const [tokenStart, token] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
|
||||||
|
|
||||||
if (token !== null && this.state.lastToken !== token) {
|
if (token !== null && this.state.lastToken !== token) {
|
||||||
this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
|
this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
|
||||||
|
@ -123,7 +123,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||||
break;
|
break;
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
// Select suggestion
|
// Select suggestion
|
||||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -200,13 +200,13 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (typeof suggestion === 'object') {
|
if (typeof suggestion === 'object') {
|
||||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||||
key = suggestion.id;
|
key = suggestion.id;
|
||||||
} else if (suggestion[0] === '#') {
|
} else if (suggestion[0] === '#') {
|
||||||
inner = suggestion;
|
inner = suggestion;
|
||||||
key = suggestion;
|
key = suggestion;
|
||||||
} else {
|
} else {
|
||||||
inner = <AutosuggestAccount id={suggestion} />;
|
inner = <AutosuggestAccount id={suggestion} />;
|
||||||
key = suggestion;
|
key = suggestion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -257,7 +257,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
ref={this.setTextarea}
|
ref={this.setTextarea}
|
||||||
className={classNames('transition-[min-height] motion-reduce:transition-none dark:bg-slate-800 px-0 border-0 text-gray-800 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500 resize-none w-full focus:shadow-none focus:border-0 focus:ring-0', {
|
className={classNames('transition-[min-height] motion-reduce:transition-none dark:bg-slate-800 px-0 mt-1 border-0 text-gray-800 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500 resize-none w-full focus:shadow-none focus:border-0 focus:ring-0', {
|
||||||
'min-h-[40px]': condensed,
|
'min-h-[40px]': condensed,
|
||||||
'min-h-[100px]': !condensed,
|
'min-h-[100px]': !condensed,
|
||||||
})}
|
})}
|
||||||
|
|
16
app/soapbox/components/ui/file-input/file-input.tsx
Normal file
16
app/soapbox/components/ui/file-input/file-input.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
|
interface IFileInput extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'required' | 'disabled' | 'name'> { }
|
||||||
|
|
||||||
|
const FileInput = forwardRef<HTMLInputElement, IFileInput>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
type='file'
|
||||||
|
className='block w-full text-sm text-gray-800 dark:text-slate-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-slate-700 dark:file:bg-slate-800 dark:file:text-slate-200'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FileInput;
|
|
@ -6,6 +6,7 @@ export { default as Column } from './column/column';
|
||||||
export { default as Counter } from './counter/counter';
|
export { default as Counter } from './counter/counter';
|
||||||
export { default as Emoji } from './emoji/emoji';
|
export { default as Emoji } from './emoji/emoji';
|
||||||
export { default as EmojiSelector } from './emoji-selector/emoji-selector';
|
export { default as EmojiSelector } from './emoji-selector/emoji-selector';
|
||||||
|
export { default as FileInput } from './file-input/file-input';
|
||||||
export { default as Form } from './form/form';
|
export { default as Form } from './form/form';
|
||||||
export { default as FormActions } from './form-actions/form-actions';
|
export { default as FormActions } from './form-actions/form-actions';
|
||||||
export { default as FormGroup } from './form-group/form-group';
|
export { default as FormGroup } from './form-group/form-group';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soap
|
||||||
import { normalizeAccount } from 'soapbox/normalizers';
|
import { normalizeAccount } from 'soapbox/normalizers';
|
||||||
import resizeImage from 'soapbox/utils/resize_image';
|
import resizeImage from 'soapbox/utils/resize_image';
|
||||||
|
|
||||||
import { Button, Column, Form, FormActions, FormGroup, Input, Textarea, HStack, Toggle } from '../../components/ui';
|
import { Button, Column, Form, FormActions, FormGroup, Input, Textarea, HStack, Toggle, FileInput } from '../../components/ui';
|
||||||
import Streamfield, { StreamfieldComponent } from '../../components/ui/streamfield/streamfield';
|
import Streamfield, { StreamfieldComponent } from '../../components/ui/streamfield/streamfield';
|
||||||
|
|
||||||
import ProfilePreview from './components/profile-preview';
|
import ProfilePreview from './components/profile-preview';
|
||||||
|
@ -179,8 +179,8 @@ const EditProfile: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const maxFields = useAppSelector(state => state.instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number);
|
const maxFields = useAppSelector(state => state.instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number);
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
@ -378,14 +378,14 @@ const EditProfile: React.FC = () => {
|
||||||
labelText={<FormattedMessage id='edit_profile.fields.header_label' defaultMessage='Choose Background Picture' />}
|
labelText={<FormattedMessage id='edit_profile.fields.header_label' defaultMessage='Choose Background Picture' />}
|
||||||
hintText={<FormattedMessage id='edit_profile.hints.header' defaultMessage='PNG, GIF or JPG. Will be downscaled to {size}' values={{ size: '1920x1080px' }} />}
|
hintText={<FormattedMessage id='edit_profile.hints.header' defaultMessage='PNG, GIF or JPG. Will be downscaled to {size}' values={{ size: '1920x1080px' }} />}
|
||||||
>
|
>
|
||||||
<input type='file' onChange={handleFileChange('header', 1920 * 1080)} className='text-sm' />
|
<FileInput onChange={handleFileChange('header', 1920 * 1080)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
labelText={<FormattedMessage id='edit_profile.fields.avatar_label' defaultMessage='Choose Profile Picture' />}
|
labelText={<FormattedMessage id='edit_profile.fields.avatar_label' defaultMessage='Choose Profile Picture' />}
|
||||||
hintText={<FormattedMessage id='edit_profile.hints.avatar' defaultMessage='PNG, GIF or JPG. Will be downscaled to {size}' values={{ size: '400x400px' }} />}
|
hintText={<FormattedMessage id='edit_profile.hints.avatar' defaultMessage='PNG, GIF or JPG. Will be downscaled to {size}' values={{ size: '400x400px' }} />}
|
||||||
>
|
>
|
||||||
<input type='file' onChange={handleFileChange('avatar', 400 * 400)} className='text-sm' />
|
<FileInput onChange={handleFileChange('avatar', 400 * 400)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,7 +36,7 @@ const HomePage: React.FC = ({ children }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Layout.Main className='pt-4 sm:pt-0 divide-y divide-gray-200 dark:divide-slate-700 divide-solid space-y-4 divide-none'>
|
<Layout.Main className='pt-4 sm:pt-0 dark:divide-slate-700 space-y-4'>
|
||||||
{me && (
|
{me && (
|
||||||
<Card variant='rounded' ref={composeBlock}>
|
<Card variant='rounded' ref={composeBlock}>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
|
Loading…
Reference in a new issue