Skip to content

Commit c0e1c10

Browse files
committed
Add countingAnimation option to counter
Introduce a new countingAnimation setting with options none, plain, and wheel (rolling digits). This separates the animation style from the counting speed, allowing users to choose both independently. The existing countingSpeed setting now only controls speed when an animation is enabled. Backwards compatibility is preserved: entries with only countingSpeed set will default to plain animation. REDMINE-21218
1 parent e5c7cc4 commit c0e1c10

14 files changed

Lines changed: 592 additions & 93 deletions

File tree

entry_types/scrolled/config/locales/de.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,17 @@ de:
277277
attributes:
278278
countingSpeed:
279279
inline_help: Lege fest, wie schnell die Zählanimation ablaufen soll.
280-
label: Zählanimation
280+
label: Zählgeschwindigkeit
281281
values:
282282
fast: Schnell
283283
medium: Mittel
284-
none: "(Keine)"
285284
slow: Langsam
285+
countingAnimation:
286+
label: Zählanimation
287+
values:
288+
none: "(Keine)"
289+
plain: Einfach
290+
wheel: Zählwerk
286291
decimalPlaces:
287292
inline_help: Bestimme, wie wieviele Nachkommastellen gezeigt werden sollen.
288293
label: Nachkommastellen

entry_types/scrolled/config/locales/en.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,17 @@ en:
270270
attributes:
271271
countingSpeed:
272272
inline_help: Determine the speed of the counting animation.
273-
label: Counting animation
273+
label: Counting speed
274274
values:
275275
fast: Fast
276276
medium: Medium
277-
none: "(None)"
278277
slow: Slow
278+
countingAnimation:
279+
label: Counting animation
280+
values:
281+
none: "(None)"
282+
plain: Simple
283+
wheel: Rolling digits
279284
decimalPlaces:
280285
inline_help: Determine how many decimal places shall be visible.
281286
label: Decimal places

entry_types/scrolled/lib/pageflow_scrolled/plugin.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def configure(config)
9090
'counterUnit-xs' => {font_size: '0.5em'},
9191
'counterDescription-lg' => {
9292
font_size: '28px', line_height: '1.1',
93-
sm: {font_size: '40px', line_height: '1',}
93+
sm: {font_size: '40px', line_height: '1'}
9494
},
9595
'counterDescription-md' => {font_size: '22px'},
9696
'counterDescription-sm' => {font_size: '18px'}

entry_types/scrolled/package/spec/contentElements/counter/Counter-spec.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ describe('Counter', () => {
6363
expect(getByText('10')).toHaveScaleCategory('counterNumber', 'lg');
6464
});
6565

