richie-education 3.1.3-dev31 → 3.1.3-dev35
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/js/settings/index.ts +1 -0
- package/js/settings/settings.prod.ts +1 -0
- package/js/widgets/Slider/components/SlidePanel.tsx +9 -0
- package/js/widgets/Slider/index.stories.tsx +53 -0
- package/js/widgets/Slider/index.tsx +21 -2
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +24 -4
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +24 -4
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +6 -2
- package/package.json +2 -1
- package/scss/colors/_theme.scss +3 -0
- package/scss/components/templates/richie/slider/_slider.scss +19 -0
- package/scss/objects/_blogpost_glimpses.scss +5 -0
package/js/settings/index.ts
CHANGED
|
@@ -20,6 +20,8 @@ type SlidePanelProps = {
|
|
|
20
20
|
activeSlideIndex: number;
|
|
21
21
|
isTransitioning: boolean;
|
|
22
22
|
onBulletClick: (index: number) => void;
|
|
23
|
+
toggleAutoplay: () => void;
|
|
24
|
+
isAutoplaying: boolean;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -31,6 +33,8 @@ const SlidePanel = ({
|
|
|
31
33
|
activeSlideIndex,
|
|
32
34
|
onBulletClick,
|
|
33
35
|
isTransitioning,
|
|
36
|
+
toggleAutoplay,
|
|
37
|
+
isAutoplaying,
|
|
34
38
|
}: SlidePanelProps) => {
|
|
35
39
|
const intl = useIntl();
|
|
36
40
|
const hasSlideContent = slides.some((slide) => slide.content);
|
|
@@ -75,6 +79,11 @@ const SlidePanel = ({
|
|
|
75
79
|
</span>
|
|
76
80
|
</button>
|
|
77
81
|
))}
|
|
82
|
+
<div className="slider__autoplay">
|
|
83
|
+
<button type="button" onClick={toggleAutoplay}>
|
|
84
|
+
{isAutoplaying ? '⏸' : '⏵'}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
78
87
|
</div>
|
|
79
88
|
</section>
|
|
80
89
|
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { IntlProvider } from 'react-intl';
|
|
3
|
+
import { Slide } from './types';
|
|
4
|
+
import Slider from '.';
|
|
5
|
+
|
|
6
|
+
const slides: Slide[] = [
|
|
7
|
+
{
|
|
8
|
+
pk: '1',
|
|
9
|
+
title: 'Slide 1',
|
|
10
|
+
content: 'Content for slide 1',
|
|
11
|
+
image: '/static/course_cover_image.jpg',
|
|
12
|
+
link_url: 'https://example.com/1',
|
|
13
|
+
link_open_blank: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
pk: '2',
|
|
17
|
+
title: 'Slide 2',
|
|
18
|
+
content: 'Content for slide 2',
|
|
19
|
+
image: '/static/course_cover_image.jpg',
|
|
20
|
+
link_url: 'https://example.com/2',
|
|
21
|
+
link_open_blank: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
pk: '3',
|
|
25
|
+
title: 'Slide 3',
|
|
26
|
+
content: 'Content for slide 3',
|
|
27
|
+
image: '/static/course_cover_image.jpg',
|
|
28
|
+
link_url: 'https://example.com/3',
|
|
29
|
+
link_open_blank: true,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
component: Slider,
|
|
35
|
+
title: 'Widgets/Slider',
|
|
36
|
+
decorators: [
|
|
37
|
+
(Story) => (
|
|
38
|
+
<IntlProvider locale="en">
|
|
39
|
+
<Story />
|
|
40
|
+
</IntlProvider>
|
|
41
|
+
),
|
|
42
|
+
],
|
|
43
|
+
} as Meta<typeof Slider>;
|
|
44
|
+
|
|
45
|
+
type Story = StoryObj<typeof Slider>;
|
|
46
|
+
|
|
47
|
+
export const Default: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
pk: 'slider-1',
|
|
50
|
+
title: 'Example Slider',
|
|
51
|
+
slides,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import useEmblaCarousel from 'embla-carousel-react';
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
|
|
4
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
4
5
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
6
|
+
import { SLIDER_SETTINGS } from 'settings';
|
|
5
7
|
import { Slide as SlideType } from './types';
|
|
6
8
|
import SlidePanel from './components/SlidePanel';
|
|
7
9
|
import Slideshow from './components/Slideshow';
|
|
@@ -22,9 +24,24 @@ type SliderProps = {
|
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
const Slider = ({ slides, title }: SliderProps) => {
|
|
25
|
-
const
|
|
27
|
+
const autoplay = useRef(Autoplay({ delay: SLIDER_SETTINGS.autoplayDelay }));
|
|
28
|
+
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [
|
|
29
|
+
WheelGesturesPlugin(),
|
|
30
|
+
autoplay.current,
|
|
31
|
+
]);
|
|
26
32
|
const [activeSlideIndex, setActiveSlideIndex] = useState(0);
|
|
27
33
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
34
|
+
const [isAutoplaying, setIsAutoplaying] = useState(true);
|
|
35
|
+
|
|
36
|
+
const toggleAutoplay = () => {
|
|
37
|
+
if (!autoplay.current) return;
|
|
38
|
+
if (isAutoplaying) {
|
|
39
|
+
autoplay.current.stop();
|
|
40
|
+
} else {
|
|
41
|
+
autoplay.current.play();
|
|
42
|
+
}
|
|
43
|
+
setIsAutoplaying(!isAutoplaying);
|
|
44
|
+
};
|
|
28
45
|
|
|
29
46
|
const handleBulletClick = useCallback(
|
|
30
47
|
(index: number) => {
|
|
@@ -101,6 +118,8 @@ const Slider = ({ slides, title }: SliderProps) => {
|
|
|
101
118
|
activeSlideIndex={activeSlideIndex}
|
|
102
119
|
onBulletClick={handleBulletClick}
|
|
103
120
|
isTransitioning={isTransitioning}
|
|
121
|
+
toggleAutoplay={toggleAutoplay}
|
|
122
|
+
isAutoplaying={isAutoplaying}
|
|
104
123
|
/>
|
|
105
124
|
<span className="offscreen" role="presentation" aria-live="polite" aria-atomic="true">
|
|
106
125
|
<FormattedMessage
|
|
@@ -111,7 +111,9 @@ const OpenedCourseRun = ({
|
|
|
111
111
|
let courseOfferMessage = null;
|
|
112
112
|
let certificationOfferMessage = null;
|
|
113
113
|
let enrollmentPrice = '';
|
|
114
|
+
let enrollmentDiscountedPrice = '';
|
|
114
115
|
let certificatePrice = '';
|
|
116
|
+
let certificateDiscountedPrice = '';
|
|
115
117
|
|
|
116
118
|
if (courseRun.offer) {
|
|
117
119
|
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
@@ -130,7 +132,7 @@ const OpenedCourseRun = ({
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
if ((courseRun.discounted_price ?? -1) >= 0) {
|
|
133
|
-
|
|
135
|
+
enrollmentDiscountedPrice = intl.formatNumber(courseRun.discounted_price!, {
|
|
134
136
|
style: 'currency',
|
|
135
137
|
currency: courseRun.price_currency,
|
|
136
138
|
});
|
|
@@ -153,7 +155,7 @@ const OpenedCourseRun = ({
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
if ((courseRun.certificate_discounted_price ?? -1) >= 0) {
|
|
156
|
-
|
|
158
|
+
certificateDiscountedPrice = intl.formatNumber(courseRun.certificate_discounted_price!, {
|
|
157
159
|
style: 'currency',
|
|
158
160
|
currency: courseRun.price_currency,
|
|
159
161
|
});
|
|
@@ -204,7 +206,16 @@ const OpenedCourseRun = ({
|
|
|
204
206
|
<dd>
|
|
205
207
|
<FormattedMessage {...courseOfferMessage} />
|
|
206
208
|
<br />
|
|
207
|
-
{
|
|
209
|
+
{enrollmentDiscountedPrice ? (
|
|
210
|
+
<>
|
|
211
|
+
<del>{enrollmentPrice}</del>
|
|
212
|
+
<span> ({courseRun.discount})</span>
|
|
213
|
+
<br />
|
|
214
|
+
<strong>{enrollmentDiscountedPrice}</strong>
|
|
215
|
+
</>
|
|
216
|
+
) : (
|
|
217
|
+
enrollmentPrice
|
|
218
|
+
)}
|
|
208
219
|
</dd>
|
|
209
220
|
</>
|
|
210
221
|
)}
|
|
@@ -216,7 +227,16 @@ const OpenedCourseRun = ({
|
|
|
216
227
|
<dd>
|
|
217
228
|
<FormattedMessage {...certificationOfferMessage} />
|
|
218
229
|
<br />
|
|
219
|
-
{
|
|
230
|
+
{certificateDiscountedPrice ? (
|
|
231
|
+
<>
|
|
232
|
+
<del>{certificatePrice}</del>
|
|
233
|
+
<span> ({courseRun.certificate_discount})</span>
|
|
234
|
+
<br />
|
|
235
|
+
<strong>{certificateDiscountedPrice}</strong>
|
|
236
|
+
</>
|
|
237
|
+
) : (
|
|
238
|
+
certificatePrice
|
|
239
|
+
)}
|
|
220
240
|
</dd>
|
|
221
241
|
</>
|
|
222
242
|
)}
|
|
@@ -102,7 +102,9 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
102
102
|
let courseOfferMessage = null;
|
|
103
103
|
let certificationOfferMessage = null;
|
|
104
104
|
let enrollmentPrice = '';
|
|
105
|
+
let enrollmentDiscountedPrice = '';
|
|
105
106
|
let certificatePrice = '';
|
|
107
|
+
let certificateDiscountedPrice = '';
|
|
106
108
|
|
|
107
109
|
if (courseRun.offer) {
|
|
108
110
|
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
@@ -121,7 +123,7 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
if ((courseRun.discounted_price ?? -1) >= 0) {
|
|
124
|
-
|
|
126
|
+
enrollmentDiscountedPrice = intl.formatNumber(courseRun.discounted_price!, {
|
|
125
127
|
style: 'currency',
|
|
126
128
|
currency: courseRun.price_currency,
|
|
127
129
|
});
|
|
@@ -144,7 +146,7 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
if ((courseRun.certificate_discounted_price ?? -1) >= 0) {
|
|
147
|
-
|
|
149
|
+
certificateDiscountedPrice = intl.formatNumber(courseRun.certificate_discounted_price!, {
|
|
148
150
|
style: 'currency',
|
|
149
151
|
currency: courseRun.price_currency,
|
|
150
152
|
});
|
|
@@ -188,7 +190,16 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
188
190
|
<dd>
|
|
189
191
|
<FormattedMessage {...courseOfferMessage} />
|
|
190
192
|
<br />
|
|
191
|
-
{
|
|
193
|
+
{enrollmentDiscountedPrice ? (
|
|
194
|
+
<>
|
|
195
|
+
<del>{enrollmentPrice}</del>
|
|
196
|
+
<span> ({courseRun.discount})</span>
|
|
197
|
+
<br />
|
|
198
|
+
<strong>{enrollmentDiscountedPrice}</strong>
|
|
199
|
+
</>
|
|
200
|
+
) : (
|
|
201
|
+
enrollmentPrice
|
|
202
|
+
)}
|
|
192
203
|
</dd>
|
|
193
204
|
</>
|
|
194
205
|
)}
|
|
@@ -200,7 +211,16 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
200
211
|
<dd>
|
|
201
212
|
<FormattedMessage {...certificationOfferMessage} />
|
|
202
213
|
<br />
|
|
203
|
-
{
|
|
214
|
+
{certificateDiscountedPrice ? (
|
|
215
|
+
<>
|
|
216
|
+
<del>{certificatePrice}</del>
|
|
217
|
+
<span> ({courseRun.certificate_discount})</span>
|
|
218
|
+
<br />
|
|
219
|
+
<strong>{certificateDiscountedPrice}</strong>
|
|
220
|
+
</>
|
|
221
|
+
) : (
|
|
222
|
+
certificatePrice
|
|
223
|
+
)}
|
|
204
224
|
</dd>
|
|
205
225
|
</>
|
|
206
226
|
)}
|
|
@@ -1486,7 +1486,9 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
1486
1486
|
);
|
|
1487
1487
|
|
|
1488
1488
|
const content = getHeaderContainer().innerHTML;
|
|
1489
|
-
expect(content).toContain(
|
|
1489
|
+
expect(content).toContain(
|
|
1490
|
+
'<dd>Paid access<br><del>€49.99</del><span> (-20%)</span><br><strong>€30.00</strong></dd>',
|
|
1491
|
+
);
|
|
1490
1492
|
});
|
|
1491
1493
|
|
|
1492
1494
|
it('renders certificate discount on SyllabusCourseRun', async () => {
|
|
@@ -1509,6 +1511,8 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
1509
1511
|
);
|
|
1510
1512
|
|
|
1511
1513
|
const content = getHeaderContainer().innerHTML;
|
|
1512
|
-
expect(content).toContain(
|
|
1514
|
+
expect(content).toContain(
|
|
1515
|
+
'<dd>Paid certificate<br><del>€100.00</del><span> (-30%)</span><br><strong>€70.00</strong></dd>',
|
|
1516
|
+
);
|
|
1513
1517
|
});
|
|
1514
1518
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richie-education",
|
|
3
|
-
"version": "3.1.3-
|
|
3
|
+
"version": "3.1.3-dev35",
|
|
4
4
|
"description": "A CMS to build learning portals for Open Education",
|
|
5
5
|
"main": "sandbox/manage.py",
|
|
6
6
|
"scripts": {
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"cljs-merge": "1.1.1",
|
|
91
91
|
"core-js": "3.41.0",
|
|
92
92
|
"downshift": "9.0.9",
|
|
93
|
+
"embla-carousel-autoplay": "8.6.0",
|
|
93
94
|
"embla-carousel-react": "8.5.2",
|
|
94
95
|
"embla-carousel-wheel-gestures": "8.0.1",
|
|
95
96
|
"eslint": ">=8.57.0 <9",
|
package/scss/colors/_theme.scss
CHANGED
|
@@ -166,6 +166,9 @@ $r-theme: (
|
|
|
166
166
|
index-color: r-color('battleship-grey'),
|
|
167
167
|
index-hover-color: r-color('indianred3'),
|
|
168
168
|
index-active-color: r-color('firebrick6'),
|
|
169
|
+
autoplay-color: r-color('white'),
|
|
170
|
+
autoplay-background-color: r-color('battleship-grey'),
|
|
171
|
+
autoplay-background-hover-color: r-color('charcoal'),
|
|
169
172
|
),
|
|
170
173
|
blogpost-glimpse: (
|
|
171
174
|
card-background: r-color('white'),
|
|
@@ -155,6 +155,25 @@ $r-slider-content-line-clamp: 4 !default;
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
.slider__autoplay {
|
|
159
|
+
display: flex;
|
|
160
|
+
justify-content: flex-end;
|
|
161
|
+
font-weight: bold;
|
|
162
|
+
margin-top: 0.5rem;
|
|
163
|
+
button {
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
border-radius: 50px;
|
|
166
|
+
padding: 0.25rem 0.5rem;
|
|
167
|
+
border: none;
|
|
168
|
+
color: r-theme-val(slider-plugin, autoplay-color);
|
|
169
|
+
background-color: r-theme-val(slider-plugin, autoplay-background-color);
|
|
170
|
+
&:hover,
|
|
171
|
+
&:focus {
|
|
172
|
+
background-color: r-theme-val(slider-plugin, autoplay-background-hover-color);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
158
177
|
.slide__content {
|
|
159
178
|
max-width: 680px;
|
|
160
179
|
|