Merge branch 'edit-profile-checkboxes' into 'develop'

EditProfile: use Toggle instead of Checkbox, other Toggle refactors

See merge request soapbox-pub/soapbox-fe!1323
This commit is contained in:
Alex Gleason 2022-05-06 20:16:16 +00:00
commit 45a7dc5bcf
8 changed files with 99 additions and 72 deletions

View File

@ -30,5 +30,6 @@ export { default as Stack } from './stack/stack';
export { default as Tabs } from './tabs/tabs'; export { default as Tabs } from './tabs/tabs';
export { default as Text } from './text/text'; export { default as Text } from './text/text';
export { default as Textarea } from './textarea/textarea'; export { default as Textarea } from './textarea/textarea';
export { default as Toggle } from './toggle/toggle';
export { default as Tooltip } from './tooltip/tooltip'; export { default as Tooltip } from './tooltip/tooltip';
export { default as Widget } from './widget/widget'; export { default as Widget } from './widget/widget';

View File

@ -0,0 +1,14 @@
import React from 'react';
import ReactToggle, { ToggleProps } from 'react-toggle';
/** A glorified checkbox. Wrapper around react-toggle. */
const Toggle: React.FC<ToggleProps> = ({ icons = false, ...rest }) => {
return (
<ReactToggle
icons={icons}
{...rest}
/>
);
};
export default Toggle;

View File

