Skip to content

Commit 651e83c

Browse files
committed
Decouple renderNumbers from renderHourMarks
Closes #229
1 parent 3c27cd2 commit 651e83c

File tree

8 files changed

+147
-43
lines changed

8 files changed

+147
-43
lines changed

packages/react-clock/src/Clock.spec.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,20 @@ describe('Clock', () => {
9292
it('has hour numbers given renderNumbers flag', () => {
9393
const { container } = render(<Clock renderNumbers />);
9494

95-
const hourMarks = container.querySelectorAll('.react-clock__hour-mark');
95+
const numberMarks = container.querySelectorAll('.react-clock__number-mark');
9696

97-
hourMarks.forEach((hourMark, index) => {
98-
expect(hourMark).toHaveTextContent(`${index + 1}`);
97+
numberMarks.forEach((numberMark, index) => {
98+
expect(numberMark).toHaveTextContent(`${index + 1}`);
9999
});
100100
});
101101

102102
it('uses formatHour properly if given', () => {
103103
const formatHour = () => 'H';
104104
const { container } = render(<Clock formatHour={formatHour} renderNumbers />);
105105

106-
const hourMarks = container.querySelectorAll('.react-clock__hour-mark');
106+
const numberMarks = container.querySelectorAll('.react-clock__number-mark');
107107

108-
expect(hourMarks[0]).toHaveTextContent('H');
108+
expect(numberMarks[0]).toHaveTextContent('H');
109109
});
110110

111111
it('has only minute marks when renderHourMarks is false', () => {

packages/react-clock/src/Clock.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { getHours, getMilliseconds, getMinutes, getSeconds } from '@wojtekmaj/da
33

44
import Hand from './Hand.js';
55
import Mark from './Mark.js';
6+
import MarkNumber from './MarkNumber.js';
67

78
import { formatHour as defaultFormatHour } from './shared/hourFormatter.js';
9+
import { safeMax } from './shared/utils.js';
810

911
import type {
1012
ClassName,
@@ -252,19 +254,43 @@ export default function Clock({
252254
angle={i * 30}
253255
length={hourMarksLength}
254256
name="hour"
255-
number={renderNumbers ? formatHour(locale, i) : undefined}
256257
width={hourMarksWidth}
257258
/>,
258259
);
259260
}
260261
return hourMarks;
261262
}
262263

264+
function renderNumbersFn() {
265+
if (!renderNumbers) {
266+
return null;
267+
}
268+
269+
const numbers = [];
270+
for (let i = 1; i <= 12; i += 1) {
271+
numbers.push(
272+
<MarkNumber
273+
key={`number_${i}`}
274+
angle={i * 30}
275+
length={safeMax(
276+
renderHourMarks && hourMarksLength,
277+
renderMinuteMarks && minuteMarksLength,
278+
0,
279+
)}
280+
name="number"
281+
number={formatHour(locale, i)}
282+
/>,
283+
);
284+
}
285+
return numbers;
286+
}
287+
263288
function renderFace() {
264289
return (
265290
<div className="react-clock__face">
266291
{renderMinuteMarksFn()}
267292
{renderHourMarksFn()}
293+
{renderNumbersFn()}
268294
</div>
269295
);
270296
}

packages/react-clock/src/Mark.spec.tsx

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,4 @@ describe('Mark', () => {
6161

6262
expect(markBody).toHaveStyle('width: 5px');
6363
});
64-
65-
it('renders number given number prop', () => {
66-
const { container } = render(<Mark name="minute" number={1} />);
67-
68-
const markNumber = container.querySelector('.react-clock__mark__number');
69-
70-
expect(markNumber).toBeInTheDocument();
71-
});
72-
73-
it('renders number angled at 0° by default', () => {
74-
const { container } = render(<Mark name="minute" number={1} />);
75-
76-
const markNumber = container.querySelector('.react-clock__mark__number');
77-
78-
expect(markNumber).toHaveStyle('transform: rotate(-0deg)');
79-
});
80-
81-
it('renders properly angled mark given angle prop', () => {
82-
const { container } = render(<Mark angle={15} name="minute" number={1} />);
83-
84-
const markNumber = container.querySelector('.react-clock__mark__number');
85-
86-
expect(markNumber).toHaveStyle('transform: rotate(-15deg)');
87-
});
8864
});

packages/react-clock/src/Mark.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ type MarkProps = {
66
angle?: number;
77
length?: MarkLength;
88
name: string;
9-
number?: React.ReactNode;
109
width?: MarkWidth;
1110
};
1211

@@ -15,7 +14,6 @@ const Mark: React.FC<MarkProps> = memo(function Mark({
1514
length = 10,
1615
name,
1716
width = 1,
18-
number,
1917
}: MarkProps): React.ReactElement {
2018
return (
2119
<div
@@ -32,17 +30,6 @@ const Mark: React.FC<MarkProps> = memo(function Mark({
3230
bottom: `${100 - length / 2}%`,
3331
}}
3432
/>
35-
{number ? (
36-
<div
37-
className="react-clock__mark__number"
38-
style={{
39-
transform: `rotate(-${angle}deg)`,
40-
top: `${length / 2}%`,
41-
}}
42-
>
43-
{number}
44-
</div>
45-
) : null}
4633
</div>
4734
);
4835
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { render } from '@testing-library/react';
3+
4+
import MarkNumber from './MarkNumber.js';
5+
6+
describe('MarkNumber', () => {
7+
it('renders number given number prop', () => {
8+
const { container } = render(<MarkNumber name="minute" number={1} />);
9+
10+
const markNumber = container.querySelector('.react-clock__mark__number');
11+
12+
expect(markNumber).toBeInTheDocument();
13+
});
14+
15+
it('renders number angled at 0° by default', () => {
16+
const { container } = render(<MarkNumber name="minute" number={1} />);
17+
18+
const markNumber = container.querySelector('.react-clock__mark__number');
19+
20+
expect(markNumber).toHaveStyle('transform: rotate(-0deg)');
21+
});
22+
23+
it('renders properly angled mark given angle prop', () => {
24+
const { container } = render(<MarkNumber angle={15} name="minute" number={1} />);
25+
26+
const markNumber = container.querySelector('.react-clock__mark__number');
27+
28+
expect(markNumber).toHaveStyle('transform: rotate(-15deg)');
29+
});
30+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { memo } from 'react';
2+
3+
import type { MarkLength } from './shared/types.js';
4+
5+
type MarkNumberProps = {
6+
angle?: number;
7+
length?: MarkLength;
8+
name: string;
9+
number: React.ReactNode;
10+
};
11+
12+
const MarkNumber: React.FC<MarkNumberProps> = memo(function MarkNumber({
13+
angle = 0,
14+
length = 10,
15+
name,
16+
number,
17+
}: MarkNumberProps): React.ReactElement {
18+
return (
19+
<div
20+
className={`react-clock__mark react-clock__${name}-mark`}
21+
style={{
22+
transform: `rotate(${angle}deg)`,
23+
}}
24+
>
25+
<div
26+
className="react-clock__mark__number"
27+
style={{
28+
transform: `rotate(-${angle}deg)`,
29+
top: `${length / 2}%`,
30+
}}
31+
>
32+
{number}
33+
</div>
34+
</div>
35+
);
36+
});
37+
38+
export default MarkNumber;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { safeMax } from './utils.js';
3+
4+
describe('safeMax', () => {
5+
it('returns -Infinity given no values', () => {
6+
const result = safeMax();
7+
8+
expect(result).toBe(Number.NEGATIVE_INFINITY);
9+
});
10+
11+
it('returns the largest value given valid numbers', () => {
12+
const result = safeMax(3, 4, 5);
13+
14+
expect(result).toBe(5);
15+
});
16+
17+
it('returns the largest value given valid numbers with zero', () => {
18+
const result = safeMax(-2, -1, 0);
19+
20+
expect(result).toBe(0);
21+
});
22+
23+
it('returns the largest value given valid number and null', () => {
24+
const result = safeMax(3, 4, null);
25+
26+
expect(result).toBe(4);
27+
});
28+
29+
it('returns the largest value given valid number and undefined', () => {
30+
const result = safeMax(3, 4, undefined);
31+
32+
expect(result).toBe(4);
33+
});
34+
35+
it('returns the largest value given valid numbers as strings', () => {
36+
const result = safeMax('3', '4');
37+
38+
expect(result).toBe(4);
39+
});
40+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function isValidNumber(num: unknown): num is number {
2+
return num !== null && num !== false && !Number.isNaN(Number(num));
3+
}
4+
5+
export function safeMax(...args: unknown[]): number {
6+
return Math.max(...args.filter(isValidNumber));
7+
}

0 commit comments

Comments
 (0)