diff --git a/src/features/core/components/header.tsx b/src/features/core/components/header.tsx index e393091..6aca616 100644 --- a/src/features/core/components/header.tsx +++ b/src/features/core/components/header.tsx @@ -1,15 +1,57 @@ +import { useLayoutEffect, useRef } from "react"; import { Title, AppShell, Flex } from "@mantine/core"; import { useLingui } from "@lingui/react"; import { HeaderConfig } from "../types/header-config"; import BackButton from "./back-button"; +const MIN_TITLE_SCALE = 0.55; + const Header = ({ collapsed, title, titleValues, withBackButton }: HeaderConfig) => { const { i18n } = useLingui(); + const titleRef = useRef(null); const resolvedTitle = typeof title === "string" || title === undefined ? title : i18n._({ ...title, values: titleValues }); + useLayoutEffect(() => { + const el = titleRef.current; + if (!el) return; + let cancelled = false; + + const textWidth = () => { + const range = document.createRange(); + range.selectNodeContents(el); + return range.getBoundingClientRect().width; + }; + + const fit = () => { + el.style.fontSize = ""; + const available = el.clientWidth - 1; + if (available <= 0) return; + let needed = textWidth(); + if (needed <= available) return; + const natural = parseFloat(getComputedStyle(el).fontSize); + let scale = Math.max(available / needed, MIN_TITLE_SCALE); + el.style.fontSize = `${natural * scale}px`; + for (let i = 0; i < 3 && scale > MIN_TITLE_SCALE && textWidth() > available; i++) { + scale = Math.max(scale * 0.98, MIN_TITLE_SCALE); + el.style.fontSize = `${natural * scale}px`; + } + }; + + fit(); + document.fonts?.ready.then(() => { + if (!cancelled) fit(); + }); + const observer = new ResizeObserver(fit); + observer.observe(el); + return () => { + cancelled = true; + observer.disconnect(); + }; + }, [resolvedTitle]); + return ( { withBackButton && } - - + <Flex justify='center' px={48} mt={8} w='100%'> + <Title + ref={titleRef} + order={1} + lts='0.08em' + style={{ + userSelect: 'none', + width: '100%', + textAlign: 'center', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} + > {resolvedTitle?.toLocaleUpperCase()}