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