Compound Typography pass (#11103)
* Integrate compound design tokens The icons should not be included in this repo, and should live in the compound design token repo, but for simplicity sake at this phase of the integration they will be added here * Delete unused or incorrect - sass variables * Typography pass * Deprecate _font-weights.pcss and use Compound instead * lint fix * Fix snapshot * Fix typography pass feedback * Remove unwanted e2e test cypress tests should test functionality not visual output. And we should not test visual output by inspecting CSS properties * lintfix * Migration script for baseFontSize * Updates after design review * Update font scaling panel to use min/max size * Fix custom font * Fix font slider e2e test * Update custom font * Update new baseFontSizeV2 * Disambiguate heading props * Fix appearance test * change max font size * fix e2ee test * fix tests * test baseFontSize migration code * typescript strict * Migrate baseFontSize account setting * Change assertion for font size * Fix font size controller test
This commit is contained in:
parent
ce479c5774
commit
9c7d935aae
199 changed files with 606 additions and 608 deletions
|
@ -94,7 +94,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
<BaseCard
|
||||
header={
|
||||
<div className="mx_BaseCard_header_title">
|
||||
<Heading size="h4" className="mx_BaseCard_header_title_heading">
|
||||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{_t("Notifications")}
|
||||
</Heading>
|
||||
</div>
|
||||
|
|
|
@ -113,7 +113,7 @@ export const ThreadPanelHeader: React.FC<{
|
|||
) : null;
|
||||
return (
|
||||
<div className="mx_BaseCard_header_title">
|
||||
<Heading size="h4" className="mx_BaseCard_header_title_heading">
|
||||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{_t("Threads")}
|
||||
</Heading>
|
||||
{!empty && (
|
||||
|
|
|
@ -357,7 +357,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
private renderThreadViewHeader = (): JSX.Element => {
|
||||
return (
|
||||
<div className="mx_BaseCard_header_title">
|
||||
<Heading size="h4" className="mx_BaseCard_header_title_heading">
|
||||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{_t("Thread")}
|
||||
</Heading>
|
||||
<ThreadListContextMenu mxEvent={this.props.mxEvent} permalinkCreator={this.props.permalinkCreator} />
|
||||
|
|
|
@ -33,7 +33,7 @@ const DialogSidebar: React.FC<Props> = ({ beacons, onBeaconClick, requestClose }
|
|||
return (
|
||||
<div className="mx_DialogSidebar">
|
||||
<div className="mx_DialogSidebar_header">
|
||||
<Heading size="h4">{_t("View List")}</Heading>
|
||||
<Heading size="4">{_t("View List")}</Heading>
|
||||
<AccessibleButton
|
||||
className="mx_DialogSidebar_closeButton"
|
||||
onClick={requestClose}
|
||||
|
|
|
@ -54,7 +54,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
|||
>
|
||||
{desktopBuilds?.get("available") && (
|
||||
<div className="mx_AppDownloadDialog_desktop">
|
||||
<Heading size="h3">{_t("Download %(brand)s Desktop", { brand })}</Heading>
|
||||
<Heading size="3">{_t("Download %(brand)s Desktop", { brand })}</Heading>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
element="a"
|
||||
|
@ -68,7 +68,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
|||
)}
|
||||
<div className="mx_AppDownloadDialog_mobile">
|
||||
<div className="mx_AppDownloadDialog_app">
|
||||
<Heading size="h3">{_t("iOS")}</Heading>
|
||||
<Heading size="3">{_t("iOS")}</Heading>
|
||||
<QRCode data={urlAppStore} margin={0} width={172} />
|
||||
<div className="mx_AppDownloadDialog_info">
|
||||
{_t("%(qrCode)s or %(appLinks)s", {
|
||||
|
@ -89,7 +89,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mx_AppDownloadDialog_app">
|
||||
<Heading size="h3">{_t("Android")}</Heading>
|
||||
<Heading size="3">{_t("Android")}</Heading>
|
||||
<QRCode data={urlAndroid} margin={0} width={172} />
|
||||
<div className="mx_AppDownloadDialog_info">
|
||||
{_t("%(qrCode)s or %(appLinks)s", {
|
||||
|
|
|
@ -174,7 +174,8 @@ export default class BaseDialog extends React.Component<IProps> {
|
|||
>
|
||||
{!!(this.props.title || headerImage) && (
|
||||
<Heading
|
||||
size="h2"
|
||||
as="h2"
|
||||
size="4"
|
||||
className={classNames("mx_Dialog_title", this.props.titleClass)}
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
|
|
|
@ -146,7 +146,7 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
|||
<div className="mx_AppPermission_content_bolder">{_t("Widget added by")}</div>
|
||||
<div>
|
||||
{avatar}
|
||||
<Heading size="h4">{displayName}</Heading>
|
||||
<Heading size="4">{displayName}</Heading>
|
||||
<div>{userId}</div>
|
||||
</div>
|
||||
<div>{warning}</div>
|
||||
|
|
|
@ -31,7 +31,7 @@ export const EnableLiveShare: React.FC<Props> = ({ onSubmit }) => {
|
|||
return (
|
||||
<div data-testid="location-picker-enable-live-share" className="mx_EnableLiveShare">
|
||||
<StyledLiveBeaconIcon className="mx_EnableLiveShare_icon" />
|
||||
<Heading className="mx_EnableLiveShare_heading" size="h3">
|
||||
<Heading className="mx_EnableLiveShare_heading" size="3">
|
||||
{_t("Live location sharing")}
|
||||
</Heading>
|
||||
<p className="mx_EnableLiveShare_description">
|
||||
|
|
|
@ -38,7 +38,7 @@ export const MapError: React.FC<MapErrorProps> = ({ error, isMinimised, classNam
|
|||
onClick={onClick}
|
||||
>
|
||||
<WarningBadge className="mx_MapError_icon" />
|
||||
<Heading className="mx_MapError_heading" size="h3">
|
||||
<Heading className="mx_MapError_heading" size="3">
|
||||
{_t("Unable to load map")}
|
||||
</Heading>
|
||||
<p className="mx_MapError_message">{getLocationShareErrorMessage(error)}</p>
|
||||
|
|
|
@ -81,7 +81,7 @@ const ShareType: React.FC<Props> = ({ setShareType, enabledShareTypes }) => {
|
|||
return (
|
||||
<div className="mx_ShareType">
|
||||
<LocationIcon className="mx_ShareType_badge" />
|
||||
<Heading className="mx_ShareType_heading" size="h3">
|
||||
<Heading className="mx_ShareType_heading" size="3">
|
||||
{_t("What location type do you want to share?")}
|
||||
</Heading>
|
||||
<div className="mx_ShareType_wrapper_options">
|
||||
|
|
|
@ -69,7 +69,7 @@ export const PollHistory: React.FC<PollHistoryProps> = ({ room, matrixClient, pe
|
|||
return (
|
||||
<div className="mx_PollHistory_content">
|
||||
{/* @TODO this probably needs some style */}
|
||||
<Heading className="mx_PollHistory_header" size="h2">
|
||||
<Heading className="mx_PollHistory_header" size="2">
|
||||
{title}
|
||||
</Heading>
|
||||
{focusedPoll ? (
|
||||
|
|
|
@ -179,7 +179,7 @@ const PinnedMessagesCard: React.FC<IProps> = ({ room, onClose, permalinkCreator
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Heading size="h4" className="mx_PinnedMessagesCard_empty_header">
|
||||
<Heading size="4" className="mx_PinnedMessagesCard_empty_header">
|
||||
{_t("Nothing pinned, yet")}
|
||||
</Heading>
|
||||
{_t(
|
||||
|
@ -225,7 +225,7 @@ const PinnedMessagesCard: React.FC<IProps> = ({ room, onClose, permalinkCreator
|
|||
<BaseCard
|
||||
header={
|
||||
<div className="mx_BaseCard_header_title">
|
||||
<Heading size="h4" className="mx_BaseCard_header_title_heading">
|
||||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{_t("Pinned messages")}
|
||||
</Heading>
|
||||
</div>
|
||||
|
|
|
@ -190,7 +190,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
|||
private renderTimelineCardHeader = (): JSX.Element => {
|
||||
return (
|
||||
<div className="mx_BaseCard_header_title">
|
||||
<Heading size="h4" className="mx_BaseCard_header_title_heading">
|
||||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{_t("Chat")}
|
||||
</Heading>
|
||||
</div>
|
||||
|
|
|
@ -73,7 +73,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
|||
|
||||
const header = (
|
||||
<div className="mx_BaseCard_header_title">
|
||||
<Heading size="h4" className="mx_BaseCard_header_title_heading">
|
||||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{WidgetUtils.getWidgetName(app)}
|
||||
</Heading>
|
||||
<ContextMenuButton
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -54,7 +54,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
fontSize: (SettingsStore.getValue("baseFontSize", null) + FontWatcher.SIZE_DIFF).toString(),
|
||||
fontSize: SettingsStore.getValue("baseFontSizeV2", null).toString(),
|
||||
useCustomFontSize: SettingsStore.getValue("useCustomFontSize"),
|
||||
layout: SettingsStore.getValue("layout"),
|
||||
};
|
||||
|
@ -80,13 +80,13 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
|
|||
|
||||
private onFontSizeChanged = (size: number): void => {
|
||||
this.setState({ fontSize: size.toString() });
|
||||
SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, size - FontWatcher.SIZE_DIFF);
|
||||
SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, size);
|
||||
};
|
||||
|
||||
private onValidateFontSize = async ({ value }: Pick<IFieldState, "value">): Promise<IValidationResult> => {
|
||||
const parsedSize = parseFloat(value!);
|
||||
const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF;
|
||||
const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF;
|
||||
const min = FontWatcher.MIN_SIZE;
|
||||
const max = FontWatcher.MAX_SIZE;
|
||||
|
||||
if (isNaN(parsedSize)) {
|
||||
return { valid: false, feedback: _t("Size must be a number") };
|
||||
|
@ -99,15 +99,12 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
|
|||
};
|
||||
}
|
||||
|
||||
SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, parseInt(value!, 10) - FontWatcher.SIZE_DIFF);
|
||||
SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, parseInt(value!, 10));
|
||||
|
||||
return { valid: true, feedback: _t("Use between %(min)s pt and %(max)s pt", { min, max }) };
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const min = 13;
|
||||
const max = 18;
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("Font size")} stretchContent data-testid="mx_FontScalingPanel">
|
||||
<EventTilePreview
|
||||
|
@ -121,8 +118,8 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
|
|||
<div className="mx_FontScalingPanel_fontSlider">
|
||||
<div className="mx_FontScalingPanel_fontSlider_smallText">Aa</div>
|
||||
<Slider
|
||||
min={min}
|
||||
max={max}
|
||||
min={FontWatcher.MIN_SIZE}
|
||||
max={FontWatcher.MAX_SIZE}
|
||||
step={1}
|
||||
value={parseInt(this.state.fontSize, 10)}
|
||||
onChange={this.onFontSizeChanged}
|
||||
|
@ -140,7 +137,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
|
|||
this.setState({ useCustomFontSize: checked });
|
||||
if (!checked) {
|
||||
const size = parseInt(this.state.fontSize, 10);
|
||||
const clamped = clamp(size, min, max);
|
||||
const clamped = clamp(size, FontWatcher.MIN_SIZE, FontWatcher.MAX_SIZE);
|
||||
if (clamped !== size) {
|
||||
this.onFontSizeChanged(clamped);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ export default class IntegrationManager extends React.Component<IProps, IState>
|
|||
if (this.props.loading) {
|
||||
return (
|
||||
<div className="mx_IntegrationManager_loading">
|
||||
<Heading size="h3">{_t("Connecting to integration manager…")}</Heading>
|
||||
<Heading size="3">{_t("Connecting to integration manager…")}</Heading>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
@ -98,7 +98,7 @@ export default class IntegrationManager extends React.Component<IProps, IState>
|
|||
if (!this.props.connected || this.state.errored) {
|
||||
return (
|
||||
<div className="mx_IntegrationManager_error">
|
||||
<Heading size="h3">{_t("Cannot connect to integration manager")}</Heading>
|
||||
<Heading size="3">{_t("Cannot connect to integration manager")}</Heading>
|
||||
<p>{_t("The integration manager is offline or it cannot reach your homeserver.")}</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -79,8 +79,8 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
|||
>
|
||||
<div className="mx_SettingsFlag">
|
||||
<div className="mx_SetIntegrationManager_heading_manager">
|
||||
<Heading size="h2">{_t("Manage integrations")}</Heading>
|
||||
<Heading size="h3">{managerName}</Heading>
|
||||
<Heading size="2">{_t("Manage integrations")}</Heading>
|
||||
<Heading size="3">{managerName}</Heading>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
id="toggle_integration"
|
||||
|
|
|
@ -134,7 +134,7 @@ export const DeviceDetailHeading: React.FC<Props> = ({ device, saveDeviceName })
|
|||
<DeviceNameEditor device={device} saveDeviceName={saveDeviceName} stopEditing={() => setIsEditing(false)} />
|
||||
) : (
|
||||
<div className="mx_DeviceDetailHeading" data-testid="device-detail-heading">
|
||||
<Heading size="h4">{device.display_name || device.device_id}</Heading>
|
||||
<Heading size="4">{device.display_name || device.device_id}</Heading>
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
onClick={() => setIsEditing(true)}
|
||||
|
|
|
@ -30,7 +30,7 @@ export interface DeviceTileProps {
|
|||
}
|
||||
|
||||
const DeviceTileName: React.FC<{ device: ExtendedDevice }> = ({ device }) => {
|
||||
return <Heading size="h4">{device.display_name || device.device_id}</Heading>;
|
||||
return <Heading size="4">{device.display_name || device.device_id}</Heading>;
|
||||
};
|
||||
|
||||
const DeviceTile: React.FC<DeviceTileProps> = ({ device, children, isSelected, onClick }) => {
|
||||
|
|
|
@ -42,7 +42,7 @@ export interface SettingsSectionProps extends HTMLAttributes<HTMLDivElement> {
|
|||
*/
|
||||
export const SettingsSection: React.FC<SettingsSectionProps> = ({ heading, children, ...rest }) => (
|
||||
<div {...rest} className="mx_SettingsSection">
|
||||
{typeof heading === "string" ? <Heading size="h2">{heading}</Heading> : <>{heading}</>}
|
||||
{typeof heading === "string" ? <Heading size="2">{heading}</Heading> : <>{heading}</>}
|
||||
<div className="mx_SettingsSection_subSections">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface SettingsSubsectionHeadingProps extends HTMLAttributes<HTMLDivEl
|
|||
|
||||
export const SettingsSubsectionHeading: React.FC<SettingsSubsectionHeadingProps> = ({ heading, children, ...rest }) => (
|
||||
<div {...rest} className="mx_SettingsSubsectionHeading">
|
||||
<Heading className="mx_SettingsSubsectionHeading_heading" size="h3">
|
||||
<Heading className="mx_SettingsSubsectionHeading_heading" size="4" as="h3">
|
||||
{heading}
|
||||
</Heading>
|
||||
{children}
|
||||
|
|
|
@ -274,7 +274,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
|||
</AccessibleButton>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{_t("Set a new custom sound")}</h4>
|
||||
<h4 className="mx_Heading_h4">{_t("Set a new custom sound")}</h4>
|
||||
<div className="mx_SettingsFlag">
|
||||
<form autoComplete="off" noValidate={true}>
|
||||
<input
|
||||
|
|
|
@ -541,7 +541,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
|||
/>
|
||||
) : null;
|
||||
const heading = (
|
||||
<Heading size="h2">
|
||||
<Heading size="2">
|
||||
{discoWarning}
|
||||
{_t("Discovery")}
|
||||
</Heading>
|
||||
|
|
|
@ -17,15 +17,26 @@ limitations under the License.
|
|||
import React, { HTMLAttributes } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Size = "h1" | "h2" | "h3" | "h4";
|
||||
type Size = "1" | "2" | "3" | "4";
|
||||
|
||||
type HTMLHeadingTags = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
|
||||
interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
|
||||
/**
|
||||
* Defines the type of heading used
|
||||
*/
|
||||
as?: HTMLHeadingTags;
|
||||
/**
|
||||
* Defines the appearance of the heading
|
||||
* Falls back to the type of heading used if `as` is not provided
|
||||
*/
|
||||
size: Size;
|
||||
}
|
||||
|
||||
const Heading: React.FC<HeadingProps> = ({ size, className, children, ...rest }) =>
|
||||
React.createElement(size || "h1", {
|
||||
const Heading: React.FC<HeadingProps> = ({ as, size = "1", className, children, ...rest }) =>
|
||||
React.createElement(as || `h${size}`, {
|
||||
...rest,
|
||||
className: classNames(`mx_Heading_${size}`, className),
|
||||
className: classNames(`mx_Heading_h${size}`, className),
|
||||
children,
|
||||
});
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ function UserOnboardingButtonInternal({ selected, minimized }: Props): JSX.Eleme
|
|||
{!minimized && (
|
||||
<>
|
||||
<div className="mx_UserOnboardingButton_content">
|
||||
<Heading size="h4" className="mx_Heading_h4">
|
||||
<Heading size="4" className="mx_Heading_h4">
|
||||
{_t("Welcome")}
|
||||
</Heading>
|
||||
<AccessibleButton className="mx_UserOnboardingButton_close" onClick={onDismiss} />
|
||||
|
|
|
@ -92,7 +92,7 @@ export function UserOnboardingHeader({ useCase }: Props): JSX.Element {
|
|||
return (
|
||||
<div className="mx_UserOnboardingHeader">
|
||||
<div className="mx_UserOnboardingHeader_content">
|
||||
<Heading size="h1">
|
||||
<Heading size="1">
|
||||
{title}
|
||||
<span className="mx_UserOnboardingHeader_dot">.</span>
|
||||
</Heading>
|
||||
|
|
|
@ -50,7 +50,7 @@ export function UserOnboardingList({ tasks }: Props): JSX.Element {
|
|||
return (
|
||||
<div className="mx_UserOnboardingList" data-testid="user-onboarding-list">
|
||||
<div className="mx_UserOnboardingList_header">
|
||||
<Heading size="h3" className="mx_UserOnboardingList_title">
|
||||
<Heading size="3" className="mx_UserOnboardingList_title">
|
||||
{waiting > 0
|
||||
? _t("Only %(count)s steps to go", {
|
||||
count: waiting,
|
||||
|
|
|
@ -45,7 +45,7 @@ export function UserOnboardingTask({ task, completed = false }: Props): JSX.Elem
|
|||
aria-labelledby={`mx_UserOnboardingTask_${task.id}`}
|
||||
/>
|
||||
<div id={`mx_UserOnboardingTask_${task.id}`} className="mx_UserOnboardingTask_content">
|
||||
<Heading size="h4" className="mx_UserOnboardingTask_title">
|
||||
<Heading size="4" className="mx_UserOnboardingTask_title">
|
||||
{title}
|
||||
</Heading>
|
||||
<div className="mx_UserOnboardingTask_description">{description}</div>
|
||||
|
|
|
@ -101,6 +101,11 @@ export enum Action {
|
|||
*/
|
||||
ToggleSpacePanel = "toggle_space_panel",
|
||||
|
||||
/**
|
||||
* Sets the apps root font size. Should be used with UpdateFontSizePayload
|
||||
*/
|
||||
MigrateBaseFontSize = "migrate_base_font_size",
|
||||
|
||||
/**
|
||||
* Sets the apps root font size. Should be used with UpdateFontSizePayload
|
||||
*/
|
||||
|
|
|
@ -655,7 +655,7 @@ export class ElementCall extends Call {
|
|||
roomId: groupCall.room.roomId,
|
||||
baseUrl: client.baseUrl,
|
||||
lang: getCurrentLanguage().replace("_", "-"),
|
||||
fontScale: `${SettingsStore.getValue("baseFontSize") / FontWatcher.DEFAULT_SIZE}`,
|
||||
fontScale: `${(SettingsStore.getValue("baseFontSizeV2") ?? 16) / FontWatcher.DEFAULT_SIZE}`,
|
||||
analyticsID,
|
||||
});
|
||||
|
||||
|
|
|
@ -459,6 +459,18 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
|||
"baseFontSize": {
|
||||
displayName: _td("Font size"),
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
default: "",
|
||||
controller: new FontSizeController(),
|
||||
},
|
||||
/**
|
||||
* With the transition to Compound we are moving to a base font size
|
||||
* of 16px. We're taking the opportunity to move away from the `baseFontSize`
|
||||
* setting that had a 5px offset.
|
||||
*
|
||||
*/
|
||||
"baseFontSizeV2": {
|
||||
displayName: _td("Font size"),
|
||||
supportedLevels: [SettingLevel.DEVICE],
|
||||
default: FontWatcher.DEFAULT_SIZE,
|
||||
controller: new FontSizeController(),
|
||||
},
|
||||
|
|
|
@ -26,10 +26,18 @@ export default class FontSizeController extends SettingController {
|
|||
}
|
||||
|
||||
public onChange(level: SettingLevel, roomId: string, newValue: any): void {
|
||||
// Dispatch font size change so that everything open responds to the change.
|
||||
dis.dispatch<UpdateFontSizePayload>({
|
||||
action: Action.UpdateFontSize,
|
||||
size: newValue,
|
||||
});
|
||||
// In a distant past, `baseFontSize` was set on the account and config
|
||||
// level. This can be accessed only after the initial sync. If we end up
|
||||
// discovering that a logged in user has this kind of setting, we want to
|
||||
// trigger another migration of the base font size.
|
||||
if (level === SettingLevel.ACCOUNT || level === SettingLevel.CONFIG) {
|
||||
dis.fire(Action.MigrateBaseFontSize);
|
||||
} else if (newValue !== "") {
|
||||
// Dispatch font size change so that everything open responds to the change.
|
||||
dis.dispatch<UpdateFontSizePayload>({
|
||||
action: Action.UpdateFontSize,
|
||||
size: newValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020 - 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -22,13 +22,20 @@ import { Action } from "../../dispatcher/actions";
|
|||
import { SettingLevel } from "../SettingLevel";
|
||||
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { clamp } from "../../utils/numbers";
|
||||
|
||||
export class FontWatcher implements IWatcher {
|
||||
public static readonly MIN_SIZE = 8;
|
||||
public static readonly DEFAULT_SIZE = 10;
|
||||
public static readonly MAX_SIZE = 15;
|
||||
// Externally we tell the user the font is size 15. Internally we use 10.
|
||||
public static readonly SIZE_DIFF = 5;
|
||||
/**
|
||||
* Value indirectly defined by Compound.
|
||||
* All `rem` calculations are made from a `16px` values in the
|
||||
* @vector-im/compound-design-tokens package
|
||||
*
|
||||
* We might want to move to using `100%` instead so we can inherit the user
|
||||
* preference set in the browser regarding font sizes.
|
||||
*/
|
||||
public static readonly DEFAULT_SIZE = 16;
|
||||
public static readonly MIN_SIZE = FontWatcher.DEFAULT_SIZE - 5;
|
||||
public static readonly MAX_SIZE = FontWatcher.DEFAULT_SIZE + 5;
|
||||
|
||||
private dispatcherRef: string | null;
|
||||
|
||||
|
@ -36,9 +43,39 @@ export class FontWatcher implements IWatcher {
|
|||
this.dispatcherRef = null;
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
public async start(): Promise<void> {
|
||||
this.updateFont();
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
/**
|
||||
* baseFontSize is an account level setting which is loaded after the initial
|
||||
* sync. Hence why we can't do that in the `constructor`
|
||||
*/
|
||||
await this.migrateBaseFontSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrating the old `baseFontSize` for Compound.
|
||||
* Everything will becomes slightly larger, and getting rid of the `SIZE_DIFF`
|
||||
* weirdness for locally persisted values
|
||||
*/
|
||||
private async migrateBaseFontSize(): Promise<void> {
|
||||
const legacyBaseFontSize = SettingsStore.getValue("baseFontSize");
|
||||
if (legacyBaseFontSize) {
|
||||
console.log("Migrating base font size for Compound, current value", legacyBaseFontSize);
|
||||
|
||||
// For some odd reason, the persisted value in user storage has an offset
|
||||
// of 5 pixels for all values stored under `baseFontSize`
|
||||
const LEGACY_SIZE_DIFF = 5;
|
||||
// Compound uses a base font size of `16px`, whereas the old Element
|
||||
// styles based their calculations off a `15px` root font size.
|
||||
const ROOT_FONT_SIZE_INCREASE = 1;
|
||||
|
||||
const baseFontSize = legacyBaseFontSize + ROOT_FONT_SIZE_INCREASE + LEGACY_SIZE_DIFF;
|
||||
|
||||
await SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, baseFontSize);
|
||||
await SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, "");
|
||||
console.log("Migration complete, deleting legacy `baseFontSize`");
|
||||
}
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
|
@ -47,7 +84,7 @@ export class FontWatcher implements IWatcher {
|
|||
}
|
||||
|
||||
private updateFont(): void {
|
||||
this.setRootFontSize(SettingsStore.getValue("baseFontSize"));
|
||||
this.setRootFontSize(SettingsStore.getValue("baseFontSizeV2"));
|
||||
this.setSystemFont({
|
||||
useSystemFont: SettingsStore.getValue("useSystemFont"),
|
||||
font: SettingsStore.getValue("systemFont"),
|
||||
|
@ -55,7 +92,9 @@ export class FontWatcher implements IWatcher {
|
|||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
if (payload.action === Action.UpdateFontSize) {
|
||||
if (payload.action === Action.MigrateBaseFontSize) {
|
||||
this.migrateBaseFontSize();
|
||||
} else if (payload.action === Action.UpdateFontSize) {
|
||||
this.setRootFontSize(payload.size);
|
||||
} else if (payload.action === Action.UpdateSystemFont) {
|
||||
this.setSystemFont(payload as UpdateSystemFontPayload);
|
||||
|
@ -72,33 +111,41 @@ export class FontWatcher implements IWatcher {
|
|||
}
|
||||
};
|
||||
|
||||
private setRootFontSize = (size: number): void => {
|
||||
const fontSize = Math.max(Math.min(FontWatcher.MAX_SIZE, size), FontWatcher.MIN_SIZE);
|
||||
private setRootFontSize = async (size: number): Promise<void> => {
|
||||
const fontSize = clamp(size, FontWatcher.MIN_SIZE, FontWatcher.MAX_SIZE);
|
||||
|
||||
if (fontSize !== size) {
|
||||
SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, fontSize);
|
||||
await SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, fontSize);
|
||||
}
|
||||
document.querySelector<HTMLElement>(":root")!.style.fontSize = toPx(fontSize);
|
||||
};
|
||||
|
||||
public static readonly FONT_FAMILY_CUSTOM_PROPERTY = "--cpd-font-family-sans";
|
||||
|
||||
private setSystemFont = ({
|
||||
useSystemFont,
|
||||
font,
|
||||
}: Pick<UpdateSystemFontPayload, "useSystemFont" | "font">): void => {
|
||||
if (useSystemFont) {
|
||||
// Make sure that fonts with spaces in their names get interpreted properly
|
||||
document.body.style.fontFamily = font
|
||||
.split(",")
|
||||
.map((font) => {
|
||||
font = font.trim();
|
||||
if (!font.startsWith('"') && !font.endsWith('"')) {
|
||||
font = `"${font}"`;
|
||||
}
|
||||
return font;
|
||||
})
|
||||
.join(",");
|
||||
/**
|
||||
* Overrides the default font family from Compound
|
||||
* Make sure that fonts with spaces in their names get interpreted properly
|
||||
*/
|
||||
document.body.style.setProperty(
|
||||
FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY,
|
||||
font
|
||||
.split(",")
|
||||
.map((font) => {
|
||||
font = font.trim();
|
||||
if (!font.startsWith('"') && !font.endsWith('"')) {
|
||||
font = `"${font}"`;
|
||||
}
|
||||
return font;
|
||||
})
|
||||
.join(","),
|
||||
);
|
||||
} else {
|
||||
document.body.style.fontFamily = "";
|
||||
document.body.style.removeProperty(FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue