mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: support multiple terminal fonts (#17257)
Fixes: https://github.com/coder/coder/issues/15024
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@fastly/performance-observer-polyfill": "2.0.0",
|
||||
"@fontsource-variable/inter": "5.1.1",
|
||||
"@fontsource/fira-code": "5.2.5",
|
||||
"@fontsource/ibm-plex-mono": "5.1.1",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@mui/icons-material": "5.16.14",
|
||||
|
||||
Generated
+8
@@ -40,6 +40,9 @@ importers:
|
||||
'@fontsource-variable/inter':
|
||||
specifier: 5.1.1
|
||||
version: 5.1.1
|
||||
'@fontsource/fira-code':
|
||||
specifier: 5.2.5
|
||||
version: 5.2.5
|
||||
'@fontsource/ibm-plex-mono':
|
||||
specifier: 5.1.1
|
||||
version: 5.1.1
|
||||
@@ -1040,6 +1043,9 @@ packages:
|
||||
'@fontsource-variable/inter@5.1.1':
|
||||
resolution: {integrity: sha512-OpXFTmiH6tHkYijMvQTycFKBLK4X+SRV6tet1m4YOUH7SzIIlMqDja+ocDtiCA72UthBH/vF+3ZtlMr2rN/wIw==, tarball: https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.1.1.tgz}
|
||||
|
||||
'@fontsource/fira-code@5.2.5':
|
||||
resolution: {integrity: sha512-Rn9PJoyfRr5D6ukEhZpzhpD+rbX2rtoz9QjkOuGxqFxrL69fQvhadMUBxQIOuTF4sTTkPRSKlAEpPjTKaI12QA==, tarball: https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.2.5.tgz}
|
||||
|
||||
'@fontsource/ibm-plex-mono@5.1.1':
|
||||
resolution: {integrity: sha512-1aayqPe/ZkD3MlvqpmOHecfA3f2B8g+fAEkgvcCd3lkPP0pS1T0xG5Zmn2EsJQqr1JURtugPUH+5NqvKyfFZMQ==, tarball: https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.1.1.tgz}
|
||||
|
||||
@@ -7012,6 +7018,8 @@ snapshots:
|
||||
|
||||
'@fontsource-variable/inter@5.1.1': {}
|
||||
|
||||
'@fontsource/fira-code@5.2.5': {}
|
||||
|
||||
'@fontsource/ibm-plex-mono@5.1.1': {}
|
||||
|
||||
'@humanwhocodes/config-array@0.11.14':
|
||||
|
||||
+12
-1
@@ -428,6 +428,7 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
|
||||
var eg errgroup.Group
|
||||
var user database.User
|
||||
var themePreference string
|
||||
var terminalFont string
|
||||
orgIDs := []uuid.UUID{}
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
@@ -436,13 +437,22 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
|
||||
})
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
themePreference, err = h.opts.Database.GetUserAppearanceSettings(ctx, apiKey.UserID)
|
||||
themePreference, err = h.opts.Database.GetUserThemePreference(ctx, apiKey.UserID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
themePreference = ""
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
terminalFont, err = h.opts.Database.GetUserTerminalFont(ctx, apiKey.UserID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
terminalFont = ""
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
memberIDs, err := h.opts.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{apiKey.UserID})
|
||||
if errors.Is(err, sql.ErrNoRows) || len(memberIDs) == 0 {
|
||||
@@ -471,6 +481,7 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
|
||||
defer wg.Done()
|
||||
userAppearance, err := json.Marshal(codersdk.UserAppearanceSettings{
|
||||
ThemePreference: themePreference,
|
||||
TerminalFont: codersdk.TerminalFontName(terminalFont),
|
||||
})
|
||||
if err == nil {
|
||||
state.UserAppearance = html.EscapeString(string(userAppearance))
|
||||
|
||||
@@ -251,6 +251,7 @@ export const updateAppearanceSettings = (
|
||||
// more responsive.
|
||||
queryClient.setQueryData(myAppearanceKey, {
|
||||
theme_preference: patch.theme_preference,
|
||||
terminal_font: patch.terminal_font,
|
||||
});
|
||||
},
|
||||
onSuccess: async () =>
|
||||
|
||||
Generated
+11
@@ -2657,6 +2657,15 @@ export interface TemplateVersionsByTemplateRequest extends Pagination {
|
||||
readonly include_archived: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export type TerminalFontName = "fira-code" | "ibm-plex-mono" | "";
|
||||
|
||||
export const TerminalFontNames: TerminalFontName[] = [
|
||||
"fira-code",
|
||||
"ibm-plex-mono",
|
||||
"",
|
||||
];
|
||||
|
||||
// From codersdk/workspacebuilds.go
|
||||
export type TimingStage =
|
||||
| "apply"
|
||||
@@ -2790,6 +2799,7 @@ export interface UpdateTemplateMeta {
|
||||
// From codersdk/users.go
|
||||
export interface UpdateUserAppearanceSettingsRequest {
|
||||
readonly theme_preference: string;
|
||||
readonly terminal_font: TerminalFontName;
|
||||
}
|
||||
|
||||
// From codersdk/notifications.go
|
||||
@@ -2906,6 +2916,7 @@ export interface UserActivityInsightsResponse {
|
||||
// From codersdk/users.go
|
||||
export interface UserAppearanceSettings {
|
||||
readonly theme_preference: string;
|
||||
readonly terminal_font: TerminalFontName;
|
||||
}
|
||||
|
||||
// From codersdk/insights.go
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
MockEntitlements,
|
||||
MockExperiments,
|
||||
MockUser,
|
||||
MockUserAppearanceSettings,
|
||||
MockWorkspace,
|
||||
MockWorkspaceAgent,
|
||||
} from "testHelpers/entities";
|
||||
@@ -76,6 +77,7 @@ const meta = {
|
||||
key: getAuthorizationKey({ checks: permissionChecks }),
|
||||
data: { editWorkspaceProxies: true },
|
||||
},
|
||||
{ key: ["me", "appearance"], data: MockUserAppearanceSettings },
|
||||
],
|
||||
chromatic: { delay: 300 },
|
||||
},
|
||||
@@ -106,6 +108,38 @@ export const Starting: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const FontFiraCode: Story = {
|
||||
decorators: [withWebSocket],
|
||||
parameters: {
|
||||
...meta.parameters,
|
||||
webSocket: [
|
||||
{
|
||||
event: "message",
|
||||
// Copied and pasted this from browser
|
||||
data: "[H[2J[1m[32m➜ [36mcoder[C[34mgit:([31mbq/refactor-web-term-notifications[34m) [33m✗",
|
||||
},
|
||||
],
|
||||
queries: [
|
||||
...meta.parameters.queries.filter(
|
||||
(q) =>
|
||||
!(
|
||||
Array.isArray(q.key) &&
|
||||
q.key[0] === "me" &&
|
||||
q.key[1] === "appearance"
|
||||
),
|
||||
),
|
||||
{
|
||||
key: ["me", "appearance"],
|
||||
data: {
|
||||
...MockUserAppearanceSettings,
|
||||
terminal_font: "fira-code",
|
||||
},
|
||||
},
|
||||
createWorkspaceWithAgent("ready"),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const Ready: Story = {
|
||||
decorators: [withWebSocket],
|
||||
parameters: {
|
||||
|
||||
@@ -7,18 +7,20 @@ import { WebLinksAddon } from "@xterm/addon-web-links";
|
||||
import { WebglAddon } from "@xterm/addon-webgl";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { deploymentConfig } from "api/queries/deployment";
|
||||
import { appearanceSettings } from "api/queries/users";
|
||||
import {
|
||||
workspaceByOwnerAndName,
|
||||
workspaceUsage,
|
||||
} from "api/queries/workspaces";
|
||||
import { useProxy } from "contexts/ProxyContext";
|
||||
import { ThemeOverride } from "contexts/ThemeProvider";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import { type FC, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useQuery } from "react-query";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import themes from "theme";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
import { DEFAULT_TERMINAL_FONT, terminalFonts } from "theme/constants";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { openMaybePortForwardedURL } from "utils/portForward";
|
||||
import { terminalWebsocketUrl } from "utils/terminal";
|
||||
@@ -100,6 +102,13 @@ const TerminalPage: FC = () => {
|
||||
handleWebLinkRef.current = handleWebLink;
|
||||
}, [handleWebLink]);
|
||||
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const appearanceSettingsQuery = useQuery(
|
||||
appearanceSettings(metadata.userAppearance),
|
||||
);
|
||||
const currentTerminalFont =
|
||||
appearanceSettingsQuery.data?.terminal_font || DEFAULT_TERMINAL_FONT;
|
||||
|
||||
// Create the terminal!
|
||||
const fitAddonRef = useRef<FitAddon>();
|
||||
useEffect(() => {
|
||||
@@ -110,7 +119,7 @@ const TerminalPage: FC = () => {
|
||||
allowProposedApi: true,
|
||||
allowTransparency: true,
|
||||
disableStdin: false,
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
fontFamily: terminalFonts[currentTerminalFont],
|
||||
fontSize: 16,
|
||||
theme: {
|
||||
background: theme.palette.background.default,
|
||||
@@ -150,7 +159,12 @@ const TerminalPage: FC = () => {
|
||||
window.removeEventListener("resize", listener);
|
||||
terminal.dispose();
|
||||
};
|
||||
}, [config.isLoading, renderer, theme.palette.background.default]);
|
||||
}, [
|
||||
config.isLoading,
|
||||
renderer,
|
||||
theme.palette.background.default,
|
||||
currentTerminalFont,
|
||||
]);
|
||||
|
||||
// Updates the reconnection token into the URL if necessary.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -18,6 +18,6 @@ type Story = StoryObj<typeof AppearanceForm>;
|
||||
|
||||
export const Example: Story = {
|
||||
args: {
|
||||
initialValues: { theme_preference: "" },
|
||||
initialValues: { theme_preference: "", terminal_font: "" },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import type { Interpolation } from "@emotion/react";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Radio from "@mui/material/Radio";
|
||||
import RadioGroup from "@mui/material/RadioGroup";
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import type { UpdateUserAppearanceSettingsRequest } from "api/typesGenerated";
|
||||
import {
|
||||
type TerminalFontName,
|
||||
TerminalFontNames,
|
||||
type UpdateUserAppearanceSettingsRequest,
|
||||
} from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { PreviewBadge } from "components/Badges/Badges";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { ThemeOverride } from "contexts/ThemeProvider";
|
||||
import type { FC } from "react";
|
||||
import themes, { DEFAULT_THEME, type Theme } from "theme";
|
||||
import { DEFAULT_TERMINAL_FONT, terminalFontLabels } from "theme/constants";
|
||||
import { Section } from "../Section";
|
||||
|
||||
export interface AppearanceFormProps {
|
||||
isUpdating?: boolean;
|
||||
@@ -22,43 +33,107 @@ export const AppearanceForm: FC<AppearanceFormProps> = ({
|
||||
initialValues,
|
||||
}) => {
|
||||
const currentTheme = initialValues.theme_preference || DEFAULT_THEME;
|
||||
const currentTerminalFont =
|
||||
initialValues.terminal_font || DEFAULT_TERMINAL_FONT;
|
||||
|
||||
const onChangeTheme = async (theme: string) => {
|
||||
if (isUpdating) {
|
||||
return;
|
||||
}
|
||||
await onSubmit({
|
||||
theme_preference: theme,
|
||||
terminal_font: currentTerminalFont,
|
||||
});
|
||||
};
|
||||
|
||||
await onSubmit({ theme_preference: theme });
|
||||
const onChangeTerminalFont = async (terminalFont: TerminalFontName) => {
|
||||
if (isUpdating) {
|
||||
return;
|
||||
}
|
||||
await onSubmit({
|
||||
theme_preference: currentTheme,
|
||||
terminal_font: terminalFont,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form>
|
||||
{Boolean(error) && <ErrorAlert error={error} />}
|
||||
|
||||
<Stack direction="row" wrap="wrap">
|
||||
<AutoThemePreviewButton
|
||||
displayName="Auto"
|
||||
active={currentTheme === "auto"}
|
||||
themes={[themes.dark, themes.light]}
|
||||
onSelect={() => onChangeTheme("auto")}
|
||||
/>
|
||||
<ThemePreviewButton
|
||||
displayName="Dark"
|
||||
active={currentTheme === "dark"}
|
||||
theme={themes.dark}
|
||||
onSelect={() => onChangeTheme("dark")}
|
||||
/>
|
||||
<ThemePreviewButton
|
||||
displayName="Light"
|
||||
active={currentTheme === "light"}
|
||||
theme={themes.light}
|
||||
onSelect={() => onChangeTheme("light")}
|
||||
/>
|
||||
</Stack>
|
||||
<Section
|
||||
title={
|
||||
<Stack direction="row" alignItems="center">
|
||||
<span>Theme</span>
|
||||
{isUpdating && <CircularProgress size={16} />}
|
||||
</Stack>
|
||||
}
|
||||
layout="fluid"
|
||||
>
|
||||
<Stack direction="row" wrap="wrap">
|
||||
<AutoThemePreviewButton
|
||||
displayName="Auto"
|
||||
active={currentTheme === "auto"}
|
||||
themes={[themes.dark, themes.light]}
|
||||
onSelect={() => onChangeTheme("auto")}
|
||||
/>
|
||||
<ThemePreviewButton
|
||||
displayName="Dark"
|
||||
active={currentTheme === "dark"}
|
||||
theme={themes.dark}
|
||||
onSelect={() => onChangeTheme("dark")}
|
||||
/>
|
||||
<ThemePreviewButton
|
||||
displayName="Light"
|
||||
active={currentTheme === "light"}
|
||||
theme={themes.light}
|
||||
onSelect={() => onChangeTheme("light")}
|
||||
/>
|
||||
</Stack>
|
||||
</Section>
|
||||
<div css={{ marginBottom: 48 }}></div>
|
||||
<Section
|
||||
title={
|
||||
<Stack direction="row" alignItems="center">
|
||||
<span>Terminal Font</span>
|
||||
{isUpdating && <CircularProgress size={16} />}
|
||||
</Stack>
|
||||
}
|
||||
layout="fluid"
|
||||
>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
aria-labelledby="fonts-radio-buttons-group-label"
|
||||
defaultValue={currentTerminalFont}
|
||||
name="fonts-radio-buttons-group"
|
||||
onChange={(_, value) =>
|
||||
onChangeTerminalFont(toTerminalFontName(value))
|
||||
}
|
||||
>
|
||||
{TerminalFontNames.filter((name) => name !== "").map((name) => (
|
||||
<FormControlLabel
|
||||
key={name}
|
||||
value={name}
|
||||
control={<Radio />}
|
||||
label={
|
||||
<div css={{ fontFamily: terminalFontLabels[name] }}>
|
||||
{terminalFontLabels[name]}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Section>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export function toTerminalFontName(value: string): TerminalFontName {
|
||||
return TerminalFontNames.includes(value as TerminalFontName)
|
||||
? (value as TerminalFontName)
|
||||
: "";
|
||||
}
|
||||
|
||||
interface AutoThemePreviewButtonProps extends Omit<ThemePreviewProps, "theme"> {
|
||||
themes: [Theme, Theme];
|
||||
onSelect?: () => void;
|
||||
|
||||
@@ -12,13 +12,14 @@ describe("appearance page", () => {
|
||||
jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({
|
||||
...MockUser,
|
||||
theme_preference: "dark",
|
||||
terminal_font: "fira-code",
|
||||
});
|
||||
|
||||
const dark = await screen.findByText("Dark");
|
||||
await userEvent.click(dark);
|
||||
|
||||
// Check if the API was called correctly
|
||||
expect(API.updateAppearanceSettings).toBeCalledTimes(0);
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("changes theme to light", async () => {
|
||||
@@ -26,6 +27,7 @@ describe("appearance page", () => {
|
||||
|
||||
jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({
|
||||
...MockUser,
|
||||
terminal_font: "ibm-plex-mono",
|
||||
theme_preference: "light",
|
||||
});
|
||||
|
||||
@@ -33,9 +35,30 @@ describe("appearance page", () => {
|
||||
await userEvent.click(light);
|
||||
|
||||
// Check if the API was called correctly
|
||||
expect(API.updateAppearanceSettings).toBeCalledTimes(1);
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledTimes(1);
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledWith({
|
||||
terminal_font: "ibm-plex-mono",
|
||||
theme_preference: "light",
|
||||
});
|
||||
});
|
||||
|
||||
it("changes font to fira code", async () => {
|
||||
renderWithAuth(<AppearancePage />);
|
||||
|
||||
jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({
|
||||
...MockUser,
|
||||
terminal_font: "fira-code",
|
||||
theme_preference: "dark",
|
||||
});
|
||||
|
||||
const ibmPlex = await screen.findByText("Fira Code");
|
||||
await userEvent.click(ibmPlex);
|
||||
|
||||
// Check if the API was called correctly
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledTimes(1);
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledWith({
|
||||
terminal_font: "fira-code",
|
||||
theme_preference: "dark",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { updateAppearanceSettings } from "api/queries/users";
|
||||
import { appearanceSettings } from "api/queries/users";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import type { FC } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { Section } from "../Section";
|
||||
import { AppearanceForm } from "./AppearanceForm";
|
||||
|
||||
export const AppearancePage: FC = () => {
|
||||
@@ -31,26 +28,15 @@ export const AppearancePage: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section
|
||||
title={
|
||||
<Stack direction="row" alignItems="center">
|
||||
<span>Theme</span>
|
||||
{updateAppearanceSettingsMutation.isLoading && (
|
||||
<CircularProgress size={16} />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
layout="fluid"
|
||||
>
|
||||
<AppearanceForm
|
||||
isUpdating={updateAppearanceSettingsMutation.isLoading}
|
||||
error={updateAppearanceSettingsMutation.error}
|
||||
initialValues={{
|
||||
theme_preference: appearanceSettingsQuery.data.theme_preference,
|
||||
}}
|
||||
onSubmit={updateAppearanceSettingsMutation.mutateAsync}
|
||||
/>
|
||||
</Section>
|
||||
<AppearanceForm
|
||||
isUpdating={updateAppearanceSettingsMutation.isLoading}
|
||||
error={updateAppearanceSettingsMutation.error}
|
||||
initialValues={{
|
||||
theme_preference: appearanceSettingsQuery.data.theme_preference,
|
||||
terminal_font: appearanceSettingsQuery.data.terminal_font,
|
||||
}}
|
||||
onSubmit={updateAppearanceSettingsMutation.mutateAsync}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -536,6 +536,7 @@ export const SuspendedMockUser: TypesGen.User = {
|
||||
|
||||
export const MockUserAppearanceSettings: TypesGen.UserAppearanceSettings = {
|
||||
theme_preference: "dark",
|
||||
terminal_font: "",
|
||||
};
|
||||
|
||||
export const MockOrganizationMember: TypesGen.OrganizationMemberWithUserData = {
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import type { TerminalFontName } from "api/typesGenerated";
|
||||
|
||||
export const borderRadius = 8;
|
||||
export const MONOSPACE_FONT_FAMILY =
|
||||
"'IBM Plex Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'Liberation Mono', 'Monaco', 'Courier New', Courier, monospace";
|
||||
export const BODY_FONT_FAMILY = `"Inter Variable", system-ui, sans-serif`;
|
||||
|
||||
export const terminalFonts: Record<TerminalFontName, string> = {
|
||||
"fira-code": MONOSPACE_FONT_FAMILY.replace("IBM Plex Mono", "Fira Code"),
|
||||
"ibm-plex-mono": MONOSPACE_FONT_FAMILY,
|
||||
|
||||
"": MONOSPACE_FONT_FAMILY,
|
||||
};
|
||||
export const terminalFontLabels: Record<TerminalFontName, string> = {
|
||||
"fira-code": "Fira Code",
|
||||
"ibm-plex-mono": "IBM Plex Mono",
|
||||
"": "", // needed for enum completeness, otherwise fails the build
|
||||
};
|
||||
export const DEFAULT_TERMINAL_FONT = "ibm-plex-mono";
|
||||
|
||||
export const navHeight = 62;
|
||||
export const containerWidth = 1380;
|
||||
export const containerWidthMedium = 1080;
|
||||
|
||||
@@ -3,3 +3,6 @@ import "@fontsource/ibm-plex-mono/400.css";
|
||||
import "@fontsource/ibm-plex-mono/600.css";
|
||||
// Main body copy font
|
||||
import "@fontsource-variable/inter";
|
||||
// Alternative font for Terminal
|
||||
import "@fontsource/fira-code/400.css";
|
||||
import "@fontsource/fira-code/600.css";
|
||||
|
||||
Reference in New Issue
Block a user