@treely/strapi-slices 3.3.0 → 4.1.0

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.
Files changed (32) hide show
  1. package/README.md +2 -2
  2. package/dist/constants/slicesConfig.d.ts +5 -0
  3. package/dist/index.d.ts +1 -2
  4. package/dist/rootMessages.de.d.ts +2 -0
  5. package/dist/rootMessages.en.d.ts +2 -0
  6. package/dist/slices/Timeline/Timeline.d.ts +27 -0
  7. package/dist/slices/Timeline/index.d.ts +2 -0
  8. package/dist/slices/Timeline/messages.de.d.ts +5 -0
  9. package/dist/slices/Timeline/messages.en.d.ts +5 -0
  10. package/dist/strapi-slices.cjs.development.js +258 -43
  11. package/dist/strapi-slices.cjs.development.js.map +1 -1
  12. package/dist/strapi-slices.cjs.production.min.js +1 -1
  13. package/dist/strapi-slices.cjs.production.min.js.map +1 -1
  14. package/dist/strapi-slices.esm.js +260 -42
  15. package/dist/strapi-slices.esm.js.map +1 -1
  16. package/package.json +1 -1
  17. package/src/components/SliceRenderer/SliceRenderer.tsx +5 -0
  18. package/src/constants/{sectionsConfig.ts → slicesConfig.ts} +3 -6
  19. package/src/index.tsx +0 -11
  20. package/src/rootMessages.de.ts +2 -0
  21. package/src/rootMessages.en.ts +2 -0
  22. package/src/slices/Timeline/Timeline.stories.tsx +196 -0
  23. package/src/slices/Timeline/Timeline.test.tsx +228 -0
  24. package/src/slices/Timeline/Timeline.tsx +331 -0
  25. package/src/slices/Timeline/index.ts +3 -0
  26. package/src/slices/Timeline/messages.de.ts +5 -0
  27. package/src/slices/Timeline/messages.en.ts +5 -0
  28. package/src/utils/mergeGlobalAndStrapiBlogPostData.ts +6 -4
  29. package/src/utils/mergeGlobalAndStrapiCustomerStoryData.ts +2 -2
  30. package/src/utils/mergeGlobalAndStrapiPageData.ts +7 -7
  31. package/src/utils/mergeGlobalAndStrapiProjectData.ts +5 -5
  32. package/dist/constants/sectionsConfig.d.ts +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treely/strapi-slices",
3
- "version": "3.3.0",
3
+ "version": "4.1.0",
4
4
  "license": "MIT",
5
5
  "author": "Tree.ly GmbH",
6
6
  "description": "@treely/strapi-slices is a open source library maintained by Tree.ly.",
@@ -39,6 +39,7 @@ import StrapiCustomerStory from '../../models/strapi/StrapiCustomerStory';
39
39
  import Comparison from '../../slices/Comparison';
40
40
  import Locale from '../../models/Locale';
41
41
  import { ContextProvider } from '../ContextProvider';
42
+ import Timeline from '../../slices/Timeline';
42
43
 
