feat: collapsible aux timers section, fix icon button variants

- Add a collapse toggle next to the aux timers settings shortcut,
  mirroring the chevron pattern already used for rundown groups. State
  persists in localStorage via the existing editorSettings store, so
  it's a durable per-user preference rather than resetting on reload.
  This matters most on small viewports, where the aux timer controls
  (3 rows) are a disproportionate cost relative to their usage.
- When collapsed, a small active-indicator dot appears next to the
  "Aux timers" label if any aux timer is currently running, so a
  running timer never goes silently invisible.
- Switch both header icon buttons (settings shortcut, collapse toggle)
  from the 'subtle' variant (blue, reserved for actions) to
  'subtle-white', matching how utility/navigation icon buttons are
  styled elsewhere (eg. the rundown group collapse chevron).

Verified in the browser: collapsing reclaims the three-row control
block, the indicator dot appears while a timer runs and the panel is
collapsed, and both icon buttons render white/gray instead of blue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCZejVTzuAY3tHTE6nB3JH
This commit is contained in:
Claude
2026-08-01 07:53:20 +00:00
parent d70d7f2fd8
commit b59dc796cf
4 changed files with 76 additions and 21 deletions
@@ -103,6 +103,14 @@ export const useAuxTimersName = createSelector((state: RuntimeStore) => {
}; };
}); });
export const useAuxTimersActive = createSelector((state: RuntimeStore) => {
return (
state.auxtimer1.playback === SimplePlayback.Start ||
state.auxtimer2.playback === SimplePlayback.Start ||
state.auxtimer3.playback === SimplePlayback.Start
);
});
export const useAuxTimerTime = (index: number) => export const useAuxTimerTime = (index: number) =>
createSelector((state: RuntimeStore) => { createSelector((state: RuntimeStore) => {
if (index === 1) return state.auxtimer1.current; if (index === 1) return state.auxtimer1.current;
@@ -13,6 +13,7 @@ type EditorSettingsStore = {
defaultTimerType: TimerType; defaultTimerType: TimerType;
defaultEndAction: EndAction; defaultEndAction: EndAction;
inheritGroupColour: boolean; inheritGroupColour: boolean;
auxTimersCollapsed: boolean;
setDefaultDuration: (defaultDuration: string) => void; setDefaultDuration: (defaultDuration: string) => void;
setLinkPrevious: (linkPrevious: boolean) => void; setLinkPrevious: (linkPrevious: boolean) => void;
setInheritGroupColour: (inheritGroupColour: boolean) => void; setInheritGroupColour: (inheritGroupColour: boolean) => void;
@@ -21,6 +22,7 @@ type EditorSettingsStore = {
setDangerTime: (dangerTime: string) => void; setDangerTime: (dangerTime: string) => void;
setDefaultTimerType: (defaultTimerType: TimerType) => void; setDefaultTimerType: (defaultTimerType: TimerType) => void;
setDefaultEndAction: (defaultEndAction: EndAction) => void; setDefaultEndAction: (defaultEndAction: EndAction) => void;
setAuxTimersCollapsed: (auxTimersCollapsed: boolean) => void;
}; };
export const editorSettingsDefaults = { export const editorSettingsDefaults = {
@@ -32,6 +34,7 @@ export const editorSettingsDefaults = {
timerType: TimerType.CountDown, timerType: TimerType.CountDown,
endAction: EndAction.None, endAction: EndAction.None,
inheritGroupColour: false, inheritGroupColour: false,
auxTimersCollapsed: false,
}; };
enum EditorSettingsKeys { enum EditorSettingsKeys {
@@ -43,6 +46,7 @@ enum EditorSettingsKeys {
DefaultTimerType = 'ontime-default-timer-type', DefaultTimerType = 'ontime-default-timer-type',
DefaultEndAction = 'ontime-default-end-action', DefaultEndAction = 'ontime-default-end-action',
InheritGroupColour = 'ontime-inherit-group-colour', InheritGroupColour = 'ontime-inherit-group-colour',
AuxTimersCollapsed = 'ontime-aux-timers-collapsed',
} }
export const useEditorSettings = create<EditorSettingsStore>((set) => { export const useEditorSettings = create<EditorSettingsStore>((set) => {
@@ -67,6 +71,10 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => {
EditorSettingsKeys.InheritGroupColour, EditorSettingsKeys.InheritGroupColour,
editorSettingsDefaults.inheritGroupColour, editorSettingsDefaults.inheritGroupColour,
), ),
auxTimersCollapsed: booleanFromLocalStorage(
EditorSettingsKeys.AuxTimersCollapsed,
editorSettingsDefaults.auxTimersCollapsed,
),
setDefaultDuration: (defaultDuration) => setDefaultDuration: (defaultDuration) =>
set(() => { set(() => {
@@ -110,5 +118,10 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => {
localStorage.setItem(EditorSettingsKeys.InheritGroupColour, String(inheritGroupColour)); localStorage.setItem(EditorSettingsKeys.InheritGroupColour, String(inheritGroupColour));
return { inheritGroupColour }; return { inheritGroupColour };
}), }),
setAuxTimersCollapsed: (auxTimersCollapsed) =>
set(() => {
localStorage.setItem(EditorSettingsKeys.AuxTimersCollapsed, String(auxTimersCollapsed));
return { auxTimersCollapsed };
}),
}; };
}); });
@@ -11,10 +11,26 @@
} }
.label { .label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: $inner-section-text-size; font-size: $inner-section-text-size;
color: $label-gray; color: $label-gray;
} }
.auxHeaderButtons {
display: flex;
align-items: center;
gap: 0.25rem;
}
.activeIndicator {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background-color: $active-indicator;
}
.auxTimers { .auxTimers {
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
@@ -1,8 +1,9 @@
import { IoSettingsOutline } from 'react-icons/io5'; import { IoChevronDown, IoChevronUp, IoSettingsOutline } from 'react-icons/io5';
import IconButton from '../../../common/components/buttons/IconButton'; import IconButton from '../../../common/components/buttons/IconButton';
import Tooltip from '../../../common/components/tooltip/Tooltip'; import Tooltip from '../../../common/components/tooltip/Tooltip';
import { usePlaybackControl } from '../../../common/hooks/useSocket'; import { useAuxTimersActive, usePlaybackControl } from '../../../common/hooks/useSocket';
import { useEditorSettings } from '../../../common/stores/editorSettings';
import useAppSettingsNavigation from '../../app-settings/useAppSettingsNavigation'; import useAppSettingsNavigation from '../../app-settings/useAppSettingsNavigation';
import AddTime from './add-time/AddTime'; import AddTime from './add-time/AddTime';
import { AuxTimer } from './aux-timer/AuxTimer'; import { AuxTimer } from './aux-timer/AuxTimer';
@@ -14,6 +15,8 @@ import style from './PlaybackControl.module.scss';
export default function PlaybackControl() { export default function PlaybackControl() {
const data = usePlaybackControl(); const data = usePlaybackControl();
const { setLocation } = useAppSettingsNavigation(); const { setLocation } = useAppSettingsNavigation();
const { auxTimersCollapsed, setAuxTimersCollapsed } = useEditorSettings();
const isAuxTimerActive = useAuxTimersActive();
return ( return (
<div className={style.mainContainer}> <div className={style.mainContainer}>
@@ -27,26 +30,41 @@ export default function PlaybackControl() {
timerPhase={data.timerPhase} timerPhase={data.timerPhase}
/> />
<div className={style.auxHeader}> <div className={style.auxHeader}>
<span className={style.label}>Aux timers</span> <span className={style.label}>
<Tooltip Aux timers
text='Name aux timers' {auxTimersCollapsed && isAuxTimerActive && <span className={style.activeIndicator} />}
render={ </span>
<IconButton <div className={style.auxHeaderButtons}>
size='small' <Tooltip
variant='subtle' text='Name aux timers'
aria-label='Name aux timers' render={
onClick={() => setLocation('settings__aux-timers')} <IconButton
/> size='small'
} variant='subtle-white'
> aria-label='Name aux timers'
<IoSettingsOutline /> onClick={() => setLocation('settings__aux-timers')}
</Tooltip> />
</div> }
<div className={style.auxTimers}> >
<AuxTimer index={1} /> <IoSettingsOutline />
<AuxTimer index={2} /> </Tooltip>
<AuxTimer index={3} /> <IconButton
size='small'
variant='subtle-white'
aria-label={auxTimersCollapsed ? 'Expand aux timers' : 'Collapse aux timers'}
onClick={() => setAuxTimersCollapsed(!auxTimersCollapsed)}
>
{auxTimersCollapsed ? <IoChevronUp /> : <IoChevronDown />}
</IconButton>
</div>
</div> </div>
{!auxTimersCollapsed && (
<div className={style.auxTimers}>
<AuxTimer index={1} />
<AuxTimer index={2} />
<AuxTimer index={3} />
</div>
)}
</div> </div>
); );
} }