diff --git a/packages/doc/content/components/components/actionrequirement/actionrequirement-web.mdx b/packages/doc/content/components/components/actionrequirement/actionrequirement-web.mdx index e4037f6afb..4d3a36b15c 100644 --- a/packages/doc/content/components/components/actionrequirement/actionrequirement-web.mdx +++ b/packages/doc/content/components/components/actionrequirement/actionrequirement-web.mdx @@ -46,6 +46,55 @@ render(() => { }); ``` +### Multiple description paragraphs + +You can pass an array of strings to `description` to render multiple paragraphs. Empty, whitespace-only and `null` entries are ignored. + +```javascript state +render(() => { + const descriptions = [ + 'Choose your activities to start the journey.', + 'Track progress and adjust goals weekly.', + 'Celebrate achievements and stay consistent!', + ]; + + function handleGetCheckableContent() { + return ( + + I have read and agree to the Terms of Use and Privacy Policy. + + ) + }; + function handleGetIllustration() { + return + } + function handleGetList() { + return ( + <> + 1x/day standard access + 4x/month 1-on-1 sessions + 2x/month premium classes + + ) + } + + return ( + + + Start working + Learn more + + + ); +}); +``` + ### Props diff --git a/packages/yoga/src/ActionRequirement/web/ActionRequirement.jsx b/packages/yoga/src/ActionRequirement/web/ActionRequirement.jsx index e8cf716edb..0a4d6b85c3 100644 --- a/packages/yoga/src/ActionRequirement/web/ActionRequirement.jsx +++ b/packages/yoga/src/ActionRequirement/web/ActionRequirement.jsx @@ -9,6 +9,7 @@ import { SecondaryButton, } from './ActionRequirementStyles'; import Text from '../../Text'; +import parseDescription from '../../utils/parseDescription'; import Box from '../../Box'; const StyledActionRequirement = styled.div` @@ -64,6 +65,8 @@ function ActionRequirement(props) { let primaryButton; let secondaryButton; + const descriptionArray = parseDescription(description); + function defineCompoundComponents() { React.Children.forEach(children, child => { if (isChildFromComponent(child, PrimaryButton)) primaryButton = child; @@ -85,9 +88,13 @@ function ActionRequirement(props) { ) : ( {title} )} - - {description} - + + {descriptionArray.map(paragraph => ( + + {paragraph} + + ))} + {list && {list}} {checkable && {checkable}} @@ -102,7 +109,7 @@ function ActionRequirement(props) { ActionRequirement.propTypes = { title: oneOfType([arrayOf(node), node]).isRequired, children: oneOfType([arrayOf(node), node]), - description: string.isRequired, + description: oneOfType([string, arrayOf(string)]).isRequired, checkable: oneOfType([arrayOf(node), node]), illustration: oneOfType([arrayOf(node), node]), list: oneOfType([arrayOf(node), node]), diff --git a/packages/yoga/src/ActionRequirement/web/ActionRequirement.test.jsx b/packages/yoga/src/ActionRequirement/web/ActionRequirement.test.jsx index 3468cd0c6c..4692ee3852 100644 --- a/packages/yoga/src/ActionRequirement/web/ActionRequirement.test.jsx +++ b/packages/yoga/src/ActionRequirement/web/ActionRequirement.test.jsx @@ -73,4 +73,22 @@ describe('', () => { expect(container).toMatchSnapshot(); }); + + it('should render multiple paragraphs when description is an array', () => { + const descriptions = [ + 'First requirement paragraph.', + 'Second requirement giving more context.', + 'Final requirement details here.', + ]; + + const { getByText } = render( + + + , + ); + + descriptions.forEach(d => { + expect(getByText(d)).toBeTruthy(); + }); + }); }); diff --git a/packages/yoga/src/ActionRequirement/web/__snapshots__/ActionRequirement.test.jsx.snap b/packages/yoga/src/ActionRequirement/web/__snapshots__/ActionRequirement.test.jsx.snap index 9ba41fcf21..73e3d5eb34 100644 --- a/packages/yoga/src/ActionRequirement/web/__snapshots__/ActionRequirement.test.jsx.snap +++ b/packages/yoga/src/ActionRequirement/web/__snapshots__/ActionRequirement.test.jsx.snap @@ -2,6 +2,18 @@ exports[` should default match snapshot 1`] = ` .c3 { + margin-top: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: 24px; +} + +.c4 { margin: 0; padding: 0; font-size: 16px; @@ -9,7 +21,6 @@ exports[` should default match snapshot 1`] = ` color: #231B22; font-family: Rubik; font-weight: 400; - margin-top: 16px; color: #6B6B78; } @@ -24,7 +35,7 @@ exports[` should default match snapshot 1`] = ` margin-bottom: 6px; } -.c4 { +.c5 { margin-top: 32px; display: -webkit-box; display: -webkit-flex; @@ -66,7 +77,7 @@ exports[` should default match snapshot 1`] = ` } @media (min-width:769px) { - .c4 { + .c5 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -118,14 +129,19 @@ exports[` should default match snapshot 1`] = ` > title -

- description -

+

+ description +

+
@@ -133,6 +149,18 @@ exports[` should default match snapshot 1`] = ` `; exports[` should match snapshot with aria-level 2 1`] = ` +.c3 { + margin-top: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: 24px; +} + .c2 { margin: 0; padding: 0; @@ -143,7 +171,7 @@ exports[` should match snapshot with aria-level 2 1`] = ` font-weight: 700; } -.c3 { +.c4 { margin: 0; padding: 0; font-size: 16px; @@ -151,11 +179,10 @@ exports[` should match snapshot with aria-level 2 1`] = ` color: #231B22; font-family: Rubik; font-weight: 400; - margin-top: 16px; color: #6B6B78; } -.c4 { +.c5 { margin-top: 32px; display: -webkit-box; display: -webkit-flex; @@ -189,7 +216,7 @@ exports[` should match snapshot with aria-level 2 1`] = ` } @media (min-width:769px) { - .c4 { + .c5 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -241,14 +268,19 @@ exports[` should match snapshot with aria-level 2 1`] = ` > title -

- description -

+

+ description +

+
@@ -256,11 +288,23 @@ exports[` should match snapshot with aria-level 2 1`] = ` `; exports[` should match snapshot with list 1`] = ` -.c4 { +.c3 { + margin-top: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: 24px; +} + +.c5 { margin-top: 24px; } -.c3 { +.c4 { margin: 0; padding: 0; font-size: 16px; @@ -268,11 +312,10 @@ exports[` should match snapshot with list 1`] = ` color: #231B22; font-family: Rubik; font-weight: 400; - margin-top: 16px; color: #6B6B78; } -.c5 { +.c6 { margin: 0; padding: 0; font-size: 16px; @@ -283,7 +326,7 @@ exports[` should match snapshot with list 1`] = ` font-size: 16px; } -.c6 { +.c7 { margin: 0; padding: 0; font-size: 16px; @@ -306,7 +349,7 @@ exports[` should match snapshot with list 1`] = ` margin-bottom: 6px; } -.c7 { +.c8 { margin-top: 32px; display: -webkit-box; display: -webkit-flex; @@ -348,7 +391,7 @@ exports[` should match snapshot with list 1`] = ` } @media (min-width:769px) { - .c7 { + .c8 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -401,31 +444,36 @@ exports[` should match snapshot with list 1`] = ` > title -

- description -

+

+ description +

+

1x/day standard access

4x/month 1-on-1 sessions

@@ -433,7 +481,7 @@ exports[` should match snapshot with list 1`] = `

@@ -441,6 +489,18 @@ exports[` should match snapshot with list 1`] = ` `; exports[` should match snapshot with text display 1`] = ` +.c3 { + margin-top: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: 24px; +} + .c2 { margin: 0; padding: 0; @@ -451,7 +511,7 @@ exports[` should match snapshot with text display 1`] = ` font-weight: 700; } -.c3 { +.c4 { margin: 0; padding: 0; font-size: 16px; @@ -459,11 +519,10 @@ exports[` should match snapshot with text display 1`] = ` color: #231B22; font-family: Rubik; font-weight: 400; - margin-top: 16px; color: #6B6B78; } -.c4 { +.c5 { margin-top: 32px; display: -webkit-box; display: -webkit-flex; @@ -497,7 +556,7 @@ exports[` should match snapshot with text display 1`] = ` } @media (min-width:769px) { - .c4 { + .c5 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -549,14 +608,19 @@ exports[` should match snapshot with text display 1`] = ` > title -

- description -

+

+ description +

+
diff --git a/packages/yoga/src/Feedback/web/Feedback.jsx b/packages/yoga/src/Feedback/web/Feedback.jsx index a2012ba1db..66b99e2472 100644 --- a/packages/yoga/src/Feedback/web/Feedback.jsx +++ b/packages/yoga/src/Feedback/web/Feedback.jsx @@ -14,6 +14,7 @@ import { TextContainer, Caption, } from './StyledFeedback'; +import parseDescription from '../../utils/parseDescription'; const ICON_SIZE = 64; const VARIANT_ICONS = { @@ -43,9 +44,7 @@ function Feedback({ center = true, ...props }) { - const descriptionArray = Array.isArray(description) - ? description - : [description]; + const descriptionArray = parseDescription(description); const iconProps = VARIANT_ICONS[variant]; let primaryButton; diff --git a/packages/yoga/src/utils/parseDescription.js b/packages/yoga/src/utils/parseDescription.js new file mode 100644 index 0000000000..997ddf3404 --- /dev/null +++ b/packages/yoga/src/utils/parseDescription.js @@ -0,0 +1,10 @@ +export function parseDescription(input) { + return (Array.isArray(input) ? input : [input]).flatMap(item => { + if (item == null || typeof item !== 'string') return []; + const trimmed = item.trim(); + + return trimmed ? [trimmed] : []; + }); +} + +export default parseDescription; diff --git a/packages/yoga/src/utils/parseDescription.test.jsx b/packages/yoga/src/utils/parseDescription.test.jsx new file mode 100644 index 0000000000..c4896dd1fe --- /dev/null +++ b/packages/yoga/src/utils/parseDescription.test.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import parseDescription from './parseDescription'; + +describe('parseDescription utility', () => { + it('should wrap single string and trim it', () => { + expect(parseDescription(' hello ')).toEqual(['hello']); + }); + + it('should filter empty, whitespace and null values', () => { + const input = [' ', '', null, 'valid', ' also valid ']; + + expect(parseDescription(input)).toEqual(['valid', 'also valid']); + }); + + it('should ignore non-string items (nodes, numbers, booleans)', () => { + const node = Node Content; + const input = [node, 42, true, ' keep ', ' ', false, null]; + const result = parseDescription(input); + + expect(result).toEqual(['keep']); + }); +});