From c01be5d3385ef3fa38591ece8976d30f3127fa20 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Sat, 3 Oct 2020 21:10:32 +0300 Subject: [PATCH] WIP --- package.json | 3 ++ src/BlockStyleWrapper/Edit.jsx | 75 +++++++-------------------- src/BlockStyleWrapper/View.jsx | 6 ++- src/BlockStyleWrapper/styles.less | 15 ------ src/StyleWrapper/StyleWrapperEdit.jsx | 72 +++++++++++++++++++++++++ src/StyleWrapper/StyleWrapperView.jsx | 49 +++++++++++++++++ src/StyleWrapper/index.js | 2 + src/StyleWrapper/schema.js | 40 ++++++++++++++ src/Widgets/Align.jsx | 29 +++++++++++ src/Widgets/SimpleColorPicker.jsx | 55 ++++++++++++++++++++ src/Widgets/Size.jsx | 25 +++++++++ src/Widgets/StyleSelect.jsx | 52 +++++++++++++++++++ src/index.js | 39 ++++++++++++++ src/styles.less | 41 +++++++++++++++ 14 files changed, 430 insertions(+), 73 deletions(-) delete mode 100644 src/BlockStyleWrapper/styles.less create mode 100644 src/StyleWrapper/StyleWrapperEdit.jsx create mode 100644 src/StyleWrapper/StyleWrapperView.jsx create mode 100644 src/StyleWrapper/index.js create mode 100644 src/StyleWrapper/schema.js create mode 100644 src/Widgets/Align.jsx create mode 100644 src/Widgets/SimpleColorPicker.jsx create mode 100644 src/Widgets/Size.jsx create mode 100644 src/Widgets/StyleSelect.jsx create mode 100644 src/styles.less diff --git a/package.json b/package.json index 6e3a523..b24a345 100644 --- a/package.json +++ b/package.json @@ -22,5 +22,8 @@ "scripts": { "release": "release-it", "bootstrap": "./bootstrap" + }, + "dependencies": { + "react-color": "2.18.1" } } diff --git a/src/BlockStyleWrapper/Edit.jsx b/src/BlockStyleWrapper/Edit.jsx index f0f5c5c..b09b568 100644 --- a/src/BlockStyleWrapper/Edit.jsx +++ b/src/BlockStyleWrapper/Edit.jsx @@ -1,63 +1,24 @@ import React from 'react'; -import { Portal } from 'react-portal'; -import themeSVG from '@plone/volto/icons/theme.svg'; -import { Icon } from '@plone/volto/components'; -import SidebarPopup from '../SidebarPopup/SidebarPopup'; -import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib'; - -import './styles.less'; +import { StyleWrapperEdit, StyleWrapperView } from '../StyleWrapper'; export default (props) => { - const { children, selected } = props; - const [isOpen, setIsOpen] = React.useState(false); - const nodeRef = React.useRef(); - - const closeSidebar = React.useCallback( - (e) => { - if (isOpen && !doesNodeContainClick(nodeRef.current, e)) setIsOpen(false); - }, - [isOpen], - ); - - React.useEffect(() => { - document.addEventListener('click', closeSidebar, false); - return () => { - document.removeEventListener('click', closeSidebar); - }; - }, [closeSidebar]); - + const { block, data, onChangeBlock } = props; return ( - <> - {selected ? ( - <> - .sidebar-container > .tabs-wrapper > .formtabs', - ) - } - > -
- -
-
- -

Blabdsadsa

