Skip to content

Commit

Permalink
Enhance ColorPickerWidget (#5585)
Browse files Browse the repository at this point in the history
Co-authored-by: Steve Piercy <web@stevepiercy.com>
Co-authored-by: ichim-david <ichim.david@gmail.com>
Co-authored-by: David Glick <david@glicksoftware.com>
  • Loading branch information
4 people committed Jan 10, 2024
1 parent 2342b63 commit 8898f12
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 84 deletions.
99 changes: 68 additions & 31 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,80 +48,117 @@ const defaultRazzleOptions = {
staticCssInDev: false,
emitOnErrors: false,
disableWebpackbar: false,
browserslist: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie 11', 'not dead']
browserslist: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie 11',
'not dead',
],
};
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
staticDirs: ['./static'],
webpackFinal: async (config, {
configType
}) => {
webpackFinal: async (config, { configType }) => {
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
// You can change the configuration based on that.
// 'PRODUCTION' is used when building the static version of storybook.

// Make whatever fine-grained changes you need
let baseConfig;
baseConfig = await createConfig('web', 'dev', {
// clearConsole: false,
modifyWebpackConfig: razzleConfig.modifyWebpackConfig,
plugins: razzleConfig.plugins
}, webpack, false, undefined, [], defaultRazzleOptions);
baseConfig = await createConfig(
'web',
'dev',
{
// clearConsole: false,
modifyWebpackConfig: razzleConfig.modifyWebpackConfig,
plugins: razzleConfig.plugins,
},
webpack,
false,
undefined,
[],
defaultRazzleOptions,
);
const AddonConfigurationRegistry = require('../addon-registry');
const registry = new AddonConfigurationRegistry(projectRootPath);
config = lessPlugin({
registry
registry,
}).modifyWebpackConfig({
env: {
target: 'web',
dev: 'dev'
dev: 'dev',
},
webpackConfig: config,
webpackObject: webpack,
options: {}
options: {},
});

// Put the SVG loader on top and prevent the asset/resource rule
// from processing the app's SVGs
config.module.rules.unshift(SVGLOADER);
const fileLoaderRule = config.module.rules.find(rule => rule.test.test('.svg'));
const fileLoaderRule = config.module.rules.find((rule) =>
rule.test.test('.svg'),
);
fileLoaderRule.exclude = /icons\/.*\.svg$/;
config.plugins.unshift(new webpack.DefinePlugin({
__DEVELOPMENT__: true,
__CLIENT__: true,
__SERVER__: false
}));
config.plugins.unshift(
new webpack.DefinePlugin({
__DEVELOPMENT__: true,
__CLIENT__: true,
__SERVER__: false,
}),
);
const resultConfig = {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
...baseConfig.resolve.alias
}
}
...baseConfig.resolve.alias,
},
},
};

// Addons have to be loaded with babel
const addonPaths = registry.addonNames.map(addon => fs.realpathSync(registry.packages[addon].modulePath));
resultConfig.module.rules[1].exclude = input =>
const addonPaths = registry.addonNames.map((addon) =>
fs.realpathSync(registry.packages[addon].modulePath),
);
resultConfig.module.rules[1].exclude = (input) =>
// exclude every input from node_modules except from @plone/volto
/node_modules\/(?!(@plone\/volto)\/)/.test(input) &&
// If input is in an addon, DON'T exclude it
!addonPaths.some(p => input.includes(p));
!addonPaths.some((p) => input.includes(p));
return resultConfig;
},
babel: async options => {
babel: async (options) => {
return {
...options,
plugins: [...options.plugins, ['./node_modules/babel-plugin-root-import/build/index.js', {
rootPathSuffix: './src'
}]]
plugins: [
...options.plugins,
[
'./node_modules/babel-plugin-root-import/build/index.js',
{
rootPathSuffix: './src',
},
],
],
// any extra options you want to set
};
},
core: {
builder: 'webpack5'
}
};
builder: 'webpack5',
},
typescript: {
check: false,
checkOptions: {},
reactDocgen: 'react-docgen-typescript-plugin',
reactDocgenTypescriptOptions: {
compilerOptions: {
allowSyntheticDefaultImports: false,
esModuleInterop: false,
},
propFilter: () => true,
},
},
};
164 changes: 164 additions & 0 deletions docs/source/recipes/color-picker-widget.md
Original file line number Diff line number Diff line change
@@ -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
<div class="has--backgroundColor--green">
...
</div>
```

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
<div class="block teaser" style="--background-color: red">
...
</div>
```

```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.
2 changes: 2 additions & 0 deletions docs/source/recipes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ appextras
contextnavigation
pluggables
widget
how-to-restrict-blocks
color-picker-widget
ie11compat
```
1 change: 1 addition & 0 deletions news/5585.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enhanced `ColorPickerWidget` with additional color definitions, saving it as an object instead of a string. @sneridagh
1 change: 1 addition & 0 deletions packages/scripts/news/5585.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for TS/TSX files in i18n machinery. @sneridagh
30 changes: 0 additions & 30 deletions src/components/manage/Widgets/ColorPickerWidget.stories.jsx

This file was deleted.

Loading

0 comments on commit 8898f12

Please sign in to comment.