Skip to content

Commit ae07fed

Browse files
committed
Refine remote bar inline layout
1 parent 65a0c54 commit ae07fed

3 files changed

Lines changed: 94 additions & 83 deletions

File tree

src/components/NotifyCompRemoteBar/NotifyCompRemoteBar.tsx

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import classNames from 'classnames';
2-
import { Link } from 'react-router-dom';
32
import { Container } from '@/components/Container';
43
import { useCompetitionRemoteControl } from '@/hooks/useCompetitionRemoteControl';
54
import { useNow } from '@/hooks/useNow';
65
import {
76
formatElapsedMMSS,
8-
formatUntilNextActivity,
7+
formatNextActivityOffset,
98
getActiveGroupStartTime,
109
getRemoteBarProgress,
1110
} from './remoteBarProgress';
@@ -34,7 +33,7 @@ export function NotifyCompRemoteBar({ competitionId }: NotifyCompRemoteBarProps)
3433
const currentTitle = activeNames.length > 0 ? activeNames.join(', ') : 'No active activity';
3534
const nextTitle = remote.nextGroup?.name || 'No next activity';
3635
const elapsed = formatElapsedMMSS(getActiveGroupStartTime(remote.activeGroups), now);
37-
const nextStatus = formatUntilNextActivity(remote.nextGroup, now);
36+
const nextOffset = formatNextActivityOffset(remote.nextGroup, now);
3837
const progress = getRemoteBarProgress({
3938
activeGroups: remote.activeGroups,
4039
nextGroup: remote.nextGroup,
@@ -76,81 +75,70 @@ export function NotifyCompRemoteBar({ competitionId }: NotifyCompRemoteBarProps)
7675
<Container
7776
fullWidth
7877
className="relative flex-row min-h-16 items-center justify-center px-2 py-2">
79-
<div className="w-full max-w-xl space-y-1">
80-
<Link
81-
to={`/competitions/${competitionId}/remote`}
82-
className={classNames(
83-
'grid grid-cols-2 gap-2 rounded px-1 py-1 hover-transition hover:bg-gray-100 dark:hover:bg-gray-700',
84-
{
85-
'opacity-60': remote.isLoading,
86-
},
87-
)}>
88-
<span className="min-w-0 space-y-0.5">
89-
<span className="block text-[0.625rem] font-medium uppercase leading-none text-muted">
90-
Current
91-
</span>
92-
<span className="block truncate text-xs font-medium text-default">
93-
{currentTitle}
94-
</span>
95-
</span>
96-
<span className="min-w-0 space-y-0.5 text-right">
97-
<span className="block text-[0.625rem] font-medium uppercase leading-none text-muted">
98-
Next
99-
</span>
100-
<span className="block truncate text-xs font-medium text-default">{nextTitle}</span>
101-
</span>
102-
</Link>
103-
104-
<div className="flex items-center justify-center gap-2">
105-
<button
106-
type="button"
107-
className={iconButtonClassName}
108-
disabled={remote.isSaving || !remote.previousGroup}
109-
aria-label="Go back to previous remote activity"
110-
onClick={() => runSwitch('previous')}>
111-
<span className="fa fa-arrow-left" aria-hidden="true" />
112-
</button>
113-
114-
<button
115-
type="button"
116-
className={primaryButtonClassName}
117-
disabled={remote.isSaving || (remote.activeGroups.length === 0 && !remote.nextGroup)}
118-
aria-label={
119-
remote.activeGroups.length > 0
120-
? 'Stop current remote activities'
121-
: 'Start next remote activity'
122-
}
123-
onClick={togglePlayback}>
124-
{remote.activeGroups.length > 0 ? <>&#9632;</> : <>&#9654;</>}
125-
</button>
126-
127-
<button
128-
type="button"
129-
className={iconButtonClassName}
130-
disabled={remote.isSaving || !remote.nextGroup}
131-
aria-label="Go to next remote activity"
132-
onClick={() => runSwitch('next')}>
133-
<span className="fa fa-arrow-right" aria-hidden="true" />
134-
</button>
78+
<div
79+
className={classNames(
80+
'grid w-full max-w-4xl grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-2 md:gap-4',
81+
{
82+
'opacity-60': remote.isLoading,
83+
},
84+
)}>
85+
<div className="min-w-0 space-y-0.5 text-left">
86+
<div className="text-xs font-medium uppercase leading-none text-muted">Current</div>
87+
<div className="truncate text-sm font-medium text-default">{currentTitle}</div>
88+
<div className="text-sm tabular-nums text-muted">{elapsed}</div>
13589
</div>
13690

137-
<Link
138-
to={`/competitions/${competitionId}/remote`}
139-
className={classNames(
140-
'grid grid-cols-[44px_minmax(0,1fr)_minmax(88px,1.5fr)] items-center gap-2 rounded px-1 py-1 hover-transition hover:bg-gray-100 dark:hover:bg-gray-700',
141-
{
142-
'opacity-60': remote.isLoading,
143-
},
144-
)}>
145-
<span className="text-right text-xs tabular-nums text-muted">{elapsed}</span>
146-
<span className="h-1 overflow-hidden rounded-full bg-tertiary">
147-
<span
148-
className="block h-full rounded-full bg-blue-500 dark:bg-blue-400"
149-
style={{ width: `${progress}%` }}
150-
/>
151-
</span>
152-
<span className="truncate text-xs text-muted">{nextStatus}</span>
153-
</Link>
91+
<div className="flex w-28 flex-col justify-center space-y-1 sm:w-40 md:w-64">
92+
<div className="flex items-center justify-center gap-2">
93+
<button
94+
type="button"
95+
className={iconButtonClassName}
96+
disabled={remote.isSaving || !remote.previousGroup}
97+
aria-label="Go back to previous remote activity"
98+
onClick={() => runSwitch('previous')}>
99+
<span className="fa fa-arrow-left" aria-hidden="true" />
100+
</button>
101+
102+
<button
103+
type="button"
104+
className={primaryButtonClassName}
105+
disabled={
106+
remote.isSaving || (remote.activeGroups.length === 0 && !remote.nextGroup)
107+
}
108+
aria-label={
109+
remote.activeGroups.length > 0
110+
? 'Stop current remote activities'
111+
: 'Start next remote activity'
112+
}
113+
onClick={togglePlayback}>
114+
{remote.activeGroups.length > 0 ? <>&#9632;</> : <>&#9654;</>}
115+
</button>
116+
117+
<button
118+
type="button"
119+
className={iconButtonClassName}
120+
disabled={remote.isSaving || !remote.nextGroup}
121+
aria-label="Go to next remote activity"
122+
onClick={() => runSwitch('next')}>
123+
<span className="fa fa-arrow-right" aria-hidden="true" />
124+
</button>
125+
</div>
126+
127+
<div className="flex h-4 items-center">
128+
<div className="h-1 w-full overflow-hidden rounded-full bg-tertiary">
129+
<span
130+
className="block h-full rounded-full bg-blue-500 dark:bg-blue-400"
131+
style={{ width: `${progress}%` }}
132+
/>
133+
</div>
134+
</div>
135+
</div>
136+
137+
<div className="min-w-0 space-y-0.5 text-right">
138+
<div className="text-xs font-medium uppercase leading-none text-muted">Next</div>
139+
<div className="truncate text-sm font-medium text-default">{nextTitle}</div>
140+
<div className="truncate text-sm text-muted">{nextOffset}</div>
141+
</div>
154142
</div>
155143
</Container>
156144
</nav>

src/components/NotifyCompRemoteBar/remoteBarProgress.test.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { RemoteActivityGroup } from '@/lib/notifyCompRemoteActivities';
22
import {
33
formatElapsedMMSS,
4-
formatUntilNextActivity,
4+
formatNextActivityOffset,
55
getRemoteBarProgress,
66
} from './remoteBarProgress';
77

@@ -37,7 +37,7 @@ describe('remoteBarProgress', () => {
3737

3838
it('describes how many minutes remain until the next activity', () => {
3939
expect(
40-
formatUntilNextActivity(
40+
formatNextActivityOffset(
4141
group({
4242
name: 'Clock, Round 1, Group 1',
4343
scheduledActivities: [
@@ -49,7 +49,23 @@ describe('remoteBarProgress', () => {
4949
}),
5050
new Date('2026-06-01T10:10:00Z'),
5151
),
52-
).toBe('5 minutes until Clock, Round 1, Group 1');
52+
).toBe('In 5 minutes');
53+
});
54+
55+
it('describes far future activity starts in hours', () => {
56+
expect(
57+
formatNextActivityOffset(
58+
group({
59+
scheduledActivities: [
60+
{
61+
...group({}).scheduledActivities[0],
62+
startTime: '2026-06-01T13:45:00Z',
63+
},
64+
],
65+
}),
66+
new Date('2026-06-01T10:10:00Z'),
67+
),
68+
).toBe('In 4 hours');
5369
});
5470

5571
it('fills progress based on live start time and next scheduled start time', () => {

src/components/NotifyCompRemoteBar/remoteBarProgress.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const getActiveGroupStartTime = (groups: RemoteActivityGroup[]) =>
5151
}),
5252
);
5353

54-
export const formatUntilNextActivity = (nextGroup: RemoteActivityGroup | undefined, now: Date) => {
54+
export const formatNextActivityOffset = (nextGroup: RemoteActivityGroup | undefined, now: Date) => {
5555
const nextStart = getGroupScheduledStartTime(nextGroup);
5656

5757
if (!nextGroup || nextStart === undefined) {
@@ -61,13 +61,20 @@ export const formatUntilNextActivity = (nextGroup: RemoteActivityGroup | undefin
6161
const remainingMs = nextStart - now.getTime();
6262

6363
if (remainingMs <= 0) {
64-
return `${nextGroup.name} should start now`;
64+
return 'Now';
6565
}
6666

6767
const remainingMinutes = Math.ceil(remainingMs / MINUTE);
68-
const unit = remainingMinutes === 1 ? 'minute' : 'minutes';
6968

70-
return `${remainingMinutes} ${unit} until ${nextGroup.name}`;
69+
if (remainingMinutes < 60) {
70+
const unit = remainingMinutes === 1 ? 'minute' : 'minutes';
71+
return `In ${remainingMinutes} ${unit}`;
72+
}
73+
74+
const remainingHours = Math.ceil(remainingMinutes / 60);
75+
const unit = remainingHours === 1 ? 'hour' : 'hours';
76+
77+
return `In ${remainingHours} ${unit}`;
7178
};
7279

7380
export const getRemoteBarProgress = ({

0 commit comments

Comments
 (0)