sforkowany z mirror/soapbox
SoapboxConfig: break extraneous components out into their own files
rodzic
37b0b972e0
commit
1bd81a2f0f
|
@ -0,0 +1,49 @@
|
|||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { SketchPicker, ColorChangeHandler } from 'react-color';
|
||||
|
||||
import { isMobile } from 'soapbox/is_mobile';
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
interface IColorPicker {
|
||||
style?: React.CSSProperties,
|
||||
value: string,
|
||||
onChange: ColorChangeHandler,
|
||||
onClose: () => void,
|
||||
}
|
||||
|
||||
const ColorPicker: React.FC<IColorPicker> = ({ style, value, onClose, onChange }) => {
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDocumentClick = (e: MouseEvent | TouchEvent) => {
|
||||
if (node.current && !node.current.contains(e.target as HTMLElement)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleDocumentClick, false);
|
||||
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick, false);
|
||||
document.removeEventListener('touchend', handleDocumentClick);
|
||||
};
|
||||
});
|
||||
|
||||
const pickerStyle: React.CSSProperties = {
|
||||
...style,
|
||||
marginLeft: isMobile(window.innerWidth) ? '20px' : '12px',
|
||||
position: 'absolute',
|
||||
zIndex: 1000,
|
||||
};
|
||||
|
||||
return (
|
||||
<div id='SketchPickerContainer' ref={node} style={pickerStyle}>
|
||||
<SketchPicker color={value} disableAlpha onChange={onChange} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
// @ts-ignore: TODO: upgrade react-overlays. v3.1 and above have TS definitions
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
|
||||
import { isMobile } from 'soapbox/is_mobile';
|
||||
|
||||
import ColorPicker from './color-picker';
|
||||
|
||||
import type { ColorChangeHandler } from 'react-color';
|
||||
|
||||
interface IColorWithPicker {
|
||||
buttonId: string,
|
||||
label: React.ReactNode,
|
||||
value: string,
|
||||
onChange: ColorChangeHandler,
|
||||
}
|
||||
|
||||
const ColorWithPicker: React.FC<IColorWithPicker> = ({ buttonId, label, value, onChange }) => {
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
const [active, setActive] = useState(false);
|
||||
const [placement, setPlacement] = useState<string | null>(null);
|
||||
|
||||
const hidePicker = () => {
|
||||
setActive(false);
|
||||
};
|
||||
|
||||
const showPicker = () => {
|
||||
setActive(true);
|
||||
setPlacement(isMobile(window.innerWidth) ? 'bottom' : 'right');
|
||||
};
|
||||
|
||||
const onToggle: React.MouseEventHandler = () => {
|
||||
if (active) {
|
||||
hidePicker();
|
||||
} else {
|
||||
showPicker();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='label_input__color'>
|
||||
<label>{label}</label>
|
||||
|
||||
<div
|
||||
ref={node}
|
||||
id={buttonId}
|
||||
className='color-swatch'
|
||||
role='presentation'
|
||||
style={{ background: value }}
|
||||
title={value}
|
||||
onClick={onToggle}
|
||||
/>
|
||||
|
||||
<Overlay show={active} placement={placement} target={node.current}>
|
||||
<ColorPicker value={value} onChange={onChange} onClose={hidePicker} />
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorWithPicker;
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
|
||||
import IconPickerDropdown from './icon_picker_dropdown';
|
||||
|
||||
interface IIconPicker {
|
||||
label: React.ReactNode,
|
||||
value: string,
|
||||
onChange: React.ChangeEventHandler,
|
||||
}
|
||||
|
||||
const IconPicker: React.FC<IIconPicker> = ({ onChange, value, label }) => {
|
||||
return (
|
||||
<div className='input with_label font_icon_picker'>
|
||||
<div className='label_input__font_icon_picker'>
|
||||
{label && (<label>{label}</label>)}
|
||||
<div className='label_input_wrapper'>
|
||||
<IconPickerDropdown value={value} onPickEmoji={onChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconPicker;
|
|
@ -1,12 +1,9 @@
|
|||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { updateConfig } from 'soapbox/actions/admin';
|
||||
|
@ -24,12 +21,12 @@ import {
|
|||
Checkbox,
|
||||
} from 'soapbox/features/forms';
|
||||
import ThemeToggle from 'soapbox/features/ui/components/theme-toggle';
|
||||
import { isMobile } from 'soapbox/is_mobile';
|
||||
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||
|
||||
import Accordion from '../ui/components/accordion';
|
||||
|
||||
import IconPickerDropdown from './components/icon_picker_dropdown';
|
||||
import ColorWithPicker from './components/color-with-picker';
|
||||
import IconPicker from './components/icon-picker';
|
||||
import SitePreview from './components/site_preview';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -60,8 +57,6 @@ const messages = defineMessages({
|
|||
singleUserModeProfileHint: { id: 'soapbox_config.single_user_mode_profile_hint', defaultMessage: '@handle' },
|
||||
});
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
const templates = {
|
||||
promoPanelItem: ImmutableMap({ icon: '', text: '', url: '' }),
|
||||
footerItem: ImmutableMap({ title: '', url: '' }),
|
||||
|
@ -461,121 +456,3 @@ class SoapboxConfig extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
class ColorPicker extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
style: PropTypes.object,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func,
|
||||
}
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, false);
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { style, value, onChange } = this.props;
|
||||
const margin_left_picker = isMobile(window.innerWidth) ? '20px' : '12px';
|
||||
|
||||
return (
|
||||
<div id='SketchPickerContainer' ref={this.setRef} style={{ ...style, marginLeft: margin_left_picker, position: 'absolute', zIndex: 1000 }}>
|
||||
<SketchPicker color={value} disableAlpha onChange={onChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ColorWithPicker extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
buttonId: PropTypes.string.isRequired,
|
||||
label: PropTypes.node,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
onToggle = (e) => {
|
||||
if (!e.key || e.key === 'Enter') {
|
||||
if (this.state.active) {
|
||||
this.onHidePicker();
|
||||
} else {
|
||||
this.onShowPicker(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state = {
|
||||
active: false,
|
||||
placement: null,
|
||||
}
|
||||
|
||||
onHidePicker = () => {
|
||||
this.setState({ active: false });
|
||||
}
|
||||
|
||||
onShowPicker = ({ target }) => {
|
||||
this.setState({ active: true });
|
||||
this.setState({ placement: isMobile(window.innerWidth) ? 'bottom' : 'right' });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { buttonId, label, value, onChange } = this.props;
|
||||
const { active, placement } = this.state;
|
||||
|
||||
return (
|
||||
<div className='label_input__color'>
|
||||
<label>{label}</label>
|
||||
<div id={buttonId} className='color-swatch' role='presentation' style={{ background: value }} title={value} value={value} onClick={this.onToggle} />
|
||||
<Overlay show={active} placement={placement} target={this}>
|
||||
<ColorPicker value={value} onChange={onChange} onClose={this.onHidePicker} />
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class IconPicker extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
icons: PropTypes.object,
|
||||
label: PropTypes.node,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onChange, value, label } = this.props;
|
||||
|
||||
return (
|
||||
<div className='input with_label font_icon_picker'>
|
||||
<div className='label_input__font_icon_picker'>
|
||||
{label && (<label>{label}</label>)}
|
||||
<div className='label_input_wrapper'>
|
||||
<IconPickerDropdown value={value} onPickEmoji={onChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
"@types/object-assign": "^4.0.30",
|
||||
"@types/object-fit-images": "^3.2.3",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react-color": "^3.0.6",
|
||||
"@types/react-datepicker": "^4.4.0",
|
||||
"@types/react-helmet": "^6.1.5",
|
||||
"@types/react-motion": "^0.0.32",
|
||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -2184,6 +2184,14 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-color@^3.0.6":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.6.tgz#602fed023802b2424e7cd6ff3594ccd3d5055f9a"
|
||||
integrity sha512-OzPIO5AyRmLA7PlOyISlgabpYUa3En74LP8mTMa0veCA719SvYQov4WLMsHvCgXP+L+KI9yGhYnqZafVGG0P4w==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/reactcss" "*"
|
||||
|
||||
"@types/react-datepicker@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.4.0.tgz#0072e18536ad305fd57786f9b6f9e499eed2b475"
|
||||
|
@ -2272,6 +2280,13 @@
|
|||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/reactcss@*":
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.6.tgz#133c1e7e896f2726370d1d5a26bf06a30a038bcc"
|
||||
integrity sha512-qaIzpCuXNWomGR1Xq8SCFTtF4v8V27Y6f+b9+bzHiv087MylI/nTCqqdChNeWS7tslgROmYB7yeiruWX7WnqNg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/redux-mock-store@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.3.tgz#895de4a364bc4836661570aec82f2eef5989d1fb"
|
||||
|
|
Ładowanie…
Reference in New Issue