Skip to content

Commit

Permalink
Added extra widgets.
Browse files Browse the repository at this point in the history
  • Loading branch information
robgietema committed Sep 30, 2024
1 parent 5e3fb52 commit fcf4777
Show file tree
Hide file tree
Showing 13 changed files with 851 additions and 7 deletions.
210 changes: 210 additions & 0 deletions packages/volto/src/components/manage/Widgets/CheckboxGroupWidget.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/**
* CheckboxGroupWidget component.
* @module components/manage/Widgets/CheckboxGroupWidget
*/

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { filter, includes, map, without } from 'lodash';
import { injectIntl } from 'react-intl';
import {
getVocabFromHint,
getVocabFromField,
getVocabFromItems,
} from '@plone/volto/helpers';
import { Checkbox } from 'semantic-ui-react';
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
import { getVocabulary, getVocabularyTokenTitle } from '@plone/volto/actions';

/**
* CheckboxGroupWidget component class.
* @function CheckboxGroupWidget
* @returns {string} Markup of the component.
*/
class CheckboxGroupWidget extends Component {
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
static propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string,
required: PropTypes.bool,
filterChoices: PropTypes.arrayOf(PropTypes.string),
error: PropTypes.arrayOf(PropTypes.string),
getVocabulary: PropTypes.func.isRequired,
getVocabularyTokenTitle: PropTypes.func.isRequired,
choices: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
),
items: PropTypes.shape({
vocabulary: PropTypes.object,
}),
widgetOptions: PropTypes.shape({
vocabulary: PropTypes.object,
}),
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.bool,
PropTypes.func,
PropTypes.array,
]),
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onClick: PropTypes.func,
onEdit: PropTypes.func,
onDelete: PropTypes.func,
};

/**
* Default properties
* @property {Object} defaultProps Default properties.
* @static
*/
static defaultProps = {
description: null,
required: false,
filterChoices: null,
items: {
vocabulary: null,
},
widgetOptions: {
vocabulary: null,
},
error: [],
choices: [],
value: null,
onChange: () => {},
onBlur: () => {},
onClick: () => {},
onEdit: null,
onDelete: null,
};

/**
* Component did mount
* @method componentDidMount
* @returns {undefined}
*/
componentDidMount() {
if (
(!this.props.choices || this.props.choices?.length === 0) &&
this.props.vocabBaseUrl
) {
this.props.getVocabulary({
vocabNameOrURL: this.props.vocabBaseUrl,
size: -1,
subrequest: this.props.lang,
});
}
}

componentDidUpdate(prevProps) {
if (
this.props.vocabBaseUrl !== prevProps.vocabBaseUrl &&
(!this.props.choices || this.props.choices?.length === 0)
) {
this.props.getVocabulary({
vocabNameOrURL: this.props.vocabBaseUrl,
size: -1,
subrequest: this.props.lang,
});
}
}

/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
const { id, choices, value, onChange, filterChoices, additionalChoices } =
this.props;

let options = this.props.vocabBaseUrl
? this.props.choices
: [
...map(choices, (option) => ({
value: option[0],
label:
// Fix "None" on the serializer, to remove when fixed in p.restapi
option[1] !== 'None' && option[1] ? option[1] : option[0],
})),
];

if (additionalChoices) {
options = [...(options || []), ...additionalChoices];
}

if (filterChoices) {
options = filter(options, (item) => filterChoices.includes(item.value));
}

return (
<FormFieldWrapper {...this.props}>
{options.map((option) => (
<Checkbox
label={option.label}
name={id}
value={option.value}
checked={includes(value, option.value)}
onChange={(e, data) => {
const newValue = value || [];
if (data.checked) {
onChange(id, [...newValue, data.value]);
} else {
onChange(id, without(newValue, data.value));
}
}}
/>
))}
</FormFieldWrapper>
);
}
}

export const CheckboxGroupWidgetComponent = injectIntl(CheckboxGroupWidget);

export default compose(
connect(
(state, props) => {
const vocabBaseUrl = !props.choices
? getVocabFromHint(props) ||
getVocabFromField(props) ||
getVocabFromItems(props)
: '';

const vocabState =
state.vocabularies?.[vocabBaseUrl]?.subrequests?.[state.intl.locale];

// If the schema already has the choices in it, then do not try to get the vocab,
// even if there is one
if (props.choices) {
return {
choices: props.choices,
lang: state.intl.locale,
};
} else if (vocabState) {
return {
vocabBaseUrl,
choices: vocabState?.items ?? [],
lang: state.intl.locale,
};
// There is a moment that vocabState is not there yet, so we need to pass the
// vocabBaseUrl to the component.
} else if (vocabBaseUrl) {
return {
vocabBaseUrl,
lang: state.intl.locale,
};
}
return { lang: state.intl.locale };
},
{ getVocabulary, getVocabularyTokenTitle },
),
)(CheckboxGroupWidgetComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import CheckboxGroupWidget, {
CheckboxGroupWidgetComponent,
} from './CheckboxGroupGroupWidget';

Check warning on line 4 in packages/volto/src/components/manage/Widgets/CheckboxGroupWidget.stories.jsx

View workflow job for this annotation

GitHub Actions / ESlint

Unable to resolve path to module './CheckboxGroupGroupWidget'
import WidgetStory from './story';

export const Default = WidgetStory.bind({
widget: CheckboxGroupWidget,
});
Default.args = {
id: 'field-empty',
title: 'field 1 title',
description: 'Optional help text',
choices: [
['Foo', 'Foo'],
['Bar', 'Bar'],
['FooBar', 'FooBar'],
],
};

export default {
title: 'Edit Widgets/Checkbox Group Widget',
component: CheckboxGroupWidgetComponent,
decorators: [
(Story) => (
<div style={{ width: '400px' }}>
<Story />
</div>
),
],
argTypes: {
// controlled value prop
value: {
control: {
disable: true,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { waitFor, render, screen } from '@testing-library/react';

import CheckboxGroupWidget from './CheckboxGroupWidget';

const mockStore = configureStore();

jest.mock('@plone/volto/helpers/Loadable/Loadable');
beforeAll(
async () =>
await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
);

test('renders a checkbox group widget component', async () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
vocabularies: {
'plone.app.vocabularies.Keywords': {
items: [{ title: 'My item', value: 'myitem' }],
itemsTotal: 1,
},
},
});

const { container } = render(
<Provider store={store}>
<CheckboxGroupWidget
id="my-field"
title="My field"
fieldSet="default"
onChange={() => {}}
onBlur={() => {}}
onClick={() => {}}
/>
</Provider>,
);

await waitFor(() => screen.getByText('My field'));
expect(container).toMatchSnapshot();
});
Loading

0 comments on commit fcf4777

Please sign in to comment.