Skip to content

Commit 7a5205a

Browse files
authored
Merge pull request #2507 from tf/logo-list
Logo list element
2 parents e0cd2ef + 5a93037 commit 7a5205a

37 files changed

Lines changed: 1548 additions & 3 deletions

‎entry_types/scrolled/config/locales/de.yml‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ de:
8686
feature_name: iframe-Embed Inhaltselement
8787
legacy_social_embed_content_elements:
8888
feature_name: Legacy Twitter und TikTok Inhaltselemente
89+
logo_list_content_element:
90+
feature_name: Logo-Liste Inhaltselement
8991
lottie_animation_content_element:
9092
feature_name: Lottie-Animation-Inhaltselement
9193
scrolled_entry_fragment_caching:
@@ -777,6 +779,41 @@ de:
777779
name: Video
778780
tabs:
779781
general: Video
782+
logoList:
783+
attributes:
784+
marquee:
785+
label: Endlos scrollen
786+
inline_help: |-
787+
Logos bewegen sich in einer Endlosschleife über den
788+
Bildschirm. Die Animation pausiert bei Mausberührung
789+
und solange ein Logo den Tastaturfokus hat. Bei
790+
bevorzugter Bewegungsreduzierung wird stattdessen
791+
eine statische Liste angezeigt.
792+
staggered:
793+
label: Versetzt
794+
inline_help: Jedes zweite Logo vertikal versetzt anzeigen.
795+
description: Reihe von Partner- oder Kunden-Logos
796+
items:
797+
add: Hinzufügen
798+
label: Logos
799+
name: Logo-Liste
800+
tabs:
801+
general: Logo-Liste
802+
edit_item:
803+
back: Zurück
804+
confirm_destroy: Soll das Logo wirklich gelöscht werden?
805+
destroy: Logo löschen
806+
attributes:
807+
alt:
808+
label: Alt-Text
809+
inline_help: |-
810+
Ersetzt den Alt-Text der Bilddatei für dieses Logo.
811+
Bei verlinkten Logos sollte er das Linkziel
812+
beschreiben, z.B. "Website von ACME".
813+
image:
814+
label: Bild
815+
tabs:
816+
item: Logo
780817
lottieAnimation:
781818
attributes:
782819
id:

‎entry_types/scrolled/config/locales/en.yml‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ en:
8686
feature_name: iframe embed content element
8787
legacy_social_embed_content_elements:
8888
feature_name: Legacy Twitter and TikTok content elements
89+
logo_list_content_element:
90+
feature_name: Logo list content element
8991
lottie_animation_content_element:
9092
feature_name: Lottie animation content element
9193
scrolled_entry_fragment_caching:
@@ -762,6 +764,40 @@ en:
762764
name: Video
763765
tabs:
764766
general: Video
767+
logoList:
768+
attributes:
769+
marquee:
770+
label: Scroll continuously
771+
inline_help: |-
772+
Logos move across the screen in an endless loop. The
773+
animation pauses on hover and while a logo has
774+
keyboard focus. Visitors who prefer reduced motion see
775+
a static list instead.
776+
staggered:
777+
label: Staggered
778+
inline_help: Offset every other logo vertically.
779+
description: Row of partner or client logos
780+
items:
781+
add: Add
782+
label: Logos
783+
name: Logo List
784+
tabs:
785+
general: Logo List
786+
edit_item:
787+
back: Back
788+
confirm_destroy: Are you sure you want to delete this logo?
789+
destroy: Delete logo
790+
attributes:
791+
alt:
792+
label: Alt-Text
793+
inline_help: |-
794+
Replaces the alt text of the image file for this
795+
logo. For linked logos, describe the link target,
796+
e.g. "ACME website".
797+
image:
798+
label: Image
799+
tabs:
800+
item: Logo
765801
lottieAnimation:
766802
attributes:
767803
id:

‎entry_types/scrolled/lib/pageflow_scrolled/plugin.rb‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ def configure(config)
220220
)
221221
end
222222

223+
c.features.register('logo_list_content_element') do |feature_config|
224+
feature_config.additional_frontend_packs.register(
225+
'pageflow-scrolled/contentElements/logoList-frontend',
226+
content_element_type_names: ['logoList']
227+
)
228+
end
229+
223230
c.features.register('datawrapper_chart_embed_opt_in')
224231
c.features.enable_by_default('datawrapper_chart_embed_opt_in')
225232
c.features.register('iframe_embed_content_element')

