Skip to content

Commit 898f3ef

Browse files
authored
Merge pull request #2229 from tf/card-surface-color
Custom card and palette colors
2 parents 4b1bbad + da44366 commit 898f3ef

File tree

23 files changed

+627
-18
lines changed

23 files changed

+627
-18
lines changed

app/assets/stylesheets/pageflow/ui/input/color_input.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
.minicolors-input-swatch {
1414
top: 5px;
1515
left: 5px;
16+
17+
.minicolors-swatch-color {
18+
background-color: var(--placeholder-color);
19+
}
1620
}
1721

1822
&.is_default input {
@@ -22,4 +26,8 @@
2226
.minicolors-focus input {
2327
color: var(--ui-on-surface-color);
2428
}
29+
30+
.minicolors-swatches .minicolors-swatch {
31+
margin: 2px;
32+
}
2533
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
de:
2+
pageflow_scrolled:
3+
editor:
4+
edit_section:
5+
attributes:
6+
cardSurfaceColor:
7+
label: Kartenhintergrundfarbe
8+
auto: "(Automatisch)"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
en:
2+
pageflow_scrolled:
3+
editor:
4+
edit_section:
5+
attributes:
6+
cardSurfaceColor:
7+
label: Cards background color
8+
auto: "(Auto)"

entry_types/scrolled/lib/pageflow_scrolled/plugin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def configure(config)
4848
c.features.register('frontend_v2')
4949
c.features.register('scrolled_entry_fragment_caching')
5050
c.features.register('backdrop_content_elements')
51+
c.features.register('custom_palette_colors')
5152

5253
c.additional_frontend_seed_data.register(
5354
'frontendVersion',
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {ScrolledEntry} from 'editor/models/ScrolledEntry';
2+
import {factories} from 'pageflow/testHelpers';
3+
import {normalizeSeed} from 'support';
4+
5+
describe('ScrolledEntry', () => {
6+
describe('#getUsedSectionBackgroundColors', () => {
7+
it('returns unique sorted list of used background colors', () => {
8+
const entry = factories.entry(
9+
ScrolledEntry,
10+
{},
11+
{
12+
entryTypeSeed: normalizeSeed({
13+
sections: [
14+
{
15+
configuration: {
16+
backdropType: 'color',
17+
backdropColor: '#400'}
18+
},
19+
{
20+
configuration: {
21+
backdropType: 'color',
22+
backdropColor: '#040'
23+
}
24+
},
25+
{
26+
configuration: {
27+
backdropType: 'color',
28+
backdropColor: '#400'
29+
}
30+
},
31+
{
32+
configuration: {
33+
appearance: 'cards',
34+
cardSurfaceColor: '#500'
35+
}
36+
},
37+
{
38+
configuration: {
39+
appearance: 'cards',
40+
cardSurfaceColor: '#400'
41+
}
42+
}
43+
]
44+
})
45+
}
46+
);
47+
48+
const colors = entry.getUsedSectionBackgroundColors();
49+
50+
expect(colors).toEqual(['#400', '#500', '#040']);
51+
});
52+
53+
it('ignores blank colors', () => {
54+
const entry = factories.entry(
55+
ScrolledEntry,
56+
{},
57+
{
58+
entryTypeSeed: normalizeSeed({
59+
sections: [
60+
{
61+
configuration: {
62+
backdropType: 'color'
63+
}
64+
}
65+
]
66+
})
67+
}
68+
);
69+
70+
const colors = entry.getUsedSectionBackgroundColors();
71+
72+
expect(colors).toEqual([]);
73+
});
74+
75+
it('ignores invisible config options', () => {
76+
const entry = factories.entry(
77+
ScrolledEntry,
78+
{},
79+
{
80+
entryTypeSeed: normalizeSeed({
81+
sections: [
82+
{
83+
configuration: {
84+
backdropType: 'image',
85+
backdropColor: '#400'}
86+
},
87+
{
88+
configuration: {
89+
appearance: 'shadow',
90+
cardSurfaceColor: '#500'
91+
}
92+
}
93+
]
94+
})
95+
}
96+
);
97+
98+
const colors = entry.getUsedSectionBackgroundColors();
99+
100+
expect(colors).toEqual([]);
101+
});
102+
});
103+
});
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import {
2+
ColorSelectOrCustomColorInputView
3+
} from 'editor/views/inputs/ColorSelectOrCustomColorInputView';
4+
import Backbone from 'backbone';
5+
6+
import userEvent from '@testing-library/user-event';
7+
import '@testing-library/jest-dom/extend-expect';
8+
import {useReactBasedBackboneViews} from 'support';
9+
import {useFakeTranslations} from 'pageflow/testHelpers';
10+
11+
describe('ColorSelectOrCustomColorInputView', () => {
12+
const {render} = useReactBasedBackboneViews();
13+
14+
useFakeTranslations({
15+
'pageflow_scrolled.editor.blank': 'Auto',
16+
'pageflow_scrolled.editor.custom_color': 'Custom Color'
17+
});
18+
19+
it('loads theme palette color value', async () => {
20+
const model = new Backbone.Model({
21+
color: 'brand-red'
22+
});
23+
24+
const inputView = new ColorSelectOrCustomColorInputView({
25+
model,
26+
label: 'Background Color',
27+
customColorTranslationKey: 'pageflow_scrolled.editor.custom_color',
28+
values: ['brand-red', 'brand-green'],
29+
texts: ['Red', 'Green'],
30+
propertyName: 'color'
31+
});
32+
33+
const {getByRole, queryByRole} = render(inputView);
34+
35+
expect(getByRole('button', {name: 'Red'})).not.toBeNull();
36+
expect(queryByRole('textbox')).toBeNull();
37+
});
38+
39+
it('loads custom color value', async () => {
40+
const model = new Backbone.Model({
41+
color: '#ff0000'
42+
});
43+
44+
const inputView = new ColorSelectOrCustomColorInputView({
45+
model,
46+
label: 'Background Color',
47+
customColorTranslationKey: 'pageflow_scrolled.editor.custom_color',
48+
values: ['brand-red', 'brand-green'],
49+
texts: ['Red', 'Green'],
50+
propertyName: 'color'
51+
});
52+
53+
const {getByRole} = render(inputView);
54+
55+
expect(getByRole('button', {name: 'Custom Color'})).not.toBeNull();
56+
expect(getByRole('textbox')).not.toBeNull();
57+
expect(getByRole('textbox')).toHaveValue('#ff0000');
58+
});
59+
60+
it('updates model when selecting theme palette color', async () => {
61+
const model = new Backbone.Model({
62+
color: '#ff0000'
63+
});
64+
65+
const inputView = new ColorSelectOrCustomColorInputView({
66+
model,
67+
label: 'Background Color',
68+
customColorTranslationKey: 'pageflow_scrolled.editor.custom_color',
69+
values: ['brand-red', 'brand-green'],
70+
texts: ['Red', 'Green'],
71+
propertyName: 'color'
72+
});
73+
74+
const user = userEvent.setup();
75+
const {getByRole} = render(inputView);
76+
await user.click(getByRole('button', {name: 'Custom Color'}));
77+
await user.click(getByRole('option', {name: 'Red'}));
78+
79+
expect(model.get('color')).toBe('brand-red');
80+
});
81+
82+
it('updates model when selecting custom color', async () => {
83+
const model = new Backbone.Model({
84+
color: 'brand-red'
85+
});
86+
87+
const inputView = new ColorSelectOrCustomColorInputView({
88+
model,
89+
label: 'Background Color',
90+
customColorTranslationKey: 'pageflow_scrolled.editor.custom_color',
91+
values: ['brand-red', 'brand-green'],
92+
texts: ['Red', 'Green'],
93+
propertyName: 'color'
94+
});
95+
96+
const user = userEvent.setup();
97+
const {getByRole} = render(inputView);
98+
await user.click(getByRole('button', {name: 'Red'}));
99+
await user.click(getByRole('option', {name: 'Custom Color'}));
100+
101+
const input = getByRole('textbox');
102+
await user.clear(input);
103+
await user.type(input, '#ff0000');
104+
await new Promise(resolve => setTimeout(resolve, 300));
105+
106+
expect(model.get('color')).toBe('#ff0000');
107+
});
108+
109+
it('shows custom color input only when custom is selected', async () => {
110+
const model = new Backbone.Model({
111+
color: 'brand-red'
112+
});
113+
114+
const inputView = new ColorSelectOrCustomColorInputView({
115+
model,
116+
label: 'Background Color',
117+
customColorTranslationKey: 'pageflow_scrolled.editor.custom_color',
118+
values: ['brand-red', 'brand-green'],
119+
texts: ['Red', 'Green'],
120+
propertyName: 'color'
121+
});
122+
123+
const user = userEvent.setup();
124+
const {getByRole, queryByRole} = render(inputView);
125+
126+
expect(queryByRole('textbox')).toBeNull();
127+
128+
await user.click(getByRole('button', {name: 'Red'}));
129+
await user.click(getByRole('option', {name: 'Custom Color'}));
130+
131+
expect(getByRole('textbox')).not.toBeNull();
132+
133+
await user.click(getByRole('button', {name: 'Custom Color'}));
134+
await user.click(getByRole('option', {name: 'Red'}));
135+
136+
expect(queryByRole('textbox')).toBeNull();
137+
});
138+
139+
it('uses provided translations', async () => {
140+
const model = new Backbone.Model();
141+
142+
const inputView = new ColorSelectOrCustomColorInputView({
143+
model,
144+
label: 'Background Color',
145+
includeBlank: true,
146+
blankTranslationKey: 'pageflow_scrolled.editor.blank',
147+
customColorTranslationKey: 'pageflow_scrolled.editor.custom_color',
148+
values: ['brand-red', 'brand-green'],
149+
texts: ['Red', 'Green'],
150+
propertyName: 'color'
151+
});
152+
153+
const {getByText, getByRole} = render(inputView);
154+
155+
expect(getByText('Background Color')).not.toBeNull();
156+
157+
// Click the button to open the dropdown
158+
await userEvent.click(getByRole('button'));
159+
160+
expect(getByRole('option', {name: 'Auto'})).not.toBeNull();
161+
});
162+
});

entry_types/scrolled/package/src/contentElements/externalLinkList/editor/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {editor} from 'pageflow-scrolled/editor';
2-
import {features} from 'pageflow/frontend';
32
import {SelectInputView, SliderInputView, SeparatorView, CheckBoxInputView} from 'pageflow/ui';
43

54
import {SidebarRouter} from './SidebarRouter';

entry_types/scrolled/package/src/contentElements/textBlock/editor.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,35 @@ editor.contentElementTypes.register('textBlock', {
1111
supportedPositions: ['inline'],
1212

1313
configurationEditor({entry, contentElement}) {
14-
this.listenTo(contentElement.transientState,
15-
'change:exampleNode',
16-
() => this.refresh());
14+
let pendingRefresh;
15+
16+
this.listenTo(
17+
contentElement.transientState,
18+
'change:exampleNode',
19+
() => {
20+
// This is a terrible hack to prevent closing the minicolors
21+
// dropdown while adjusting colors. Calling refresh is needed
22+
// to update typography drop downs. Delay until color picker
23+
// is closed.
24+
if (document.activeElement &&
25+
document.activeElement.tagName === 'INPUT' &&
26+
document.activeElement.className === 'minicolors-input') {
27+
28+
if (!pendingRefresh) {
29+
document.activeElement.addEventListener('blur', () => {
30+
pendingRefresh = false;
31+
this.refresh()
32+
}, {once: true});
33+
34+
pendingRefresh = true;
35+
}
36+
37+
return;
38+
}
39+
40+
this.refresh()
41+
}
42+
);
1743

1844
this.tab('general', function() {
1945
const exampleNode = ensureTextContent(

entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {insertContentElement} from './insertContentElement';
1010
import {moveContentElement} from './moveContentElement';
1111
import {deleteContentElement} from './deleteContentElement';
1212

13+
import {sortColors} from './sortColors';
14+
1315
const typographySizeSuffixes = ['xl', 'lg', 'md', 'sm', 'xs'];
1416

1517
export const ScrolledEntry = Entry.extend({
@@ -276,6 +278,22 @@ export const ScrolledEntry = Entry.extend({
276278
return [values, texts];
277279
},
278280

281+
getUsedSectionBackgroundColors() {
282+
const colors = new Set();
283+
284+
this.sections.map(section => {
285+
if (section.configuration.get('backdropType') === 'color') {
286+
colors.add(section.configuration.get('backdropColor'));
287+
}
288+
289+
if (section.configuration.get('appearance') === 'cards') {
290+
colors.add(section.configuration.get('cardSurfaceColor'));
291+
}
292+
});
293+
294+
return sortColors([...colors].filter(Boolean));
295+
},
296+
279297
supportsSectionWidths() {
280298
const theme = this.scrolledSeed.config.theme;
281299

0 commit comments

Comments
 (0)