-
- - ) : ( - '' - )} - {children} - + + onChangeBlock(block, { + ...data, + styles: { + ...data?.styles, + [id]: value, + }, + }) + } + > + + ); }; diff --git a/src/BlockStyleWrapper/View.jsx b/src/BlockStyleWrapper/View.jsx index 8978f7d..d10b662 100644 --- a/src/BlockStyleWrapper/View.jsx +++ b/src/BlockStyleWrapper/View.jsx @@ -1,2 +1,6 @@ import React from 'react'; -export default (props) =>
Edit
; +import { StyleWrapperView } from '../StyleWrapper'; + +export default ({ data, children }) => ( + {children} +); diff --git a/src/BlockStyleWrapper/styles.less b/src/BlockStyleWrapper/styles.less deleted file mode 100644 index fc295ac..0000000 --- a/src/BlockStyleWrapper/styles.less +++ /dev/null @@ -1,15 +0,0 @@ -@type: 'extra'; -@element: 'custom'; - -@import (multiple, reference) '../../theme.config'; - -#open-styles-button { - width: 100%; - padding: 0 1em; - margin: auto 0; - text-align: right; - - button { - color: @linkColor; - } -} diff --git a/src/StyleWrapper/StyleWrapperEdit.jsx b/src/StyleWrapper/StyleWrapperEdit.jsx new file mode 100644 index 0000000..ae39279 --- /dev/null +++ b/src/StyleWrapper/StyleWrapperEdit.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Portal } from 'react-portal'; +import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib'; +import { Icon } from '@plone/volto/components'; +import themeSVG from '@plone/volto/icons/theme.svg'; +import SidebarPopup from '../SidebarPopup/SidebarPopup'; +import InlineForm from '@plone/volto/components/manage/Form/InlineForm'; +import { StyleSchema } from './schema'; + +export default (props) => { + const { children, selected, onChangeValue, data } = props; + const [isOpen, setIsOpen] = React.useState(false); + const nodeRef = React.useRef(); + + const closeSidebar = React.useCallback( + (e) => { + if (isOpen && !doesNodeContainClick(nodeRef.current, e)) setIsOpen(false); + }, + [isOpen], + ); + + React.useEffect(() => { + document.addEventListener('click', closeSidebar, false); + return () => { + document.removeEventListener('click', closeSidebar); + }; + }, [closeSidebar]); + + const schema = React.useMemo(() => StyleSchema(), []); + + return ( + <> + {selected ? ( + <> + .sidebar-container > .tabs-wrapper > .formtabs', + ) + } + > +
+ +
+
+ + { + onChangeValue(id, value); + }} + formData={data} + /> + + + ) : ( + '' + )} + {children} + + ); +}; diff --git a/src/StyleWrapper/StyleWrapperView.jsx b/src/StyleWrapper/StyleWrapperView.jsx new file mode 100644 index 0000000..bf42b14 --- /dev/null +++ b/src/StyleWrapper/StyleWrapperView.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import cx from 'classnames'; +import { settings } from '~/config'; + +export function getStyles(data) { + return { + backgroundColor: data.backgroundColor, + color: data.textColor, + textAlign: data.textAlign, + // fill in more + }; +} + +export function getStyle(name) { + const { pluggableStyles = [] } = settings; + return pluggableStyles.find(({ id }) => id === name); +} + +export default (props) => { + const { data = {}, children } = props; + const { style_name, align, size } = data; + const style = getStyle(style_name); + const ViewWrapper = style?.viewComponent; + + return ( +
+
+ {ViewWrapper ? : children} +
+
+ ); +}; diff --git a/src/StyleWrapper/index.js b/src/StyleWrapper/index.js new file mode 100644 index 0000000..df009b4 --- /dev/null +++ b/src/StyleWrapper/index.js @@ -0,0 +1,2 @@ +export StyleWrapperEdit from './StyleWrapperEdit'; +export StyleWrapperView from './StyleWrapperView'; diff --git a/src/StyleWrapper/schema.js b/src/StyleWrapper/schema.js new file mode 100644 index 0000000..41b1f62 --- /dev/null +++ b/src/StyleWrapper/schema.js @@ -0,0 +1,40 @@ +export const StyleSchema = () => ({ + title: 'Styles', + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: ['style_name', 'align', 'size'], + }, + { + id: 'advanced', + title: 'Advanced', + fields: ['backgroundColor', 'textColor'], + }, + ], + properties: { + style_name: { + title: 'Style', + widget: 'style_select', + }, + align: { + title: 'Align', + widget: 'style_align', + }, + size: { + title: 'Size', + widget: 'style_size', + }, + backgroundColor: { + title: 'Background color', + type: 'color', + widget: 'style_simple_color', + }, + textColor: { + title: 'Text color', + type: 'color', + widget: 'style_simple_color', + }, + }, + required: [], +}); diff --git a/src/Widgets/Align.jsx b/src/Widgets/Align.jsx new file mode 100644 index 0000000..2530276 --- /dev/null +++ b/src/Widgets/Align.jsx @@ -0,0 +1,29 @@ +/** + * AlignWidget component. + * To benefit from styling integration, use with a field named 'align' + * @module components/manage/Widgets/AlignWidget + */ + +import React from 'react'; +import { injectIntl } from 'react-intl'; + +import { FormFieldWrapper } from '@plone/volto/components'; +import { AlignBlock } from '@plone/volto/helpers'; + +const AlignWidget = (props) => { + const { id, onChange, value } = props; + return ( + +
+ onChange(id, align)} + data={{ align: value }} + block={id} + /> +
+
+ ); +}; + +export default injectIntl(AlignWidget); diff --git a/src/Widgets/SimpleColorPicker.jsx b/src/Widgets/SimpleColorPicker.jsx new file mode 100644 index 0000000..27a4795 --- /dev/null +++ b/src/Widgets/SimpleColorPicker.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { GithubPicker } from 'react-color'; +import { FormFieldWrapper, Icon } from '@plone/volto/components'; +import { Button } from 'semantic-ui-react'; +import clearSVG from '@plone/volto/icons/clear.svg'; + +export default (props) => { + const { id, value, onChange, available_colors } = props; + const [showPicker, setShowPicker] = React.useState(false); + + return ( + +
+ + + + + + {showPicker ? ( + { + setShowPicker(false); + onChange(id, value.hex); + }} + /> + ) : ( + '' + )} +
+
+ ); +}; diff --git a/src/Widgets/Size.jsx b/src/Widgets/Size.jsx new file mode 100644 index 0000000..1785acf --- /dev/null +++ b/src/Widgets/Size.jsx @@ -0,0 +1,25 @@ +/** + * AlignWidget component. + * To benefit from styling integration, use with a field named 'align' + * @module components/manage/Widgets/AlignWidget + */ + +import React from 'react'; + +import { FormFieldWrapper } from '@plone/volto/components'; +import ImageSizeWidget from '@plone/volto/components/manage/Blocks/Image/ImageSizeWidget'; + +const SizeWidget = (props) => { + const { id, onChange, value } = props; + return ( + + onChange(id, size)} + data={{ size: value }} + block={id} + /> + + ); +}; + +export default SizeWidget; diff --git a/src/Widgets/StyleSelect.jsx b/src/Widgets/StyleSelect.jsx new file mode 100644 index 0000000..95e392a --- /dev/null +++ b/src/Widgets/StyleSelect.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Card, Item } from 'semantic-ui-react'; +import { FormFieldWrapper } from '@plone/volto/components'; +import { settings } from '~/config'; +import cx from 'classnames'; + +const StyleSelectWidget = (props) => { + const { id, value, onChange } = props; + const { pluggableStyles = [], previewText = 'Lorem lipsum' } = settings; + + const renderPreview = React.useCallback( + (style) => { + const Preview = style.previewComponent || style.component; + + return Preview ? ( + + {previewText} + + ) : ( +
{previewText}
+ ); + }, + [previewText], + ); + + return ( + <> + + + {pluggableStyles.map((style) => { + return ( + onChange(id, style.id)} + key={style.id} + className={cx({ active: style.id === value })} + > + + {renderPreview(style)} + + {style.title} + + ); + })} + + + + ); +}; + +export default StyleSelectWidget; diff --git a/src/index.js b/src/index.js index 0fbd9fa..240ce68 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,12 @@ import React from 'react'; import StyleWrapperEdit from './BlockStyleWrapper/Edit'; import StyleWrapperView from './BlockStyleWrapper/View'; +import StyleSelectWidget from './Widgets/StyleSelect'; +import AlignWidget from './Widgets/Align'; +import SizeWidget from './Widgets/Size'; +import SimpleColorPicker from './Widgets/SimpleColorPicker'; + +import './styles.less'; const applyConfig = (config) => { const whitelist = ['maps']; @@ -21,7 +27,40 @@ const applyConfig = (config) => { ); }); + + config.widgets.widget.style_select = StyleSelectWidget; + config.widgets.widget.style_align = AlignWidget; // avoid conflict for now + config.widgets.widget.style_size = SizeWidget; // avoid conflict for now + config.widgets.widget.style_simple_color = SimpleColorPicker; + return config; }; export default applyConfig; + +export const installDemoStyles = (config) => { + config.settings.pluggableStyles = [ + ...(config.settings.pluggableStyles || []), + { + id: 'greenBox', + title: 'Green box', + cssClass: 'green-demo-box', + }, + { + id: 'blueShade', + title: 'Blue Shade', + cssClass: 'blue-demo-box', + previewComponent: (props) => ( +
+ {props.children} +
+ ), + viewComponent: (props) => ( +
{props.children}
+ ), + // TODO: support also editComponent ? + }, + ]; + + return config; +}; diff --git a/src/styles.less b/src/styles.less new file mode 100644 index 0000000..cce2a4e --- /dev/null +++ b/src/styles.less @@ -0,0 +1,41 @@ +@type: 'extra'; +@element: 'custom'; + +@import (multiple, reference) '../../theme.config'; + +#open-styles-button { + width: 100%; + padding: 0 1em; + margin: auto 0; + text-align: right; + + button { + color: @linkColor; + } +} + +.style-select-widget { + .card.active { + .extra.content { + background-color: #eee; + font-weight: bold; + } + } +} + +.green-demo-box { + -webkit-box-shadow: 0 1px 6px 0 #c7d5d8; + box-shadow: 0 1px 6px 0 #c7d5d8; + border-radius: 5px; + padding: 1em; + background-color: #aef2de; +} + +.preview-blue-demo-box { + background: #1b217d; + color: white !important; + padding: 0.3rem; + -webkit-box-shadow: 0 0 12px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 11px rgba(0, 0, 0, 0.2); + border-radius: 4px; +}