From 91733fb43157eab26c885f0652adaf9276f372d4 Mon Sep 17 00:00:00 2001 From: Amal K Joy <153802538+amal-k-joy@users.noreply.github.com> Date: Wed, 28 Aug 2024 01:42:31 +0530 Subject: [PATCH] feat(ConditionBuilder): enhancing the conditional operators section that manages the primary logic flow (#5921) * feat(conditionbuilder): design review changes 2 * feat(conditionBuilder): test case fix for delete conditions * fix(ConditionBuilder): incomplete state icon and text order * chore(ConditionBuilder): add typescript for condition builder files * chore(ConditionBuilder): add typescript for condition builder files2 * feat(Conditionbuilder): renaming both variants * feat(Conditionbuilder): renaming both variants 2 * feat(conditionBuilder): enhancing the conditional operators * feat(conditionBuilder): enhancing the conditional operators2 * feat(conditionBuilder): review comments * feat(conditionBuilder): review comment change --------- Co-authored-by: David Menendez --- .../styles/_conditionBuilderItem.scss | 11 +- .../ConditionBlock/ConditionBlock.tsx | 21 ++- .../ConditionBuilder.stories.jsx | 5 +- .../ConditionBuilder/ConditionBuilder.test.js | 5 +- .../ConditionBuilder/ConditionBuilder.tsx | 8 +- .../ConditionBuilder.types.ts | 4 +- .../ConditionBuilderActions.tsx | 2 +- .../ConditionBuilderAdd.tsx | 2 +- .../ConditionBuilderButton.tsx | 6 +- .../ConditionConnector.tsx | 9 +- .../GroupConnector.js | 7 +- .../ConditionBuilderContent.tsx | 8 +- .../ConditionBuilderProvider.tsx | 2 +- .../ConditionBuilderContext/DataConfigs.js | 146 ---------------- .../translationObject.js | 5 + .../ConditionBuilderItem.tsx | 33 ++-- .../ConditionBuilderItemDate.tsx | 4 +- .../ConditionBuilderItemNumber.tsx | 2 +- .../ConditionBuilderItemOption/ItemOption.tsx | 27 ++- .../ItemOptionForValueField.tsx | 2 +- .../ConditionBuilderItemText.tsx | 3 +- .../ConditionBuilderItemTime.tsx | 2 +- .../ConditionGroupBuilder.tsx | 15 +- .../ConditionPreview/ConditionPreview.tsx | 2 +- .../ConditionBuilder/assets/SampleData.js | 12 +- .../utils/handleKeyboardEvents.js | 4 +- .../ConditionBuilder/utils/useDataConfigs.js | 160 ++++++++++++++++++ .../ConditionBuilder/utils/useTranslations.js | 6 +- .../components/ConditionBuilder/utils/util.js | 46 ++++- 29 files changed, 321 insertions(+), 238 deletions(-) delete mode 100644 packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/DataConfigs.js create mode 100644 packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js diff --git a/packages/ibm-products-styles/src/components/ConditionBuilder/styles/_conditionBuilderItem.scss b/packages/ibm-products-styles/src/components/ConditionBuilder/styles/_conditionBuilderItem.scss index 82bb042482..5c93c2f71a 100644 --- a/packages/ibm-products-styles/src/components/ConditionBuilder/styles/_conditionBuilderItem.scss +++ b/packages/ibm-products-styles/src/components/ConditionBuilder/styles/_conditionBuilderItem.scss @@ -54,7 +54,8 @@ $block-class: #{c4p-settings.$pkg-prefix}--condition-builder; } .#{$block-class}__item-option__option { - height: 2rem; + display: grid; + min-height: 2rem; cursor: pointer; padding-inline: $spacing-05; } @@ -297,3 +298,11 @@ $colors: ( fill: $icon-primary; } } + +.#{$block-class}__statement_wrapper { + padding: $spacing-03 0; + + :nth-child(2) { + color: $text-secondary; + } +} diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBlock/ConditionBlock.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBlock/ConditionBlock.tsx index f7940492ea..f736dce610 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBlock/ConditionBlock.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBlock/ConditionBlock.tsx @@ -9,12 +9,6 @@ import React, { useContext, useState } from 'react'; import { Close } from '@carbon/react/icons'; import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; import PropTypes from 'prop-types'; -import { - HIERARCHICAL_VARIANT, - NON_HIERARCHICAL_VARIANT, - operatorConfig, - statementConfig, -} from '../ConditionBuilderContext/DataConfigs'; import cx from 'classnames'; import ConditionConnector from '../ConditionBuilderConnector/ConditionConnector'; import { ConditionBuilderItemNumber } from '../ConditionBuilderItem/ConditionBuilderItemNumber/ConditionBuilderItemNumber'; @@ -22,13 +16,20 @@ import { ConditionBuilderItemText } from '../ConditionBuilderItem/ConditionBuild import { ConditionBuilderItemDate } from '../ConditionBuilderItem/ConditionBuilderItemDate/ConditionBuilderItemDate'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuilderButton'; -import { blockClass } from '../ConditionBuilderContext/DataConfigs'; -import { checkIsValid, focusThisField } from '../utils/util'; +import { + blockClass, + checkIsValid, + focusThisField, + HIERARCHICAL_VARIANT, + NON_HIERARCHICAL_VARIANT, +} from '../utils/util'; import { ConditionBuilderItemTime } from '../ConditionBuilderItem/ConditionBuilderItemTime/ConditionBuilderItemTime'; import ConditionBuilderAdd from '../ConditionBuilderAdd/ConditionBuilderAdd'; import { ItemOption } from '../ConditionBuilderItem/ConditionBuilderItemOption/ItemOption'; import { ItemOptionForValueField } from '../ConditionBuilderItem/ConditionBuilderItemOption/ItemOptionForValueField'; import { useTranslations } from '../utils/useTranslations'; +import { useDataConfigs } from '../utils/useDataConfigs'; + import { Condition, ConditionGroup, @@ -110,6 +111,8 @@ const ConditionBlock = (props: ConditionBlockProps) => { 'removeConditionText', ]); + const { statementConfig, operatorConfig } = useDataConfigs(); + //filtering the current property to access its properties and config options const getCurrentConfig = (property) => { return ( @@ -269,7 +272,7 @@ const ConditionBlock = (props: ConditionBlockProps) => { label: conditionText, }} onChange={onStatementChangeHandler} - config={{ options: statementConfig }} + config={{ options: statementConfig, isStatement: true }} /> )} diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx index baa53283dc..8c748b281d 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx @@ -19,10 +19,7 @@ import { sampleDataStructure_Hierarchical, } from './assets/SampleData'; import uuidv4 from '../../global/js/utils/uuidv4'; -import { - NON_HIERARCHICAL_VARIANT, - HIERARCHICAL_VARIANT, -} from './ConditionBuilderContext/DataConfigs'; +import { HIERARCHICAL_VARIANT, NON_HIERARCHICAL_VARIANT } from './utils/util'; export default { title: 'Experimental/Components/ConditionBuilder', component: ConditionBuilder, diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js index 8bd102f48e..de0c45a829 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js @@ -21,10 +21,7 @@ import { sampleDataStructure_nonHierarchical, sampleDataStructure_Hierarchical, } from './assets/SampleData'; -import { - NON_HIERARCHICAL_VARIANT, - HIERARCHICAL_VARIANT, -} from './ConditionBuilderContext/DataConfigs'; +import { HIERARCHICAL_VARIANT, NON_HIERARCHICAL_VARIANT } from './utils/util'; const blockClass = `${pkg.prefix}--condition-builder`; const componentName = ConditionBuilder.displayName; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx index cc0ee87b1f..c6821d8b26 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.tsx @@ -20,14 +20,12 @@ import { getDevtoolsProps } from '../../global/js/utils/devtools'; import ConditionBuilderContent from './ConditionBuilderContent/ConditionBuilderContent'; import { ConditionBuilderProvider } from './ConditionBuilderContext/ConditionBuilderProvider'; import { pkg } from '../../settings'; -import { - blockClass, - NON_HIERARCHICAL_VARIANT, -} from './ConditionBuilderContext/DataConfigs'; -import { handleKeyDown } from './utils/handleKeyboardEvents'; import { ConditionBuilderProps } from './ConditionBuilder.types'; +import { handleKeyDown } from './utils/handleKeyboardEvents'; +import { blockClass, NON_HIERARCHICAL_VARIANT } from './utils/util'; + // Carbon and package components we use. /* TODO: @import(s) of carbon components and other package components. */ diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts index fc321c64c5..608e111be6 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.types.ts @@ -16,7 +16,7 @@ import { } from 'react'; export type LogicalOperator = 'and' | 'or'; -export type StatementOperator = 'if' | 'if-not'; +export type StatementOperator = 'ifAll' | 'ifAny' | 'unlessAll' | 'unlessAny'; type CoreOperator = 'is'; type NumberOperator = 'greater' | 'greaterEqual' | 'lower' | 'lowerEqual'; @@ -168,6 +168,8 @@ export type Action = { label?: string; }; +export type variantsType = 'Non-Hierarchical' | 'Hierarchical'; + export type ConditionBuilderProps = { inputConfig: inputConfig; initialState?: ConditionBuilderState; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderActions/ConditionBuilderActions.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderActions/ConditionBuilderActions.tsx index 8c330c9947..3f2c389321 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderActions/ConditionBuilderActions.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderActions/ConditionBuilderActions.tsx @@ -12,7 +12,6 @@ import { Close } from '@carbon/react/icons'; /**@ts-ignore */ import { Section, Heading } from '@carbon/react'; import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; -import { blockClass } from '../ConditionBuilderContext/DataConfigs'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; import ConditionBuilderAdd from '../ConditionBuilderAdd/ConditionBuilderAdd'; import uuidv4 from '../../../global/js/utils/uuidv4'; @@ -20,6 +19,7 @@ import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuild import { useTranslations } from '../utils/useTranslations'; import { ItemOptionForValueField } from '../ConditionBuilderItem/ConditionBuilderItemOption/ItemOptionForValueField'; import { Action, Option } from '../ConditionBuilder.types'; +import { blockClass } from '../utils/util'; interface ConditionBuilderActionsProps { actions: Action[] | Option[]; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderAdd/ConditionBuilderAdd.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderAdd/ConditionBuilderAdd.tsx index 6612853c57..9df509aa9d 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderAdd/ConditionBuilderAdd.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderAdd/ConditionBuilderAdd.tsx @@ -10,8 +10,8 @@ import cx from 'classnames'; import { AddAlt, TextNewLine } from '@carbon/react/icons'; import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuilderButton'; import PropTypes from 'prop-types'; -import { blockClass } from '../ConditionBuilderContext/DataConfigs'; import { useTranslations } from '../utils/useTranslations'; +import { blockClass } from '../utils/util'; interface ConditionBuilderAddProps { className?: string; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderButton/ConditionBuilderButton.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderButton/ConditionBuilderButton.tsx index e923080909..ec6dea385b 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderButton/ConditionBuilderButton.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderButton/ConditionBuilderButton.tsx @@ -9,16 +9,16 @@ import React from 'react'; import cx from 'classnames'; import PropTypes from 'prop-types'; -import { Tooltip } from '@carbon/react'; -import { blockClass } from '../ConditionBuilderContext/DataConfigs'; +import { PopoverAlignment, Tooltip } from '@carbon/react'; import { CarbonIconType, WarningAltFilled } from '@carbon/react/icons'; import { usePrefix } from '@carbon/react'; +import { blockClass } from '../utils/util'; interface ConditionBuilderButtonProps { className?: string; label: string; hideLabel?: boolean; - tooltipAlign?: string; + tooltipAlign?: PopoverAlignment; renderIcon?: CarbonIconType; onClick?: (e: React.MouseEvent) => void; onBlur?: React.FocusEventHandler; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/ConditionConnector.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/ConditionConnector.tsx index 2d56ce1879..039d170664 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/ConditionConnector.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/ConditionConnector.tsx @@ -8,15 +8,15 @@ import React, { useCallback, useContext } from 'react'; import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; import { ItemOption } from '../ConditionBuilderItem/ConditionBuilderItemOption/ItemOption'; +import PropTypes from 'prop-types'; import { blockClass, - connectorConfig, + focusThisField, HIERARCHICAL_VARIANT, -} from '../ConditionBuilderContext/DataConfigs'; -import PropTypes from 'prop-types'; -import { focusThisField } from '../utils/util'; +} from '../utils/util'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; import { useTranslations } from '../utils/useTranslations'; +import { useDataConfigs } from '../utils/useDataConfigs'; import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuilderButton'; interface ConditionConnectorProps { @@ -32,6 +32,7 @@ const ConditionConnector = ({ }: ConditionConnectorProps) => { const { variant, conditionBuilderRef } = useContext(ConditionBuilderContext); const [connectorText] = useTranslations(['connectorText']); + const { connectorConfig } = useDataConfigs(); const handleConnectorHover = useCallback((parentGroup, isHover) => { if (isHover) { diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/GroupConnector.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/GroupConnector.js index f03118ebcd..837d78f63a 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/GroupConnector.js +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderConnector/GroupConnector.js @@ -7,17 +7,16 @@ import React, { useContext } from 'react'; import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; -import { - blockClass, - connectorConfig, -} from '../ConditionBuilderContext/DataConfigs'; import { ItemOption } from '../ConditionBuilderItem/ConditionBuilderItemOption/ItemOption'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; import { useTranslations } from '../utils/useTranslations'; +import { blockClass } from '../utils/util'; +import { useDataConfigs } from '../utils/useDataConfigs'; const GroupConnector = () => { const { rootState, setRootState } = useContext(ConditionBuilderContext); const [conditionText] = useTranslations(['conditionText']); + const { connectorConfig } = useDataConfigs(); const onStatementChangeHandler = (updatedStatement) => { setRootState({ diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx index 65c7df2abb..22f84b4e48 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx @@ -14,10 +14,6 @@ import { ConditionBuilderContext, emptyState, } from '../ConditionBuilderContext/ConditionBuilderProvider'; -import { - blockClass, - HIERARCHICAL_VARIANT, -} from '../ConditionBuilderContext/DataConfigs'; import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuilderButton'; import uuidv4 from '../../../global/js/utils/uuidv4'; import ConditionPreview from '../ConditionPreview/ConditionPreview'; @@ -33,7 +29,7 @@ import { ConditionBuilderState, ConditionGroup, } from '../ConditionBuilder.types'; - +import { blockClass, HIERARCHICAL_VARIANT } from '../utils/util'; interface ConditionBuilderContentProps { startConditionLabel: string; getConditionState: (state: ConditionBuilderState) => void; @@ -129,7 +125,7 @@ const ConditionBuilderContent = ({ const addConditionGroupHandler = () => { const newGroup: ConditionGroup = { - statement: 'if', // 'if|exclude if', + statement: 'ifAll', // 'if|exclude if', groupOperator: 'and', id: uuidv4(), conditions: [ diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx index 8305245380..143bab6d37 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/ConditionBuilderProvider.tsx @@ -19,7 +19,7 @@ export const emptyState: ConditionBuilderState = { groups: [ { groupOperator: 'and', - statement: 'if', + statement: 'ifAll', id: uuidv4(), conditions: [ { diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/DataConfigs.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/DataConfigs.js deleted file mode 100644 index 91308c36c2..0000000000 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/DataConfigs.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright IBM Corp. 2024 - * - * 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 { pkg } from '../../../settings'; - -export const statementConfig = [ - { - label: 'if', - id: 'if', - connector: 'and', - }, - { - label: 'excl.if', - id: 'excl_if', - connector: 'or', - }, -]; - -export const connectorConfig = [ - { - label: 'and', - id: 'and', - }, - { - label: 'or', - id: 'or', - }, -]; -export const actionConfig = [ - { - label: 'then', - id: 'then', - }, -]; - -export const operatorConfig = [ - { - label: 'is', - id: 'is', - type: 'all', - }, - { - label: 'is greater than', - id: 'greater', - type: 'number', - }, - { - label: 'is greater than or equal to', - id: 'greaterEqual', - type: 'number', - }, - { - label: 'is lower than', - id: 'lower', - type: 'number', - }, - { - label: 'is lower than or equal to', - id: 'lowerEqual', - type: 'number', - }, - { - label: 'starts with', - id: 'startsWith', - type: 'text,textarea', - }, - { - label: 'ends with', - id: 'endsWith', - type: 'text,textarea', - }, - { - label: 'contains', - id: 'contains', - type: 'text,textarea', - }, - { - label: 'is one of', - id: 'oneOf', - type: 'option', - }, - { - label: 'is before', - id: 'before', - type: 'date,time', - }, - { - label: 'is after', - id: 'after', - type: 'date,time', - }, - { - label: 'is between', - id: 'between', - type: 'date', - }, -]; -export const NON_HIERARCHICAL_VARIANT = 'Non-Hierarchical'; -export const HIERARCHICAL_VARIANT = 'Hierarchical'; -// The block part of our conventional BEM class names (blockClass__E--M). -export const blockClass = `${pkg.prefix}--condition-builder`; - -const formatDate = (date) => { - const day = String(date.getDate()).padStart(2, '0'); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const year = date.getFullYear(); - return `${day}/${month}/${year}`; -}; -//const translationsObjectCurrent = translationsObject['en']; // TO DO: need to discuss if language is to be passed as prop - -export const valueRenderers = { - text: (val) => val, - textarea: (val) => val, - time: (val) => val, - number: (val) => val, - option: (value) => { - if (value && typeof value !== 'string') { - const selectedValues = Array.isArray(value) ? value : [value]; - return selectedValues.map((option) => option.label).join(', '); - } - - return value; - }, - date: (value) => { - if (Array.isArray(value) && value.length > 1) { - const start = - value?.[0] && !isNaN(new Date(value[0])) - ? formatDate(new Date(value[0])) - : ''; - const end = - value?.[1] && !isNaN(new Date(value[1])) - ? formatDate(new Date(value[1])) - : ''; - return `${start} To ${end}`; - } else if (Array.isArray(value) && !isNaN(new Date(value[0]))) { - return formatDate(new Date(value[0])); - } else { - return value; - } - }, - custom: (value) => value, -}; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/translationObject.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/translationObject.js index 049efe7f0c..d5497bc845 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/translationObject.js +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContext/translationObject.js @@ -7,10 +7,15 @@ export const translationsObject = { ifText: 'if', + unlessText: 'unless', excl_if: 'excl.if', and: 'and', or: 'or', is: 'is', + ifAll: 'if all', + ifAny: 'if any', + unlessAll: 'unless all', + unlessAny: 'unless any', greater: 'is greater than', greaterEqual: 'is greater than or equal to', lower: 'is lower than', diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx index 5696fdf07f..d1d01f7dcd 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx @@ -20,10 +20,6 @@ import React, { import { Popover, PopoverContent, Layer } from '@carbon/react'; import PropTypes from 'prop-types'; import { Add, CarbonIconType } from '@carbon/react/icons'; -import { - blockClass, - valueRenderers, -} from '../ConditionBuilderContext/DataConfigs'; import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuilderButton'; import { useTranslations } from '../utils/useTranslations'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; @@ -34,6 +30,7 @@ import { Action, Option, } from '../ConditionBuilder.types'; +import { blockClass, getValue } from '../utils/util'; interface ConditionBuilderItemProps extends PropsWithChildren { className?: string; @@ -71,7 +68,12 @@ export const ConditionBuilderItem = ({ }: ConditionBuilderItemProps) => { const popoverRef = useRef(null); const [open, setOpen] = useState(false); - + const statementIdMap = { + ifAll: 'if', + ifAny: 'if', + unlessAll: 'unless', + unlessAny: 'unless', + }; const [ invalidText, addConditionText, @@ -79,14 +81,17 @@ export const ConditionBuilderItem = ({ addOperatorText, addValueText, labelText, - ] = useTranslations([ - 'invalidText', - 'addConditionText', - 'addPropertyText', - 'addOperatorText', - 'addValueText', - label, - ]); + ] = useTranslations( + [ + 'invalidText', + 'addConditionText', + 'addPropertyText', + 'addOperatorText', + 'addValueText', + label, + ], + statementIdMap + ); const { conditionBuilderRef } = useContext(ConditionBuilderContext); const getPropertyDetails = () => { const { property, operator } = condition || {}; @@ -102,7 +107,7 @@ export const ConditionBuilderItem = ({ } const propertyId = rest['data-name'] == 'valueField' && type - ? valueRenderers[type](label, config) + ? getValue[type](label, config) : labelText; return { diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemDate/ConditionBuilderItemDate.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemDate/ConditionBuilderItemDate.tsx index ad1bd29ff2..9fe4f425d0 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemDate/ConditionBuilderItemDate.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemDate/ConditionBuilderItemDate.tsx @@ -9,12 +9,10 @@ import React, { ForwardedRef, useRef } from 'react'; import { DatePicker, DatePickerInput } from '@carbon/react'; -import { pkg } from '../../../../settings'; import PropTypes from 'prop-types'; import { useTranslations } from '../../utils/useTranslations'; import { Condition } from '../../ConditionBuilder.types'; - -const blockClass = `${pkg.prefix}--condition-builder`; +import { blockClass } from '../../utils/util'; interface ConditionBuilderItemDate { conditionState: Condition; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemNumber/ConditionBuilderItemNumber.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemNumber/ConditionBuilderItemNumber.tsx index 4e691ac662..fd88d3f05d 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemNumber/ConditionBuilderItemNumber.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemNumber/ConditionBuilderItemNumber.tsx @@ -10,9 +10,9 @@ import React from 'react'; import { NumberInput } from '@carbon/react'; import PropTypes from 'prop-types'; -import { blockClass } from '../../ConditionBuilderContext/DataConfigs'; import { useTranslations } from '../../utils/useTranslations'; import { Condition, PropertyConfigNumber } from '../../ConditionBuilder.types'; +import { blockClass } from '../../utils/util'; interface ConditionBuilderItemNumberProps { conditionState: Condition; 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 2e811cad39..83f421674c 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.tsx @@ -13,16 +13,16 @@ import { Checkmark } from '@carbon/react/icons'; import PropTypes from 'prop-types'; import { ConditionBuilderContext } from '../../ConditionBuilderContext/ConditionBuilderProvider'; -import { blockClass } from '../../ConditionBuilderContext/DataConfigs'; import { useTranslations } from '../../utils/useTranslations'; import { PropertyConfigOption } from '../../ConditionBuilder.types'; +import { blockClass } from '../../utils/util'; interface ItemOptionProps { conditionState: { label?: string; value?: string; }; - config: PropertyConfigOption['config']; + config: PropertyConfigOption['config'] & { isStatement?: boolean }; onChange: (value: string, e: Event) => void; } export const ItemOption = ({ @@ -41,9 +41,11 @@ export const ItemOption = ({ const selection = conditionState.value; - const filteredItems = allOptions?.filter((opt) => - opt.label.toLowerCase().includes(searchValue.toLowerCase()) - ); + const filteredItems = searchValue + ? allOptions?.filter((opt) => + opt.label?.toLowerCase().includes(searchValue.toLowerCase()) + ) + : allOptions; useEffect(() => { //this will focus the first input field in the popover @@ -72,6 +74,17 @@ export const ItemOption = ({ return conditionState.label ? conditionState.label : propertyText; }; + const getStatementContent = (option) => { + return ( +
+
+ {option.text1} ({option.connector}) +
+
{option.text2}
+
+ ); + }; + if (!allOptions) { return; } @@ -108,7 +121,9 @@ export const ItemOption = ({
{Icon && } - {option.label} + {config.isStatement + ? getStatementContent(option) + : option.label} {isSelected && ( diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOptionForValueField.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOptionForValueField.tsx index caf8d61298..4dcb08f9b5 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOptionForValueField.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemOption/ItemOptionForValueField.tsx @@ -19,7 +19,6 @@ import { SelectSkeleton } from '@carbon/react'; import PropTypes from 'prop-types'; import { ConditionBuilderContext } from '../../ConditionBuilderContext/ConditionBuilderProvider'; -import { blockClass } from '../../ConditionBuilderContext/DataConfigs'; import { useTranslations } from '../../utils/useTranslations'; import { Condition, @@ -27,6 +26,7 @@ import { Option, PropertyConfigOption, } from '../../ConditionBuilder.types'; +import { blockClass } from '../../utils/util'; interface ItemOptionForValueFieldProps { conditionState: Condition & { label?: string }; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemText/ConditionBuilderItemText.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemText/ConditionBuilderItemText.tsx index 54d44dcf2e..b2065ebc82 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemText/ConditionBuilderItemText.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemText/ConditionBuilderItemText.tsx @@ -10,8 +10,7 @@ import React from 'react'; import { TextArea, TextInput } from '@carbon/react'; import PropTypes from 'prop-types'; -import { blockClass } from '../../ConditionBuilderContext/DataConfigs'; -import { checkIsValid } from '../../utils/util'; +import { blockClass, checkIsValid } from '../../utils/util'; import { Condition, PropertyConfigText, diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemTime/ConditionBuilderItemTime.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemTime/ConditionBuilderItemTime.tsx index bb4d06773b..e6290b0385 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemTime/ConditionBuilderItemTime.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItemTime/ConditionBuilderItemTime.tsx @@ -9,8 +9,8 @@ import React, { useEffect, useState } from 'react'; import { TimePicker, TimePickerSelect, SelectItem } from '@carbon/react'; import PropTypes from 'prop-types'; -import { blockClass } from '../../ConditionBuilderContext/DataConfigs'; import { PropertyConfigTime } from '../../ConditionBuilder.types'; +import { blockClass } from '../../utils/util'; interface ConditionBuilderItemTime { onChange: (value: string | undefined) => void; diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionGroupBuilder/ConditionGroupBuilder.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionGroupBuilder/ConditionGroupBuilder.tsx index 48047687ca..049a227e10 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionGroupBuilder/ConditionGroupBuilder.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionGroupBuilder/ConditionGroupBuilder.tsx @@ -11,14 +11,14 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; +import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; import { blockClass, + focusThisField, HIERARCHICAL_VARIANT, + manageTabIndexAndFocus, NON_HIERARCHICAL_VARIANT, - statementConfig, -} from '../ConditionBuilderContext/DataConfigs'; -import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; -import { focusThisField, manageTabIndexAndFocus } from '../utils/util'; +} from '../utils/util'; import ConditionConnector from '../ConditionBuilderConnector/ConditionConnector'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; import uuidv4 from '../../../global/js/utils/uuidv4'; @@ -30,6 +30,7 @@ import { ConditionGroup, LogicalOperator, } from '../ConditionBuilder.types'; +import { useDataConfigs } from '../utils/useDataConfigs'; /** * * state - this is the current group that is being rendered . This can be a inner group or outer group @@ -62,6 +63,7 @@ const ConditionGroupBuilder = ({ 'conditionText', 'conditionBuilderText', ]); + const { statementConfig } = useDataConfigs(); const { variant, conditionBuilderRef } = useContext(ConditionBuilderContext); const [showConditionPreview, setShowConditionPreview] = useState(-1); const [showConditionSubGroupPreview, setShowConditionSubGroupPreview] = @@ -260,7 +262,7 @@ const ConditionGroupBuilder = ({ ? group.conditions.slice(0, conditionIndex + 1) : []), { - statement: 'if', + statement: 'ifAll', groupOperator: 'and', conditions: [ { @@ -301,7 +303,6 @@ const ConditionGroupBuilder = ({ const groupOperator = statementConfig.find( (statement) => statement.id == updatedStatement )?.connector as LogicalOperator; - onChange({ ...group, groupOperator: groupOperator, @@ -395,7 +396,7 @@ const ConditionGroupBuilder = ({ focusThisField(evt, conditionBuilderRef); onStatementChangeHandler(v); }} - config={{ options: statementConfig }} + config={{ options: statementConfig, isStatement: true }} />
diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionPreview/ConditionPreview.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionPreview/ConditionPreview.tsx index abce41da0e..1cd499d2cd 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionPreview/ConditionPreview.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionPreview/ConditionPreview.tsx @@ -8,12 +8,12 @@ import React, { useEffect, useState } from 'react'; import cx from 'classnames'; import PropTypes from 'prop-types'; -import { blockClass } from '../ConditionBuilderContext/DataConfigs'; import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem'; import ConditionConnector from '../ConditionBuilderConnector/ConditionConnector'; import { useTranslations } from '../utils/useTranslations'; import { Bee } from '@carbon/react/icons'; import { ConditionGroup } from '../ConditionBuilder.types'; +import { blockClass } from '../utils/util'; interface ConditionPreviewProps { previewType: 'newGroup' | 'subGroup' | 'condition'; diff --git a/packages/ibm-products/src/components/ConditionBuilder/assets/SampleData.js b/packages/ibm-products/src/components/ConditionBuilder/assets/SampleData.js index b4971c0c22..718f608970 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/assets/SampleData.js +++ b/packages/ibm-products/src/components/ConditionBuilder/assets/SampleData.js @@ -12,7 +12,7 @@ export const sampleDataStructure_Hierarchical = { groups: [ { groupOperator: 'and', //'and|or', - statement: 'if', // 'if|exclude if', + statement: 'ifAll', // 'if|exclude if', id: uuidv4(), conditions: [ { @@ -42,7 +42,7 @@ export const sampleDataStructure_Hierarchical = { //group object repeats { groupOperator: 'and', //'and|or', - statement: 'if', // 'if|exclude if', + statement: 'unlessAll', // 'if|exclude if', id: uuidv4(), conditions: [ { @@ -71,8 +71,8 @@ export const sampleDataStructure_Hierarchical = { }, //group object repeats { - groupOperator: 'and', //'and|or', - statement: 'if', // 'if|exclude if', + groupOperator: 'or', //'and|or', + statement: 'ifAny', // 'if|exclude if', id: uuidv4(), conditions: [ { @@ -102,7 +102,7 @@ export const sampleDataStructure_Hierarchical = { }, { groupOperator: 'and', //'and|or', - statement: 'if', // 'if|exclude if', + statement: 'ifAll', // 'if|exclude if', id: uuidv4(), conditions: [ { @@ -132,7 +132,7 @@ export const sampleDataStructure_nonHierarchical = { groups: [ { groupOperator: 'and', //'and|or', - statement: 'if', // 'if|exclude if', + statement: 'ifAll', // 'if|exclude if', id: uuidv4(), conditions: [ { diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js b/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js index b5a94f6535..897fa173b7 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js +++ b/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js @@ -7,12 +7,10 @@ import { blockClass, - HIERARCHICAL_VARIANT, -} from '../ConditionBuilderContext/DataConfigs'; -import { checkForHoldingKey, focusThisField, focusThisItem, + HIERARCHICAL_VARIANT, manageTabIndexAndFocus, traverseClockVise, traverseReverse, diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js b/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js new file mode 100644 index 0000000000..e8a61ab969 --- /dev/null +++ b/packages/ibm-products/src/components/ConditionBuilder/utils/useDataConfigs.js @@ -0,0 +1,160 @@ +/** + * Copyright IBM Corp. 2024 + * + * 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 { useTranslations } from './useTranslations'; + +export const useDataConfigs = () => { + const [ + ifAll, + ifAny, + unlessAll, + unlessAny, + and, + or, + is, + greater, + greaterEqual, + lower, + lowerEqual, + startsWith, + endsWith, + contains, + oneOf, + before, + after, + between, + ] = useTranslations([ + 'ifAll', + 'ifAny', + 'unlessAll', + 'unlessAny', + 'and', + 'or', + 'is', + 'greater', + 'greaterEqual', + 'lower', + 'lowerEqual', + 'startsWith', + 'endsWith', + 'contains', + 'oneOf', + 'before', + 'after', + 'between', + ]); + + const statementConfig = [ + { + label: 'ifText', + id: 'ifAll', + connector: 'and', + text1: ifAll, + text2: '(a && b)', + }, + { + label: 'ifText', + id: 'ifAny', + connector: 'or', + text1: ifAny, + text2: '(a || b)', + }, + { + label: 'unlessText', + id: 'unlessAll', + connector: 'and', + text1: unlessAll, + text2: '! (a && b)', + }, + { + label: 'unlessText', + id: 'unlessAny', + connector: 'or', + text1: unlessAny, + text2: '! (a || b)', + }, + ]; + + const connectorConfig = [ + { + label: and, + id: 'and', + }, + { + label: or, + id: 'or', + }, + ]; + + const operatorConfig = [ + { + label: is, + id: 'is', + type: 'all', + }, + { + label: greater, + id: 'greater', + type: 'number', + }, + { + label: greaterEqual, + id: 'greaterEqual', + type: 'number', + }, + { + label: lower, + id: 'lower', + type: 'number', + }, + { + label: lowerEqual, + id: 'lowerEqual', + type: 'number', + }, + { + label: startsWith, + id: 'startsWith', + type: 'text,textarea', + }, + { + label: endsWith, + id: 'endsWith', + type: 'text,textarea', + }, + { + label: contains, + id: 'contains', + type: 'text,textarea', + }, + { + label: oneOf, + id: 'oneOf', + type: 'option', + }, + { + label: before, + id: 'before', + type: 'date,time', + }, + { + label: after, + id: 'after', + type: 'date,time', + }, + { + label: between, + id: 'between', + type: 'date', + }, + ]; + + return { + statementConfig, + connectorConfig, + operatorConfig, + }; +}; diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/useTranslations.js b/packages/ibm-products/src/components/ConditionBuilder/utils/useTranslations.js index 2e1e1dd48e..9960b121e3 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/utils/useTranslations.js +++ b/packages/ibm-products/src/components/ConditionBuilder/utils/useTranslations.js @@ -9,10 +9,12 @@ import { useContext } from 'react'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider'; import { translationsObject } from '../ConditionBuilderContext/translationObject'; -export const useTranslations = (translationKeys) => { +export const useTranslations = (translationKeys, alterTranslationKeyMap) => { const { translateWithId } = useContext(ConditionBuilderContext); - return translationKeys.map((translationKey) => { + if (alterTranslationKeyMap?.[translationKey]) { + translationKey = alterTranslationKeyMap[translationKey]; + } if (translateWithId?.(translationKey)) { return translateWithId(translationKey); } else if (translationsObject[translationKey]) { diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/util.js b/packages/ibm-products/src/components/ConditionBuilder/utils/util.js index e2d041be05..b882f7aad1 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/utils/util.js +++ b/packages/ibm-products/src/components/ConditionBuilder/utils/util.js @@ -5,7 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { blockClass } from '../ConditionBuilderContext/DataConfigs'; +import { pkg } from '../../../settings'; + +export const blockClass = `${pkg.prefix}--condition-builder`; +export const NON_HIERARCHICAL_VARIANT = 'Non-Hierarchical'; +export const HIERARCHICAL_VARIANT = 'Hierarchical'; export const focusThisField = (evt, conditionBuilderRef) => { if (evt) { @@ -102,3 +106,43 @@ export const manageTabIndexAndFocus = (currentElement, conditionBuilderRef) => { ?.setAttribute('tabindex', '1'); currentElement?.focus(); }; + +const formatDate = (date) => { + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const year = date.getFullYear(); + return `${day}/${month}/${year}`; +}; + +export const getValue = { + text: (value) => value, + textarea: (value) => value, + time: (value) => value, + number: (value) => value, + option: (value) => { + if (value && typeof value !== 'string') { + const selectedValues = Array.isArray(value) ? value : [value]; + return selectedValues.map((option) => option.label).join(', '); + } + + return value; + }, + date: (value) => { + if (Array.isArray(value) && value.length > 1) { + const start = + value?.[0] && !isNaN(new Date(value[0])) + ? formatDate(new Date(value[0])) + : ''; + const end = + value?.[1] && !isNaN(new Date(value[1])) + ? formatDate(new Date(value[1])) + : ''; + return `${start} To ${end}`; + } else if (Array.isArray(value) && !isNaN(new Date(value[0]))) { + return formatDate(new Date(value[0])); + } else { + return value; + } + }, + custom: (value) => value, +};