diff --git a/docs/source/recipes/color-picker-widget.md b/docs/source/recipes/color-picker-widget.md new file mode 100644 index 0000000000..18abdf4b69 --- /dev/null +++ b/docs/source/recipes/color-picker-widget.md @@ -0,0 +1,164 @@ +--- +myst: + html_meta: + "description": "How to use the color picker widget in blocks settings and forms" + "property=og:description": "How to use the color picker widget in blocks settings and forms" + "property=og:title": "How to use the color picker widget" + "keywords": "Volto, Plone, frontend, React, blocks, forms, widget, color, picker" +--- + +# Color picker widget + +Volto comes with a color picker widget that can be used in any Volto form. +It allows to pick a color from a preset list of colors. +This preset list of colors is passed using the `colors` prop. +You can [try a demo of the default color picker](https://6.docs.plone.org/storybook/?path=/story/edit-widgets-colorpicker--default). +You can combine the color picker widget with the {doc}`../blocks/block-style-wrapper` to have a powerful, yet simple way to manage color properties in your blocks. +You can use it either in your custom block's styles schema or enhance an existing block as follows: + +```{code-block} js +:emphasize-lines: 13-16, 31-42 +import { addStyling } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer'; +import { defineMessages } from 'react-intl'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + backgroundColor: { + id: 'Background color', + defaultMessage: 'Background color', + }, +}); + +export const defaultStylingSchema = ({ schema, formData, intl }) => { + const BG_COLORS = [ + { name: 'transparent', label: 'Transparent' }, + { name: 'grey', label: 'Grey' }, + ]; + + // You could allow passing the color definition from the config or from the default + // defined above + const colors = + config.blocks?.blocksConfig?.[formData['@type']]?.colors || BG_COLORS; + + // Same for the default used (or undefined) + const defaultBGColor = + config.blocks?.blocksConfig?.[formData['@type']]?.defaultBGColor; + + // This adds the StyleWrapper support to your block + addStyling({ schema, intl }); + + // Then we add the field to the fieldset inside the StyleWrapper `styles` field schema fieldset array + schema.properties.styles.schema.fieldsets[0].fields = [ + ...schema.properties.styles.schema.fieldsets[0].fields, + 'backGroundColor', + ]; + + // and finally, we add the field to the StyleWrapper `styles` object field schema properties + schema.properties.styles.schema.properties['backGroundColor'] = { + widget: 'color_picker', + title: intl.formatMessage(messages.backgroundColor), + colors, + default: defaultBGColor, + }; + + return schema; +}; +``` + +The color picker widget's discriminator is `color_picker`. + +## Color definitions + +```{versionchanged} 17.9.0 +Enhanced `ColorPickerWidget` with additional color definitions, saving it as an object instead of a string. +``` + +The `colors` property of the widget controls which colors are available to choose in the widget. +This is the signature of the object along with an example: + +```ts +type Color = + | { + name: string; + label: string; + style: Record<`--${string}`, string>; + } + | { + name: string; + label: string; + style: undefined; + }; + +const colors: Color[] = [ + { + name: 'red', + label: 'red', + style: { '--background-color': 'red' } }, + { + name: 'yellow', + label: 'yellow', + style: { '--background-color': 'yellow' }, + }, + { + name: 'green', + label: 'green' + }, +] +``` + +### Basic color definition + +The basic color definition is the one that saves a string as the widget value. +This string is the one defined by the `name` key. +You can use it on your own code by reading it from the resultant data and use it according your designed solution. + +When combined with the `StyleWrapper`, the value will be injected as a class name of the form `has--PROPERTY_NAME--PROPERTY_VALUE`: + +```html +
+ ... +
+``` + +Then you should create the CSS rules according to these injected class names. + +### Custom CSS properties as color definitions + +The `style` key defines a set of custom CSS properties to be added as the value to the HTML attribute `style`. +They will be injected by the `StyleWrapper` as style definitions, so you can use them in your CSS rules. + +```html +
+... +
+``` + +```css +.block.teaser { + background-color: var(--background-color, transparent); +} +``` + +The `name` key is mandatory in order to generate proper markup in the resultant HTML in both forms. + +You can also use this selector, where an element with class names `block` and `teaser` with a child element whose HTML attribute `style` contains the value of `--background-color`: + +```css +.block.teaser { + &[style*='--background-color'] { + padding: 20px 0; + } + ``` + +```{seealso} +See the MDN CSS Reference for selectors. + +- [Attribute selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) +- [`&` nesting selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector) +``` + + +## Use both basic and custom CSS properties definitions + +You can combine both basic and custom CSS properties definitions. +It's up to you how to mix and match them. diff --git a/docs/source/recipes/index.md b/docs/source/recipes/index.md index 8c8c15656f..d75154ba39 100644 --- a/docs/source/recipes/index.md +++ b/docs/source/recipes/index.md @@ -28,5 +28,6 @@ contextnavigation pluggables widget how-to-restrict-blocks +color-picker-widget ie11compat ``` diff --git a/packages/scripts/i18n.cjs b/packages/scripts/i18n.cjs index 52b6d84869..a19b98c5cf 100755 --- a/packages/scripts/i18n.cjs +++ b/packages/scripts/i18n.cjs @@ -30,7 +30,7 @@ function extractMessages() { // If so, we should do it in the config object or somewhere else // We also ignore the addons folder since they are populated using // their own locales files and taken care separatedly in this script - glob('src/**/*.js?(x)', { + glob('src/**/*.{js,jsx,ts,tsx}', { ignore: ['src/customizations/**', 'src/addons/**'], }), (filename) => { diff --git a/packages/scripts/news/5585.feature b/packages/scripts/news/5585.feature new file mode 100644 index 0000000000..7bc83eeb8a --- /dev/null +++ b/packages/scripts/news/5585.feature @@ -0,0 +1 @@ +Added support for TS/TSX files in i18n machinery. @sneridagh diff --git a/packages/volto/.storybook/main.js b/packages/volto/.storybook/main.js index c11cf59ece..76d33e53d4 100644 --- a/packages/volto/.storybook/main.js +++ b/packages/volto/.storybook/main.js @@ -149,4 +149,16 @@ module.exports = { core: { builder: 'webpack5', }, + typescript: { + check: false, + checkOptions: {}, + reactDocgen: 'react-docgen-typescript-plugin', + reactDocgenTypescriptOptions: { + compilerOptions: { + allowSyntheticDefaultImports: false, + esModuleInterop: false, + }, + propFilter: () => true, + }, + }, }; diff --git a/packages/volto/news/5585.feature b/packages/volto/news/5585.feature new file mode 100644 index 0000000000..7d116752d6 --- /dev/null +++ b/packages/volto/news/5585.feature @@ -0,0 +1 @@ +Enhanced `ColorPickerWidget` with additional color definitions, saving it as an object instead of a string. @sneridagh diff --git a/packages/volto/package.json b/packages/volto/package.json index 4b22e7a2ce..c8ceaa60b0 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -361,6 +361,7 @@ "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "8.0.1", "@types/jest": "^29.5.8", + "@types/lodash": "^4.14.201", "@types/react": "^17.0.52", "@types/react-dom": "^17", "@types/react-test-renderer": "18.0.1", @@ -377,6 +378,7 @@ "jest-environment-jsdom": "^26", "jsdom": "^16.7.0", "jsonwebtoken": "9.0.0", + "react-docgen-typescript-plugin": "^1.0.5", "react-error-overlay": "6.0.9", "react-is": "^16.13.1", "release-it": "^16.2.1", diff --git a/packages/volto/src/components/manage/Widgets/ColorPickerWidget.stories.jsx b/packages/volto/src/components/manage/Widgets/ColorPickerWidget.stories.jsx deleted file mode 100644 index c683b3eba4..0000000000 --- a/packages/volto/src/components/manage/Widgets/ColorPickerWidget.stories.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import ColorPickerWidget from './ColorPickerWidget'; -import WidgetStory from './story'; - -export const Default = WidgetStory.bind({ - widget: ColorPickerWidget, -}); - -Default.args = { - id: 'favoriteColor', - title: 'Favorite Color', - colors: [ - { name: 'red', label: 'red' }, - { name: 'yellow', label: 'yellow' }, - { name: 'green', label: 'green' }, - ], -}; - -export default { - title: 'Edit Widgets/ColorPicker', - component: Default, - decorators: [ - (Story) => ( -
- -
- ), - ], - argTypes: {}, -}; diff --git a/packages/volto/src/components/manage/Widgets/ColorPickerWidget.stories.tsx b/packages/volto/src/components/manage/Widgets/ColorPickerWidget.stories.tsx new file mode 100644 index 0000000000..3806dbfebd --- /dev/null +++ b/packages/volto/src/components/manage/Widgets/ColorPickerWidget.stories.tsx @@ -0,0 +1,48 @@ +import ColorPickerWidget from './ColorPickerWidget'; +import WidgetStory from './story'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + title: 'Edit Widgets/ColorPicker', + component: WidgetStory.bind({ + widget: ColorPickerWidget, + }), + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + id: 'favoriteColor', + title: 'Favorite Color', + colors: [ + { name: 'red', label: 'red' }, + { name: 'yellow', label: 'yellow' }, + { name: 'green', label: 'green' }, + ], + }, +}; + +export const WithEnhancedStyleConfig: Story = { + args: { + id: 'favoriteColor', + title: 'Favorite Color', + colors: [ + { name: 'red', label: 'red', style: { '--background-color': 'red' } }, + { + name: 'yellow', + label: 'yellow', + style: { '--background-color': 'yellow' }, + }, + { name: 'green', label: 'green' }, + ], + }, +}; diff --git a/packages/volto/src/components/manage/Widgets/ColorPickerWidget.jsx b/packages/volto/src/components/manage/Widgets/ColorPickerWidget.tsx similarity index 61% rename from packages/volto/src/components/manage/Widgets/ColorPickerWidget.jsx rename to packages/volto/src/components/manage/Widgets/ColorPickerWidget.tsx index 97c88c99be..9ec043f946 100644 --- a/packages/volto/src/components/manage/Widgets/ColorPickerWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/ColorPickerWidget.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Form } from 'semantic-ui-react'; import { Grid, Button } from 'semantic-ui-react'; +import { isEqual } from 'lodash'; import { defineMessages, useIntl } from 'react-intl'; const messages = defineMessages({ @@ -11,7 +11,31 @@ const messages = defineMessages({ }, }); -const ColorPickerWidget = (props) => { +type Color = + | { + name: string; + label: string; + style: Record<`--${string}`, string>; + } + | { + name: string; + label: string; + style: undefined; + }; + +export type ColorPickerWidgetProps = { + id: string; + title: string; + value: string; + default: string; + required: boolean; + missing_value: unknown; + className: string; + onChange: (id: string, value: any) => void; + colors: Color[]; +}; + +const ColorPickerWidget = (props: ColorPickerWidgetProps) => { const { id, title, required, value, onChange, colors, className } = props; const intl = useIntl(); @@ -30,6 +54,7 @@ const ColorPickerWidget = (props) => { className={className} id={'field-' + id} > + {/* @ts-ignore */} {
{colors.map((color) => { + let colorValue: string | Color['style']; + const colorName = color.name; + if (color.style !== undefined) { + colorValue = color.style; + } else { + colorValue = color.name; + } return (