43
44
  export interface CustomSliceProps {
44
45
  slice: any;
@@ -296,6 +297,10 @@ export const SliceRenderer = ({
296
297
  slice={slice}
297
298
  />
298
299
  );
300
+ case 'sections.timeline':
301
+ return (
302
+ <Timeline key={`${slice.__component}-${slice.id}`} slice={slice} />
303
+ );
299
304
  default:
300
305
  if (CustomSlice) {
301
306
  return (
@@ -9,12 +9,9 @@ export const DARK_THEME_HEADER_SECTIONS = [
9
9
  'sections.hero',
10
10
  'sections.small-hero',
11
11
  ];
12
- export const SECTIONS_WITH_BLOG_POSTS = [
13
- 'sections.blog',
14
- 'sections.blog-cards',
15
- ];
16
- export const SECTIONS_WITH_CUSTOMER_STORIES = ['sections.customer-stories'];
17
- export const SECTIONS_WITH_PROJECTS = [
12
+ export const SLICES_WITH_BLOG_POSTS = ['sections.blog', 'sections.blog-cards'];
13
+ export const SLICES_WITH_CUSTOMER_STORIES = ['sections.customer-stories'];
14
+ export const SLICES_WITH_PROJECTS = [
18
15
  'sections.projects-grid',
19
16
  'sections.projects-map',
20
17
  'sections.project-facts',
package/src/index.tsx CHANGED
@@ -52,12 +52,6 @@ import mergeGlobalAndStrapiProjectData from './utils/mergeGlobalAndStrapiProject
52
52
  import strapiLinkUrl from './utils/strapiLinkUrl';
53
53
  import strapiMediaUrl from './utils/strapiMediaUrl';
54
54
 
55
- import {
56
- SECTIONS_WITH_BLOG_POSTS,
57
- SECTIONS_WITH_CUSTOMER_STORIES,
58
- SECTIONS_WITH_PROJECTS,
59
- } from './constants/sectionsConfig';
60
-
61
55
  export * from './components/SEOTags';
62
56
  export * from './components/SliceRenderer';
63
57
 
@@ -69,11 +63,6 @@ export {
69
63
  mergeGlobalAndStrapiProjectData,
70
64
  strapiLinkUrl,
71
65
  strapiMediaUrl,
72
-
73
- // Constants
74
- SECTIONS_WITH_BLOG_POSTS,
75
- SECTIONS_WITH_CUSTOMER_STORIES,
76
- SECTIONS_WITH_PROJECTS,
77
66
  };
78
67
 
79
68
  export type {
@@ -10,6 +10,7 @@ import portfolioDocumentsDownloadListMessagesDe from './components/portfolio/Doc
10
10
  import portfolioProjectInfoMessagesDe from './components/portfolio/ProjectInfo/messages.de';
11
11
  import portfolioSmallCheckoutMessagesDe from './components/portfolio/SmallCheckout/messages.de';
12
12
  import portfolioProjectCardMessagesDe from './components/portfolio/PortfolioProjectCard/messages.de';
13
+ import timelineMessagesDe from './slices/Timeline/messages.de';
13
14
 
14
15
  import unitMessagesDe from './unit.messages.en';
15
16
 
@@ -33,6 +34,7 @@ const rootMessagesDe = {
33
34
  ...projectFactsMessagesDe,
34
35
  ...projectsMapMessagesDe,
35
36
  ...shopCheckoutMessagesDe,
37
+ ...timelineMessagesDe,
36
38
 
37
39
  //
38
40
  // Units
@@ -10,6 +10,7 @@ import portfolioDocumentsDownloadListMessagesEn from './components/portfolio/Doc
10
10
  import portfolioProjectInfoMessagesEn from './components/portfolio/ProjectInfo/messages.en';
11
11
  import portfolioSmallCheckoutMessagesEn from './components/portfolio/SmallCheckout/messages.en';
12
12
  import portfolioProjectCardMessagesEn from './components/portfolio/PortfolioProjectCard/messages.en';
13
+ import timelineMessagesEn from './slices/Timeline/messages.en';
13
14
 
14
15
  import unitMessagesEn from './unit.messages.en';
15
16
 
@@ -33,6 +34,7 @@ const rootMessagesEn = {
33
34
  ...projectFactsMessagesEn,
34
35
  ...projectsMapMessagesEn,
35
36
  ...shopCheckoutMessagesEn,
37
+ ...timelineMessagesEn,
36
38
 
37
39
  //
38
40
  // Units
@@ -0,0 +1,196 @@
1
+ import React from 'react';
2
+ import { StoryFn, Meta } from '@storybook/react';
3
+ import {
4
+ storybookStrapiAvatarMock,
5
+ storybookStrapiCoverMock,
6
+ storybookStrapiTreeIconMock,
7
+ } from '../../test/storybookMocks/storybookStrapiMedia';
8
+ import Timeline from '.';
9
+ import { TimelineItem } from './Timeline';
10
+
11
+ export default {
12
+ title: 'slices/Timeline',
13
+ component: Timeline,
14
+ } as Meta<typeof Timeline>;
15
+
16
+ const Template: StoryFn<typeof Timeline> = (args) => <Timeline {...args} />;
17
+
18
+ const timelineItem: TimelineItem = {
19
+ id: 1,
20
+ tagline: '08 Jan 2024',
21
+ title: 'Title',
22
+ text: 'After a site visit in the summer of 2022, the project has now been successfully audited by TÜV Nord Cert according to ISO 14064-2, making it the second certified forest adaption climate protection project in the world.',
23
+ backgroundShapes: false,
24
+ };
25
+ const button = {
26
+ id: 1,
27
+ text: 'Button',
28
+ url: 'https://tree.ly',
29
+ };
30
+ const badge = { text: 'Badge', variant: 'green' };
31
+ const image = { id: 71, alt: 'Alt', img: { data: storybookStrapiCoverMock } };
32
+ const icon = {
33
+ id: 1,
34
+ alt: 'Icon alt text',
35
+ img: { data: storybookStrapiTreeIconMock },
36
+ };
37
+ const logo = {
38
+ id: 2,
39
+ alt: 'Avatar image alt text',
40
+ img: { data: storybookStrapiAvatarMock },
41
+ objectFit: 'contain',
42
+ };
43
+
44
+ export const Minimal = Template.bind({});
45
+ Minimal.args = {
46
+ slice: {
47
+ title: 'Title',
48
+ timelineItems: [
49
+ timelineItem,
50
+ timelineItem,
51
+ timelineItem,
52
+ timelineItem,
53
+ timelineItem,
54
+ ],
55
+ },
56
+ };
57
+
58
+ export const WithTaglineAndText = Template.bind({});
59
+ WithTaglineAndText.args = {
60
+ slice: {
61
+ tagline: 'Tagline',
62
+ title: 'Title',
63
+ text: 'Tree.ly connects forest owners with companies, helping forest owners earn extra income by managing forests for climate resilience. Companies can purchase high-quality CO₂ credits, backing forest owners and showing their commitment to measurable climate protection.',
64
+ timelineItems: [
65
+ timelineItem,
66
+ timelineItem,
67
+ timelineItem,
68
+ timelineItem,
69
+ timelineItem,
70
+ ],
71
+ },
72
+ };
73
+
74
+ export const WithBackgroundShapes = Template.bind({});
75
+ WithBackgroundShapes.args = {
76
+ slice: {
77
+ tagline: 'Tagline',
78
+ title: 'Title',
79
+ text: 'Text',
80
+ timelineItems: [
81
+ { ...timelineItem, backgroundShapes: true },
82
+ { ...timelineItem, backgroundShapes: true },
83
+ { ...timelineItem, backgroundShapes: true },
84
+ { ...timelineItem, backgroundShapes: true },
85
+ { ...timelineItem, backgroundShapes: true },
86
+ ],
87
+ },
88
+ };
89
+
90
+ export const WithButton = Template.bind({});
91
+ WithButton.args = {
92
+ slice: {
93
+ tagline: 'Tagline',
94
+ title: 'Title',
95
+ text: 'Text',
96
+ timelineItems: [
97
+ {
98
+ ...timelineItem,
99
+ button,
100
+ },
101
+ {
102
+ ...timelineItem,
103
+ button,
104
+ },
105
+ {
106
+ ...timelineItem,
107
+ button,
108
+ },
109
+ {
110
+ ...timelineItem,
111
+ button,
112
+ },
113
+ {
114
+ ...timelineItem,
115
+ button,
116
+ },
117
+ ],
118
+ },
119
+ };
120
+
121
+ export const WithBadgeOrLogo = Template.bind({});
122
+ WithBadgeOrLogo.args = {
123
+ slice: {
124
+ tagline: 'Tagline',
125
+ title: 'Title',
126
+ text: 'Text',
127
+ timelineItems: [
128
+ { ...timelineItem, badge },
129
+ {
130
+ ...timelineItem,
131
+ logo,
132
+ },
133
+ { ...timelineItem, logo, badge },
134
+ { ...timelineItem, badge },
135
+ { ...timelineItem, badge },
136
+ ],
137
+ },
138
+ };
139
+
140
+ export const WithImage = Template.bind({});
141
+ WithImage.args = {
142
+ slice: {
143
+ tagline: 'Tagline',
144
+ title: 'Title',
145
+ text: 'Text',
146
+ timelineItems: [
147
+ {
148
+ ...timelineItem,
149
+ image,
150
+ },
151
+ {
152
+ ...timelineItem,
153
+ image,
154
+ },
155
+ {
156
+ ...timelineItem,
157
+ image,
158
+ },
159
+ {
160
+ ...timelineItem,
161
+ image,
162
+ },
163
+ {
164
+ ...timelineItem,
165
+ image,
166
+ },
167
+ ],
168
+ },
169
+ };
170
+
171
+ export const WithIcon = Template.bind({});
172
+ WithIcon.args = {
173
+ slice: {
174
+ tagline: 'Tagline',
175
+ title: 'Title',
176
+ text: 'Text',
177
+ timelineItems: [
178
+ {
179
+ ...timelineItem,
180
+ icon,
181
+ },
182
+ {
183
+ ...timelineItem,
184
+ icon,
185
+ },
186
+ {
187
+ ...timelineItem,
188
+ icon,
189
+ },
190
+ {
191
+ ...timelineItem,
192
+ icon,
193
+ },
194
+ ],
195
+ },
196
+ };
@@ -0,0 +1,228 @@
1
+ import React from 'react';
2
+ import Timeline from '.';
3
+ import { mergeDeep } from '../../utils/mergeDeep';
4
+ import { TimelineProps } from './Timeline';
5
+ import { pushSpy, useRouter } from '../../../__mocks__/next/router';
6
+ import { render, screen, userEvent, waitFor } from '../../test/testUtils';
7
+ import { strapiMediaMock } from '../../test/strapiMocks/strapiMedia';
8
+ import messagesEn from './messages.en';
9
+
10
+ const defaultProps: TimelineProps = {
11
+ slice: {
12
+ title: 'Title',
13
+ timelineItems: [
14
+ {
15
+ id: 1,
16
+ title: 'Item 1',
17
+ },
18
+ ],
19
+ },
20
+ };
21
+
22
+ const setup = (props = {}) => {
23
+ const combinedProps = mergeDeep(defaultProps, props);
24
+ render(<Timeline {...combinedProps} />);
25
+ };
26
+
27
+ describe('The Timeline component', () => {
28
+ it('displays the correct tagline and text, if they are defined in the slice', () => {
29
+ setup({
30
+ slice: {
31
+ ...defaultProps.slice,
32
+ tagline: 'Timeline tagline',
33
+ text: 'Timeline text',
34
+ },
35
+ });
36
+ expect(screen.getByText('Timeline tagline')).toBeInTheDocument();
37
+ expect(screen.getByText('Timeline text')).toBeInTheDocument();
38
+ });
39
+
40
+ it('displays the timeline items', () => {
41
+ setup({
42
+ slice: {
43
+ ...defaultProps.slice,
44
+ timelineItems: [
45
+ defaultProps.slice.timelineItems[0],
46
+ { id: 2, title: 'Item 2' },
47
+ ],
48
+ },
49
+ });
50
+
51
+ expect(screen.getByText('Item 1')).toBeInTheDocument();
52
+ expect(screen.getByText('Item 2')).toBeInTheDocument();
53
+ });
54
+
55
+ it('displays the tagline and text in the timeline items, if they are defined in the slice', () => {
56
+ setup({
57
+ slice: {
58
+ ...defaultProps.slice,
59
+ timelineItems: [
60
+ {
61
+ ...defaultProps.slice.timelineItems[0],
62
+ tagline: 'Item 1 tagline',
63
+ text: 'Item 1 text',
64
+ },
65
+ ],
66
+ },
67
+ });
68
+
69
+ expect(screen.getByText('Item 1 tagline')).toBeInTheDocument();
70
+ expect(screen.getByText('Item 1 text')).toBeInTheDocument();
71
+ });
72
+
73
+ it('displays the button in the timeline items, if it is defined in the slice', () => {
74
+ setup({
75
+ slice: {
76
+ ...defaultProps.slice,
77
+ timelineItems: [
78
+ {
79
+ ...defaultProps.slice.timelineItems[0],
80
+ button: {
81
+ id: '1',
82
+ url: 'https://tree.ly',
83
+ text: 'Optional Button',
84
+ },
85
+ },
86
+ ],
87
+ },
88
+ });
89
+
90
+ expect(screen.getByRole('link')).toBeInTheDocument();
91
+ expect(screen.getByText('Optional Button')).toBeInTheDocument();
92
+ });
93
+
94
+ it('displays the badge if it is defined in the slice', () => {
95
+ setup({
96
+ slice: {
97
+ ...defaultProps.slice,
98
+ timelineItems: [
99
+ {
100
+ ...defaultProps.slice.timelineItems[0],
101
+ badge: { text: 'Badge', variant: 'green' },
102
+ },
103
+ ],
104
+ },
105
+ });
106
+
107
+ expect(screen.getAllByText('Badge')[0]).toBeInTheDocument();
108
+ });
109
+
110
+ it('displays the logo, icon and image if they are defined in the slice', () => {
111
+ setup({
112
+ slice: {
113
+ ...defaultProps.slice,
114
+ timelineItems: [
115
+ {
116
+ ...defaultProps.slice.timelineItems[0],
117
+ logo: {
118
+ id: 1,
119
+ alt: 'Logo alt text',
120
+ img: { data: strapiMediaMock },
121
+ },
122
+ image: {
123
+ id: 1,
124
+ alt: 'Image alt text',
125
+ img: { data: strapiMediaMock },
126
+ },
127
+ icon: {
128
+ id: 1,
129
+ alt: 'Icon alt text',
130
+ img: { data: strapiMediaMock },
131
+ },
132
+ },
133
+ ],
134
+ },
135
+ });
136
+
137
+ expect(screen.getAllByRole('img')[2]).toHaveAttribute(
138
+ 'alt',
139
+ 'Image alt text'
140
+ );
141
+ expect(screen.getAllByRole('img')[1]).toHaveAttribute(
142
+ 'alt',
143
+ 'Logo alt text'
144
+ );
145
+ expect(screen.getAllByRole('img')[0]).toHaveAttribute(
146
+ 'alt',
147
+ 'Icon alt text'
148
+ );
149
+ });
150
+
151
+ it('displays the background shapes if if it is defined in the slice', () => {
152
+ setup({
153
+ slice: {
154
+ ...defaultProps.slice,
155
+ timelineItems: [
156
+ { ...defaultProps.slice.timelineItems[0], backgroundShapes: true },
157
+ ],
158
+ },
159
+ });
160
+
161
+ expect(
162
+ screen.getByAltText(messagesEn['sections.timeline.backgroundShapes'])
163
+ ).toBeInTheDocument();
164
+ });
165
+
166
+ it('displays the "Show three more milestones" button if more than three timeline items are defined in the slice', () => {
167
+ setup({
168
+ slice: {
169
+ ...defaultProps.slice,
170
+ timelineItems: [
171
+ {
172
+ ...defaultProps.slice.timelineItems[0],
173
+ },
174
+ {
175
+ id: 2,
176
+ title: 'Item 2',
177
+ },
178
+ {
179
+ id: 3,
180
+ title: 'Item 3',
181
+ },
182
+ {
183
+ id: 4,
184
+ title: 'Item 4',
185
+ },
186
+ ],
187
+ },
188
+ });
189
+
190
+ expect(screen.queryByText('Item 4')).not.toBeInTheDocument();
191
+ expect(
192
+ screen.getByText(messagesEn['sections.timeline.showMoreButton'])
193
+ ).toBeInTheDocument();
194
+ });
195
+
196
+ it('renders three more timeline items if the "Show three more milestones" button is clicked, when all timeline items are rendered, the "Show three more milestones" button is not displayed', () => {
197
+ setup({
198
+ slice: {
199
+ ...defaultProps.slice,
200
+ timelineItems: [
201
+ {
202
+ ...defaultProps.slice.timelineItems[0],
203
+ },
204
+ {
205
+ id: 2,
206
+ title: 'Item 2',
207
+ },
208
+ {
209
+ id: 3,
210
+ title: 'Item 3',
211
+ },
212
+ {
213
+ id: 4,
214
+ title: 'Item 4',
215
+ },
216
+ ],
217
+ },
218
+ });
219
+
220
+ waitFor(() => {
221
+ userEvent.click(
222
+ screen.getByText(messagesEn['sections.timeline.showMoreButton'])
223
+ );
224
+ expect(screen.findByText('Item 4')).toBeInTheDocument();
225
+ expect(screen.findByRole('button')).not.toBeInTheDocument();
226
+ });
227
+ });
228
+ });