diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8136f1..b304cd4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,35 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
+#### [3.3.4](https://github.com/eea/volto-block-style/compare/3.3.3...3.3.4)
+
+- New controls [`#23`](https://github.com/eea/volto-block-style/pull/23)
+- Improve api [`#21`](https://github.com/eea/volto-block-style/pull/21)
+- Add clear button to size [`#20`](https://github.com/eea/volto-block-style/pull/20)
+- Code linting [`0d9ab18`](https://github.com/eea/volto-block-style/commit/0d9ab18f0817171e38153f61d8299771e2633384)
+- Add missing component [`2c7110c`](https://github.com/eea/volto-block-style/commit/2c7110c9612e59bd85a563543364afa0f0eb7ecd)
+- Fix config [`8029e6e`](https://github.com/eea/volto-block-style/commit/8029e6ef8a005464a165c6b24c69ec419f581702)
+- Add new layout controls [`613d015`](https://github.com/eea/volto-block-style/commit/613d015aa17d058b1ebe181115deb9a1bbe42a22)
+- Make sidebar form more usable [`7960ff8`](https://github.com/eea/volto-block-style/commit/7960ff86f21bf6f839caedea368075ab2518293e)
+- Merge develop [`0274ef8`](https://github.com/eea/volto-block-style/commit/0274ef8da1a8faa57de01ffd31f9413b0c978d45)
+- Add extra controls [`15ebf89`](https://github.com/eea/volto-block-style/commit/15ebf8925e1ea099b759a47da6e567a0a79c9621)
+- Improve box shadow calculation [`177db51`](https://github.com/eea/volto-block-style/commit/177db51146d7bc25fbda365abadefb76260d992b)
+- Improve box shadow calculation [`2fffb80`](https://github.com/eea/volto-block-style/commit/2fffb802795b8af96f64740c1b07d493422c4730)
+- Add range slider [`8a91ef1`](https://github.com/eea/volto-block-style/commit/8a91ef168df01e4a0f114596d50a401a14a41251)
+- Separate block wrapping; don't apply multiple times [`2adffd5`](https://github.com/eea/volto-block-style/commit/2adffd58a99af59bb42d72c54f2a05be3b9b2ea1)
+- Separate block wrapping; don't apply multiple times [`584e7a9`](https://github.com/eea/volto-block-style/commit/584e7a9859c7d417708f9aa78b83e94547f2f1ab)
+- Add Sonarqube tag using frontend addons list [`f6737ef`](https://github.com/eea/volto-block-style/commit/f6737ef68ba906c5fb72e18c77b060aa82ed0a73)
+- Add Sonarqube tag using frontend addons list [`6a4c6c1`](https://github.com/eea/volto-block-style/commit/6a4c6c16acc88471279a82a62b92555dc1ac20cc)
+- Add Sonarqube tag using frontend addons list [`2101b55`](https://github.com/eea/volto-block-style/commit/2101b550588402b37cd91b5d582e27a6255d6cac)
+- Add Sonarqube tag using frontend addons list [`2583c68`](https://github.com/eea/volto-block-style/commit/2583c68ea49ddbedc7b90b9d477cbbe4e2b779fd)
+- Add hidden toggle [`3b8ed26`](https://github.com/eea/volto-block-style/commit/3b8ed26a82c31ff430301adceea1174e92464a77)
+- Add a clear button to the box size widget [`4bdf803`](https://github.com/eea/volto-block-style/commit/4bdf8030d7a4d39d3a48b56ed6c8cddc311b3ff7)
+
#### [3.3.3](https://github.com/eea/volto-block-style/compare/3.3.2...3.3.3)
-- Make add-on dependencies more flexible [`0f542cb`](https://github.com/eea/volto-block-style/commit/0f542cb741e96b1286ba2a10a1f5d2a86d72eaf7)
+> 25 June 2021
+
+- Make add-on dependencies more flexible [`#19`](https://github.com/eea/volto-block-style/pull/19)
#### [3.3.2](https://github.com/eea/volto-block-style/compare/3.3.1...3.3.2)
diff --git a/Jenkinsfile b/Jenkinsfile
index 3c540ef..b60644d 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -4,7 +4,7 @@ pipeline {
environment {
GIT_NAME = "volto-block-style"
NAMESPACE = "@eeacms"
- SONARQUBE_TAGS = "volto.eea.europa.eu"
+ SONARQUBE_TAGS = "volto.eea.europa.eu,climate-energy.eea.europa.eu,forest.eea.europa.eu,clms.land.copernicus.eu,biodiversity.europa.eu"
DEPENDENCIES = ""
}
diff --git a/package.json b/package.json
index b56d622..978b29f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@eeacms/volto-block-style",
- "version": "3.3.3",
+ "version": "3.3.4",
"description": "volto-block-style: Volto add-on",
"main": "src/index.js",
"author": "European Environment Agency: IDM2 A-Team",
diff --git a/src/ErrorBoundary.jsx b/src/ErrorBoundary.jsx
new file mode 100644
index 0000000..4ec48df
--- /dev/null
+++ b/src/ErrorBoundary.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+export default class ErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error) {
+ // Update state so the next render will show the fallback UI.
+ return { hasError: true };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ // eslint-disable-next-line
+ console.error(error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ // You can render any custom fallback UI
+ return Error in component;
+ }
+
+ return this.props.children;
+ }
+}
diff --git a/src/StyleWrapper/StyleWrapperView.jsx b/src/StyleWrapper/StyleWrapperView.jsx
index 88d9a0b..e6996aa 100644
--- a/src/StyleWrapper/StyleWrapperView.jsx
+++ b/src/StyleWrapper/StyleWrapperView.jsx
@@ -4,13 +4,50 @@ import cx from 'classnames';
import config from '@plone/volto/registry';
import { withCachedImages } from '@eeacms/volto-block-style/hocs';
+const getLineHeight = (fontSize) => {
+ switch (fontSize) {
+ case 'large':
+ return '110%';
+ case 'x-large':
+ return '130%';
+ default:
+ return;
+ }
+};
+
+const getSide = (side, v) =>
+ `${v[side] ? `${v[side]}${v.unit ? v.unit : 'px'}` : ''}`;
+
+const getSides = (v) => {
+ return `${getSide('top', v)} ${getSide('right', v)} ${getSide(
+ 'bottom',
+ v,
+ )} ${getSide('left', v)}`;
+};
+
+const hexColorToRGB = (hex) => {
+ const R = parseInt(hex.slice(1, 3), 16);
+ const G = parseInt(hex.slice(3, 5), 16);
+ const B = parseInt(hex.slice(5, 7), 16);
+ return [R, G, B];
+};
+
+const h2rgb = (hex) => {
+ if (!hex) return '0, 0, 0, ';
+ const [R, G, B] = hexColorToRGB(hex);
+ return `${R}, ${G}, ${B},`;
+};
+
export function getInlineStyles(data, props = {}) {
return {
+ ...(data.hidden && props.mode !== 'edit' ? { display: 'none' } : {}),
...(data.backgroundColor ? { backgroundColor: data.backgroundColor } : {}),
...(data.textColor ? { color: data.textColor } : {}),
...(data.textAlign ? { textAlign: data.textAlign } : {}),
- ...(data.fontSize ? { fontSize: data.fontSize } : {}),
- ...(data.isScreenHeight && props.screen.height
+ ...(data.fontSize
+ ? { fontSize: data.fontSize, lineHeight: getLineHeight(data.fontSize) }
+ : {}),
+ ...(data.isScreenHeight && props.screen.screenHeight
? {
minHeight: (
props.screen.height -
@@ -19,6 +56,16 @@ export function getInlineStyles(data, props = {}) {
).toPixel(),
}
: {}),
+ ...(data.shadowDepth && {
+ boxShadow: `0px 0px ${data.shadowDepth}px rgba(${h2rgb(
+ data.shadowColor,
+ )} ${(data.shadowDepth * 100) / 0.24})`,
+ }),
+ ...(data.margin && { margin: getSides(data.margin) }),
+ ...(data.padding && { padding: getSides(data.padding) }),
+ ...(data.borderRadius && {
+ borderRadius: data.borderRadius,
+ }),
// fill in more
};
}
@@ -38,6 +85,7 @@ const StyleWrapperView = (props) => {
customId,
isDropCap,
isScreenHeight,
+ hidden = false,
} = styleData;
const containerType = data['@type'];
const backgroundImage = styleData.backgroundImage;
@@ -53,6 +101,7 @@ const StyleWrapperView = (props) => {
size ||
customClass ||
isDropCap ||
+ hidden ||
customId;
const attrs = {
diff --git a/src/StyleWrapper/schema.js b/src/StyleWrapper/schema.js
index 15749ce..43e903d 100644
--- a/src/StyleWrapper/schema.js
+++ b/src/StyleWrapper/schema.js
@@ -6,6 +6,11 @@ export const StyleSchema = () => ({
{
id: 'default',
title: 'Default',
+ fields: [],
+ },
+ {
+ id: 'presets',
+ title: 'Preset styles',
fields: ['style_name'],
},
{
@@ -14,17 +19,27 @@ export const StyleSchema = () => ({
fields: ['textAlign', 'fontSize', 'align', 'size', 'isDropCap'],
},
{
- id: 'advanced',
- title: 'Advanced',
+ id: 'decorations',
+ title: 'Decorations',
fields: [
- 'isScreenHeight',
'backgroundImage',
'backgroundColor',
'textColor',
- 'customClass',
- 'customId',
+ 'borderRadius',
+ 'shadowDepth',
+ 'shadowColor',
],
},
+ {
+ id: 'layout',
+ title: 'Layout',
+ fields: ['margin', 'padding', 'size', 'align'], // todo: width, conflicts with size
+ },
+ {
+ id: 'advanced',
+ title: 'Advanced',
+ fields: ['hidden', 'isScreenHeight', 'customClass', 'customId'],
+ },
],
properties: {
style_name: {
@@ -53,6 +68,14 @@ export const StyleSchema = () => ({
['xxx-large', 'xxx-large'],
],
},
+ margin: {
+ title: 'Margin',
+ widget: 'quad_size',
+ },
+ padding: {
+ title: 'Padding',
+ widget: 'quad_size',
+ },
size: {
title: 'Box size',
widget: 'style_size',
@@ -91,6 +114,37 @@ export const StyleSchema = () => ({
description: 'First letter is styled as a drop cop',
type: 'boolean',
},
+ hidden: {
+ title: 'Hidden',
+ description: 'Hide this block',
+ type: 'boolean',
+ },
+ shadowDepth: {
+ widget: 'slider',
+ title: 'Shadow depth',
+ settings: {
+ min: 0,
+ max: 24,
+ step: 1,
+ start: 0,
+ },
+ },
+ shadowColor: {
+ title: 'Shadow color',
+ type: 'color',
+ widget: 'style_simple_color',
+ available_colors: config.settings.available_colors,
+ },
+ borderRadius: {
+ widget: 'slider',
+ title: 'Rounded Corner',
+ settings: {
+ min: 0,
+ max: 24,
+ step: 1,
+ start: 0,
+ },
+ },
},
required: [],
});
diff --git a/src/Widgets/QuadSize.jsx b/src/Widgets/QuadSize.jsx
new file mode 100644
index 0000000..9c71bd5
--- /dev/null
+++ b/src/Widgets/QuadSize.jsx
@@ -0,0 +1,154 @@
+import React from 'react';
+import { Field, FormFieldWrapper } from '@plone/volto/components';
+import ErrorBoundary from '../ErrorBoundary';
+import { Grid } from 'semantic-ui-react';
+import { Slider } from './Slider';
+
+const fields = {
+ unitField: {
+ title: 'Unit',
+ columns: 2,
+ placeholder: 'Unit',
+ defaultValue: 'px',
+ choices: [
+ ['px', 'px'],
+ ['%', 'percentage'],
+ ['em', 'em'],
+ ['rem', 'rem'],
+ ],
+ },
+};
+
+const getMax = (unit) => {
+ switch (unit) {
+ case '%':
+ return 100;
+ case 'px':
+ return 100;
+ case 'em':
+ return 24;
+ case 'rem':
+ return 24;
+ default:
+ return 10;
+ }
+};
+
+const QuadSizeWidget = (props) => {
+ const {
+ value = {},
+ id,
+ onChange,
+ sliderSettings = {
+ max: 12,
+ min: 0,
+ step: 1,
+ start: 0,
+ },
+ } = props;
+ const {
+ top = 0,
+ right = 0,
+ bottom = 0,
+ left = 0,
+ unit = 'px',
+ unlock = false,
+ } = value;
+ const settings = {
+ ...sliderSettings,
+ max: getMax(unit),
+ };
+ // console.log('value', value);
+
+ return (
+
+
+ onChange(id, { ...value, unit: val })}
+ value={value.unit || 'px'}
+ />
+
+ {unlock ? (
+
+
+
+ onChange(id, { ...value, top: val }),
+ ...settings,
+ }}
+ value={top}
+ extra={{top}}
+ />
+
+
+
+ onChange(id, { ...value, left: val }),
+ ...settings,
+ }}
+ value={left}
+ extra={{left}}
+ />
+
+
+ onChange(id, { ...value, right: val }),
+ ...settings,
+ }}
+ value={right}
+ extra={{right}}
+ />
+
+
+
+ onChange(id, { ...value, bottom: val }),
+ ...settings,
+ }}
+ extra={{bottom}}
+ value={bottom}
+ />
+
+
+
+ ) : (
+ {
+ onChange(id, {
+ ...value,
+ top: val,
+ left: val,
+ bottom: val,
+ right: val,
+ });
+ }}
+ value={top}
+ title="Size"
+ widget="slider"
+ columns={2}
+ />
+ )}
+
+ onChange(id, { ...value, unlock: val })}
+ value={unlock}
+ title="Customize"
+ type="boolean"
+ columns={1}
+ />
+
+
+ );
+};
+
+export default QuadSizeWidget;
diff --git a/src/Widgets/Size.jsx b/src/Widgets/Size.jsx
index 1d5a6b2..24a2e1a 100644
--- a/src/Widgets/Size.jsx
+++ b/src/Widgets/Size.jsx
@@ -6,8 +6,10 @@
import React from 'react';
-import { FormFieldWrapper } from '@plone/volto/components';
+import { FormFieldWrapper, Icon } from '@plone/volto/components';
import ImageSizeWidget from '@plone/volto/components/manage/Blocks/Image/ImageSizeWidget';
+import { Button } from 'semantic-ui-react';
+import clearSVG from '@plone/volto/icons/clear.svg';
// TODO: copy the styles from Volto's stylesheet
@@ -21,6 +23,18 @@ const SizeWidget = (props) => {
data={{ size: value }}
block={id}
/>
+
+
+
);
diff --git a/src/Widgets/Slider.jsx b/src/Widgets/Slider.jsx
new file mode 100644
index 0000000..c78269f
--- /dev/null
+++ b/src/Widgets/Slider.jsx
@@ -0,0 +1,468 @@
+// Copied from MIT-licensed https://github.com/iozbeyli/react-semantic-ui-range
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import ReactDOM from 'react-dom';
+import { FormFieldWrapper } from '@plone/volto/components';
+// import { withParentSize } from '@visx/responsive';
+
+import styles from './range.css.js';
+
+export class Slider extends Component {
+ constructor(props) {
+ super(props);
+ let value = this.props.value
+ ? this.props.value
+ : props.multiple
+ ? [...props.settings.start]
+ : props.settings.start;
+ this.state = {
+ value: value,
+ position: props.multiple ? [] : 0,
+ numberOfKnobs: props.multiple ? value.length : 1,
+ offset: 10,
+ precision: 0,
+ mouseDown: false,
+ };
+ this.determinePosition = this.determinePosition.bind(this);
+ this.rangeMouseUp = this.rangeMouseUp.bind(this);
+ this.refresh = this.refresh.bind(this);
+ }
+
+ componentDidMount() {
+ this.determinePrecision();
+ const value = this.props.value ? this.props.value : this.state.value;
+ this.setValuesAndPositions(value, false);
+ window.addEventListener('mouseup', this.rangeMouseUp);
+ window.addEventListener('resize', this.refresh);
+ }
+
+ refresh() {
+ const value = this.props.value ? this.props.value : this.state.value;
+ this.setValuesAndPositions(value, false);
+ }
+
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ const isValueUnset =
+ nextProps.value === null || nextProps.value === undefined;
+
+ if (!isValueUnset && nextProps.value !== this.state.value) {
+ if (this.props.multiple) {
+ const different = this.isDifferentArrays(
+ nextProps.value,
+ this.state.value,
+ );
+ if (different) {
+ this.setValuesAndPositions(nextProps.value, true);
+ }
+ } else {
+ this.setValuesAndPositions(nextProps.value, true);
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ this.inner = undefined;
+ this.innerLeft = undefined;
+ this.innerRight = undefined;
+ window.removeEventListener('mouseup', this.rangeMouseUp);
+ window.removeEventListener('resize', this.refresh);
+ }
+
+ setValuesAndPositions(value, triggeredByUser) {
+ if (this.props.multiple) {
+ const positions = [...this.state.position];
+ value.forEach((val, i) => {
+ this.setValue(val, triggeredByUser, i);
+ positions[i] = this.determinePosition(val);
+ });
+ this.setState({
+ position: positions,
+ });
+ } else {
+ this.setValue(value, triggeredByUser);
+ this.setState({
+ position: this.determinePosition(value),
+ });
+ }
+ }
+
+ isDifferentArrays(a, b) {
+ let different = false;
+ a.some((val, i) => {
+ if (val !== b[i]) {
+ different = true;
+ return true;
+ }
+ return false;
+ });
+ return different;
+ }
+
+ determinePosition(value) {
+ const trackLeft = ReactDOM.findDOMNode(this.track).getBoundingClientRect()
+ .left;
+ const innerLeft = ReactDOM.findDOMNode(this.inner).getBoundingClientRect()
+ .left;
+ const ratio =
+ (value - this.props.settings.min) /
+ (this.props.settings.max - this.props.settings.min);
+ const position =
+ Math.round(ratio * this.inner.offsetWidth) +
+ trackLeft -
+ innerLeft -
+ this.state.offset;
+ return position;
+ }
+
+ determinePrecision() {
+ let split = String(this.props.settings.step).split('.');
+ let decimalPlaces;
+ if (split.length === 2) {
+ decimalPlaces = split[1].length;
+ } else {
+ decimalPlaces = 0;
+ }
+ this.setState({
+ precision: Math.pow(10, decimalPlaces),
+ });
+ }
+
+ determineValue(startPos, endPos, currentPos) {
+ let ratio = (currentPos - startPos) / (endPos - startPos);
+ let range = this.props.settings.max - this.props.settings.min;
+ let difference =
+ Math.round((ratio * range) / this.props.settings.step) *
+ this.props.settings.step;
+ // Use precision to avoid ugly Javascript floating point rounding issues
+ // (like 35 * .01 = 0.35000000000000003)
+ difference =
+ Math.round(difference * this.state.precision) / this.state.precision;
+ return difference + this.props.settings.min;
+ }
+
+ determineKnob(position, value) {
+ if (!this.props.multiple) {
+ return 0;
+ }
+ if (position <= this.state.position[0]) {
+ return 0;
+ }
+ if (position >= this.state.position[this.state.numberOfKnobs - 1]) {
+ return this.state.numberOfKnobs - 1;
+ }
+ let index = 0;
+
+ for (let i = 0; i < this.state.numberOfKnobs - 1; i++) {
+ if (
+ position >= this.state.position[i] &&
+ position < this.state.position[i + 1]
+ ) {
+ const distanceToSecond = Math.abs(
+ position - this.state.position[i + 1],
+ );
+ const distanceToFirst = Math.abs(position - this.state.position[i]);
+ if (distanceToSecond <= distanceToFirst) {
+ return i + 1;
+ } else {
+ return i;
+ }
+ }
+ }
+ return index;
+ }
+
+ setValue(value, triggeredByUser, knobIndex) {
+ if (typeof triggeredByUser === 'undefined') {
+ triggeredByUser = true;
+ }
+ const currentValue = this.props.multiple
+ ? this.state.value[knobIndex]
+ : this.state.value;
+ if (currentValue !== value) {
+ let newValue = [];
+ if (this.props.multiple) {
+ newValue = [...this.state.value];
+ newValue[knobIndex] = value;
+ this.setState({
+ value: newValue,
+ });
+ } else {
+ newValue = value;
+ this.setState({
+ value: value,
+ });
+ }
+ if (this.props.settings.onChange) {
+ this.props.settings.onChange(newValue, {
+ triggeredByUser: triggeredByUser,
+ });
+ }
+ }
+ }
+
+ setValuePosition(value, triggeredByUser, knobIndex) {
+ if (this.props.multiple) {
+ const positions = [...this.state.position];
+ positions[knobIndex] = this.determinePosition(value);
+ this.setValue(value, triggeredByUser, knobIndex);
+ this.setState({
+ position: positions,
+ });
+ } else {
+ this.setValue(value, triggeredByUser);
+ this.setState({
+ position: this.determinePosition(value),
+ });
+ }
+ }
+
+ setPosition(position, knobIndex) {
+ if (this.props.multiple) {
+ const newPosition = [...this.state.position];
+ newPosition[knobIndex] = position;
+ this.setState({
+ position: newPosition,
+ });
+ } else {
+ this.setState({
+ position: position,
+ });
+ }
+ }
+
+ rangeMouseDown(isTouch, e) {
+ e.stopPropagation();
+ if (!this.props.disabled) {
+ if (!isTouch) {
+ e.preventDefault();
+ }
+
+ this.setState({
+ mouseDown: true,
+ });
+ let innerBoundingClientRect = ReactDOM.findDOMNode(
+ this.inner,
+ ).getBoundingClientRect();
+ this.innerLeft = innerBoundingClientRect.left;
+ this.innerRight = this.innerLeft + this.inner.offsetWidth;
+ this.rangeMouse(isTouch, e);
+ }
+ }
+
+ rangeMouse(isTouch, e) {
+ let pageX;
+ let event = isTouch ? e.touches[0] : e;
+ if (event.pageX) {
+ pageX = event.pageX;
+ } else {
+ // eslint-disable-next-line
+ console.log('PageX undefined');
+ }
+ let value = this.determineValue(this.innerLeft, this.innerRight, pageX);
+ if (pageX >= this.innerLeft && pageX <= this.innerRight) {
+ if (
+ value >= this.props.settings.min &&
+ value <= this.props.settings.max
+ ) {
+ const position = pageX - this.innerLeft - this.state.offset;
+ const knobIndex = this.props.multiple
+ ? this.determineKnob(position)
+ : undefined;
+ if (this.props.discrete) {
+ this.setValuePosition(value, false, knobIndex);
+ } else {
+ this.setPosition(position, knobIndex);
+ this.setValue(value, undefined, knobIndex);
+ }
+ }
+ }
+ }
+
+ rangeMouseMove(isTouch, e) {
+ e.stopPropagation();
+ if (!isTouch) {
+ e.preventDefault();
+ }
+ if (this.state.mouseDown) {
+ this.rangeMouse(isTouch, e);
+ }
+ }
+
+ rangeMouseUp() {
+ this.setState({
+ mouseDown: false,
+ });
+ }
+
+ render() {
+ return (
+
+
this.rangeMouseDown(false, event)}
+ onMouseMove={(event) => this.rangeMouseMove(false, event)}
+ onMouseUp={(event) => this.rangeMouseUp(false, event)}
+ onTouchEnd={(event) => this.rangeMouseUp(true, event)}
+ onTouchMove={(event) => this.rangeMouseMove(true, event)}
+ onTouchStart={(event) => this.rangeMouseDown(true, event)}
+ style={{
+ ...styles.range,
+ ...(this.props.disabled ? styles.disabled : {}),
+ ...(this.props.style ? this.props.style : {}),
+ }}
+ >
+
{
+ this.inner = inner;
+ }}
+ style={{
+ ...styles.inner,
+ ...(this.props.style
+ ? this.props.style.inner
+ ? this.props.style.inner
+ : {}
+ : {}),
+ }}
+ >
+
{
+ this.track = track;
+ }}
+ style={{
+ ...styles.track,
+ ...(this.props.inverted ? styles.invertedTrack : {}),
+ ...(this.props.style
+ ? this.props.style.track
+ ? this.props.style.track
+ : {}
+ : {}),
+ }}
+ />
+
{
+ this.trackFill = trackFill;
+ }}
+ style={{
+ ...styles.trackFill,
+ ...(this.props.inverted ? styles.invertedTrackFill : {}),
+ ...styles[
+ this.props.inverted
+ ? 'inverted-' + this.props.color
+ : this.props.color
+ ],
+ ...(this.props.style
+ ? this.props.style.trackFill
+ ? this.props.style.trackFill
+ : {}
+ : {}),
+ ...(this.props.disabled ? styles.disabledTrackFill : {}),
+ ...(this.props.style
+ ? this.props.style.disabledTrackFill
+ ? this.props.style.disabledTrackFill
+ : {}
+ : {}),
+ ...{ width: this.state.position + this.state.offset + 'px' },
+ ...(this.props.multiple && this.state.position.length > 0
+ ? {
+ left: this.state.position[0],
+ width:
+ this.state.position[this.state.numberOfKnobs - 1] -
+ this.state.position[0],
+ }
+ : {}),
+ }}
+ />
+
+ {this.props.multiple ? (
+ this.state.position.map((pos, i) => (
+
+ ))
+ ) : (
+
+ {this.props.extra}
+
+ )}
+
+
+
+ );
+ }
+}
+
+Slider.defaultProps = {
+ color: 'red',
+ settings: {
+ min: 0,
+ max: 10,
+ step: 1,
+ start: 0,
+ },
+};
+
+Slider.propTypes = {
+ color: PropTypes.string,
+ disabled: PropTypes.bool,
+ discrete: PropTypes.bool,
+ inverted: PropTypes.bool,
+ multiple: PropTypes.bool,
+ settings: PropTypes.shape({
+ min: PropTypes.number,
+ max: PropTypes.number,
+ step: PropTypes.number,
+ start: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.arrayOf(PropTypes.number),
+ ]),
+ onChange: PropTypes.func,
+ }),
+};
+
+const SliderWidget = (props) => {
+ const { id, onChange, value, settings = {}, ...rest } = props;
+ return (
+
+ {
+ onChange(id, value);
+ },
+ }}
+ value={value}
+ extra={{value}}
+ />
+
+ );
+};
+
+export default SliderWidget;
diff --git a/src/Widgets/StyleSelect.jsx b/src/Widgets/StyleSelect.jsx
index 25f94a7..599c303 100644
--- a/src/Widgets/StyleSelect.jsx
+++ b/src/Widgets/StyleSelect.jsx
@@ -43,7 +43,7 @@ const StyleSelectWidget = (props) => {
className={cx({ active: style.id === value })}
>
- {renderPreview(style)}
+ {renderPreview(style)}
{style.title}
diff --git a/src/Widgets/range.css.js b/src/Widgets/range.css.js
new file mode 100644
index 0000000..74ea060
--- /dev/null
+++ b/src/Widgets/range.css.js
@@ -0,0 +1,179 @@
+const styles = {
+ range: {
+ cursor: 'pointer',
+ width: '100%',
+ height: '20px',
+ },
+ inner: {
+ margin: '0 10px 0 10px',
+ height: '20px',
+ position: 'relative',
+ },
+ /*
+ .ui.range .inner:hover {
+ cursor: pointer;
+ }*/
+ track: {
+ position: 'absolute',
+ width: '100%',
+ height: '4px',
+ borderRadius: '4px',
+ top: '9px',
+ left: '0',
+ backgroundColor: 'rgba(0,0,0,.05)',
+ },
+ invertedTrack: {
+ backgroundColor: 'rgba(255,255,255,.08)',
+ },
+ trackFill: {
+ position: 'absolute',
+ width: '0',
+ height: '4px',
+ borderRadius: '4px',
+ top: '9px',
+ left: '0',
+ backgroundColor: '#1b1c1d',
+ },
+ invertedTrackFill: {
+ backgroundColor: '#545454',
+ },
+ knob: {
+ position: 'absolute',
+ top: '0px',
+ left: '0',
+ height: '20px',
+ width: '20px',
+ background: '#fff linear-gradient(transparent, rgba(0, 0, 0, 0.5))',
+ // background: '#fff -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.5))',
+ // background: '#fff -o-linear-gradient(transparent, rgba(0, 0, 0, 0.5))',
+ // background: '#fff -moz-linear-gradient(transparent, rgba(0, 0, 0, 0.5))',
+ borderRadius: '6px',
+ backgroundColor: '#205c90',
+ boxShadow:
+ '0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset',
+ display: 'flex',
+ color: 'white',
+ flexDirection: 'column',
+ textAlign: 'center',
+ fontSize: 'xx-small',
+ },
+ red: {
+ backgroundColor: '#DB2828',
+ },
+ 'inverted-red': {
+ backgroundColor: '#FF695E',
+ },
+ /* Orange */
+ orange: {
+ backgroundColor: '#F2711C',
+ },
+ 'inverted-orange': {
+ backgroundColor: '#FF851B',
+ },
+ /* Yellow */
+ yellow: {
+ backgroundColor: '#FBBD08',
+ },
+ 'inverted-yellow': {
+ backgroundColor: '#FFE21F',
+ },
+ /* Olive */
+ olive: {
+ backgroundColor: '#B5CC18',
+ },
+ 'inverted-olive': {
+ backgroundColor: '#D9E778',
+ },
+ /* Green */
+ green: {
+ backgroundColor: '#21BA45',
+ },
+ 'inverted-green': {
+ backgroundColor: '#2ECC40',
+ },
+ /* Teal */
+ teal: {
+ backgroundColor: '#00B5AD',
+ },
+ 'inverted-teal': {
+ backgroundColor: '#6DFFFF',
+ },
+ /* Blue */
+ blue: {
+ backgroundColor: '#2185D0',
+ },
+ 'inverted-blue': {
+ backgroundColor: '#54C8FF',
+ },
+ /* Violet */
+ violet: {
+ backgroundColor: '#6435C9',
+ },
+ 'inverted-violet': {
+ backgroundColor: '#A291FB',
+ },
+ /* Purple */
+ purple: {
+ backgroundColor: '#A333C8',
+ },
+ 'inverted-purple': {
+ backgroundColor: '#DC73FF',
+ },
+ /* Pink */
+ pink: {
+ backgroundColor: '#E03997',
+ },
+ 'inverted-pink': {
+ backgroundColor: '#FF8EDF',
+ },
+ /* Brown */
+ brown: {
+ backgroundColor: '#A5673F',
+ },
+ 'inverted-brown': {
+ backgroundColor: '#D67C1C',
+ },
+ /* Grey */
+ grey: {
+ backgroundColor: '#767676',
+ },
+ 'inverted-grey': {
+ backgroundColor: '#DCDDDE',
+ },
+ /* Black */
+ black: {
+ backgroundColor: '#1b1c1d',
+ },
+ 'inverted-black': {
+ backgroundColor: '#545454',
+ },
+ /*--------------
+ Disabled
+---------------*/
+ disabled: {
+ cursor: 'not-allowed',
+ opacity: '.5',
+ },
+
+ /*--------------
+ Disabled
+---------------*/
+
+ disabledTrackFill: {
+ backgroundColor: '#ccc',
+ },
+
+ /*--------------
+ Invalid-Input
+---------------*/
+ invalidInputTrack: {
+ cursor: 'not-allowed',
+ opacity: '.3',
+ background: '#ff0000',
+ },
+ invalidInputTrackFill: {
+ opacity: '.0',
+ },
+};
+
+export default styles;
diff --git a/src/index.js b/src/index.js
index a0108ae..c47f6a9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,11 +6,47 @@ import {
import StyleSelectWidget from './Widgets/StyleSelect';
import AlignWidget from './Widgets/Align';
import TextAlignWidget from './Widgets/TextAlign';
+import SliderWidget from './Widgets/Slider';
import SizeWidget from './Widgets/Size';
import SimpleColorPicker from './Widgets/SimpleColorPicker';
+import QuadSizeWidget from './Widgets/QuadSize';
import './styles.less';
+/**
+ * Given a block's config object, it wrapps the view and edit in style wrappers
+ */
+export const applyStyleWrapperToBlock = (blockConfig) => {
+ const BaseEditComponent = blockConfig.edit;
+ let EditComponent = BaseEditComponent;
+ if (!EditComponent._styleWrapped) {
+ EditComponent = (props) => (
+
+
+
+ );
+ EditComponent.displayName = `
`;
+ EditComponent._styleWrapped = true;
+ }
+
+ const BaseViewComponent = blockConfig.view;
+ let ViewComponent = BaseViewComponent;
+ if (!ViewComponent._styleWrapped) {
+ ViewComponent = (props) => (
+
+
+
+ );
+ ViewComponent.displayName = ``;
+ ViewComponent._styleWrapped = true;
+ }
+ return {
+ ...blockConfig,
+ view: ViewComponent,
+ edit: EditComponent,
+ };
+};
+
const applyConfig = (config) => {
const { settings } = config;
const whitelist = settings.pluggableStylesBlocksWhitelist;
@@ -23,21 +59,7 @@ const applyConfig = (config) => {
(whitelist ? whitelist.includes(name) : true),
);
okBlocks.forEach((name) => {
- const EditComponent = blocksConfig[name].edit;
- const ViewComponent = blocksConfig[name].view;
- blocksConfig[name].edit = (props) => (
-
-
-
- );
- blocksConfig[name].edit.displayName = 'EditBlockWithStyleWrapper';
-
- blocksConfig[name].view = (props) => (
-
-
-
- );
- blocksConfig[name].view.displayName = 'ViewBlockWithStyleWrapper';
+ blocksConfig[name] = applyStyleWrapperToBlock(blocksConfig[name]);
});
config.widgets.widget.style_select = StyleSelectWidget;
@@ -45,6 +67,8 @@ const applyConfig = (config) => {
config.widgets.widget.style_text_align = TextAlignWidget; // avoid conflict for now
config.widgets.widget.style_size = SizeWidget; // avoid conflict for now
config.widgets.widget.style_simple_color = SimpleColorPicker;
+ config.widgets.widget.slider = SliderWidget;
+ config.widgets.widget.quad_size = QuadSizeWidget;
// types of blocks that natively integrate with the volto-block-style and
// allow passing the style as a prop;
diff --git a/src/styles.less b/src/styles.less
index 0b44096..d6ee3bd 100644
--- a/src/styles.less
+++ b/src/styles.less
@@ -32,6 +32,10 @@
.style-select-widget {
.card {
cursor: pointer;
+
+ .extra.content {
+ padding: 0.4em !important;
+ }
}
.card.active {
@@ -52,14 +56,17 @@
}
&.small {
+ min-width: 10%;
max-width: 25%;
}
&.medium {
+ min-width: 25%;
max-width: 50%;
}
&.large {
+ min-width: 50%;
max-width: 100%;
}