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) =>
createSelector((state: RuntimeStore) => {
if (index === 1) return state.auxtimer1.current;
@@ -13,6 +13,7 @@ type EditorSettingsStore = {
defaultTimerType: TimerType;
defaultEndAction: EndAction;
inheritGroupColour: boolean;
auxTimersCollapsed: boolean;
setDefaultDuration: (defaultDuration: string) => void;
setLinkPrevious: (linkPrevious: boolean) => void;
setInheritGroupColour: (inheritGroupColour: boolean) => void;
@@ -21,6 +22,7 @@ type EditorSettingsStore = {
setDangerTime: (dangerTime: string) => void;
setDefaultTimerType: (defaultTimerType: TimerType) => void;
setDefaultEndAction: (defaultEndAction: EndAction) => void;
setAuxTimersCollapsed: (auxTimersCollapsed: boolean) => void;
};
export const editorSettingsDefaults = {
@@ -32,6 +34,7 @@ export const editorSettingsDefaults = {
timerType: TimerType.CountDown,
endAction: EndAction.None,
inheritGroupColour: false,
auxTimersCollapsed: false,
};
enum EditorSettingsKeys {
@@ -43,6 +46,7 @@ enum EditorSettingsKeys {
DefaultTimerType = 'ontime-default-timer-type',
DefaultEndAction = 'ontime-default-end-action',
InheritGroupColour = 'ontime-inherit-group-colour',
AuxTimersCollapsed = 'ontime-aux-timers-collapsed',
}
export const useEditorSettings = create<EditorSettingsStore>((set) => {
@@ -67,6 +71,10 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => {
EditorSettingsKeys.InheritGroupColour,
editorSettingsDefaults.inheritGroupColour,
),
auxTimersCollapsed: booleanFromLocalStorage(
EditorSettingsKeys.AuxTimersCollapsed,
editorSettingsDefaults.auxTimersCollapsed,
),
setDefaultDuration: (defaultDuration) =>
set(() => {
@@ -110,5 +118,10 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => {
localStorage.setItem(EditorSettingsKeys.InheritGroupColour, String(inheritGroupColour));
return { inheritGroupColour };
}),
setAuxTimersCollapsed: (auxTimersCollapsed) =>
set(() => {
localStorage.setItem(EditorSettingsKeys.AuxTimersCollapsed, String(auxTimersCollapsed));
return { auxTimersCollapsed };
}),
};
});
@@ -11,10 +11,26 @@
}
.label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: $inner-section-text-size;
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 {
display: grid;
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 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 AddTime from './add-time/AddTime';
import { AuxTimer } from './aux-timer/AuxTimer';
@@ -14,6 +15,8 @@ import style from './PlaybackControl.module.scss';
export default function PlaybackControl() {
const data = usePlaybackControl();
const { setLocation } = useAppSettingsNavigation();
const { auxTimersCollapsed, setAuxTimersCollapsed } = useEditorSettings();
const isAuxTimerActive = useAuxTimersActive();
return (
<div className={style.mainContainer}>
@@ -27,26 +30,41 @@ export default function PlaybackControl() {
timerPhase={data.timerPhase}
/>
<div className={style.auxHeader}>
<span className={style.label}>Aux timers</span>
<Tooltip
text='Name aux timers'
render={
<IconButton
size='small'
variant='subtle'
aria-label='Name aux timers'
onClick={() => setLocation('settings__aux-timers')}
/>
}
>
<IoSettingsOutline />
</Tooltip>
</div>
<div className={style.auxTimers}>
<AuxTimer index={1} />
<AuxTimer index={2} />
<AuxTimer index={3} />
<span className={style.label}>
Aux timers
{auxTimersCollapsed && isAuxTimerActive && <span className={style.activeIndicator} />}
</span>
<div className={style.auxHeaderButtons}>
<Tooltip
text='Name aux timers'
render={
<IconButton
size='small'
variant='subtle-white'
aria-label='Name aux timers'
onClick={() => setLocation('settings__aux-timers')}
/>
}
>
<IoSettingsOutline />
</Tooltip>
<IconButton
size='small'
variant='subtle-white'
aria-label={auxTimersCollapsed ? 'Expand aux timers' : 'Collapse aux timers'}
onClick={() => setAuxTimersCollapsed(!auxTimersCollapsed)}
>
{auxTimersCollapsed ? <IoChevronUp /> : <IoChevronDown />}
</IconButton>
</div>
</div>
{!auxTimersCollapsed && (
<div className={style.auxTimers}>
<AuxTimer index={1} />
<AuxTimer index={2} />
<AuxTimer index={3} />
</div>
)}
</div>
);
}