Skip to content

Commit a064205

Browse files
CopilotTechQuery
andauthored
refactor: port BootCell TimeUnit algorithm, add className/onEnd props, move cell styles to CSS
Agent-Logs-Url: https://github.com/Open-Source-Bazaar/Open-Source-Bazaar.github.io/sessions/28a4703b-d5c6-4dc4-8076-10f2873c9ddf Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com>
1 parent 5214914 commit a064205

3 files changed

Lines changed: 108 additions & 59 deletions

File tree

components/Activity/Hackathon/Countdown.tsx

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,90 @@ import { computed, observable } from 'mobx';
22
import { observer } from 'mobx-react';
33
import { ObservedComponent, reaction } from 'mobx-react-helper';
44

5-
import styles from './Hero.module.less';
5+
export interface TimeUnit {
6+
scale: number;
7+
label: string;
8+
}
9+
10+
interface TimeSection {
11+
value: number;
12+
label: string;
13+
}
614

715
export interface CountdownProps {
8-
countdownTo?: string;
9-
unitLabels: string[];
16+
className?: string;
17+
endTime?: string | Date | number;
18+
onEnd?: () => void;
19+
units: TimeUnit[];
1020
}
1121

1222
@observer
1323
export class Countdown extends ObservedComponent<CountdownProps> {
1424
@observable
15-
accessor rest: number | null = null;
25+
accessor rest = 0;
1626

1727
private timer?: number;
1828

1929
private get target() {
20-
const { countdownTo } = this.observedProps;
21-
const value = countdownTo ? new Date(countdownTo).getTime() : NaN;
30+
const { endTime } = this.observedProps;
2231

23-
return Number.isFinite(value) ? value : NaN;
24-
}
25-
26-
@computed
27-
get sections() {
28-
const { rest } = this;
32+
if (!endTime) return NaN;
2933

30-
if (rest === null) return ['--', '--', '--', '--'];
34+
const ms =
35+
typeof endTime === 'number'
36+
? endTime
37+
: endTime instanceof Date
38+
? endTime.getTime()
39+
: new Date(endTime).getTime();
3140

32-
const totalSeconds = Math.floor(Math.max(0, rest) / 1000);
33-
const days = Math.floor(totalSeconds / 86400);
34-
const hours = Math.floor((totalSeconds % 86400) / 3600);
35-
const minutes = Math.floor((totalSeconds % 3600) / 60);
36-
const seconds = totalSeconds % 60;
41+
return Number.isFinite(ms) ? ms : NaN;
42+
}
3743

38-
return [days, hours, minutes, seconds].map(value => String(value).padStart(2, '0'));
44+
@computed
45+
get timeSections(): TimeSection[] {
46+
const { units } = this.observedProps;
47+
let { rest } = this;
48+
49+
return units.reduce(
50+
(list, { label }, index) => {
51+
const scale = units
52+
.slice(index)
53+
.map(({ scale }) => scale)
54+
.reduce((sum, scale) => sum * scale, 1);
55+
56+
const value = ~~(rest / scale);
57+
rest -= value * scale;
58+
59+
list.push({ value, label });
60+
return list;
61+
},
62+
[] as TimeSection[],
63+
);
3964
}
4065

4166
tick = () => {
42-
this.rest = Math.max(0, this.target - Date.now());
67+
const rest = this.target - Date.now();
68+
69+
if (rest > 0) {
70+
this.rest = rest;
71+
} else {
72+
this.rest = 0;
73+
74+
if (this.timer) {
75+
window.clearInterval(this.timer);
76+
this.timer = undefined;
77+
this.props.onEnd?.();
78+
}
79+
}
4380
};
4481

45-
@reaction((_this: Countdown) => _this.observedProps.countdownTo)
4682
componentDidMount() {
4783
super.componentDidMount();
84+
this.initTimer();
85+
}
4886

87+
@reaction((_this: Countdown) => _this.observedProps.endTime)
88+
initTimer() {
4989
if (this.timer) {
5090
window.clearInterval(this.timer);
5191
this.timer = undefined;
@@ -62,18 +102,18 @@ export class Countdown extends ObservedComponent<CountdownProps> {
62102
}
63103

64104
render() {
65-
const { unitLabels } = this.observedProps;
66-
const { sections } = this;
105+
const { className } = this.props;
106+
const { timeSections } = this;
67107

68108
return (
69-
<ol className={`list-unstyled ${styles.countdownGrid} m-0`}>
70-
{sections.map((value, index) => (
109+
<ol className={`list-unstyled${className ? ` ${className}` : ''} m-0`}>
110+
{timeSections.map(({ value, label }, index) => (
71111
<li
72-
key={`${index}-${unitLabels[index]}`}
73-
className={`${styles.countdownCell} d-flex flex-column justify-content-center align-items-center`}
112+
key={`${index}-${label}`}
113+
className="d-flex flex-column justify-content-center align-items-center"
74114
>
75-
<strong>{value}</strong>
76-
<span>{unitLabels[index]}</span>
115+
<strong>{String(value).padStart(2, '0')}</strong>
116+
<span>{label}</span>
77117
</li>
78118
))}
79119
</ol>

components/Activity/Hackathon/Hero.module.less

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -197,32 +197,32 @@
197197
display: grid;
198198
grid-template-columns: repeat(4, minmax(0, 1fr));
199199
gap: 0.8rem;
200-
}
201200

202-
.countdownCell {
203-
gap: 0.7rem;
204-
box-shadow:
205-
inset 0 0 0 1px rgba(255, 255, 255, 0.03),
206-
0 0 26px rgba(44, 232, 255, 0.08);
207-
border: 1px solid rgba(44, 232, 255, 0.26);
208-
border-radius: 18px;
209-
background: linear-gradient(180deg, rgba(44, 232, 255, 0.08), rgba(44, 232, 255, 0.03));
210-
min-height: 120px;
201+
li {
202+
gap: 0.7rem;
203+
box-shadow:
204+
inset 0 0 0 1px rgba(255, 255, 255, 0.03),
205+
0 0 26px rgba(44, 232, 255, 0.08);
206+
border: 1px solid rgba(44, 232, 255, 0.26);
207+
border-radius: 18px;
208+
background: linear-gradient(180deg, rgba(44, 232, 255, 0.08), rgba(44, 232, 255, 0.03));
209+
min-height: 120px;
211210

212-
strong {
213-
color: #fff;
214-
font-size: clamp(2.3rem, 4vw, 3.8rem);
215-
line-height: 1;
216-
font-family: @heading;
217-
letter-spacing: 0.08em;
218-
}
211+
strong {
212+
color: #fff;
213+
font-size: clamp(2.3rem, 4vw, 3.8rem);
214+
line-height: 1;
215+
font-family: @heading;
216+
letter-spacing: 0.08em;
217+
}
219218

220-
span {
221-
color: rgba(255, 255, 255, 0.72);
222-
font-size: 0.82rem;
223-
font-family: @heading;
224-
letter-spacing: 0.2em;
225-
text-transform: uppercase;
219+
span {
220+
color: rgba(255, 255, 255, 0.72);
221+
font-size: 0.82rem;
222+
font-family: @heading;
223+
letter-spacing: 0.2em;
224+
text-transform: uppercase;
225+
}
226226
}
227227
}
228228

@@ -445,13 +445,13 @@
445445

446446
.countdownGrid {
447447
grid-template-columns: repeat(2, minmax(0, 1fr));
448-
}
449448

450-
.countdownCell {
451-
min-height: 96px;
449+
li {
450+
min-height: 96px;
452451

453-
strong {
454-
font-size: 2rem;
452+
strong {
453+
font-size: 2rem;
454+
}
455455
}
456456
}
457457
}

components/Activity/Hackathon/Hero.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FC } from 'react';
33
import { Container } from 'react-bootstrap';
44

55
import { LarkImage } from '../../LarkImage';
6-
import { Countdown } from './Countdown';
6+
import { Countdown, TimeUnit } from './Countdown';
77
import styles from './Hero.module.less';
88

99
export type HackathonHeroNavItem = Record<'label' | 'href', string>;
@@ -43,6 +43,8 @@ const BadgeToneClass = [
4343
styles.heroBadgeRose,
4444
];
4545

46+
const DEFAULT_UNIT_SCALES: TimeUnit['scale'][] = [24, 60, 60, 1000];
47+
4648
const HeroLink: FC<{
4749
action: HackathonHeroAction;
4850
variant: 'ghost' | 'primary';
@@ -165,7 +167,14 @@ export const HackathonHero: FC<HackathonHeroProps> = ({
165167
{countdownLabel && (
166168
<p className={`${styles.countdownLabel} m-0`}>{countdownLabel}</p>
167169
)}
168-
<Countdown countdownTo={countdownTo} unitLabels={countdownUnitLabels} />
170+
<Countdown
171+
className={styles.countdownGrid}
172+
endTime={countdownTo}
173+
units={countdownUnitLabels.map((label, i) => ({
174+
scale: DEFAULT_UNIT_SCALES[i] ?? 1,
175+
label,
176+
}))}
177+
/>
169178
</div>
170179
)}
171180

0 commit comments

Comments
 (0)