@ -4,16 +4,12 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { updateNotificationSettings } from 'soapbox/actions/accounts'; import { updateNotificationSettings } from 'soapbox/actions/accounts';
import { patchMe } from 'soapbox/actions/me'; import { patchMe } from 'soapbox/actions/me';
import snackbar from 'soapbox/actions/snackbar'; import snackbar from 'soapbox/actions/snackbar';
import { import List, { ListItem } from 'soapbox/components/list';
Checkbox,
} from 'soapbox/features/forms';
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
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 } from '../../components/ui'; import { Button, Column, Form, FormActions, FormGroup, Input, Textarea, HStack, Toggle } from '../../components/ui';
import HStack from '../../components/ui/hstack/hstack';
import Stack from '../../components/ui/stack/stack';
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';
@ -394,63 +390,79 @@ const EditProfile: React.FC = () => {
</div> </div>
</div> </div>
{/* HACK: wrap these checkboxes in a .simple_form container so they get styled (for now) */} <List>
{/* Need a either move, replace, or refactor these checkboxes. */}
<Stack space={2} className='simple_form'>
{features.followRequests && ( {features.followRequests && (
<Checkbox <ListItem
label={<FormattedMessage id='edit_profile.fields.locked_label' defaultMessage='Lock account' />} label={<FormattedMessage id='edit_profile.fields.locked_label' defaultMessage='Lock account' />}
hint={<FormattedMessage id='edit_profile.hints.locked' defaultMessage='Requires you to manually approve followers' />} hint={<FormattedMessage id='edit_profile.hints.locked' defaultMessage='Requires you to manually approve followers' />}
>
<Toggle
checked={data.locked} checked={data.locked}
onChange={handleCheckboxChange('locked')} onChange={handleCheckboxChange('locked')}
/> />
</ListItem>
)} )}
{features.hideNetwork && ( {features.hideNetwork && (
<Checkbox <ListItem
label={<FormattedMessage id='edit_profile.fields.hide_network_label' defaultMessage='Hide network' />} label={<FormattedMessage id='edit_profile.fields.hide_network_label' defaultMessage='Hide network' />}
hint={<FormattedMessage id='edit_profile.hints.hide_network' defaultMessage='Who you follow and who follows you will not be shown on your profile' />} hint={<FormattedMessage id='edit_profile.hints.hide_network' defaultMessage='Who you follow and who follows you will not be shown on your profile' />}
>
<Toggle
checked={account ? hidesNetwork(account): false} checked={account ? hidesNetwork(account): false}
onChange={handleHideNetworkChange} onChange={handleHideNetworkChange}
/> />
</ListItem>
)} )}
{features.bots && ( {features.bots && (
<Checkbox <ListItem
label={<FormattedMessage id='edit_profile.fields.bot_label' defaultMessage='This is a bot account' />} label={<FormattedMessage id='edit_profile.fields.bot_label' defaultMessage='This is a bot account' />}
hint={<FormattedMessage id='edit_profile.hints.bot' defaultMessage='This account mainly performs automated actions and might not be monitored' />} hint={<FormattedMessage id='edit_profile.hints.bot' defaultMessage='This account mainly performs automated actions and might not be monitored' />}
>
<Toggle
checked={data.bot} checked={data.bot}
onChange={handleCheckboxChange('bot')} onChange={handleCheckboxChange('bot')}
/> />
</ListItem>
)} )}
{features.muteStrangers && ( {features.muteStrangers && (
<Checkbox <ListItem
label={<FormattedMessage id='edit_profile.fields.stranger_notifications_label' defaultMessage='Block notifications from strangers' />} label={<FormattedMessage id='edit_profile.fields.stranger_notifications_label' defaultMessage='Block notifications from strangers' />}
hint={<FormattedMessage id='edit_profile.hints.stranger_notifications' defaultMessage='Only show notifications from people you follow' />} hint={<FormattedMessage id='edit_profile.hints.stranger_notifications' defaultMessage='Only show notifications from people you follow' />}
>
<Toggle
checked={muteStrangers} checked={muteStrangers}
onChange={(e) => setMuteStrangers(e.target.checked)} onChange={(e) => setMuteStrangers(e.target.checked)}
/> />
</ListItem>
)} )}
{features.profileDirectory && ( {features.profileDirectory && (
<Checkbox <ListItem
label={<FormattedMessage id='edit_profile.fields.discoverable_label' defaultMessage='Allow account discovery' />} label={<FormattedMessage id='edit_profile.fields.discoverable_label' defaultMessage='Allow account discovery' />}
hint={<FormattedMessage id='edit_profile.hints.discoverable' defaultMessage='Display account in profile directory and allow indexing by external services' />} hint={<FormattedMessage id='edit_profile.hints.discoverable' defaultMessage='Display account in profile directory and allow indexing by external services' />}
>
<Toggle
checked={data.discoverable} checked={data.discoverable}
onChange={handleCheckboxChange('discoverable')} onChange={handleCheckboxChange('discoverable')}
/> />
</ListItem>
)} )}
{features.emailList && ( {features.emailList && (
<Checkbox <ListItem
label={<FormattedMessage id='edit_profile.fields.accepts_email_list_label' defaultMessage='Subscribe to newsletter' />} label={<FormattedMessage id='edit_profile.fields.accepts_email_list_label' defaultMessage='Subscribe to newsletter' />}
hint={<FormattedMessage id='edit_profile.hints.accepts_email_list' defaultMessage='Opt-in to news and marketing updates.' />} hint={<FormattedMessage id='edit_profile.hints.accepts_email_list' defaultMessage='Opt-in to news and marketing updates.' />}
>
<Toggle
checked={data.accepts_email_list} checked={data.accepts_email_list}
onChange={handleCheckboxChange('accepts_email_list')} onChange={handleCheckboxChange('accepts_email_list')}
/> />
</ListItem>
)} )}
</Stack> </List>
{features.profileFields && ( {features.profileFields && (
<Streamfield <Streamfield

View File

@ -1,35 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Toggle from 'react-toggle';
export default class SettingToggle extends ImmutablePureComponent {
static propTypes = {
id: PropTypes.string,
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired,
settingPath: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
}
onChange = ({ target }) => {
this.props.onChange(this.props.settingPath, target.checked);
}
render() {
const { id, settings, settingPath } = this.props;
return (
<Toggle
id={id}
checked={settings.getIn(settingPath)}
onChange={this.onChange}
icons={false}
onKeyDown={this.onKeyDown}
/>
);
}
}

View File

@ -0,0 +1,35 @@
import React from 'react';
import { Toggle } from 'soapbox/components/ui';
import type { Map as ImmutableMap } from 'immutable';
interface ISettingToggle {
/** Unique identifier for the Toggle. */
id?: string,
/** The full user settings map. */
settings: ImmutableMap<string, any>,
/** Array of key names leading into the setting map. */
settingPath: string[],
/** Callback when the setting is toggled. */
onChange: (settingPath: string[], checked: boolean) => void,
}
/** Stateful toggle to change user settings. */
const SettingToggle: React.FC<ISettingToggle> = ({ id, settings, settingPath, onChange }) => {
const handleChange: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
onChange(settingPath, target.checked);
};
return (
<Toggle
id={id}
checked={!!settings.getIn(settingPath)}
onChange={handleChange}
/>
);
};
export default SettingToggle;

View File

@ -101,7 +101,7 @@ const Preferences = () => {
// dispatch(changeSetting(['defaultContentType'], event.target.value)); // dispatch(changeSetting(['defaultContentType'], event.target.value));
// }; // };
const onToggleChange = (key: string, checked: boolean) => { const onToggleChange = (key: string[], checked: boolean) => {
dispatch(changeSetting(key, checked, intl)); dispatch(changeSetting(key, checked, intl));
}; };

View File

@ -1,7 +1,6 @@
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import { updateConfig } from 'soapbox/actions/admin'; import { updateConfig } from 'soapbox/actions/admin';
import { uploadMedia } from 'soapbox/actions/media'; import { uploadMedia } from 'soapbox/actions/media';
@ -17,6 +16,7 @@ import {
Input, Input,
Textarea, Textarea,
Button, Button,
Toggle,
} from 'soapbox/components/ui'; } from 'soapbox/components/ui';
import Streamfield from 'soapbox/components/ui/streamfield/streamfield'; import Streamfield from 'soapbox/components/ui/streamfield/streamfield';
import ThemeSelector from 'soapbox/features/ui/components/theme-selector'; import ThemeSelector from 'soapbox/features/ui/components/theme-selector';

View File

@ -28,7 +28,7 @@
} }
.react-toggle-track { .react-toggle-track {
@apply bg-gray-200 w-[50px] p-0 rounded-full transition-colors; @apply bg-gray-300 dark:bg-slate-700 w-[50px] p-0 rounded-full transition-colors;
height: var(--input-height); height: var(--input-height);
} }