66-
it('defaults to md size for number', () => {
66+
it('defaults to xl size for number', () => {
6767
const {getByText} = renderCounter();
6868

69-
expect(getByText('10')).toHaveScaleCategory('counterNumber', 'md');
69+
expect(getByText('10')).toHaveScaleCategory('counterNumber', 'xl');
7070
});
7171

7272
it('defaults to md size for unit', () => {
@@ -319,6 +319,35 @@ describe('Counter', () => {
319319
});
320320
});
321321

322+
describe('countingAnimation', () => {
323+
it('renders digit wheels when countingAnimation is wheel', () => {
324+
const {container} = renderCounter({countingAnimation: 'wheel'});
325+
const digitSpans = container.querySelectorAll('span');
326+
const wheels = Array.from(digitSpans).filter(span =>
327+
/^\d$/.test(span.textContent)
328+
);
329+
330+
expect(wheels.length).toBeGreaterThan(0);
331+
});
332+
333+
it('renders plain number when countingAnimation is wheel but startValue equals targetValue', () => {
334+
const {queryAllByText} = renderCounter({
335+
countingAnimation: 'wheel',
336+
startValue: 10,
337+
targetValue: 10
338+
});
339+
340+
expect(queryAllByText('10').length).toBeGreaterThan(0);
341+
expect(queryAllByText('1').length).toBe(0);
342+
});
343+
344+
it('defaults to plain when countingSpeed is set but countingAnimation is not', () => {
345+
const {getByText} = renderCounter({countingSpeed: 'medium'});
346+
347+
expect(getByText('10')).toBeInTheDocument();
348+
});
349+
});
350+
322351
describe('space reservation', () => {
323352
it('renders hidden placeholder with target value when counting up', () => {
324353
const {container} = renderCounter({
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React from 'react';
2+
import {render} from '@testing-library/react';
3+
4+
import {WheelNumber} from 'contentElements/counter/WheelNumber';
5+
6+
import '@testing-library/jest-dom/extend-expect';
7+
8+
describe('WheelNumber', () => {
9+
function renderWheelNumber(props) {
10+
return render(
11+
<WheelNumber
12+
value={0}
13+
startValue={0}
14+
targetValue={100}
15+
{...props}
16+
/>
17+
);
18+
}
19+
20+
it('renders 3 wheels with 10 digits each for 3-digit target value', () => {
21+
const {container} = renderWheelNumber();
22+
23+
const digitSpans = Array.from(container.querySelectorAll('span')).filter(span =>
24+
/^\d$/.test(span.textContent)
25+
);
26+
27+
expect(digitSpans.length).toBe(30); // 3 wheels × 10 digits
28+
});
29+
30+
it('sets rotation values to show correct digits when value equals target', () => {
31+
const {container} = renderWheelNumber({
32+
value: 123,
33+
targetValue: 123
34+
});
35+
36+
const zeroDigits = Array.from(container.querySelectorAll('span')).filter(span =>
37+
span.textContent === '0'
38+
);
39+
40+
const rotationValues = zeroDigits.map(digit =>
41+
parseFloat(digit.parentElement.style.getPropertyValue('--val'))
42+
);
43+
44+
expect(rotationValues).toEqual([1, 2, 3]);
45+
});
46+
47+
it('hides zero digit in leading zero wheels', () => {
48+
const {container} = renderWheelNumber({
49+
value: 5,
50+
startValue: 0,
51+
targetValue: 100
52+
});
53+
54+
const wheels = container.querySelectorAll('[class*="wheel"]');
55+
const getDigit = (wheel, d) => Array.from(wheel.children).find(span => span.textContent === String(d));
56+
57+
expect(getDigit(wheels[0], 0)).not.toBeVisible();
58+
expect(getDigit(wheels[0], 1)).toBeVisible();
59+
expect(getDigit(wheels[1], 0)).not.toBeVisible();
60+
expect(getDigit(wheels[1], 1)).toBeVisible();
61+
expect(getDigit(wheels[2], 0)).toBeVisible();
62+
});
63+
64+
it('renders text entries as static text', () => {
65+
const {container} = renderWheelNumber({
66+
value: -1,
67+
startValue: 0,
68+
targetValue: -1
69+
});
70+
71+
expect(container.textContent).toContain('-');
72+
73+
const wheels = container.querySelectorAll('[class*="wheel"]');
74+
expect(wheels.length).toBe(1);
75+
});
76+
77+
it('applies hidden class to text entries with hide flag', () => {
78+
const {container} = renderWheelNumber({
79+
value: -0.4,
80+
startValue: 0,
81+
targetValue: -1
82+
});
83+
84+
const minusSpan = Array.from(container.querySelectorAll('span')).find(
85+
span => span.textContent === '-'
86+
);
87+
88+
expect(minusSpan.className).toMatch(/hidden/);
89+
});
90+
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {createLegacyConfigurationDelegator} from 'contentElements/counter/createLegacyConfigurationDelegator';
2+
3+
describe('createLegacyConfigurationDelegator', () => {
4+
it('returns numberSize when set', () => {
5+
const model = fakeModel({numberSize: 'lg'});
6+
const delegator = createLegacyConfigurationDelegator(model);
7+
8+
expect(delegator.get('numberSize')).toBe('lg');
9+
});
10+
11+
it('maps legacy textSize large to xxxl', () => {
12+
const model = fakeModel({textSize: 'large'});
13+
const delegator = createLegacyConfigurationDelegator(model);
14+
15+
expect(delegator.get('numberSize')).toBe('xxxl');
16+
});
17+
18+
it('maps legacy textSize medium to xl', () => {
19+
const model = fakeModel({textSize: 'medium'});
20+
const delegator = createLegacyConfigurationDelegator(model);
21+
22+
expect(delegator.get('numberSize')).toBe('xl');
23+
});
24+
25+
it('maps legacy textSize small to md', () => {
26+
const model = fakeModel({textSize: 'small'});
27+
const delegator = createLegacyConfigurationDelegator(model);
28+
29+
expect(delegator.get('numberSize')).toBe('md');
30+
});
31+
32+
it('maps legacy textSize verySmall to xs', () => {
33+
const model = fakeModel({textSize: 'verySmall'});
34+
const delegator = createLegacyConfigurationDelegator(model);
35+
36+
expect(delegator.get('numberSize')).toBe('xs');
37+
});
38+
39+
it('prefers numberSize over textSize', () => {
40+
const model = fakeModel({numberSize: 'sm', textSize: 'large'});
41+
const delegator = createLegacyConfigurationDelegator(model);
42+
43+
expect(delegator.get('numberSize')).toBe('sm');
44+
});
45+
46+
it('returns countingAnimation when set', () => {
47+
const model = fakeModel({countingAnimation: 'wheel'});
48+
const delegator = createLegacyConfigurationDelegator(model);
49+
50+
expect(delegator.get('countingAnimation')).toBe('wheel');
51+
});
52+
53+
it('defaults countingAnimation to plain when countingSpeed is set', () => {
54+
const model = fakeModel({countingSpeed: 'medium'});
55+
const delegator = createLegacyConfigurationDelegator(model);
56+
57+
expect(delegator.get('countingAnimation')).toBe('plain');
58+
});
59+
60+
it('does not default countingAnimation when countingSpeed is none', () => {
61+
const model = fakeModel({countingSpeed: 'none'});
62+
const delegator = createLegacyConfigurationDelegator(model);
63+
64+
expect(delegator.get('countingAnimation')).toBeUndefined();
65+
});
66+
67+
it('passes through other properties', () => {
68+
const model = fakeModel({unit: 'kg'});
69+
const delegator = createLegacyConfigurationDelegator(model);
70+
71+
expect(delegator.get('unit')).toBe('kg');
72+
});
73+
74+
function fakeModel(attributes) {
75+
return {
76+
get(name) {
77+
return attributes[name];
78+
},
79+
has(name) {
80+
return name in attributes;
81+
}
82+
};
83+
}
84+
});

entry_types/scrolled/package/spec/contentElements/counter/createLegacyTextSizeDelegator-spec.js

Lines changed: 0 additions & 63 deletions
This file was deleted.

0 commit comments

Comments
 (0)