Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ConditionBuilder): customize primary conditions using custom statement configuration #6663

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,18 @@ const ConditionBuilderTemplate = (args) => {
* TODO: Declare one or more stories, generally one per design scenario.
* NB no need for a 'Playground' because all stories have all controls anyway.
*/
const statementConfigCustom = [
{
id: 'if',
connector: 'and',
label: 'if',
},
{
id: 'exclIf',
connector: 'or',
label: 'excl. if',
},
];
export const conditionBuilder = ConditionBuilderTemplate.bind({});
conditionBuilder.storyName = 'Condition Builder';
conditionBuilder.args = {
Expand Down Expand Up @@ -249,6 +261,17 @@ conditionBuilderWithInitialState.args = {
translateWithId: translateWithId,
};

export const conditionBuilderWithCustomStatements =
ConditionBuilderTemplate.bind({});
conditionBuilderWithCustomStatements.storyName =
'With Custom statement configuration';
conditionBuilderWithCustomStatements.args = {
inputConfig: inputData,
variant: NON_HIERARCHICAL_VARIANT,
translateWithId: translateWithId,
statementConfigCustom: statementConfigCustom,
};

export const conditionBuilderWithActions = ConditionBuilderTemplate.bind({});
conditionBuilderWithActions.storyName = 'With Actions';
conditionBuilderWithActions.args = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,68 @@ describe(componentName, () => {
expect(selectedItem);
});

it('check with custom statement configuration ', async () => {
const statementConfigCustom = [
{
id: 'if',
connector: 'and',
label: 'if',
},
{
id: 'exclIf',
connector: 'or',
label: 'excl. if',
},
];

render(
<ConditionBuilder
{...defaultProps}
inputConfig={inputData}
statementConfigCustom={statementConfigCustom}
/>
);

// add one condition
await act(() => userEvent.click(screen.getByText('Add condition')));

expect(screen.getByRole('option', { name: 'Continent' }));

await act(() =>
userEvent.click(screen.getByRole('option', { name: 'Continent' }))
);

expect(screen.getByRole('option', { name: 'is' }));

await act(() =>
userEvent.click(screen.getByRole('option', { name: 'is' }))
);

expect(screen.getByRole('option', { name: 'Africa' }));

await act(() =>
userEvent.click(screen.getByRole('option', { name: 'Africa' }))
);

const selectedItem = screen.getByRole('button', { name: 'Africa' });

expect(selectedItem);

//change statement option

expect(screen.getByRole('button', { name: 'if' }));
await act(() =>
userEvent.click(screen.getByRole('button', { name: 'if' }))
);
expect(screen.getByRole('option', { name: 'if (and)' }));
expect(screen.getByRole('option', { name: 'excl. if (or)' }));

await act(() =>
userEvent.click(screen.getByRole('option', { name: 'excl. if (or)' }))
);
expect(screen.getByRole('button', { name: 'excl. if' }));
});

// keyboard navigation tests
//for Non-Hierarchical variant
it('add and remove conditions using keyboard', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export let ConditionBuilder = React.forwardRef(
variant = NON_HIERARCHICAL_VARIANT,
actions,
translateWithId,
statementConfigCustom,
...rest
}: ConditionBuilderProps,
ref: ForwardedRef<HTMLDivElement>
Expand All @@ -84,6 +85,7 @@ export let ConditionBuilder = React.forwardRef(
variant={variant}
translateWithId={translateWithId}
conditionBuilderRef={conditionBuilderRef}
statementConfigCustom={statementConfigCustom}
>
<div
{
Expand Down Expand Up @@ -145,6 +147,7 @@ ConditionBuilder.propTypes = {
* Provide an optional class to be applied to the containing node.
*/
className: PropTypes.string,

/**
* This is a callback that gives back the updated action state
*/
Expand Down Expand Up @@ -265,6 +268,18 @@ ConditionBuilder.propTypes = {
* Provide a label to the button that starts condition builder
*/
startConditionLabel: PropTypes.string,
/**
* Optional prop for passing custom configuration for statement option from default op
*/
/**@ts-ignore */
statementConfigCustom: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
connector: PropTypes.oneOf(['and', 'or']).isRequired,
label: PropTypes.string.isRequired,
secondaryLabel: PropTypes.string,
})
),
/**
* Optional prop, if you need to pass translations to the texts on the component instead of the defined defaults.
* This callback function will receive the message id and you need to return the corresponding text for that id.
Expand All @@ -273,6 +288,7 @@ ConditionBuilder.propTypes = {
*/
/**@ts-ignore */
translateWithId: PropTypes.func,

/* TODO: add types and DocGen for all props. */
/**
* Provide the condition builder variant: Non-Hierarchical/ Hierarchical
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ export type Operators = {
date: DateOperator;
};

export type option = {
id: string;
label: string;
icon?: CarbonIconType;
};
export type PropertyConfigOption = {
type: 'option';
config?: {
options?: {
id: string;
label: string;
icon?: CarbonIconType;
}[];
options?: option[];
};
};

Expand Down Expand Up @@ -170,6 +171,13 @@ export type Action = {

export type variantsType = 'Non-Hierarchical' | 'Hierarchical';

export type statementConfig = {
id: string;
connector: 'and' | 'or';
label: string;
secondaryLabel?: string;
};

export type ConditionBuilderProps = {
inputConfig: inputConfig;
initialState?: InitialState;
Expand All @@ -185,6 +193,7 @@ export type ConditionBuilderProps = {
startConditionLabel?: string;
variant?: 'Non-Hierarchical' | 'Hierarchical';
translateWithId: (id: string) => string;
statementConfigCustom: statementConfig[];
};

export type InitialState = {
Expand All @@ -202,6 +211,7 @@ export interface ConditionBuilderContextInputProps extends PropsWithChildren {
) => Promise<Option[]>;
variant?: string;
translateWithId?: (id: string) => string;
statementConfigCustom?: statementConfig[];

conditionBuilderRef?: ForwardedRef<HTMLDivElement>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const ConditionBuilderProvider: React.FC<
variant: props.variant,
translateWithId: props.translateWithId,
conditionBuilderRef: props.conditionBuilderRef,
statementConfigCustom: props.statementConfigCustom,
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
Option,
} from '../ConditionBuilder.types';
import { blockClass, getValue } from '../utils/util';
import { translationsObject } from '../ConditionBuilderContext/translationObject';

interface ConditionBuilderItemProps extends PropsWithChildren {
className?: string;
Expand Down Expand Up @@ -68,12 +69,22 @@ export const ConditionBuilderItem = ({
}: ConditionBuilderItemProps) => {
const popoverRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);

const { conditionBuilderRef, statementConfigCustom } = useContext(
ConditionBuilderContext
);

const statementIdMap = {
ifAll: 'if',
ifAny: 'if',
unlessAll: 'unless',
unlessAny: 'unless',
ifAll: translationsObject.ifText,
ifAny: translationsObject.ifText,
unlessAll: translationsObject.unlessText,
unlessAny: translationsObject.unlessText,
};
//Appending statements from custom statement configuration if present
statementConfigCustom?.forEach((statement) => {
statementIdMap[statement.id] = statement.label;
});

const [
invalidText,
addConditionText,
Expand All @@ -92,7 +103,7 @@ export const ConditionBuilderItem = ({
],
statementIdMap
);
const { conditionBuilderRef } = useContext(ConditionBuilderContext);

const getPropertyDetails = () => {
const { property, operator } = condition || {};
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ import { Checkmark } from '@carbon/react/icons';
import PropTypes from 'prop-types';
import { ConditionBuilderContext } from '../../ConditionBuilderContext/ConditionBuilderProvider';
import { useTranslations } from '../../utils/useTranslations';
import { PropertyConfigOption } from '../../ConditionBuilder.types';
import { option, statementConfig } from '../../ConditionBuilder.types';
import { blockClass } from '../../utils/util';

interface ItemOptionProps {
conditionState: {
label?: string;
value?: string;
};
config: PropertyConfigOption['config'] & { isStatement?: boolean };
config: { options?: option[] | statementConfig[] } & {
isStatement?: boolean;
};
onChange: (value: string, e: Event) => void;
}
export const ItemOption = ({
Expand Down Expand Up @@ -78,9 +80,9 @@ export const ItemOption = ({
return (
<div className={`${blockClass}__statement_wrapper`}>
<div>
{option.text1} ({option.connector})
{option.label} ({option.connector})
</div>
<div>{option.text2}</div>
<div>{option.secondaryLabel}</div>
</div>
);
};
Expand All @@ -104,7 +106,7 @@ export const ItemOption = ({
<ul aria-label={getAriaLabel()} role="listbox">
{filteredItems?.map((option) => {
const isSelected = selection === option.id;
const Icon = option.icon;
const Icon = (option as option).icon;

return (
<li
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { useContext } from 'react';
import { useTranslations } from './useTranslations';
import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider';

export const useDataConfigs = () => {
const [
Expand Down Expand Up @@ -46,35 +48,32 @@ export const useDataConfigs = () => {
'after',
'between',
]);
const { statementConfigCustom } = useContext(ConditionBuilderContext);

const statementConfig = [
const statementConfigDefault = [
{
label: 'ifText',
id: 'ifAll',
connector: 'and',
text1: ifAll,
text2: '(a && b)',
label: ifAll,
secondaryLabel: '(a && b)',
},
{
label: 'ifText',
id: 'ifAny',
connector: 'or',
text1: ifAny,
text2: '(a || b)',
label: ifAny,
secondaryLabel: '(a || b)',
},
{
label: 'unlessText',
id: 'unlessAll',
connector: 'and',
text1: unlessAll,
text2: '! (a && b)',
label: unlessAll,
secondaryLabel: '! (a && b)',
},
{
label: 'unlessText',
id: 'unlessAny',
connector: 'or',
text1: unlessAny,
text2: '! (a || b)',
label: unlessAny,
secondaryLabel: '! (a || b)',
},
];

Expand Down Expand Up @@ -153,7 +152,7 @@ export const useDataConfigs = () => {
];

return {
statementConfig,
statementConfig: statementConfigCustom ?? statementConfigDefault,
connectorConfig,
operatorConfig,
};
Expand Down
Loading