diff --git a/index.js b/index.js new file mode 100644 index 0000000000000000000000000000000000000000..27d9ba3d3219996f8680dd6b64ba98b5aae5967a --- /dev/null +++ b/index.js @@ -0,0 +1,8 @@ +export { default as AlignButtons } from "./src/AlignButtons/AlignButtons"; +export { default as BlockPlaceholders } from "./src/BlockPlaceholders/BlockPlaceholders"; +export { default as ColorSelector } from "./src/ColorSelector/ColorSelector"; +export { default as DuoInput } from "./src/DuoInput/DuoInput"; +export { default as FlexibleTable } from "./src/FlexibleTable/FlexibleTable"; +export { default as FontStyles } from "./src/FontStyles/FontStyles"; +export { default as NumberWithUnit } from "./src/NumberWithUnit/NumberWithUnit"; +export { default as QuatroInput } from "./src/QuatroInput/QuatroInput"; diff --git a/package-lock.json b/package-lock.json index 13a50e2790f0e77a088257fbdd093ba6d74dd021..e42dea269b1e40102b94874da543f98b4d8ec01e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "@wordpress/block-editor": "^14.13.0", "@wordpress/components": "^29.4.0", "@wordpress/i18n": "^5.18.0", - "react": "^18.3.1" + "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1" } }, "node_modules/@ariakit/core": { @@ -892,6 +893,16 @@ "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==", "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/mousetrap": { "version": "1.6.15", "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz", @@ -929,6 +940,27 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-redux": { + "version": "7.1.34", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", + "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, + "node_modules/@types/react-redux/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/@use-gesture/core": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", @@ -1946,6 +1978,15 @@ "node": ">=10" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "license": "MIT", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2382,6 +2423,12 @@ "integrity": "sha512-yywVJy8ctVlN5lNPxsep5urnZ6TTclwPEyigM9M3Bi8vseJBOfqNrGWN/r8NzuIt3PovM323W04blJfGQfQSVg==", "license": "MIT" }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -2677,6 +2724,12 @@ "react-is": "^16.13.1" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", + "license": "MIT" + }, "node_modules/re-resizable": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.0.tgz", @@ -2714,6 +2767,35 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-beautiful-dnd/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/react-colorful": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", @@ -2757,6 +2839,37 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/react-remove-scroll": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", @@ -3075,6 +3188,12 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index bb0dd5efebb77d292dc34690294010f00b91bf1b..677b5e5d7d362445c210dc426db142e9a56c5c5f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@wordpress/block-editor": "^14.13.0", "@wordpress/components": "^29.4.0", "@wordpress/i18n": "^5.18.0", - "react": "^18.3.1" + "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1" } } diff --git a/src/AlignButtons/AlignButtons.jsx b/src/AlignButtons/AlignButtons.jsx new file mode 100644 index 0000000000000000000000000000000000000000..03bebeaeda1e2d79660d4b69195b6259439a3b95 --- /dev/null +++ b/src/AlignButtons/AlignButtons.jsx @@ -0,0 +1,35 @@ +import {Button} from "@wordpress/components"; +export default function AlignButtons ({label, onChange}) { + + + const handleChange = (newAlignment) => { + if (onChange) { + onChange(newAlignment); + } + } + + return ( + <div className='fi-field-wrapper'> + <div className='fi-field-label-wrapper'> + <span className='fi-field-label'>{label}</span> + </div> + <div className='fi-field-buttons-wrapper fi-alignbuttons-wrapper'> + <Button + icon="editor-alignleft" + className="has-dashicons" + onClick={() => handleChange('left')} + /> + <Button + icon="editor-aligncenter" + className="has-dashicons" + onClick={() => handleChange('center')} + /> + <Button + icon="editor-alignright" + className="has-dashicons" + onClick={() => handleChange('right')} + /> + </div> + </div> + ); +} diff --git a/src/BlockPlaceholders/BlockPlaceholders.jsx b/src/BlockPlaceholders/BlockPlaceholders.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9728205e63f473d9b795268980daeb21dc7fff38 --- /dev/null +++ b/src/BlockPlaceholders/BlockPlaceholders.jsx @@ -0,0 +1,16 @@ +import {__} from "@wordpress/i18n"; +import {PanelBody} from "@wordpress/components"; +import Placeholder from "./Placeholder"; + +export default function BlockPlaceholders({placeholders, children}) { + return ( + <PanelBody title={__('Placeholders', 'flexible-invoices-core')}> + {children && <p className='fi-placeholders-list-description'>{children}</p>} + { + placeholders.map((placeholder) => { + return <Placeholder label={placeholder.label}>{placeholder.description}</Placeholder> + }) + } + </PanelBody> + ); +} diff --git a/src/BlockPlaceholders/Placeholder.jsx b/src/BlockPlaceholders/Placeholder.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0062dd5ac426e69b7436ea4627c81408d3b3f835 --- /dev/null +++ b/src/BlockPlaceholders/Placeholder.jsx @@ -0,0 +1,9 @@ +export default function Placeholder({label, children}) { + return ( + <div> + <input type='text' disabled value={label}/> + <p className='fi-placeholders-list-description'>{children}</p> + <hr/> + </div> + ); +} diff --git a/src/ColorSelector/ColorSelector.jsx b/src/ColorSelector/ColorSelector.jsx new file mode 100644 index 0000000000000000000000000000000000000000..18452f03e603ca83420eee64e54dc788868f8822 --- /dev/null +++ b/src/ColorSelector/ColorSelector.jsx @@ -0,0 +1,58 @@ +import {ColorPicker} from "@wordpress/components"; +import {useState} from "react"; + +export default function ColorSelector({label, onChange, value, enableAlpha = true, returnFormat = 'rgb'}) { + + const [isPopupActive, setIsPopupActive] = useState(false); + + const handleColorChange = function (newColor) { + let color = newColor; + if (returnFormat === 'hex') { + color = newColor.hex; + } else if (returnFormat === 'hsv') { + color = newColor.hsv; + } else if (returnFormat === 'hsl') { + color = newColor.hsl; + } else if (returnFormat === 'rgb') { + if (enableAlpha === true) { + color = `rgba(${newColor.rgb.r}, ${newColor.rgb.g}, ${newColor.rgb.b}, ${newColor.rgb.a})`; + } else { + color = `rgb(${newColor.rgb.r}, ${newColor.rgb.g}, ${newColor.rgb.b})`; + } + } + + + onChange(color); + } + + return ( + <div className='fi-field-wrapper'> + <div className='fi-field-label-wrapper'> + <span className='fi-field-label'>{label}</span> + </div> + <div className='fi-field-buttons-wrapper fi-colorpicker-wrapper fi-input-with-columns' style={{ + width: '216px', //Width of the ColorPicker element + border: '1px solid #8c8f94', + borderRadius: '0px', + }}> + <div className='fi-color-preview' style={{ + background: value, + height: '32px', + width: '100%' + }} + onClick={() => { + setIsPopupActive((isPopupActive) => !isPopupActive) + }} + ></div> + </div> + {isPopupActive && + <div className='fi-field-buttons-wrapper fi-colorpicker-wrapper'> + <ColorPicker + enableAlpha={enableAlpha} + color={value} + onChangeComplete={handleColorChange} + /> + </div>} + </div> + ); +} diff --git a/src/DuoInput/DuoInput.jsx b/src/DuoInput/DuoInput.jsx new file mode 100644 index 0000000000000000000000000000000000000000..81b6e0eb7ba66aa39e7632633516a2d6b85c34a3 --- /dev/null +++ b/src/DuoInput/DuoInput.jsx @@ -0,0 +1,38 @@ +import NumberWithUnit from "../NumberWithUnit/NumberWithUnit"; + +export default function DuoInput({label, onChange, values, layout = 'column'}) { + + const keys = Object.keys(values); + const valuesArray = Object.values(values); + const handleChange = (side, value) => { + const changedSide = keys[side]; + const newValues = {...values, [changedSide]: value}; + + if (onChange) { + onChange(newValues); + } + }; + return ( + <div className='fi-field-wrapper'> + <div className='fi-field-label-wrapper'> + <span className='fi-field-label'>{label}</span> + </div> + <div className='fi-field-buttons-wrapper fi-duo-fields-wrapper'> + <div className='fi-duo-fields' style={{ display: 'flex', flexDirection: layout}}> + <NumberWithUnit + className='fi-duo-field fi-duo-field-input' + value={valuesArray[0]} + onChange={(e) => { + handleChange(0, e) + }} + /> + <NumberWithUnit + className='fi-duo-field fi-duo-field-input' + value={valuesArray[1]} + onChange={(e) => handleChange(1, e)} + /> + </div> + </div> + </div> + ); +} diff --git a/src/FlexibleTable/FlexibleTable.jsx b/src/FlexibleTable/FlexibleTable.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c9ad42965bb17fdcb58c4b3d3cff1089229f837f --- /dev/null +++ b/src/FlexibleTable/FlexibleTable.jsx @@ -0,0 +1,415 @@ +import {__} from '@wordpress/i18n'; +import {InspectorControls, useBlockProps,} from '@wordpress/block-editor'; +import {PanelBody, ToggleControl} from '@wordpress/components'; +import {DragDropContext, Draggable, Droppable} from "react-beautiful-dnd"; +import {DuoInput, NumberWithUnit, ColorSelector, FontStyles, AlignButtons, BlockPlaceholders} from "../../index"; +import TableBody from "./TableBody"; +import TableHead from "./TableHead"; + +export default function FlexibleTable({ + attributes, + setAttributes, + placeholders, + additionalOptions, + tableHeader, + tableBody, + tableFooter, + }) { + const { + headers, + rows, + states, + + headerBackground, + headerTextColor, + headerTextAlign, + headerFontWeight, + headerFontStyle, + headerTextDecoration, + headerFontSize, + + tableBorderColor, + tableBorderWidth, + + bodyBorderColumnColor, + bodyBorderColumnWidthLeft, + bodyBorderColumnWidthRight, + bodyBorderRowColor, + bodyBorderRowWidthTop, + bodyBorderRowWidthBottom, + + headerBorderColumnColor, + headerBorderColumnWidthLeft, + headerBorderColumnWidthRight, + headerBorderRowColor, + headerBorderRowWidthTop, + headerBorderRowWidthBottom, + + bodyBackground, + multipleBodyBackground, + bodyBackgroundEven, + bodyTextColorEven, + bodyTextColor, + bodyTextAlign, + bodyFontWeight, + bodyFontStyle, + bodyTextDecoration, + bodyFontSize, + } = attributes; + + const blockProps = useBlockProps(); + + const handleMultipleAttributesChange = (newValues) => { + setAttributes({...attributes, ...newValues}); + } + + const updateHeader = (index, value) => { + const newHeaders = [...headers]; + newHeaders[index] = value; + setAttributes({headers: newHeaders}); + }; + + const updateCell = (index, value) => { + const newRows = [...rows]; + newRows[index] = value; + setAttributes({rows: newRows}); + }; + + const toggleColumn = (index, value) => { + const newStates = [...states]; + newStates[index] = value; + setAttributes({states: newStates}); + }; + + const handleDragEnd = (result) => { + if (!result.destination) { + return; + } + + const {source, destination} = result; + const newHeaders = [...headers]; + const newStates = [...states]; + const newRows = [...rows]; + + const [movedHeader] = newHeaders.splice(source.index, 1); + newHeaders.splice(destination.index, 0, movedHeader); + + const [movedState] = newStates.splice(source.index, 1); + newStates.splice(destination.index, 0, movedState); + + const [movedRow] = newRows.splice(source.index, 1); + newRows.splice(destination.index, 0, movedRow); + + setAttributes({ + headers: newHeaders, + states: newStates, + rows: newRows + }); + }; + + return ( + <> + <InspectorControls> + <PanelBody title={__("Table columns", "flexible-invoices-core")}> + <DragDropContext onDragEnd={handleDragEnd}> + <Droppable droppableId="table-columns"> + {(provided) => ( + <ul + {...provided.droppableProps} + ref={provided.innerRef} + style={{padding: 0, listStyle: "none"}} + > + {headers.map((header, index) => ( + <Draggable key={header} draggableId={header} index={index}> + {(provided) => ( + <li + ref={provided.innerRef} + {...provided.draggableProps} + {...provided.dragHandleProps} + className="fi-settings-toggle" + style={{ + ...provided.draggableProps.style, + }} + > + <div className="fi-settings-toggle-header"> + <span className="dashicons dashicons-ellipsis"></span> + <span>{header}</span> + </div> + <ToggleControl + checked={states[index]} + onChange={(newValue) => toggleColumn(index, newValue)} + /> + </li> + )} + </Draggable> + ))} + {provided.placeholder} + </ul> + )} + </Droppable> + </DragDropContext> + </PanelBody> + <PanelBody title={__('Header appearance', 'flexible-invoices-core')}> + <ColorSelector + label={__('Text Color', 'flexible-invoices-core')} + value={headerTextColor} + enableAlpha={false} + onChange={(value) => setAttributes({headerTextColor: value})} + /> + <ColorSelector + label={__('Background Color', 'flexible-invoices-core')} + value={headerBackground} + onChange={(value) => setAttributes({headerBackground: value})} + /> + <NumberWithUnit + label={__('Font Size', 'flexible-invoices-core')} + value={headerFontSize} + onChange={(value) => setAttributes({headerFontSize: value})} + /> + <AlignButtons + label={__('Text Align', 'flexible-invoices-core')} + onChange={(value) => setAttributes({headerTextAlign: value})} + /> + <FontStyles + label={__('Font style', 'flexible-invoices-core')} + onChange={(newFontStyles) => setAttributes({ + ...attributes, + headerFontStyle: newFontStyles.fontStyle, + headerFontWeight: newFontStyles.fontWeight, + headerTextDecoration: newFontStyles.textDecoration + })} + values={ + { + fontStyle: headerFontStyle, + fontWeight: headerFontWeight, + textDecoration: headerTextDecoration + } + } + /> + + <ColorSelector + label={__('Table border color', 'flexible-invoices-core')} + value={tableBorderColor} + enableAlpha={false} + returnFormat={'hex'} + onChange={(value) => setAttributes({tableBorderColor: value})} + /> + + <NumberWithUnit + label={__('Table border width', 'flexible-invoices-core')} + value={tableBorderWidth} + onChange={(value) => setAttributes({tableBorderWidth: value})} + /> + + <ColorSelector + label={__('Border row color', 'flexible-invoices-core')} + value={headerBorderRowColor} + enableAlpha={false} + returnFormat={'hex'} + onChange={(value) => setAttributes({headerBorderRowColor: value})} + /> + + <DuoInput + label={__("Border row width", 'flexible-invoices-core')} + values={ + { + headerBorderRowWidthTop, + headerBorderRowWidthBottom, + } + } + onChange={handleMultipleAttributesChange} + /> + <ColorSelector + label={__('Border cell color', 'flexible-invoices-core')} + value={headerBorderColumnColor} + enableAlpha={false} + returnFormat={'hex'} + onChange={(value) => setAttributes({headerBorderColumnColor: value})} + /> + <DuoInput + label={__("Border cell width", 'flexible-invoices-core')} + values={ + { + headerBorderColumnWidthLeft, + headerBorderColumnWidthRight, + } + } + layout={'row'} + onChange={handleMultipleAttributesChange} + /> + + </PanelBody> + <PanelBody title={__('Body appearance', 'flexible-invoices-core')}> + <ColorSelector + label={__('Text color', 'flexible-invoices-core')} + value={bodyTextColor} + enableAlpha={false} + onChange={(value) => setAttributes({bodyTextColor: value})} + /> + <ColorSelector + label={__('Background color', 'flexible-invoices-core')} + value={bodyBackground} + enableAlpha={false} + onChange={(value) => setAttributes({bodyBackground: value})} + /> + + <ToggleControl + label={__('Duo color table', 'flexible-invoices-core')} + checked={multipleBodyBackground} + onChange={(newValue) => setAttributes({multipleBodyBackground: newValue})} + /> + + {multipleBodyBackground ? + <> + <ColorSelector + label={__('Background color (even rows)', 'flexible-invoices-core')} + value={bodyBackgroundEven} + onChange={(value) => setAttributes({bodyBackgroundEven: value})} + /> + <ColorSelector + label={__('Text color (even rows)', 'flexible-invoices-core')} + value={bodyTextColorEven} + enableAlpha={false} + onChange={(value) => setAttributes({bodyTextColorEven: value})} + /> + </> + : '' + + } + + <NumberWithUnit + label={__('Font size', 'flexible-invoices-core')} + value={bodyFontSize} + onChange={(value) => setAttributes({bodyFontSize: value})} + /> + <AlignButtons + label={__('Text align', 'flexible-invoices-core')} + onChange={(value) => setAttributes({bodyTextAlign: value})} + /> + <FontStyles + label={__('Font style', 'flexible-invoices-core')} + onChange={(newFontStyles) => setAttributes({ + ...attributes, + bodyFontStyle: newFontStyles.fontStyle, + bodyFontWeight: newFontStyles.fontWeight, + bodyTextDecoration: newFontStyles.textDecoration + })} + values={ + { + fontStyle: bodyFontStyle, + fontWeight: bodyFontWeight, + textDecoration: bodyTextDecoration + } + } + /> + <ColorSelector + label={__('Border row color', 'flexible-invoices-core')} + value={bodyBorderRowColor} + enableAlpha={false} + returnFormat={'hex'} + onChange={(value) => setAttributes({bodyBorderRowColor: value})} + /> + <DuoInput + label={__("Border row width", 'flexible-invoices-core')} + values={ + { + bodyBorderRowWidthTop, + bodyBorderRowWidthBottom, + } + } + onChange={handleMultipleAttributesChange} + /> + <ColorSelector + label={__('Border cell color', 'flexible-invoices-core')} + value={bodyBorderColumnColor} + enableAlpha={false} + returnFormat={'hex'} + onChange={(value) => setAttributes({bodyBorderColumnColor: value})} + /> + <DuoInput + label={__("Border cell width", 'flexible-invoices-core')} + values={ + { + bodyBorderColumnWidthRight, + bodyBorderColumnWidthLeft, + } + } + layout={'row'} + onChange={handleMultipleAttributesChange} + /> + </PanelBody> + {additionalOptions} + {placeholders && <BlockPlaceholders + placeholders={placeholders} + />} + </InspectorControls> + <div {...blockProps}> + <table className="fitb-item-table has-background" style={{ + borderStyle: "solid", + borderColor: tableBorderColor, + borderWidth: tableBorderWidth, + borderCollapse: 'collapse' + }}> + {tableHeader ? tableHeader : + <TableHead + headers={headers} + updateHeader={updateHeader} + tableStyles={{ + headerBackground, + headerTextColor, + headerTextAlign, + headerFontStyle, + headerTextDecoration, + headerFontSize, + headerFontWeight, + + headerBorderColumnColor, + headerBorderColumnWidthLeft, + headerBorderColumnWidthRight, + headerBorderRowColor, + headerBorderRowWidthTop, + headerBorderRowWidthBottom + }} + states={states} + /> + } + { + tableBody ? tableBody : + <TableBody + rows={rows} + states={states} + updateCell={updateCell} + tableStyles={ + { + bodyBackground, + bodyBackgroundEven, + bodyTextColor, + bodyTextColorEven, + bodyTextDecoration, + bodyTextAlign, + bodyFontStyle, + bodyFontSize, + bodyFontWeight, + + bodyBorderColumnColor, + bodyBorderColumnWidthLeft, + bodyBorderColumnWidthRight, + bodyBorderRowColor, + bodyBorderRowWidthTop, + bodyBorderRowWidthBottom, + + multipleBodyBackground + } + } + /> + } + {tableFooter && + <tfoot> + {tableFooter} + </tfoot> + } + </table> + </div> + </> + ); +} diff --git a/src/FlexibleTable/TableBody.jsx b/src/FlexibleTable/TableBody.jsx new file mode 100644 index 0000000000000000000000000000000000000000..24f0a66405472f3f84b9c5eba62dc813e584f172 --- /dev/null +++ b/src/FlexibleTable/TableBody.jsx @@ -0,0 +1,69 @@ +import {RichText} from "@wordpress/block-editor"; + + +export default function TableBody({tableStyles, updateCell, rows, states}) { + + const { + bodyBackground, + bodyBackgroundEven, + bodyTextColor, + bodyTextColorEven, + bodyTextDecoration, + bodyTextAlign, + bodyFontStyle, + bodyFontSize, + bodyFontWeight, + + bodyBorderColumnColor, + bodyBorderColumnWidthLeft, + bodyBorderColumnWidthRight, + bodyBorderRowColor, + bodyBorderRowWidthTop, + bodyBorderRowWidthBottom, + + multipleBodyBackground + } = tableStyles; + + return ( + <tbody className={"fitb-item-table-body"}> + + {Array.from({length: 2}).map((_, trIndex) => ( + <tr className={"fitb-item-table-row"} style={{ + background: (multipleBodyBackground && trIndex === 1) ? bodyBackgroundEven : bodyBackground, + borderStyle: "solid", + borderColor: bodyBorderRowColor, + borderTopWidth: bodyBorderRowWidthTop, + borderBottomWidth: bodyBorderRowWidthBottom, + }}> + {rows.map((row, index) => ( + states[index] ? + <td className={"fitb-item-table-cell"} key={index} + style={{ + borderStyle: "solid", + borderColor: bodyBorderColumnColor, + borderLeftWidth: bodyBorderColumnWidthLeft, + borderRightWidth: bodyBorderColumnWidthRight, + }}> + <RichText + tagName="div" + key={index} + value={row} + style={{ + color: (multipleBodyBackground && trIndex === 1) ? bodyTextColorEven : bodyTextColor, + textAlign: bodyTextAlign, + fontStyle: bodyFontStyle, + fontSize: bodyFontSize, + textDecoration: bodyTextDecoration, + fontWeight: bodyFontWeight, + }} + onChange={(value) => + updateCell(index, value) + } + /> + </td> : '' + ))} + </tr> + ))} + </tbody> + ); +} diff --git a/src/FlexibleTable/TableHead.jsx b/src/FlexibleTable/TableHead.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1b07ee2bb81a1295270960230c3d981bac835e39 --- /dev/null +++ b/src/FlexibleTable/TableHead.jsx @@ -0,0 +1,58 @@ +import {RichText} from "@wordpress/block-editor"; + +export default function TableHead( {headers, updateHeader, tableStyles, states} ) { + const { + headerBackground, + headerTextColor, + headerTextAlign, + headerFontStyle, + headerTextDecoration, + headerFontSize, + headerFontWeight, + + headerBorderColumnColor, + headerBorderColumnWidthLeft, + headerBorderColumnWidthRight, + headerBorderRowColor, + headerBorderRowWidthTop, + headerBorderRowWidthBottom, + } = tableStyles; + + return ( + <thead className={"fitb-item-table-header"} style={{ + borderStyle: "solid", + borderColor: headerBorderRowColor, + borderTopWidth: headerBorderRowWidthTop, + borderBottomWidth: headerBorderRowWidthBottom, + }}> + <tr className={"fitb-item-table-header-row"} style={{ + background: headerBackground, + }}> + {headers.map((header, index) => ( + states[index] ? + <th className={"fitb-item-table-header-cell"} key={index} + style={{ + borderStyle: "solid", + borderColor: headerBorderColumnColor, + borderLeftWidth: headerBorderColumnWidthLeft, + borderRightWidth: headerBorderColumnWidthRight, + }}> + <RichText + tagName="div" + key={index} + value={header} + style={{ + color: headerTextColor, + textAlign: headerTextAlign, + fontStyle: headerFontStyle, + textDecoration: headerTextDecoration, + fontSize: headerFontSize, + fontWeight: headerFontWeight, + }} + onChange={(value) => updateHeader(index, value)} + /> + </th> : '' + ))} + </tr> + </thead>); +} diff --git a/src/FontStyles/FontStyles.jsx b/src/FontStyles/FontStyles.jsx new file mode 100644 index 0000000000000000000000000000000000000000..897f720d7115d71bcc5b6230c60b00538304593b --- /dev/null +++ b/src/FontStyles/FontStyles.jsx @@ -0,0 +1,97 @@ +import {Button} from "@wordpress/components"; +import {useState} from "react"; + +export default function FontStyles({label, onChange, values} ) { + + const [isItalic, setIsItalic] = useState(values.fontStyle === 'italic'); + const [isBold, setIsBold] = useState(values.fontWeight === 'bold'); + const [isUnderline, setIsUnderline] = useState(values.textDecoration.includes('underline')); + const [isLineThrough, setIsLineThrough] = useState(values.textDecoration.includes('line-through')); + + const fontStyle = isItalic ? 'italic' : 'normal'; + const fontWeight = isBold ? 'bold' : 'normal'; + const textDecoration = isUnderline ? 'underline' : isLineThrough ? 'line-through' : 'none'; + + const handleChange = function (updatedValues) { + const newStyles = { + fontStyle: updatedValues.fontStyle || fontStyle, + fontWeight: updatedValues.fontWeight || fontWeight, + textDecoration: (updatedValues.textDecoration === '' ? 'none' : updatedValues.textDecoration) || textDecoration + }; + setIsItalic( newStyles.fontStyle === 'italic'); + setIsBold( newStyles.fontWeight === 'bold'); + setIsUnderline( newStyles.textDecoration.includes('underline')); + setIsLineThrough( newStyles.textDecoration.includes('line-through')); + + if (onChange) { + onChange(newStyles); + } + }; + + + const handleButtonPress = function (styleToChange) { + switch (styleToChange) { + case 'bold': + setIsBold(prev => { + const newValue = !prev; + handleChange({fontWeight: newValue ? 'bold' : 'normal'}); + return newValue; + }); + break; + case 'italic': + setIsItalic(prev => { + const newValue = !prev; + handleChange({fontStyle: newValue ? 'italic' : 'normal'}); + return newValue; + }); + break; + case 'underline': + setIsUnderline(prev => { + const newValue = !prev; + handleChange({textDecoration: (newValue ? 'underline ' : '') + (isLineThrough ? 'line-through' : '')}); + return newValue; + }); + break; + case 'line-through': + setIsLineThrough(prev => { + const newValue = !prev; + handleChange({textDecoration: (newValue ? 'line-through ' : '') + (isUnderline ? 'underline' : '')}); + return newValue; + }); + break; + default: + break; + } + }; + + + return ( + <div className='fi-field-wrapper'> + <div className='fi-field-label-wrapper'> + <span className='fi-field-label'>{label}</span> + </div> + <div className='fi-field-buttons-wrapper fi-fontstylingbuttons-wrapper'> + <Button + icon="editor-italic" + className={"has-dashicons" + (isItalic ? ' fi-btn-active' : '')} + onClick={() => handleButtonPress('italic')} + /> + <Button + icon="editor-bold" + className={"has-dashicons" + (isBold ? ' fi-btn-active' : '')} + onClick={() => handleButtonPress('bold')} + /> + <Button + icon="editor-strikethrough" + className={"has-dashicons" + (isLineThrough ? ' fi-btn-active' : '')} + onClick={() => handleButtonPress('line-through')} + /> + <Button + icon="editor-underline" + className={"has-dashicons" + (isUnderline ? ' fi-btn-active' : '')} + onClick={() => handleButtonPress('underline')} + /> + </div> + </div> + ); +} diff --git a/src/NumberWithUnit/NumberWithUnit.jsx b/src/NumberWithUnit/NumberWithUnit.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b4853afdd19f431804f6c3d4f863756b50d292a4 --- /dev/null +++ b/src/NumberWithUnit/NumberWithUnit.jsx @@ -0,0 +1,69 @@ +import {SelectControl, TextControl} from "@wordpress/components"; +import {useState} from "react"; + +export default function NumberWithUnit({label, onChange, value, className}) { + + const splitValues = function (fontSize) { + + if( !fontSize ) { + return {size: "0", unit: "px"}; + } + + const match = fontSize.match(/^(\d+)([a-z%]+)$/i); + + if (!match) { + return {size: "16", unit: "px"}; + } + return { + size: match[1], + unit: match[2] + }; + } + + const [unit, setUnit] = useState(splitValues(value).unit); + const [size, setSize] = useState(splitValues(value).size); + + + + const handleChange = (newSize, newUnit) => { + if (onChange) { + onChange(newSize + newUnit); + } + }; + + + return ( + <div className='fi-field-wrapper'> + <div className='fi-field-label-wrapper'> + <span className='fi-field-label'>{label}</span> + </div> + <div className={'fi-field-buttons-wrapper fi-input-with-columns fi-numberwithfields-wrapper' + ' ' + className}> + <TextControl + style={{flex: 3}} + type={'number'} + placeholder={'16'} + value={size} + onChange={(value) => { + setSize(value); + handleChange(value, unit); + }} + /> + <SelectControl + options={[ + {label: 'px', value: 'px'}, + {label: '%', value: '%'}, + {label: 'em', value: 'em'}, + {label: 'pt', value: 'pt'}, + ]} + style={{minWidth:'56px'}} + value={unit} + onChange={(value) => { + setUnit(value); + handleChange(size, value); + }} + + /> + </div> + </div> + ); +} diff --git a/src/QuatroInput/QuatroInput.jsx b/src/QuatroInput/QuatroInput.jsx new file mode 100644 index 0000000000000000000000000000000000000000..15e2795acd6b66bcb41b48a8bef817f6b842cb0e --- /dev/null +++ b/src/QuatroInput/QuatroInput.jsx @@ -0,0 +1,82 @@ +import NumberWithUnit from "../NumberWithUnit/NumberWithUnit"; + +export default function QuatroInput({label, onChange, values, layout = 'cross'}) { + + const keys = Object.keys(values); + const valuesArray = Object.values(values); + const handleChange = (side, value) => { + const changedSide = keys[side]; + const newValues = {...values, [changedSide]: value}; + + if (onChange) { + onChange(newValues); + } + }; + return ( + <div className='fi-field-wrapper'> + <div className='fi-field-label-wrapper'> + <span className='fi-field-label'>{label}</span> + </div> + {layout === 'cross' && + <div className='fi-field-buttons-wrapper fi-quatro-fields-wrapper'> + <div className='fi-quatro-fields'> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[0]} + onChange={(e) => { + handleChange(0, e) + }} + /> + </div> + <div className='fi-quatro-fields fields-mid'> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[1]} + onChange={(e) => handleChange(1, e)} + /> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[2]} + onChange={(e) => handleChange(2, e)} + /> + </div> + <div className='fi-quatro-fields'> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[3]} + onChange={(e) => handleChange(3, e)} + /> + </div> + </div>} + {layout === 'box' && + <div className='fi-field-buttons-wrapper fi-quatro-fields-wrapper'> + <div className='fi-quatro-fields'> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[0]} + onChange={(e) => { + handleChange(0, e) + }} + /> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[1]} + onChange={(e) => handleChange(1, e)} + /> + </div> + <div className='fi-quatro-fields fields-mid'> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[2]} + onChange={(e) => handleChange(2, e)} + /> + <NumberWithUnit + className='fi-quatro-field fi-quatro-field-input' + value={valuesArray[3]} + onChange={(e) => handleChange(3, e)} + /> + </div> + </div>} + </div> + ); +}