@eeacms/volto-cca-policy 0.3.24 → 0.3.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [0.3.26](https://github.com/eea/volto-cca-policy/compare/0.3.25...0.3.26) - 15 April 2025
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: add extractFirstURL to parse first URL from text; update formatTextToHTML - refs #285361 [kreafox - [`a348454`](https://github.com/eea/volto-cca-policy/commit/a348454d52a06c4cbd668a7ffeaebed74d4a6788)]
12
+
13
+ #### :nail_care: Enhancements
14
+
15
+ - change: better link handler, update planning section, update tests - refs #285361 [kreafox - [`547fbb7`](https://github.com/eea/volto-cca-policy/commit/547fbb7ac308c274b94ff961b4abbae629adf93e)]
16
+ - change: update Governance section - refs #285296 [kreafox - [`0a9b924`](https://github.com/eea/volto-cca-policy/commit/0a9b92433239cf8d288885e561d6ecdd1beac6b5)]
17
+
18
+ #### :house: Internal changes
19
+
20
+ - style: Automated code fix [eea-jenkins - [`7396574`](https://github.com/eea/volto-cca-policy/commit/7396574df65205350cedb1e105927cb9d01ce3ae)]
21
+ - style: Automated code fix [eea-jenkins - [`2f6ca50`](https://github.com/eea/volto-cca-policy/commit/2f6ca50ad766066511659ac8dd85a3daceeca1d8)]
22
+
23
+ ### [0.3.25](https://github.com/eea/volto-cca-policy/compare/0.3.24...0.3.25) - 14 April 2025
24
+
25
+ #### :bug: Bug Fixes
26
+
27
+ - fix: code cleanup [kreafox - [`6608927`](https://github.com/eea/volto-cca-policy/commit/660892792e10ef6f38c66d8f2d6b7f2993973bdf)]
28
+
29
+ #### :nail_care: Enhancements
30
+
31
+ - change: update planning section, add more tests - refs #285361 [kreafox - [`1d148c8`](https://github.com/eea/volto-cca-policy/commit/1d148c87f0f523effdf7399d7f8a1ddc4620e03e)]
32
+ - change: update planning section - refs #285361 [kreafox - [`50de23f`](https://github.com/eea/volto-cca-policy/commit/50de23f8e75053602b7d0bc1441ae1c59fe0944d)]
33
+
34
+ #### :house: Internal changes
35
+
36
+ - style: Automated code fix [eea-jenkins - [`e4f700e`](https://github.com/eea/volto-cca-policy/commit/e4f700ea7bcb425ae168308d47729ec59b15b2c4)]
37
+
38
+ #### :hammer_and_wrench: Others
39
+
40
+ - Remove console log [Tiberiu Ichim - [`a5ae067`](https://github.com/eea/volto-cca-policy/commit/a5ae06713b54cb1f5fce475827db0a8cd678ecf0)]
41
+ - More langs [Tiberiu Ichim - [`e17b887`](https://github.com/eea/volto-cca-policy/commit/e17b8870eb9e0cdadcab3eca126119287fd2bac2)]
42
+ - test: increase code coverage [kreafox - [`7aeb818`](https://github.com/eea/volto-cca-policy/commit/7aeb818b31fcf2c5225f0b4a6d0294664313b8aa)]
43
+ - increase code coverage [kreafox - [`f82df41`](https://github.com/eea/volto-cca-policy/commit/f82df41ed61fbd901fc5013d966f745c2a97f315)]
44
+ - fix tests [kreafox - [`cf4d267`](https://github.com/eea/volto-cca-policy/commit/cf4d2677c523609b0387f6d8142cb1ad58f0353e)]
45
+ - fix tests [kreafox - [`2151ffc`](https://github.com/eea/volto-cca-policy/commit/2151ffc3d27ff41bd7e72c6c0f373274925e11e8)]
46
+ - fix tests [kreafox - [`3971083`](https://github.com/eea/volto-cca-policy/commit/3971083f85a79d6f99d15b622ca8140a67b1a665)]
7
47
  ### [0.3.24](https://github.com/eea/volto-cca-policy/compare/0.3.23...0.3.24) - 11 April 2025
8
48
 
9
49
  #### :rocket: Dependency updates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.3.24",
3
+ "version": "0.3.26",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import '@testing-library/jest-dom';
3
+ import { render, fireEvent } from '@testing-library/react';
4
+ import AccordionList from './AccordionList';
5
+
6
+ describe('AccordionList', () => {
7
+ const mockAccordions = [
8
+ { title: 'Section 1', content: 'Content for section 1' },
9
+ { title: 'Section 2', content: 'Content for section 2' },
10
+ ];
11
+
12
+ it('renders all accordion titles', () => {
13
+ const { getByText } = render(<AccordionList accordions={mockAccordions} />);
14
+ expect(getByText('Section 1')).toBeInTheDocument();
15
+ expect(getByText('Section 2')).toBeInTheDocument();
16
+ });
17
+
18
+ it('toggles accordion content on title click', () => {
19
+ const { getByText, container } = render(
20
+ <AccordionList accordions={mockAccordions} />,
21
+ );
22
+
23
+ const contentDiv = container.querySelector('.content');
24
+
25
+ expect(contentDiv).not.toHaveClass('active');
26
+
27
+ fireEvent.click(getByText('Section 1'));
28
+ expect(contentDiv).toHaveClass('active');
29
+
30
+ fireEvent.click(getByText('Section 1'));
31
+ expect(contentDiv).not.toHaveClass('active');
32
+ });
33
+ });
@@ -11,7 +11,9 @@ import './style.less';
11
11
 
12
12
  const MissionSignatoriesProfileView = (props) => {
13
13
  const { data } = props;
14
- const result = data?._v_results?.[0] || {};
14
+ const result = data?._v_results || {};
15
+ const governance = result?.governance?.[0] || {};
16
+
15
17
  // const dataJson = JSON.parse(result?.Cooperation_Experience);
16
18
  const [activeIndex, setActiveIndex] = React.useState(0);
17
19
 
@@ -23,7 +25,9 @@ const MissionSignatoriesProfileView = (props) => {
23
25
  'DescribeDetailCooperationEnhance'
24
26
  ]
25
27
  } */}
26
- <h1>{result?.Signatory}</h1>
28
+ <h2>{result?.planning_titles?.[0].Signatory}</h2>
29
+
30
+ <br />
27
31
 
28
32
  <Tab
29
33
  menu={{
@@ -42,19 +46,28 @@ const MissionSignatoriesProfileView = (props) => {
42
46
  },
43
47
  {
44
48
  menuItem: 'Governance',
45
- render: () => <GovernanceTab result={result} />,
49
+ render: () => <GovernanceTab result={governance} />,
46
50
  },
47
51
  {
48
52
  menuItem: 'Assessment',
49
- render: () => <AssessmentTab result={result} />,
53
+ render: () => <AssessmentTab />,
50
54
  },
51
55
  {
52
56
  menuItem: 'Planning',
53
- render: () => <PlanningTab result={result} />,
57
+ render: () => (
58
+ <PlanningTab
59
+ result={{
60
+ planning_goals: result?.planning_goals,
61
+ planning_titles: result?.planning_titles,
62
+ planning_climate_action: result?.planning_climate_action,
63
+ planning_climate_sectors: result?.planning_climate_sectors,
64
+ }}
65
+ />
66
+ ),
54
67
  },
55
68
  {
56
69
  menuItem: 'Action Pages',
57
- render: () => <ActionPagesTab result={result} />,
70
+ render: () => <ActionPagesTab />,
58
71
  },
59
72
  ]}
60
73
  />
@@ -1,43 +1,107 @@
1
1
  import React from 'react';
2
2
  import '@testing-library/jest-dom/extend-expect';
3
- import { render, fireEvent } from '@testing-library/react';
3
+ import { render, fireEvent, waitFor } from '@testing-library/react';
4
4
  import MissionSignatoriesProfileView from './MissionSignatoriesProfileView';
5
5
 
6
+ // Mock components for tabs
7
+ jest.mock('./TabSections/IntroductionTab', () => () => (
8
+ <div>Introduction Content</div>
9
+ ));
10
+ jest.mock('./TabSections/GovernanceTab', () => () => (
11
+ <div>Governance Content</div>
12
+ ));
13
+ jest.mock('./TabSections/AssessmentTab', () => () => (
14
+ <div>Assessment Content</div>
15
+ ));
16
+ jest.mock('./TabSections/PlanningTab', () => () => <div>Planning Content</div>);
17
+ jest.mock('./TabSections/ActionPagesTab', () => () => (
18
+ <div>Action Pages Content</div>
19
+ ));
20
+
6
21
  describe('MissionSignatoriesProfileView', () => {
7
- it('should render the component with data', () => {
8
- const data = {
9
- _v_results: [
10
- {
11
- Signatory: 'Test Signatory',
12
- Describe: 'Test description',
13
- Provide: 'Test evidence',
14
- },
15
- ],
16
- };
22
+ const data = {
23
+ _v_results: {
24
+ planning_titles: [{ Signatory: 'Test Signatory Title' }],
25
+ planning_goals: [],
26
+ planning_climate_action: [],
27
+ planning_climate_sectors: [],
28
+ governance: [{}],
29
+ },
30
+ };
17
31
 
32
+ it('should render the component with data and tabs', () => {
18
33
  const { getByText } = render(<MissionSignatoriesProfileView data={data} />);
19
34
 
20
- expect(getByText('Test Signatory')).toBeInTheDocument();
21
35
  expect(getByText('Governance')).toBeInTheDocument();
22
36
  expect(getByText('Assessment')).toBeInTheDocument();
23
37
  expect(getByText('Planning')).toBeInTheDocument();
24
38
  expect(getByText('Action Pages')).toBeInTheDocument();
39
+ expect(getByText('Introduction')).toBeInTheDocument();
25
40
  });
26
41
 
27
- it('should render tabs and switch content', () => {
28
- const data = {
29
- _v_results: [
30
- {
31
- Signatory: 'Test Signatory',
32
- },
33
- ],
34
- };
42
+ it('should render Signatory title', () => {
43
+ const { getByText } = render(<MissionSignatoriesProfileView data={data} />);
44
+ expect(getByText('Test Signatory Title')).toBeInTheDocument();
45
+ });
46
+
47
+ it('should handle missing planning_titles gracefully', () => {
48
+ const data = { _v_results: { governance: [{}] } };
49
+ const { container } = render(<MissionSignatoriesProfileView data={data} />);
50
+ expect(container).toBeInTheDocument();
51
+ });
35
52
 
53
+ it('should handle empty _v_results object gracefully', () => {
54
+ const { getByText } = render(
55
+ <MissionSignatoriesProfileView data={{ _v_results: {} }} />,
56
+ );
57
+ expect(getByText('Introduction')).toBeInTheDocument();
58
+ });
59
+
60
+ it('should handle completely missing data prop', () => {
61
+ const { getByText } = render(<MissionSignatoriesProfileView data={{}} />);
62
+ expect(getByText('Planning')).toBeInTheDocument();
63
+ expect(getByText('Introduction')).toBeInTheDocument();
64
+ });
65
+
66
+ it('should render all tab labels', () => {
67
+ const { getByText } = render(<MissionSignatoriesProfileView data={{}} />);
68
+ [
69
+ 'Introduction',
70
+ 'Governance',
71
+ 'Assessment',
72
+ 'Planning',
73
+ 'Action Pages',
74
+ ].forEach((label) => {
75
+ expect(getByText(label)).toBeInTheDocument();
76
+ });
77
+ });
78
+
79
+ it('should switch between tabs and display correct content', async () => {
36
80
  const { getByText } = render(<MissionSignatoriesProfileView data={data} />);
37
- const governanceTab = getByText('Governance');
38
- fireEvent.click(governanceTab);
39
- expect(
40
- getByText('Opportunities and benefits of climate action'),
41
- ).toBeInTheDocument();
81
+
82
+ fireEvent.click(getByText('Governance'));
83
+ await waitFor(() =>
84
+ expect(getByText('Governance Content')).toBeInTheDocument(),
85
+ );
86
+
87
+ fireEvent.click(getByText('Introduction'));
88
+ await waitFor(() =>
89
+ expect(getByText('Introduction Content')).toBeInTheDocument(),
90
+ );
91
+
92
+ fireEvent.click(getByText('Assessment'));
93
+ await waitFor(() =>
94
+ expect(getByText('Assessment Content')).toBeInTheDocument(),
95
+ );
96
+
97
+ fireEvent.click(getByText('Planning'));
98
+ await waitFor(() =>
99
+ expect(getByText('Planning Content')).toBeInTheDocument(),
100
+ );
101
+
102
+ fireEvent.click(getByText('Action Pages'));
103
+ await waitFor(() =>
104
+ expect(getByText('Action Pages Content')).toBeInTheDocument(),
105
+ );
42
106
  });
43
107
  });
@@ -10,10 +10,29 @@ import {
10
10
  } from 'semantic-ui-react';
11
11
  import { Callout } from '@eeacms/volto-eea-design-system/ui';
12
12
  import AccordionList from './../AccordionList';
13
- import ItemsSection from './../ItemsSection';
14
13
 
15
14
  import image from '@eeacms/volto-cca-policy/../theme//assets/images/image-narrow.svg';
16
15
 
16
+ const ItemsSection = ({ items }) => {
17
+ return (
18
+ <ItemGroup className="items-group">
19
+ <Item>
20
+ <Image size="small" src={image} />
21
+ <ItemContent verticalAlign="middle">
22
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit
23
+ </ItemContent>
24
+ </Item>
25
+
26
+ <Item>
27
+ <Image size="small" src={image} />
28
+ <ItemContent verticalAlign="middle">
29
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit
30
+ </ItemContent>
31
+ </Item>
32
+ </ItemGroup>
33
+ );
34
+ };
35
+
17
36
  const AssessmentTab = () => {
18
37
  const [activeIndex, setActiveIndex] = React.useState(0);
19
38
  return (
@@ -4,85 +4,29 @@ import { Callout } from '@eeacms/volto-eea-design-system/ui';
4
4
  import { HTMLField } from '@eeacms/volto-cca-policy/helpers';
5
5
  import { formatTextToHTML } from '@eeacms/volto-cca-policy/utils';
6
6
  import AccordionList from './../AccordionList';
7
- import StatisticsSection from './../StatisticsSection';
8
7
 
9
8
  const GovernanceTab = ({ result }) => {
10
- const statisticsData = [
11
- {
12
- value: '460km',
13
- label: 'Duis non quam et nisi tincidunt',
14
- },
15
- {
16
- value: '51-60%',
17
- label: 'Vestibulum ante ipsum primis',
18
- },
19
- {
20
- value: '2.431.213',
21
- label: 'Aliquam erat volutpat',
22
- },
23
- {
24
- value: '2023',
25
- label: 'Etiam accumsan urna a mauris',
26
- },
27
- ];
9
+ const { Introduction, Describe_Title, Describe, Provide_Title, Provide } =
10
+ result || {};
28
11
 
29
12
  return (
30
13
  <Tab.Pane>
31
14
  <h2>Governance</h2>
32
15
  <Callout>
33
- <p>
34
- Sed at risus vel nulla consequat fermentum. Donec et orci mauris.
35
- Nullam tempor velit id mi luctus, a scelerisque libero accumsan. In
36
- hac habitasse platea dictumst. Cras ac nunc nec massa tristique
37
- fringilla.
38
- </p>
16
+ <p>{Introduction}</p>
39
17
  </Callout>
40
18
 
41
- <StatisticsSection statistics={statisticsData} />
19
+ <h3>{Describe_Title}</h3>
42
20
 
43
- <h3>Climate related issues</h3>
44
- <AccordionList
45
- accordions={[
46
- {
47
- title: 'Vestibulum ante ipsum primis',
48
- content: 'No additional details provided.',
49
- },
50
- {
51
- title: 'Etiam accumsan urna a mauris',
52
- content: 'No additional details provided.',
53
- },
54
- ]}
55
- />
21
+ <HTMLField value={{ data: formatTextToHTML(Describe) }} />
56
22
 
57
- <h3>Opportunities and benefits of climate action</h3>
58
-
59
- <HTMLField value={{ data: formatTextToHTML(result?.Describe) }} />
23
+ <br />
60
24
 
61
25
  <AccordionList
62
26
  accordions={[
63
27
  {
64
- title: ' Further details and evidence',
65
- content: (
66
- <HTMLField value={{ data: formatTextToHTML(result?.Provide) }} />
67
- ),
68
- },
69
- ]}
70
- />
71
-
72
- <h3>
73
- {result?.Signatory} engages with other levels of government regarding
74
- their:
75
- </h3>
76
-
77
- <AccordionList
78
- accordions={[
79
- {
80
- title: 'Vestibulum ante ipsum primis',
81
- content: 'No additional details provided.',
82
- },
83
- {
84
- title: 'Etiam accumsan urna a mauris',
85
- content: 'No additional details provided.',
28
+ title: <>{Provide_Title}</>,
29
+ content: <HTMLField value={{ data: formatTextToHTML(Provide) }} />,
86
30
  },
87
31
  ]}
88
32
  />
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import GovernanceTab from './GovernanceTab';
5
+
6
+ describe('GovernanceTab', () => {
7
+ const mockResult = {
8
+ Describe_Title: 'Opportunities and benefits of climate action',
9
+ Provide_Title: 'Further details and evidence',
10
+ };
11
+
12
+ it('renders the governance tab correctly', () => {
13
+ const { getByText } = render(<GovernanceTab result={mockResult} />);
14
+
15
+ expect(
16
+ getByText('Opportunities and benefits of climate action'),
17
+ ).toBeInTheDocument();
18
+ expect(getByText('Further details and evidence')).toBeInTheDocument();
19
+ });
20
+ });
@@ -1,117 +1,191 @@
1
1
  import React from 'react';
2
- import { Tab, Message } from 'semantic-ui-react';
2
+ import {
3
+ Tab,
4
+ Message,
5
+ Segment,
6
+ Grid,
7
+ Item,
8
+ ItemGroup,
9
+ ItemContent,
10
+ Image,
11
+ } from 'semantic-ui-react';
3
12
  import { Callout } from '@eeacms/volto-eea-design-system/ui';
13
+ import { HTMLField } from '@eeacms/volto-cca-policy/helpers';
14
+ import {
15
+ formatTextToHTML,
16
+ extractPlanNameAndURL,
17
+ } from '@eeacms/volto-cca-policy/utils';
4
18
  import AccordionList from './../AccordionList';
5
- import ItemsSection from './../ItemsSection';
19
+ import image from '@eeacms/volto-cca-policy/../theme/assets/images/image-narrow.svg';
20
+
21
+ const ItemsSection = ({ items }) => {
22
+ if (!items?.length) return null;
23
+
24
+ return (
25
+ <ItemGroup className="items-group">
26
+ {items.map((sector, index) => (
27
+ <Item key={index}>
28
+ <Image size="small" src={image} />
29
+ <ItemContent verticalAlign="middle">{sector}</ItemContent>
30
+ </Item>
31
+ ))}
32
+ </ItemGroup>
33
+ );
34
+ };
35
+
36
+ const PlanningGoalContent = ({ goal }) => {
37
+ const hasHazards = goal?.Climate_Hazards?.length > 0;
38
+ const hasComments = !!goal?.Comments;
39
+ const hasDescription = !!goal?.Description;
40
+
41
+ return (
42
+ <div>
43
+ <Grid columns="12">
44
+ {hasHazards && (
45
+ <Grid.Column mobile={12} tablet={12} computer={6}>
46
+ <h5>{goal.Climate_Hazards_Addressed_Label}</h5>
47
+ <ul>
48
+ {goal.Climate_Hazards.map((hazard, index) => (
49
+ <li key={index}>{hazard}</li>
50
+ ))}
51
+ </ul>
52
+ </Grid.Column>
53
+ )}
54
+ <Grid.Column mobile={12} tablet={12} computer={hasHazards ? 6 : 12}>
55
+ {hasComments && (
56
+ <>
57
+ <h5 className="small-label">{goal.Comments_Label}</h5>
58
+ <Segment>
59
+ <HTMLField value={{ data: formatTextToHTML(goal.Comments) }} />
60
+ </Segment>
61
+ </>
62
+ )}
63
+ {hasDescription && (
64
+ <>
65
+ <h5 className="small-label">{goal.Description_Label}</h5>
66
+ <Segment>
67
+ <HTMLField
68
+ value={{ data: formatTextToHTML(goal.Description) }}
69
+ />
70
+ </Segment>
71
+ </>
72
+ )}
73
+ </Grid.Column>
74
+ </Grid>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ const PlanningTab = ({ result }) => {
80
+ const {
81
+ planning_goals = [],
82
+ planning_titles = [],
83
+ planning_climate_action = [],
84
+ } = result || {};
85
+
86
+ const titleData = planning_titles?.[0];
87
+ const goalData = planning_goals?.[0];
88
+
89
+ const sortedGoals = [...planning_goals].sort((a, b) => {
90
+ const aNum = parseInt(a.Adaptation_Goal_Id.replace(/\D/g, ''), 10);
91
+ const bNum = parseInt(b.Adaptation_Goal_Id.replace(/\D/g, ''), 10);
92
+ return aNum - bNum;
93
+ });
6
94
 
7
- const PlanningTab = () => {
8
95
  return (
9
96
  <Tab.Pane>
10
- <h2>Planning</h2>
11
- <Callout>
12
- <p>
13
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
14
- posuere cubilia curae; Pellentesque sodales, velit nec euismod
15
- scelerisque, lectus est interdum eros, sit amet bibendum eros sapien
16
- in magna.
17
- </p>
18
- </Callout>
19
- <div className="section-wrapper">
20
- <h5>
21
- <span className="section-number">1. </span>
22
- Donec in laoreet leo. Quisque suscipit ligula eu turpis dignissim, a
23
- eleifend ipsum cursus.
24
- </h5>
25
- <AccordionList
26
- variation="secondary"
27
- accordions={[
28
- {
29
- title: 'Vestibulum ante ipsum primis',
30
- content: 'No additional details provided.',
31
- },
32
- ]}
33
- />
34
- </div>
35
- <div className="section-wrapper">
36
- <h5>
37
- <span className="section-number">2. </span>
38
- Donec in laoreet leo. Quisque suscipit ligula eu turpis dignissim, a
39
- eleifend ipsum cursus.
40
- </h5>
41
- <AccordionList
42
- variation="secondary"
43
- accordions={[
44
- {
45
- title: 'Vestibulum ante ipsum primis',
46
- content: 'No additional details provided.',
47
- },
48
- ]}
49
- />
50
- </div>
51
- <div className="section-wrapper">
52
- <h5>
53
- <span className="section-number">3. </span>
54
- Donec in laoreet leo. Quisque suscipit ligula eu turpis dignissim, a
55
- eleifend ipsum cursus.
56
- </h5>
57
- <AccordionList
58
- variation="secondary"
59
- accordions={[
60
- {
61
- title: 'Vestibulum ante ipsum primis',
62
- content: 'No additional details provided.',
63
- },
64
- ]}
65
- />
66
- </div>
67
- <h2>
68
- Quisque suscipit ligula eu turpis dignissim, a eleifend ipsum cursus.
69
- </h2>
70
-
71
- <Callout>
72
- <p>
73
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
74
- posuere cubilia curae; Pellentesque sodales, velit nec euismod
75
- scelerisque, lectus est interdum eros, sit amet bibendum eros sapien
76
- in magna.
77
- </p>
78
- </Callout>
79
- <br />
80
- <Message>
81
- <p>
82
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
83
- posuere cubilia curae. Nunc euismod bibendum augue. Cras nec ligula
84
- velit. Donec in laoreet leo. Quisque suscipit ligula eu turpis
85
- dignissim, a eleifend ipsum cursus.
86
- </p>
87
- </Message>
88
- <ItemsSection />
89
- <p>
90
- Curabitur at felis non libero suscipit fermentum. Duis volutpat, ante et
91
- scelerisque luctus, sem nulla placerat leo, at aliquet libero justo id
92
- nulla. Integer at dui nec magna posuere fringilla. Nunc euismod bibendum
93
- augue. Cras nec ligula velit. Donec in laoreet leo. Quisque suscipit
94
- ligula eu turpis dignissim, a eleifend ipsum cursus.
95
- </p>
96
- <p>
97
- Year of formal approval of plan: <strong className="date">2023</strong>{' '}
98
- End year of plan: {''}
99
- <strong className="date">2030</strong>
100
- </p>
101
-
102
- <p>
103
- <a href="/">
104
- <strong>Nunc euismod bibendum augue</strong>
105
- </a>
106
- </p>
107
-
108
- <p>
109
- <a href="/">
110
- <strong>
111
- Donec in laoreet leo. Quisque suscipit ligula eu turpis dignissim
112
- </strong>
113
- </a>
114
- </p>
97
+ {titleData?.Title && <h2>{titleData.Title}</h2>}
98
+ {titleData?.Abstract_Line && (
99
+ <Callout>
100
+ <p>{titleData.Abstract_Line}</p>
101
+ </Callout>
102
+ )}
103
+
104
+ {sortedGoals.map((goal, index) => {
105
+ return (
106
+ <div key={index} className="section-wrapper">
107
+ <span className="goal-title-label">{goal?.Title_Label}</span>
108
+
109
+ <HTMLField value={{ data: formatTextToHTML(goal?.Title) }} />
110
+
111
+ <AccordionList
112
+ variation="tertiary"
113
+ accordions={[
114
+ {
115
+ title: goal?.More_Details_Label || 'More details',
116
+ content: <PlanningGoalContent goal={goal} />,
117
+ },
118
+ ]}
119
+ />
120
+ </div>
121
+ );
122
+ })}
123
+
124
+ {goalData?.Climate_Action_Title && (
125
+ <h2>{goalData.Climate_Action_Title}</h2>
126
+ )}
127
+
128
+ {goalData?.Climate_Action_Abstract && (
129
+ <Callout>
130
+ <p>{goalData.Climate_Action_Abstract}</p>
131
+ </Callout>
132
+ )}
133
+
134
+ {planning_climate_action.map((action, index) => {
135
+ return (
136
+ <React.Fragment key={index}>
137
+ <br />
138
+ {action?.Sectors_Introduction && (
139
+ <Message>
140
+ <p>{action.Sectors_Introduction}</p>
141
+ </Message>
142
+ )}
143
+
144
+ <ItemsSection items={action?.Sectors} />
145
+ {action?.Description && <p>{action.Description}</p>}
146
+
147
+ {(action?.Approval_Year || action?.End_Year) && (
148
+ <p>
149
+ {action?.Year_Of_Approval_Label}{' '}
150
+ <strong className="date">{action.Approval_Year}</strong>{' '}
151
+ {action?.End_Year_Of_Plan_Label}{' '}
152
+ <strong className="date">{action.End_Year}</strong>
153
+ </p>
154
+ )}
155
+
156
+ {action?.Name_Of_Plan_And_Hyperlink && (
157
+ <p>
158
+ {(() => {
159
+ const { name, url } = extractPlanNameAndURL(
160
+ action.Name_Of_Plan_And_Hyperlink,
161
+ );
162
+
163
+ return url ? (
164
+ <a href={url} title={name} target="_blank" rel="noreferrer">
165
+ <strong>
166
+ {action.Further_Information_Link_Text}
167
+ {name && ` [${name}]`}
168
+ </strong>
169
+ </a>
170
+ ) : (
171
+ <strong>
172
+ {action.Further_Information_Link_Text}
173
+ {name && ` [${name}]`}
174
+ </strong>
175
+ );
176
+ })()}
177
+ </p>
178
+ )}
179
+ {action?.Attachment && (
180
+ <p>
181
+ <a href={action.Attachment}>
182
+ <strong>{action.Explore_Plan_Link_Text}</strong>
183
+ </a>
184
+ </p>
185
+ )}
186
+ </React.Fragment>
187
+ );
188
+ })}
115
189
  </Tab.Pane>
116
190
  );
117
191
  };
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import PlanningTab from './PlanningTab';
5
+
6
+ jest.mock('@eeacms/volto-cca-policy/helpers', () => ({
7
+ HTMLField: ({ value }) => (
8
+ <div dangerouslySetInnerHTML={{ __html: value.data }} />
9
+ ),
10
+ }));
11
+
12
+ jest.mock('@eeacms/volto-cca-policy/utils', () => ({
13
+ formatTextToHTML: (text) => text,
14
+ extractPlanNameAndURL: (str) => ({
15
+ name: 'Plan Example',
16
+ url: 'https://plan-link.com',
17
+ }),
18
+ }));
19
+
20
+ describe('PlanningTab', () => {
21
+ const mockResult = {
22
+ planning_titles: [
23
+ { Title: 'Planning Title', Abstract_Line: 'Abstract info' },
24
+ ],
25
+ planning_goals: [
26
+ {
27
+ Adaptation_Goal_Id: 'AG-001',
28
+ Title: 'Goal Title 1',
29
+ Title_Label: 'Goal 1 Label',
30
+ More_Details_Label: 'Details',
31
+ Climate_Hazards: ['Heat', 'Flood'],
32
+ Climate_Hazards_Addressed_Label: 'Hazards',
33
+ Comments_Label: 'Comments',
34
+ Comments: 'Some comments',
35
+ Description_Label: 'Description',
36
+ Description: 'Detailed description',
37
+ Climate_Action_Title: 'Action Title',
38
+ Climate_Action_Abstract: 'Action abstract',
39
+ },
40
+ {
41
+ Adaptation_Goal_Id: 'AG-002',
42
+ Title: 'Goal Title 2',
43
+ Title_Label: 'Goal 2 Label',
44
+ More_Details_Label: 'Details',
45
+ Climate_Hazards: ['Drought', 'Storm'],
46
+ Climate_Hazards_Addressed_Label: 'Hazards',
47
+ Comments_Label: 'Comments',
48
+ Comments: 'Other comments',
49
+ Description_Label: 'Description',
50
+ Description: 'Another detailed description',
51
+ Climate_Action_Title: 'Action Title',
52
+ Climate_Action_Abstract: 'Another action abstract',
53
+ },
54
+ ],
55
+ planning_climate_action: [
56
+ {
57
+ Sectors_Introduction: 'Intro to sectors',
58
+ Description: 'Sector description',
59
+ Year_Of_Approval_Label: 'Approval Year:',
60
+ Approval_Year: '2023',
61
+ End_Year_Of_Plan_Label: 'End Year:',
62
+ End_Year: '2030',
63
+ Name_Of_Plan_And_Hyperlink: 'http://example.com; https://plan-link.com',
64
+ Further_Information_Link_Text: 'More Info',
65
+ Attachment: 'http://attachment.com',
66
+ Explore_Plan_Link_Text: 'Explore Plan',
67
+ Sectors: ['Agriculture'],
68
+ },
69
+ ],
70
+ };
71
+
72
+ it('renders planning tab with basic data', () => {
73
+ const { getByText } = render(<PlanningTab result={mockResult} />);
74
+
75
+ expect(getByText('Planning Title')).toBeInTheDocument();
76
+ expect(getByText('Abstract info')).toBeInTheDocument();
77
+
78
+ expect(getByText('Goal Title 1')).toBeInTheDocument();
79
+ expect(getByText('Goal Title 2')).toBeInTheDocument();
80
+
81
+ expect(getByText('Action Title')).toBeInTheDocument();
82
+ expect(getByText('Intro to sectors')).toBeInTheDocument();
83
+ expect(getByText('Sector description')).toBeInTheDocument();
84
+
85
+ expect(getByText(/Approval Year:/)).toBeInTheDocument();
86
+ expect(getByText(/2023/)).toBeInTheDocument();
87
+ expect(getByText(/End Year:/)).toBeInTheDocument();
88
+ expect(getByText(/2030/)).toBeInTheDocument();
89
+ });
90
+
91
+ it('renders ItemsSection if there are sectors', () => {
92
+ const { getByText } = render(<PlanningTab result={mockResult} />);
93
+
94
+ expect(getByText(/Agriculture/)).toBeInTheDocument();
95
+ });
96
+
97
+ it('renders image in ItemsSection', () => {
98
+ const { container } = render(<PlanningTab result={mockResult} />);
99
+ const images = container.querySelectorAll('img');
100
+ expect(images.length).toBeGreaterThan(0);
101
+ });
102
+
103
+ it('renders hyperlink with extracted name and URL', () => {
104
+ const { getByText } = render(<PlanningTab result={mockResult} />);
105
+
106
+ const link = getByText(/More Info \[Plan Example\]/);
107
+ expect(link).toBeInTheDocument();
108
+ expect(link.closest('a')).toHaveAttribute('href', 'https://plan-link.com');
109
+ });
110
+ });
@@ -8,6 +8,10 @@
8
8
  margin: 2em 0;
9
9
  }
10
10
 
11
+ .column > .ui.segment {
12
+ background-color: #fff !important;
13
+ }
14
+
11
15
  .section-wrapper-info {
12
16
  display: flex;
13
17
  justify-content: space-between;
@@ -20,12 +24,24 @@
20
24
  }
21
25
 
22
26
  .section-wrapper {
23
- padding: 1.5em;
24
- margin: 2em 0;
25
- background-color: #f9f9f9;
27
+ margin: 1em 0;
28
+
29
+ .goal-title-label {
30
+ display: inline-block;
31
+ padding: 0.3em 0.5em;
32
+ margin: 1em 0;
33
+ background-color: #dbe7f4;
34
+ font-size: 14px;
35
+ font-weight: bold;
36
+ text-transform: uppercase;
37
+ }
38
+
39
+ .ui.accordion {
40
+ margin-top: 1em;
41
+ }
26
42
 
27
- .section-number {
28
- color: @pineGreen;
43
+ .small-label {
44
+ font-size: 1em;
29
45
  }
30
46
  }
31
47
 
@@ -0,0 +1,51 @@
1
+ export const download_fields = [
2
+ // { field: 'cca_uid', name: 'UID' },
3
+ { field: 'about', name: 'About' },
4
+ { field: 'title', name: 'Title' },
5
+ { field: 'created', name: 'Creation Date' },
6
+ { field: 'issued', name: 'Publication Date' },
7
+ { field: 'creators', name: 'Creator' },
8
+ { field: 'objectProvides', name: 'Content type' },
9
+ { field: 'cca_keywords', name: 'Keywords' },
10
+ { field: 'cca_adaptation_sectors', name: 'Sectors' },
11
+ { field: 'cca_climate_impacts', name: 'Climate impact' },
12
+ { field: 'transnational_regions', name: 'Transnational regions' },
13
+ { field: 'cca_adaptation_elements', name: 'Adaptation Approaches' },
14
+ { field: 'cca_funding_programme', name: 'Funding programme' },
15
+ { field: 'cca_key_type_measure', name: 'Key type measure' },
16
+ { field: 'cca_geographic_countries', name: 'Countries' },
17
+ { field: 'cca_origin_websites', name: 'Origin website' },
18
+ { field: 'cca_health_impacts', name: 'Health impacts' },
19
+ { field: 'cca_partner_contributors', name: 'Observatory impacts' },
20
+ // { field: 'fulltext', name: 'Description' },
21
+ ];
22
+
23
+ export const eea_languages = [
24
+ { name: 'English', code: 'en' },
25
+ { name: 'български', code: 'bg' },
26
+ { name: 'Español', code: 'es' },
27
+ // { name: 'Čeština', code: 'cs' },
28
+ { name: 'Dansk', code: 'da' },
29
+ { name: 'Deutsch', code: 'de' },
30
+ // { name: 'Eesti keel', code: 'et' },
31
+ { name: 'Ελληνικά', code: 'el' },
32
+ { name: 'Français', code: 'fr' },
33
+ // { name: 'Gaeilge', code: 'ga' },
34
+ { name: 'Hrvatski', code: 'hr' },
35
+ { name: 'Italiano', code: 'it' },
36
+ // { name: 'Latviešu valoda', code: 'lv' },
37
+ // { name: 'Lietuvių kalba', code: 'lt' },
38
+ // { name: 'Magyar', code: 'hu' },
39
+ // { name: 'Malti', code: 'mt' },
40
+ // { name: 'Nederlands', code: 'nl' },
41
+ { name: 'Polski', code: 'pl' },
42
+ { name: 'Português', code: 'pt' },
43
+ { name: 'Română', code: 'ro' },
44
+ // { name: 'Slovenčina', code: 'sk' },
45
+ // { name: 'Slovenščina', code: 'sl' },
46
+ { name: 'Suomi', code: 'fi' },
47
+ { name: 'Svenska', code: 'sv' },
48
+ { name: 'Íslenska', code: 'is' },
49
+ { name: 'Nynorsk', code: 'nn' },
50
+ // { name: 'Türkçe', code: 'tr' },
51
+ ];
package/src/index.js CHANGED
@@ -42,6 +42,8 @@ import eeaWhiteLogo from '@eeacms/volto-eea-design-system/../theme/themes/eea/as
42
42
  import './slate-styles.less';
43
43
  import BrokenLinks from './components/theme/Views/BrokenLinks';
44
44
 
45
+ import { eea_languages } from './constants';
46
+
45
47
  const getEnv = () => (typeof window !== 'undefined' ? window.env : process.env);
46
48
 
47
49
  const pathToNegRegex = (p) => `(?!(${p}))`;
@@ -140,35 +142,7 @@ const applyConfig = (config) => {
140
142
  // EEA customizations
141
143
  config.settings.eea = {
142
144
  ...(config.settings.eea || {}),
143
- languages: [
144
- { name: 'English', code: 'en' },
145
- // { name: 'български', code: 'bg' },
146
- { name: 'Español', code: 'es' },
147
- // { name: 'Čeština', code: 'cs' },
148
- { name: 'Dansk', code: 'da' },
149
- { name: 'Deutsch', code: 'de' },
150
- // { name: 'Eesti keel', code: 'et' },
151
- { name: 'Ελληνικά', code: 'el' },
152
- { name: 'Français', code: 'fr' },
153
- // { name: 'Gaeilge', code: 'ga' },
154
- { name: 'Hrvatski', code: 'hr' },
155
- { name: 'Italiano', code: 'it' },
156
- // { name: 'Latviešu valoda', code: 'lv' },
157
- // { name: 'Lietuvių kalba', code: 'lt' },
158
- // { name: 'Magyar', code: 'hu' },
159
- // { name: 'Malti', code: 'mt' },
160
- // { name: 'Nederlands', code: 'nl' },
161
- { name: 'Polski', code: 'pl' },
162
- { name: 'Português', code: 'pt' },
163
- // { name: 'Română', code: 'ro' },
164
- // { name: 'Slovenčina', code: 'sk' },
165
- // { name: 'Slovenščina', code: 'sl' },
166
- { name: 'Suomi', code: 'fi' },
167
- { name: 'Svenska', code: 'sv' },
168
- { name: 'Íslenska', code: 'is' },
169
- { name: 'Nynorsk', code: 'nn' },
170
- // { name: 'Türkçe', code: 'tr' },
171
- ],
145
+ languages: eea_languages,
172
146
  headerOpts: {
173
147
  ...(config.settings.eea?.headerOpts || {}),
174
148
  logo: ccaLogo,
@@ -1,9 +1,10 @@
1
1
  import { mergeConfig } from '@eeacms/search';
2
2
  import { getClientProxyAddress } from '../utils';
3
- import vocabs from '../vocabulary';
3
+ import { vocab } from '../vocabulary';
4
4
 
5
5
  import facets from './facets';
6
6
  import views from './views';
7
+ import { download_fields } from '../../constants';
7
8
 
8
9
  const ccaConfig = {
9
10
  title: 'ClimateAdapt Main',
@@ -76,30 +77,10 @@ export default function installMainSearch(config) {
76
77
  index_name: 'data_searchui',
77
78
  host: process.env.RAZZLE_ES_PROXY_ADDR || 'http://localhost:3000',
78
79
  runtime_mappings: cca_build_runtime_mappings,
79
- ...vocabs,
80
+ vocab,
80
81
  };
81
82
 
82
- config.searchui.ccaSearch.download_fields = [
83
- // { field: 'cca_uid', name: 'UID' },
84
- { field: 'about', name: 'About' },
85
- { field: 'title', name: 'Title' },
86
- { field: 'created', name: 'Creation Date' },
87
- { field: 'issued', name: 'Publication Date' },
88
- { field: 'creators', name: 'Creator' },
89
- { field: 'objectProvides', name: 'Content type' },
90
- { field: 'cca_keywords', name: 'Keywords' },
91
- { field: 'cca_adaptation_sectors', name: 'Sectors' },
92
- { field: 'cca_climate_impacts', name: 'Climate impact' },
93
- { field: 'transnational_regions', name: 'Transnational regions' },
94
- { field: 'cca_adaptation_elements', name: 'Adaptation Approaches' },
95
- { field: 'cca_funding_programme', name: 'Funding programme' },
96
- { field: 'cca_key_type_measure', name: 'Key type measure' },
97
- { field: 'cca_geographic_countries', name: 'Countries' },
98
- { field: 'cca_origin_websites', name: 'Origin website' },
99
- { field: 'cca_health_impacts', name: 'Health impacts' },
100
- { field: 'cca_partner_contributors', name: 'Observatory impacts' },
101
- // { field: 'fulltext', name: 'Description' },
102
- ];
83
+ config.searchui.ccaSearch.download_fields = download_fields;
103
84
 
104
85
  const { ccaSearch } = config.searchui;
105
86
 
@@ -1,3 +1,4 @@
1
+ import { eea_languages } from '@eeacms/volto-cca-policy/constants';
1
2
  import { booleanFacet } from '@eeacms/search';
2
3
  import { getTodayWithTime } from './utils';
3
4
 
@@ -192,42 +193,44 @@ export const language = {
192
193
  type: 'any',
193
194
  };
194
195
  },
195
- facetValues: [
196
- 'de',
197
- 'en',
198
- 'es',
199
- 'fr',
200
- 'it',
201
- 'pl',
202
- // 'ar',
203
- // 'bg',
204
- // 'bs',
205
- // 'cs',
206
- // 'da',
207
- // 'el',
208
- // 'et',
209
- // 'fi',
210
- // 'ga',
211
- // 'hr',
212
- // 'hu',
213
- // 'is',
214
- // 'lt',
215
- // 'lv',
216
- // 'mk',
217
- // 'mt',
218
- // 'nl',
219
- // 'no',
220
- // 'pt',
221
- // 'ro',
222
- // 'ru',
223
- // 'sh',
224
- // 'sk',
225
- // 'sl',
226
- // 'sq',
227
- // 'sr',
228
- // 'sv',
229
- // 'tr',
230
- ],
196
+ facetValues: eea_languages.map(({ code }) => code),
231
197
  sortOn: 'custom',
232
198
  sortOnCustomLabel: 'Alphabetical',
233
199
  };
200
+
201
+ // [
202
+ // 'de',
203
+ // 'en',
204
+ // 'es',
205
+ // 'fr',
206
+ // 'it',
207
+ // 'pl',
208
+ // // 'ar',
209
+ // // 'bg',
210
+ // // 'bs',
211
+ // // 'cs',
212
+ // // 'da',
213
+ // // 'el',
214
+ // // 'et',
215
+ // // 'fi',
216
+ // // 'ga',
217
+ // // 'hr',
218
+ // // 'hu',
219
+ // // 'is',
220
+ // // 'lt',
221
+ // // 'lv',
222
+ // // 'mk',
223
+ // // 'mt',
224
+ // // 'nl',
225
+ // // 'no',
226
+ // // 'pt',
227
+ // // 'ro',
228
+ // // 'ru',
229
+ // // 'sh',
230
+ // // 'sk',
231
+ // // 'sl',
232
+ // // 'sq',
233
+ // // 'sr',
234
+ // // 'sv',
235
+ // // 'tr',
236
+ // ]
@@ -1,7 +1,7 @@
1
1
  import { mergeConfig } from '@eeacms/search';
2
2
  import { build_runtime_mappings } from '@eeacms/volto-globalsearch/utils';
3
3
  import { getClientProxyAddress } from './../utils';
4
- import vocabs from './../vocabulary';
4
+ import { vocab } from './../vocabulary';
5
5
 
6
6
  import facets from './facets-health';
7
7
  import views from './views-health';
@@ -90,7 +90,7 @@ export default function installMainSearch(config) {
90
90
  elastic_index: '_es/globalsearch',
91
91
  index_name: 'data_searchui',
92
92
  host: process.env.RAZZLE_ES_PROXY_ADDR || 'http://localhost:3000',
93
- ...vocabs,
93
+ vocab,
94
94
  runtime_mappings: build_runtime_mappings(clusters),
95
95
  };
96
96
 
@@ -1,4 +1,6 @@
1
- const vocab = {
1
+ import { eea_languages } from '@eeacms/volto-cca-policy/constants';
2
+
3
+ export const vocab = {
2
4
  cluster_name: {
3
5
  cca: 'Climate-ADAPT',
4
6
  },
@@ -19,6 +21,8 @@ const vocab = {
19
21
  'Publication reference': 'Publications and reports',
20
22
  Video: 'Videos and podcasts',
21
23
  },
24
+ language: Object.assign(
25
+ {},
26
+ ...eea_languages.map(({ name, code }) => ({ [code]: name })),
27
+ ),
22
28
  };
23
-
24
- export default { vocab };
package/src/utils.js CHANGED
@@ -81,11 +81,41 @@ export const filterBlocks = (content, blockTypes = []) => {
81
81
  export const formatTextToHTML = (text) => {
82
82
  if (!text) return '';
83
83
 
84
- // Replace \\n\\n with </p><p> (separate paragraphs)
85
- let formattedText = text.replace(/\\n\\n/g, '</p><p>');
86
-
87
- // Replace \\n with <br /> (line breaks within paragraphs)
88
- formattedText = formattedText.replace(/\\n/g, '<br />');
84
+ let formattedText = text
85
+ .replace(/\\\\/g, '\\') // unescape backslashes
86
+ .replace(/\\'/g, "'") // unescape single quotes
87
+ .replace(/\\"/g, '"') // unescape double quotes
88
+ .replace(/\\t\\n/g, '') // handle \t\n
89
+ .replace(/\\n\\n/g, '</p><p>') // double line break = paragraph
90
+ .replace(/\\no\s*/g, '<br />• ') // list-like "o " to bullet point
91
+ .replace(/\\n/g, '<br />'); // single line break
89
92
 
90
93
  return `<p>${formattedText}</p>`;
91
94
  };
95
+
96
+ export const extractPlanNameAndURL = (text) => {
97
+ if (!text) return { name: '', url: '' };
98
+
99
+ // Match URL inside parentheses
100
+ const parenthesisMatch = text.match(/\((https?:\/\/[^\s)]+)\)/);
101
+ // Match first direct URL not inside parentheses
102
+ const directMatch = text.match(/https?:\/\/[^\s,;)]+/);
103
+ const url = parenthesisMatch?.[1] || directMatch?.[0] || '';
104
+
105
+ let name = text;
106
+
107
+ if (url) {
108
+ // Remove URL and any punctuation before it
109
+ name = name
110
+ .replace(`(${url})`, '')
111
+ .replace(url, '')
112
+ .replace(/[-–;,:\s]+$/, '')
113
+ .replace(/[-–;,:\s]+$/, '')
114
+ .trim();
115
+ }
116
+
117
+ return {
118
+ name: name,
119
+ url,
120
+ };
121
+ };
@@ -1,54 +0,0 @@
1
- import React from 'react';
2
- import { Item, ItemGroup, ItemContent, Image } from 'semantic-ui-react';
3
-
4
- import image from '@eeacms/volto-cca-policy/../theme//assets/images/image-narrow.svg';
5
-
6
- const ItemsSection = ({ items }) => {
7
- return (
8
- <ItemGroup className="items-group">
9
- <Item>
10
- <Image size="small" src={image} />
11
- <ItemContent verticalAlign="middle">
12
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
13
- </ItemContent>
14
- </Item>
15
-
16
- <Item>
17
- <Image size="small" src={image} />
18
- <ItemContent verticalAlign="middle">
19
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
20
- </ItemContent>
21
- </Item>
22
-
23
- <Item>
24
- <Image size="small" src={image} />
25
- <ItemContent verticalAlign="middle">
26
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
27
- </ItemContent>
28
- </Item>
29
-
30
- <Item>
31
- <Image size="small" src={image} />
32
- <ItemContent verticalAlign="middle">
33
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
34
- </ItemContent>
35
- </Item>
36
-
37
- <Item>
38
- <Image size="small" src={image} />
39
- <ItemContent verticalAlign="middle">
40
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
41
- </ItemContent>
42
- </Item>
43
-
44
- <Item>
45
- <Image size="small" src={image} />
46
- <ItemContent verticalAlign="middle">
47
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
48
- </ItemContent>
49
- </Item>
50
- </ItemGroup>
51
- );
52
- };
53
-
54
- export default ItemsSection;