From b23a81fce14d3d79a7e0a1a45c6f33356f835a70 Mon Sep 17 00:00:00 2001 From: Amal K Joy Date: Thu, 21 Nov 2024 16:05:37 +0530 Subject: [PATCH 1/4] feat: add support to customize primary conditions --- .../ConditionBuilder.stories.jsx | 13 +++++++++++++ .../ConditionBuilder/ConditionBuilder.tsx | 16 ++++++++++++++++ .../ConditionBuilder/ConditionBuilder.types.ts | 9 +++++++++ .../ConditionBuilderProvider.tsx | 1 + .../ConditionBuilder/utils/useDataConfigs.js | 7 +++++-- 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx index 1a535d4719..8d7d67d51d 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx @@ -220,11 +220,24 @@ 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', + text1: 'if', + }, + { + id: 'exclIf', + connector: 'or', + text1: 'excl. if', + }, +]; export const conditionBuilder = ConditionBuilderTemplate.bind({}); conditionBuilder.storyName = 'Condition Builder'; conditionBuilder.args = { inputConfig: inputData, variant: NON_HIERARCHICAL_VARIANT, + statementConfigCustom: statementConfigCustom, }; export const conditionBuilderDynamicOptions = ConditionBuilderTemplate.bind({}); diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx index 9faeb1d9a0..664a5efd1c 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx @@ -65,6 +65,7 @@ export let ConditionBuilder = React.forwardRef( variant = NON_HIERARCHICAL_VARIANT, actions, translateWithId, + statementConfigCustom, ...rest }: ConditionBuilderProps, ref: ForwardedRef @@ -84,6 +85,7 @@ export let ConditionBuilder = React.forwardRef( variant={variant} translateWithId={translateWithId} conditionBuilderRef={conditionBuilderRef} + statementConfigCustom={statementConfigCustom} >
string; + statementConfigCustom: statementConfig[]; }; export type InitialState = { @@ -202,6 +210,7 @@ export interface ConditionBuilderContextInputProps extends PropsWithChildren { ) => Promise; variant?: string; translateWithId?: (id: string) => string; + statementConfigCustom?: statementConfig[]; conditionBuilderRef?: ForwardedRef; } diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx index 143bab6d37..ec5eb70aee 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx @@ -60,6 +60,7 @@ export const ConditionBuilderProvider: React.FC< variant: props.variant, translateWithId: props.translateWithId, conditionBuilderRef: props.conditionBuilderRef, + statementConfigCustom: props.statementConfigCustom, }; return ( diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js b/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js index e8a61ab969..5acb083392 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js +++ b/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js @@ -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 [ @@ -46,8 +48,9 @@ export const useDataConfigs = () => { 'after', 'between', ]); + const { statementConfigCustom } = useContext(ConditionBuilderContext); - const statementConfig = [ + const statementConfigDefault = [ { label: 'ifText', id: 'ifAll', @@ -153,7 +156,7 @@ export const useDataConfigs = () => { ]; return { - statementConfig, + statementConfig: statementConfigCustom ?? statementConfigDefault, connectorConfig, operatorConfig, }; From 28b560b444602e72b6380f06f56070f2968eee72 Mon Sep 17 00:00:00 2001 From: Amal K Joy Date: Fri, 3 Jan 2025 15:23:40 +0530 Subject: [PATCH 2/4] fix: correct label text displayed for custom statements --- .../ConditionBuilder/ConditionBuilder.stories.jsx | 12 +++++++++++- .../ConditionBuilderItem/ConditionBuilderItem.tsx | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx index 8d7d67d51d..fbf72f7d99 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx @@ -237,7 +237,6 @@ conditionBuilder.storyName = 'Condition Builder'; conditionBuilder.args = { inputConfig: inputData, variant: NON_HIERARCHICAL_VARIANT, - statementConfigCustom: statementConfigCustom, }; export const conditionBuilderDynamicOptions = ConditionBuilderTemplate.bind({}); @@ -262,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 = { diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx index d1d01f7dcd..6a98ae175c 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx @@ -68,12 +68,21 @@ export const ConditionBuilderItem = ({ }: ConditionBuilderItemProps) => { const popoverRef = useRef(null); const [open, setOpen] = useState(false); + + const { conditionBuilderRef, statementConfigCustom } = useContext( + ConditionBuilderContext + ); const statementIdMap = { ifAll: 'if', ifAny: 'if', unlessAll: 'unless', unlessAny: 'unless', }; + //Appending statements from custom statement configuration if present + statementConfigCustom?.forEach((statement) => { + statementIdMap[statement.id] = statement.text1; + }); + const [ invalidText, addConditionText, @@ -92,7 +101,7 @@ export const ConditionBuilderItem = ({ ], statementIdMap ); - const { conditionBuilderRef } = useContext(ConditionBuilderContext); + const getPropertyDetails = () => { const { property, operator } = condition || {}; if ( From 8b3bb96b0b5bb3c0d2b71147038b3a8df0aa22cd Mon Sep 17 00:00:00 2001 From: Amal K Joy Date: Mon, 6 Jan 2025 14:10:23 +0530 Subject: [PATCH 3/4] fix: add test coverage --- .../ConditionBuilder/ConditionBuilder.test.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js index b53be1ec6d..38fab8b220 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js @@ -1622,6 +1622,68 @@ describe(componentName, () => { expect(selectedItem); }); + it('check with custom statement configuration ', async () => { + const statementConfigCustom = [ + { + id: 'if', + connector: 'and', + text1: 'if', + }, + { + id: 'exclIf', + connector: 'or', + text1: 'excl. if', + }, + ]; + + render( + + ); + + // 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 () => { From e26cabfe948b53a16004c5884e2a8efd484aa561 Mon Sep 17 00:00:00 2001 From: Amal K Joy Date: Mon, 6 Jan 2025 15:32:38 +0530 Subject: [PATCH 4/4] fix: type fix and refactor --- .../ConditionBuilder.stories.jsx | 4 ++-- .../ConditionBuilder/ConditionBuilder.test.js | 4 ++-- .../ConditionBuilder/ConditionBuilder.tsx | 4 ++-- .../ConditionBuilder.types.ts | 15 +++++++------- .../ConditionBuilderItem.tsx | 12 ++++++----- .../ConditionBuilderItemOption/ItemOption.tsx | 12 ++++++----- .../ConditionBuilder/utils/useDataConfigs.js | 20 ++++++++----------- 7 files changed, 36 insertions(+), 35 deletions(-) diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx index fbf72f7d99..4332fa5554 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx @@ -224,12 +224,12 @@ const statementConfigCustom = [ { id: 'if', connector: 'and', - text1: 'if', + label: 'if', }, { id: 'exclIf', connector: 'or', - text1: 'excl. if', + label: 'excl. if', }, ]; export const conditionBuilder = ConditionBuilderTemplate.bind({}); diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js index 38fab8b220..17a6ed8d7c 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js @@ -1627,12 +1627,12 @@ describe(componentName, () => { { id: 'if', connector: 'and', - text1: 'if', + label: 'if', }, { id: 'exclIf', connector: 'or', - text1: 'excl. if', + label: 'excl. if', }, ]; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx index 664a5efd1c..63300d1051 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx @@ -276,8 +276,8 @@ ConditionBuilder.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, connector: PropTypes.oneOf(['and', 'or']).isRequired, - text1: PropTypes.string.isRequired, - text2: PropTypes.string, + label: PropTypes.string.isRequired, + secondaryLabel: PropTypes.string, }) ), /** diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts index 33cad0701f..49f572490b 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts @@ -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[]; }; }; @@ -173,8 +174,8 @@ export type variantsType = 'Non-Hierarchical' | 'Hierarchical'; export type statementConfig = { id: string; connector: 'and' | 'or'; - text1: string; - text2?: string; + label: string; + secondaryLabel?: string; }; export type ConditionBuilderProps = { diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx index 6a98ae175c..a61bb1d110 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx @@ -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; @@ -72,15 +73,16 @@ export const ConditionBuilderItem = ({ 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.text1; + statementIdMap[statement.id] = statement.label; }); const [ diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.tsx index 83f421674c..35ef954f0d 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.tsx @@ -14,7 +14,7 @@ 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 { @@ -22,7 +22,9 @@ interface ItemOptionProps { label?: string; value?: string; }; - config: PropertyConfigOption['config'] & { isStatement?: boolean }; + config: { options?: option[] | statementConfig[] } & { + isStatement?: boolean; + }; onChange: (value: string, e: Event) => void; } export const ItemOption = ({ @@ -78,9 +80,9 @@ export const ItemOption = ({ return (
- {option.text1} ({option.connector}) + {option.label} ({option.connector})
-
{option.text2}
+
{option.secondaryLabel}
); }; @@ -104,7 +106,7 @@ export const ItemOption = ({
    {filteredItems?.map((option) => { const isSelected = selection === option.id; - const Icon = option.icon; + const Icon = (option as option).icon; return (
  • { 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)', }, ];