‎entry_types/scrolled/package/config/webpack.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ module.exports = {
7373
'pageflow-scrolled/contentElements/lottieAnimation-frontend.css'
7474
]
7575
},
76+
'pageflow-scrolled/contentElements/logoList-frontend': {
77+
import: [
78+
'pageflow-scrolled/contentElements/logoList-frontend',
79+
'pageflow-scrolled/contentElements/logoList-frontend.css'
80+
]
81+
},
7682
'pageflow-scrolled/widgets/defaultNavigation': {
7783
import: [
7884
'pageflow-scrolled/widgets/defaultNavigation',

‎entry_types/scrolled/package/contentElements-server.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'pageflow-scrolled/contentElements-frontend';
22
import 'pageflow-scrolled/contentElements/hotspots-frontend';
33
import 'pageflow-scrolled/contentElements/inlineBeforeAfter-frontend';
4+
import 'pageflow-scrolled/contentElements/logoList-frontend';
45
import 'pageflow-scrolled/contentElements/lottieAnimation-frontend';
56
import 'pageflow-scrolled/contentElements/socialEmbed-frontend';
67
import 'pageflow-scrolled/contentElements/tikTokEmbed-frontend';
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
3+
import {LogoList} from 'contentElements/logoList/LogoList';
4+
import styles from 'contentElements/logoList/LogoList.module.css';
5+
6+
import {renderInContentElement} from 'pageflow-scrolled/testHelpers';
7+
import userEvent from '@testing-library/user-event';
8+
import '@testing-library/jest-dom/extend-expect'
9+
10+
describe('LogoList editor selection', () => {
11+
const seed = {
12+
imageFileUrlTemplates: {medium: ':id_partition/medium/image.webp'},
13+
imageFiles: [{id: 1, permaId: 100}, {id: 2, permaId: 101}]
14+
};
15+
16+
const items = [{id: 1, image: 100}, {id: 2, image: 101}];
17+
18+
function renderLogoList(inlineEditing) {
19+
return renderInContentElement(
20+
<LogoList configuration={{items}} contentElementId={1} />,
21+
{seed, inlineEditing: {isSelected: true, ...inlineEditing}}
22+
);
23+
}
24+
25+
it('sets selected item id in transient state', async () => {
26+
const setTransientState = jest.fn();
27+
const user = userEvent.setup();
28+
const {getAllByRole} = renderLogoList({transientState: setTransientState});
29+
30+
await user.click(getAllByRole('listitem')[1]);
31+
32+
expect(setTransientState).toHaveBeenCalledWith({selectedItemId: 2});
33+
});
34+
35+
it('resets selected item id in transient state when clicking outside logos', async () => {
36+
const setTransientState = jest.fn();
37+
const user = userEvent.setup();
38+
const {getByRole} = renderLogoList({transientState: setTransientState});
39+
40+
await user.click(getByRole('list'));
41+
42+
expect(setTransientState).toHaveBeenCalledWith({selectedItemId: null});
43+
});
44+
45+
it('supports setting selected item via command', () => {
46+
const {getAllByRole, triggerEditorCommand} = renderLogoList();
47+
48+
triggerEditorCommand({type: 'SET_SELECTED_ITEM', index: 1});
49+
50+
expect(getAllByRole('listitem')[0]).not.toHaveClass(styles.selected);
51+
expect(getAllByRole('listitem')[1]).toHaveClass(styles.selected);
52+
});
53+
54+
it('supports resetting selected item via command', () => {
55+
const {container, triggerEditorCommand} = renderLogoList();
56+
57+
triggerEditorCommand({type: 'SET_SELECTED_ITEM', index: 1});
58+
triggerEditorCommand({type: 'SET_SELECTED_ITEM', index: -1});
59+
60+
expect(container.querySelector(`.${styles.selected}`)).toBeNull();
61+
});
62+
63+
it('supports highlighting item via command', () => {
64+
const {getAllByRole, triggerEditorCommand} = renderLogoList();
65+
66+
triggerEditorCommand({type: 'HIGHLIGHT_ITEM', index: 1});
67+
68+
expect(getAllByRole('listitem')[0]).not.toHaveClass(styles.highlighted);
69+
expect(getAllByRole('listitem')[1]).toHaveClass(styles.highlighted);
70+
});
71+
72+
it('supports resetting item highlight via command', () => {
73+
const {container, triggerEditorCommand} = renderLogoList();
74+
75+
triggerEditorCommand({type: 'HIGHLIGHT_ITEM', index: 1});
76+
triggerEditorCommand({type: 'RESET_ITEM_HIGHLIGHT'});
77+
78+
expect(container.querySelector(`.${styles.highlighted}`)).toBeNull();
79+
});
80+
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from 'react';
2+
import '@testing-library/jest-dom/extend-expect'
3+
4+
import {LogoList} from 'contentElements/logoList/LogoList';
5+
6+
import {renderInContentElement} from 'pageflow-scrolled/testHelpers';
7+
8+
describe('LogoList images', () => {
9+
const imageFileUrlTemplates = {
10+
medium: ':id_partition/medium/image.webp',
11+
original: ':id_partition/original/:basename.:extension'
12+
};
13+
14+
function renderLogoList({configuration, imageFiles}) {
15+
const result = renderInContentElement(
16+
<LogoList configuration={configuration} contentElementId={1} />,
17+
{seed: {imageFileUrlTemplates, imageFiles}}
18+
);
19+
20+
result.simulateScrollPosition('near viewport');
21+
22+
return result;
23+
}
24+
25+
it('renders medium variant of item images', () => {
26+
const {getAllByRole} = renderLogoList({
27+
configuration: {items: [{id: 1, image: 100}, {id: 2, image: 101}]},
28+
imageFiles: [{id: 1, permaId: 100}, {id: 2, permaId: 101}]
29+
});
30+
31+
expect(getAllByRole('img').map(img => img.getAttribute('src'))).toEqual([
32+
'000/000/001/medium/image.webp',
33+
'000/000/002/medium/image.webp'
34+
]);
35+
});
36+
37+
it('uses original url of svg images', () => {
38+
const {getByRole} = renderLogoList({
39+
configuration: {items: [{id: 1, image: 100}]},
40+
imageFiles: [{id: 1, permaId: 100, basename: 'logo', extension: 'svg'}]
41+
});
42+
43+
expect(getByRole('img')).toHaveAttribute('src', '000/000/001/original/logo.svg');
44+
});
45+
46+
it('renders alt text of image file', () => {
47+
const {getByRole} = renderLogoList({
48+
configuration: {items: [{id: 1, image: 100}]},
49+
imageFiles: [{id: 1, permaId: 100, configuration: {alt: 'ACME Inc.'}}]
50+
});
51+
52+
expect(getByRole('img', {name: 'ACME Inc.'})).toBeInTheDocument();
53+
});
54+
55+
it('prefers alt text of item', () => {
56+
const {getByRole} = renderLogoList({
57+
configuration: {items: [{id: 1, image: 100, alt: 'ACME website'}]},
58+
imageFiles: [{id: 1, permaId: 100, configuration: {alt: 'ACME Inc.'}}]
59+
});
60+
61+
expect(getByRole('img', {name: 'ACME website'})).toBeInTheDocument();
62+
});
63+
64+
it('passes image dimensions to preserve aspect ratio', () => {
65+
const {getByRole} = renderLogoList({
66+
configuration: {items: [{id: 1, image: 100}]},
67+
imageFiles: [{id: 1, permaId: 100, width: 400, height: 100}]
68+
});
69+
70+
expect(getByRole('img')).toHaveAttribute('width', '400');
71+
expect(getByRole('img')).toHaveAttribute('height', '100');
72+
});
73+
74+
it('skips items with missing image files', () => {
75+
const {getAllByRole} = renderLogoList({
76+
configuration: {items: [{id: 1, image: 100}, {id: 2, image: 999}]},
77+
imageFiles: [{id: 1, permaId: 100}]
78+
});
79+
80+
expect(getAllByRole('listitem')).toHaveLength(1);
81+
});
82+
83+
it('does not load images before content element is near viewport', () => {
84+
const {queryByRole} = renderInContentElement(
85+
<LogoList configuration={{items: [{id: 1, image: 100}]}} contentElementId={1} />,
86+
{seed: {imageFileUrlTemplates, imageFiles: [{id: 1, permaId: 100}]}}
87+
);
88+
89+
expect(queryByRole('img')).toBeNull();
90+
});
91+
});

0 commit comments

Comments
 (0)