refactor: remove size limits in title

This commit is contained in:
Carlos Valente
2024-06-20 21:13:11 +02:00
committed by Carlos Valente
parent cfc3aab893
commit 6c20cb5e99
14 changed files with 90 additions and 69 deletions
@@ -1,25 +1,37 @@
@use '../../../theme/viewerDefs' as *; @use '../../../theme/viewerDefs' as *;
.title-card { .title-card {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.25rem;
} }
.inline { .title-card__title,
display: flex; .title-card__secondary {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.title-card__title { .title-card__title {
font-weight: 600; font-weight: 600;
font-size: clamp(32px, 3.5vw, 50px); font-size: clamp(1.5rem, 3vw, 3rem);
color: var(--color-override, $viewer-color); color: var(--color-override, $viewer-color);
line-height: 1.1em; line-height: 1.2em;
}
.title-card__secondary {
font-size: clamp(1rem, 2vw, 2.25rem);
color: var(--secondary-color-override, $viewer-secondary-color);
line-height: 1.2em;
} }
.title-card__label { .title-card__label {
position: absolute;
right: 1rem;
top: 0.5rem;
font-size: clamp(1rem, 1.5vw, 1.5rem); font-size: clamp(1rem, 1.5vw, 1.5rem);
font-weight: 400;
color: var(--secondary-color-override, $viewer-secondary-color); color: var(--secondary-color-override, $viewer-secondary-color);
margin-left: auto; margin-left: auto;
text-transform: uppercase; text-transform: uppercase;
@@ -28,13 +40,3 @@
color: var(--accent-color-override, $accent-color); color: var(--accent-color-override, $accent-color);
} }
} }
.title-card__secondary {
font-size: clamp(1.5rem, 2vw, 2.25rem);
color: var(--secondary-color-override, $viewer-secondary-color);
line-height: 1.1em;
&::after {
content: '\200b';
}
}
@@ -1,28 +1,32 @@
import { ForwardedRef, forwardRef } from 'react';
import { useTranslation } from '../../../translation/TranslationProvider'; import { useTranslation } from '../../../translation/TranslationProvider';
import './TitleCard.scss'; import './TitleCard.scss';
interface TitleCardProps { interface TitleCardProps {
label: 'now' | 'next';
title: string; title: string;
label?: 'now' | 'next';
secondary?: string; secondary?: string;
className?: string;
} }
export default function TitleCard(props: TitleCardProps) { const TitleCard = forwardRef((props: TitleCardProps, ref: ForwardedRef<HTMLDivElement>) => {
const { label, title, secondary } = props; const { label, title, secondary, className = '' } = props;
const { getLocalizedString } = useTranslation(); const { getLocalizedString } = useTranslation();
const accent = label === 'now'; const accent = label === 'now';
return ( return (
<div className='title-card'> <div className={`title-card ${className}`} ref={ref}>
<div className='inline'> <span className='title-card__title'>{title}</span>
<span className='title-card__title'>{title}</span> <span className={accent ? 'title-card__label title-card__label--accent' : 'title-card__label'}>
<span className={accent ? 'title-card__label title-card__label--accent' : 'title-card__label'}> {label && getLocalizedString(`common.${label}`)}
{getLocalizedString(`common.${label}`)} </span>
</span>
</div>
<div className='title-card__secondary'>{secondary}</div> <div className='title-card__secondary'>{secondary}</div>
</div> </div>
); );
} });
TitleCard.displayName = 'TitleCard';
export default TitleCard;
@@ -65,12 +65,15 @@
font-size: 1.5rem; font-size: 1.5rem;
letter-spacing: 0.5px; letter-spacing: 0.5px;
color: $ui-white; color: $ui-white;
@include ellipsis-text;
} }
.secondaryField { .secondaryField {
grid-area: secondary; grid-area: secondary;
font-size: 1.25rem; font-size: 1.25rem;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@include ellipsis-text;
} }
.schedule { .schedule {
@@ -6,14 +6,14 @@ import { EditorUpdateFields } from '../EventEditor';
import style from '../EventEditor.module.scss'; import style from '../EventEditor.module.scss';
interface CountedTextInputProps extends InputProps { interface EventTextInputProps extends InputProps {
field: EditorUpdateFields; field: EditorUpdateFields;
label: string; label: string;
initialValue: string; initialValue: string;
submitHandler: (field: EditorUpdateFields, value: string) => void; submitHandler: (field: EditorUpdateFields, value: string) => void;
} }
export default function EventTextInput(props: CountedTextInputProps) { export default function EventTextInput(props: EventTextInputProps) {
const { field, label, initialValue, submitHandler, maxLength } = props; const { field, label, initialValue, submitHandler, maxLength } = props;
const ref = useRef<HTMLInputElement | null>(null); const ref = useRef<HTMLInputElement | null>(null);
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]); const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
@@ -34,7 +34,7 @@ export default function EventTextInput(props: CountedTextInputProps) {
variant='ontime-filled' variant='ontime-filled'
data-testid='input-textfield' data-testid='input-textfield'
value={value} value={value}
maxLength={maxLength || 50} maxLength={maxLength || 100}
onChange={onChange} onChange={onChange}
onBlur={onBlur} onBlur={onBlur}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
@@ -25,6 +25,8 @@ import { getPropertyValue } from '../common/viewUtils';
import './Backstage.scss'; import './Backstage.scss';
export const MotionTitleCard = motion(TitleCard);
interface BackstageProps { interface BackstageProps {
customFields: CustomFields; customFields: CustomFields;
isMirrored: boolean; isMirrored: boolean;
@@ -124,7 +126,7 @@ export default function Backstage(props: BackstageProps) {
animate='visible' animate='visible'
exit='exit' exit='exit'
> >
<TitleCard label='now' title={eventNow.title} secondary={secondaryTextNow} /> <TitleCard title={eventNow.title} secondary={secondaryTextNow} />
<div className='timer-group'> <div className='timer-group'>
<div className='aux-timers'> <div className='aux-timers'>
<div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div> <div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div>
@@ -151,16 +153,17 @@ export default function Backstage(props: BackstageProps) {
<AnimatePresence> <AnimatePresence>
{eventNext && ( {eventNext && (
<motion.div <MotionTitleCard
className='event next' className='event next'
key='next' key='next'
variants={titleVariants} variants={titleVariants}
initial='hidden' initial='hidden'
animate='visible' animate='visible'
exit='exit' exit='exit'
> label='next'
<TitleCard label='next' title={eventNext.title} secondary={secondaryTextNext} /> title={eventNext.title}
</motion.div> secondary={secondaryTextNext}
/>
)} )}
</AnimatePresence> </AnimatePresence>
</div> </div>
@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
import { millisToString, removeLeadingZero } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/constants'; import { overrideStylesURL } from '../../../common/api/constants';
import { getCountdownOptions } from '../../../common/components/view-params-editor/constants'; import { getCountdownOptions } from '../../../common/components/view-params-editor/constants';
@@ -12,6 +11,7 @@ import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider'; import { useTranslation } from '../../../translation/TranslationProvider';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { getFormattedTimer, isStringBoolean } from '../common/viewUtils';
import { fetchTimerData, TimerMessage } from './countdown.helpers'; import { fetchTimerData, TimerMessage } from './countdown.helpers';
import CountdownSelect from './CountdownSelect'; import CountdownSelect from './CountdownSelect';
@@ -84,24 +84,17 @@ export default function Countdown(props: CountdownProps) {
const standby = time.playback !== Playback.Play && time.playback !== Playback.Roll && selectedId === follow?.id; const standby = time.playback !== Playback.Play && time.playback !== Playback.Roll && selectedId === follow?.id;
const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt; const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
const isRunningFinished = finished && runningMessage === TimerMessage.running; const isRunningFinished = finished && runningMessage === TimerMessage.running;
const isSelected = runningMessage === TimerMessage.running;
const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : ''; const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : '';
const clock = formatTime(time.clock); const clock = formatTime(time.clock);
const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay); const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay);
const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay); const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay);
const formatTimer = (): string => { const hideSeconds = searchParams.get('hideTimerSeconds');
if (runningMessage === TimerMessage.ended) { const formattedTimer = getFormattedTimer(runningTimer, time.timerType, getLocalizedString('common.minutes'), {
return formatTime(runningTimer, { format12: 'hh:mm a', format24: 'HH:mm' }); removeSeconds: isStringBoolean(hideSeconds),
} removeLeadingZero: false,
let formattedTime = millisToString(isSelected ? runningTimer : runningTimer + delay); });
if (isSelected || runningMessage === TimerMessage.waiting) {
formattedTime = removeLeadingZero(formattedTime);
}
return formattedTime;
};
const formattedTimer = formatTimer();
const defaultFormat = getDefaultFormat(settings?.timeFormat); const defaultFormat = getDefaultFormat(settings?.timeFormat);
const timeOption = getCountdownOptions(defaultFormat); const timeOption = getCountdownOptions(defaultFormat);
@@ -72,6 +72,8 @@
font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif; font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif;
text-align: var(--lowerThird-text-align-override, left); text-align: var(--lowerThird-text-align-override, left);
white-space: nowrap; white-space: nowrap;
@include ellipsis-text();
&::after { &::after {
content: '\200b'; content: '\200b';
} }
@@ -21,6 +21,8 @@ import { getPropertyValue } from '../common/viewUtils';
import './Public.scss'; import './Public.scss';
export const MotionTitleCard = motion(TitleCard);
interface BackstageProps { interface BackstageProps {
customFields: CustomFields; customFields: CustomFields;
isMirrored: boolean; isMirrored: boolean;
@@ -83,31 +85,33 @@ export default function Public(props: BackstageProps) {
<div className='now-container'> <div className='now-container'>
<AnimatePresence> <AnimatePresence>
{publicEventNow && ( {publicEventNow && (
<motion.div <MotionTitleCard
className='event now' className='event now'
key='now' key='now'
variants={titleVariants} variants={titleVariants}
initial='hidden' initial='hidden'
animate='visible' animate='visible'
exit='exit' exit='exit'
> label='now'
<TitleCard label='now' title={publicEventNow.title} secondary={secondaryTextNow} /> title={publicEventNow.title}
</motion.div> secondary={secondaryTextNow}
/>
)} )}
</AnimatePresence> </AnimatePresence>
<AnimatePresence> <AnimatePresence>
{publicEventNext && ( {publicEventNext && (
<motion.div <MotionTitleCard
className='event next' className='event next'
key='next' key='next'
variants={titleVariants} variants={titleVariants}
initial='hidden' initial='hidden'
animate='visible' animate='visible'
exit='exit' exit='exit'
> label='next'
<TitleCard label='next' title={publicEventNext.title} secondary={secondaryTextNext} /> title={publicEventNext.title}
</motion.div> secondary={secondaryTextNext}
/>
)} )}
</AnimatePresence> </AnimatePresence>
</div> </div>
@@ -168,7 +168,6 @@ $orange-active: #f60;
display: flex; display: flex;
align-items: start; align-items: start;
gap: 0.5em; gap: 0.5em;
white-space: nowrap;
&--now { &--now {
color: var(--studio-active-label, $red-active); color: var(--studio-active-label, $red-active);
@@ -36,7 +36,7 @@ export default function StudioClock(props: StudioClockProps) {
// TODO: can we prevent the Flash of Unstyled Content on the 7segment fonts? // TODO: can we prevent the Flash of Unstyled Content on the 7segment fonts?
// deferring rendering seems to affect styling (font and useFitText) // deferring rendering seems to affect styling (font and useFitText)
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { fontSize: titleFontSize, ref: titleRef } = useFitText({ maxFontSize: 500 }); const { fontSize: titleFontSize, ref: titleRef } = useFitText({ minFontSize: 150, maxFontSize: 500 });
const activeIndicators = [...Array(12).keys()]; const activeIndicators = [...Array(12).keys()];
const secondsIndicators = [...Array(60).keys()]; const secondsIndicators = [...Array(60).keys()];
@@ -15,7 +15,7 @@
padding: min(2vh, 16px) clamp(16px, 10vw, 64px); padding: min(2vh, 16px) clamp(16px, 10vw, 64px);
display: grid; display: grid;
grid-template-columns: auto; grid-template-columns: 100%;
grid-template-rows: auto 1fr auto auto auto; grid-template-rows: auto 1fr auto auto auto;
grid-template-areas: grid-template-areas:
'clock' 'clock'
@@ -43,6 +43,8 @@ const titleVariants = {
}, },
}; };
export const MotionTitleCard = motion(TitleCard);
interface TimerProps { interface TimerProps {
customFields: CustomFields; customFields: CustomFields;
eventNext: OntimeEvent | null; eventNext: OntimeEvent | null;
@@ -211,31 +213,33 @@ export default function Timer(props: TimerProps) {
<> <>
<AnimatePresence> <AnimatePresence>
{eventNow?.title && ( {eventNow?.title && (
<motion.div <MotionTitleCard
className='event now' className='event now'
key='now' key='now'
variants={titleVariants} variants={titleVariants}
initial='hidden' initial='hidden'
animate='visible' animate='visible'
exit='exit' exit='exit'
> label='now'
<TitleCard label='now' title={mainFieldNow} secondary={secondaryTextNow} /> title={mainFieldNow}
</motion.div> secondary={secondaryTextNow}
/>
)} )}
</AnimatePresence> </AnimatePresence>
<AnimatePresence> <AnimatePresence>
{eventNext?.title && ( {eventNext?.title && (
<motion.div <MotionTitleCard
className='event next'
key='next' key='next'
variants={titleVariants} variants={titleVariants}
initial='hidden' initial='hidden'
animate='visible' animate='visible'
exit='exit' exit='exit'
> label='next'
<TitleCard label='next' title={mainFieldNext} secondary={secondaryTextNext} /> title={mainFieldNext}
</motion.div> secondary={secondaryTextNext}
className='event next'
/>
)} )}
</AnimatePresence> </AnimatePresence>
</> </>
+7 -1
View File
@@ -1,4 +1,4 @@
@use "ontimeStyles" as *; @use 'ontimeStyles' as *;
//////////////////////////////////// general app elements //////////////////////////////////// general app elements
@@ -15,3 +15,9 @@
color: $ontime-color; color: $ontime-color;
} }
} }
@mixin ellipsis-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
+1
View File
@@ -61,6 +61,7 @@ export default defineConfig({
additionalData: ` additionalData: `
@use './src/theme/ontimeColours' as *; @use './src/theme/ontimeColours' as *;
@use './src/theme/ontimeStyles' as *; @use './src/theme/ontimeStyles' as *;
@use './src/theme/mixins' as *;
`, `,
}, },
}, },