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%; }