diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs
index f08e282..ca94bd9 100644
--- a/crates/store/src/lib.rs
+++ b/crates/store/src/lib.rs
@@ -1554,6 +1554,7 @@ mod tests {
sidebar_collapsed: true,
top_bars_collapsed: false,
status_bar_collapsed: true,
+ reduce_motion: true,
line_numbers_visible: false,
text_wrapping_enabled: false,
syntax_highlighting_enabled: false,
@@ -1591,6 +1592,7 @@ mod tests {
drop(conn);
let loaded = store.ui_layout_get().unwrap();
+ assert!(!loaded.reduce_motion);
assert!(loaded.line_numbers_visible);
assert!(loaded.text_wrapping_enabled);
assert!(loaded.syntax_highlighting_enabled);
diff --git a/crates/store/src/settings.rs b/crates/store/src/settings.rs
index 75c8217..1097282 100644
--- a/crates/store/src/settings.rs
+++ b/crates/store/src/settings.rs
@@ -74,6 +74,8 @@ pub struct UiLayoutSettings {
pub sidebar_collapsed: bool,
pub top_bars_collapsed: bool,
pub status_bar_collapsed: bool,
+ #[serde(default)]
+ pub reduce_motion: bool,
#[serde(default = "default_true")]
pub line_numbers_visible: bool,
#[serde(default = "default_true")]
@@ -100,6 +102,7 @@ impl Default for UiLayoutSettings {
sidebar_collapsed: false,
top_bars_collapsed: false,
status_bar_collapsed: false,
+ reduce_motion: false,
line_numbers_visible: true,
text_wrapping_enabled: true,
syntax_highlighting_enabled: true,
diff --git a/src/App.css b/src/App.css
index b4deeae..7a445cb 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,262 +1,7 @@
@import "tailwindcss";
@plugin "@egoist/tailwindcss-icons";
@plugin "@tailwindcss/forms";
-
-@font-face {
- font-family: "Writer IBM Plex Mono";
- src: url("/fonts/ibm-plex-mono-400-normal.ttf") format("truetype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Mono";
- src: url("/fonts/ibm-plex-mono-400-italic.ttf") format("truetype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Mono";
- src: url("/fonts/ibm-plex-mono-700-normal.ttf") format("truetype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Mono";
- src: url("/fonts/ibm-plex-mono-700-italic.ttf") format("truetype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Sans";
- src: url("/fonts/ibm-plex-sans-400-normal.ttf") format("truetype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Sans";
- src: url("/fonts/ibm-plex-sans-400-italic.ttf") format("truetype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Sans";
- src: url("/fonts/ibm-plex-sans-700-normal.ttf") format("truetype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Sans";
- src: url("/fonts/ibm-plex-sans-700-italic.ttf") format("truetype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Serif";
- src: url("/fonts/ibm-plex-serif-400-normal.ttf") format("truetype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Serif";
- src: url("/fonts/ibm-plex-serif-400-italic.ttf") format("truetype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Serif";
- src: url("/fonts/ibm-plex-serif-700-normal.ttf") format("truetype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer IBM Plex Serif";
- src: url("/fonts/ibm-plex-serif-700-italic.ttf") format("truetype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Argon";
- src: url("/fonts/monaspace-argon-400-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Argon";
- src: url("/fonts/monaspace-argon-400-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Argon";
- src: url("/fonts/monaspace-argon-700-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Argon";
- src: url("/fonts/monaspace-argon-700-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Krypton";
- src: url("/fonts/monaspace-krypton-400-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Krypton";
- src: url("/fonts/monaspace-krypton-400-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Krypton";
- src: url("/fonts/monaspace-krypton-700-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Krypton";
- src: url("/fonts/monaspace-krypton-700-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Neon";
- src: url("/fonts/monaspace-neon-400-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Neon";
- src: url("/fonts/monaspace-neon-400-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Neon";
- src: url("/fonts/monaspace-neon-700-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Neon";
- src: url("/fonts/monaspace-neon-700-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Radon";
- src: url("/fonts/monaspace-radon-400-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Radon";
- src: url("/fonts/monaspace-radon-400-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Radon";
- src: url("/fonts/monaspace-radon-700-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Radon";
- src: url("/fonts/monaspace-radon-700-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Xenon";
- src: url("/fonts/monaspace-xenon-400-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Xenon";
- src: url("/fonts/monaspace-xenon-400-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 400;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Xenon";
- src: url("/fonts/monaspace-xenon-700-normal.otf") format("opentype");
- font-style: normal;
- font-weight: 700;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Writer Monaspace Xenon";
- src: url("/fonts/monaspace-xenon-700-italic.otf") format("opentype");
- font-style: italic;
- font-weight: 700;
- font-display: swap;
-}
+@import "./styles/fonts.css";
@theme {
--font-sans:
@@ -269,15 +14,18 @@
"Writer IBM Plex Mono", "IBM Plex Mono", "SF Mono", Monaco,
"Cascadia Code", monospace;
+ /* FIXME: Remove in favor of tw built-ins */
--font-weight-light: 300;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
+ /* FIXME: remove bg prefix */
--color-bg-primary: #161616;
--color-bg-hover: #262626;
--color-bg-active: #393939;
+
--color-layer-01: #262626;
--color-layer-02: #393939;
--color-layer-03: #525252;
@@ -289,10 +37,13 @@
--color-field-02: #262626;
--color-field-hover-01: #262626;
--color-field-hover-02: #393939;
+
+ /* FIXME: remove border prefix */
--color-border-subtle: #393939;
--color-border-strong: #525252;
--color-border-inverse: #ffffff;
--color-border-interactive: #33b1ff;
+
--color-text-primary: #f2f4f8;
--color-text-secondary: #dde1e6;
--color-text-placeholder: #525252;
@@ -344,6 +95,7 @@
--weight-preview-heading: 600;
--weight-preview-table-heading: 600;
+ /* FIXME: remove in favor of tw built-ins */
--radius-none: 0;
--radius-sm: 2px;
--radius-md: 4px;
@@ -352,6 +104,7 @@
--radius-2xl: 12px;
--radius-full: 9999px;
+ /* FIXME: remove in favor of tw built-ins */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.5);
diff --git a/src/App.tsx b/src/App.tsx
index 06d03c6..2fdbcfd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,3 @@
-import { Button } from "$components/Button";
import { AppHeaderBar } from "./components/layout/AppHeaderBar";
import { BackendAlerts } from "./components/layout/BackendAlerts";
import { FocusModePanel } from "./components/layout/FocusModePanel";
@@ -9,15 +8,6 @@ import { PdfExportDialog } from "./components/pdf/ExportDialog/ExportDialog";
import { useAppChromeController } from "./hooks/controllers/useAppChromeController";
import { useWorkspaceViewController } from "./hooks/controllers/useWorkspaceViewController";
-const ShowButton = ({ clickHandler, title, label }: { clickHandler: () => void; title: string; label: string }) => (
-
-);
-
const AppContent = ({ isFocusMode }: { isFocusMode: boolean }) => {
const { workspacePanelProps, focusModePanelProps, handleExportPdf } = useWorkspaceViewController();
@@ -37,20 +27,12 @@ const AppContent = ({ isFocusMode }: { isFocusMode: boolean }) => {
};
function App() {
- const { theme, isFocusMode, isSidebarCollapsed, showToggleControls, handleShowSidebar } = useAppChromeController();
+ const { theme, isFocusMode } = useAppChromeController();
return (
- {!isFocusMode && showToggleControls && (
-
- {isSidebarCollapsed && (
-
- )}
-
- )}
-
{isFocusMode ? null :
}
diff --git a/src/__tests__/ports.test.ts b/src/__tests__/ports.test.ts
index 560b741..7dfbdc3 100644
--- a/src/__tests__/ports.test.ts
+++ b/src/__tests__/ports.test.ts
@@ -758,6 +758,7 @@ describe("ui layout Commands", () => {
sidebar_collapsed: true,
top_bars_collapsed: false,
status_bar_collapsed: true,
+ reduce_motion: false,
line_numbers_visible: false,
text_wrapping_enabled: true,
syntax_highlighting_enabled: false,
@@ -779,6 +780,7 @@ describe("ui layout Commands", () => {
sidebar_collapsed: true,
top_bars_collapsed: false,
status_bar_collapsed: true,
+ reduce_motion: false,
line_numbers_visible: false,
text_wrapping_enabled: true,
syntax_highlighting_enabled: false,
diff --git a/src/components/Sidebar/DocumentItem.tsx b/src/components/Sidebar/DocumentItem.tsx
index 83a52ab..61d1c23 100644
--- a/src/components/Sidebar/DocumentItem.tsx
+++ b/src/components/Sidebar/DocumentItem.tsx
@@ -1,8 +1,10 @@
import { FileTextIcon } from "$icons";
import type { DocMeta } from "$types";
-import { useCallback, useMemo } from "react";
+import { useCallback } from "react";
import { TreeItem } from "./TreeItem";
+const fileTextIcon = { Component: FileTextIcon, size: "sm" as const };
+
export function DocumentItem(
{ doc, isSelected, selectedDocPath, onSelectDocument, id }: {
doc: DocMeta;
@@ -12,12 +14,11 @@ export function DocumentItem(
id: number;
},
) {
- const fileTextIcon = useMemo(() => ({ Component: FileTextIcon, size: "sm" as const }), []);
const handleClick = useCallback(() => onSelectDocument(id, doc.rel_path), [id, onSelectDocument, doc.rel_path]);
return (
void; hideTitle: boolean }) => (
+const AppTitle = ({ hideTitle }: { hideTitle: boolean }) => (
-
);
@@ -16,8 +16,10 @@ const AppTitle = ({ onToggleSidebar, hideTitle }: { onToggleSidebar: () => void;
const SearchRow = (
{
onOpenSearch,
+ onToggleSidebar,
onToggleTabBar,
onToggleStatusBar,
+ sidebarCollapsed,
tabBarCollapsed,
statusBarCollapsed,
iconOnly,
@@ -25,73 +27,95 @@ const SearchRow = (
compactTabLabel,
}: {
onOpenSearch: () => void;
+ onToggleSidebar: () => void;
onToggleTabBar: () => void;
onToggleStatusBar: () => void;
+ sidebarCollapsed: boolean;
tabBarCollapsed: boolean;
statusBarCollapsed: boolean;
iconOnly: boolean;
showSearchShortcut: boolean;
compactTabLabel: boolean;
},
-) => (
-
-
+) => {
+ const statusbarId = useMemo(() => {
+ if (compactTabLabel) {
+ return {
+ label: statusBarCollapsed ? "Show Status" : "Hide Status",
+ title: statusBarCollapsed ? "Show Status" : "Hide Status",
+ };
+ }
+ return {
+ label: statusBarCollapsed ? "Show Status Bar" : "Hide Status Bar",
+ title: statusBarCollapsed ? "Show Status Bar" : "Hide Status Bar",
+ };
+ }, [compactTabLabel, statusBarCollapsed]);
-
+ const tabbarId = useMemo(() => {
+ const title = `${tabBarCollapsed ? "Show" : "Hide"} tab bar (Ctrl+Shift+B)`;
+ if (compactTabLabel) {
+ return { label: tabBarCollapsed ? "Show Tabs" : "Hide Tabs", title };
+ }
+ return { label: tabBarCollapsed ? "Show Tab Bar" : "Hide Tab Bar", title };
+ }, [compactTabLabel, tabBarCollapsed]);
+
+ const sidebarId = useMemo(() => {
+ const title = `${sidebarCollapsed ? "Show" : "Hide"} sidebar (Ctrl+B)`;
+ const label = sidebarCollapsed ? "Show Sidebar" : "Hide Sidebar";
+ return { label, title };
+ }, [sidebarCollapsed]);
-
+
+
+
+ {iconOnly ? null : {sidebarId.label}}
+
+
+
+
+ {iconOnly ? null : {tabbarId.label}}
+
+
+
+
+ {iconOnly ? null : {statusbarId.label}}
+
+
+ );
+};
export const AppHeaderBar = () => {
const {
+ sidebarCollapsed,
tabBarCollapsed,
statusBarCollapsed,
toggleSidebarCollapsed,
@@ -100,21 +124,22 @@ export const AppHeaderBar = () => {
setShowSearch,
} = useAppHeaderBarState();
const { viewportWidth, isCompact, isNarrow } = useViewportTier();
+ const showSearchShortcut = useMemo(() => viewportWidth >= 1240, [viewportWidth]);
+ const iconOnly = useMemo(() => viewportWidth < 760, [viewportWidth]);
const handleOpenSearch = useCallback(() => {
setShowSearch(true);
}, [setShowSearch]);
- const showSearchShortcut = useMemo(() => viewportWidth >= 1240, [viewportWidth]);
- const iconOnly = useMemo(() => viewportWidth < 760, [viewportWidth]);
-
return (
-
+
+ {isVisible && (
+
+ {children}
+
+ )}
+
+ );
+}
+
export function WorkspacePanel({ toolbar, editor, preview, statusBar, calmUiVisibility }: WorkspacePanelProps) {
+ const skipAnimation = useSkipAnimation();
const { viewportWidth } = useViewportTier(FALLBACK_VIEWPORT_WIDTH);
const { sidebarCollapsed } = useWorkspacePanelSidebarState();
const { isSplitView, isPreviewVisible } = useWorkspacePanelModeState();
@@ -178,30 +212,69 @@ export function WorkspacePanel({ toolbar, editor, preview, statusBar, calmUiVisi
const sidebarStyle = useMemo(() => ({ width: `${sidebarWidth}px` }), [sidebarWidth]);
+ const sectionTransition = useMemo(() => skipAnimation ? NO_MOTION_TRANSITION : CHROME_SECTION_TRANSITION, [
+ skipAnimation,
+ ]);
+
const newDocumentHandler = useMemo(() => toolbar.isNewDocumentDisabled ? void 0 : toolbar.onNewDocument, [
toolbar.isNewDocumentDisabled,
toolbar.onNewDocument,
]);
+ const sidebarMotionProps = useMemo(
+ () => ({
+ initial: { opacity: 0, x: -16 },
+ animate: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: -12 },
+ transition: sectionTransition,
+ }),
+ [sectionTransition],
+ );
+
+ const tabBarMotionProps = useMemo(
+ () => ({
+ initial: { opacity: 0, y: -8, height: 0 },
+ animate: { opacity: 1, y: 0, height: "auto" },
+ exit: { opacity: 0, y: -8, height: 0 },
+ transition: sectionTransition,
+ }),
+ [sectionTransition],
+ );
+
+ const statusBarMotionProps = useMemo(
+ () => ({
+ initial: { opacity: 0, y: 8, height: 0 },
+ animate: { opacity: 1, y: 0, height: "auto" },
+ exit: { opacity: 0, y: 8, height: 0 },
+ transition: sectionTransition,
+ }),
+ [sectionTransition],
+ );
+
return (
- {effectiveSidebarVisible && (
-
- )}
+
+ {/* oxlint-disable react/jsx-max-depth */}
- {effectiveTabBarVisible && }
+
- {effectiveStatusBarVisible && }
+
+
+ {/* oxlint-enable react/jsx-max-depth */}
);
}
diff --git a/src/editor/themes/oxocarbon-dark.ts b/src/editor/themes/oxocarbon-dark.ts
index ea4716c..b1e62b6 100644
--- a/src/editor/themes/oxocarbon-dark.ts
+++ b/src/editor/themes/oxocarbon-dark.ts
@@ -35,10 +35,15 @@ export const oxocarbonDarkTheme = EditorView.theme({
"&": {
backgroundColor: colors.background,
color: colors.text,
- fontSize: "14px",
- fontFamily: "\"IBM Plex Mono\", \"SF Mono\", Monaco, monospace",
+ fontSize: "var(--editor-font-size, 14px)",
+ fontFamily: "var(--editor-font-family, \"IBM Plex Mono\", \"SF Mono\", Monaco, monospace)",
+ },
+ ".cm-scroller": { fontFamily: "var(--editor-font-family, \"IBM Plex Mono\", \"SF Mono\", Monaco, monospace)" },
+ ".cm-content": {
+ fontFamily: "var(--editor-font-family, \"IBM Plex Mono\", \"SF Mono\", Monaco, monospace)",
+ caretColor: colors.cyan,
+ padding: "16px 0",
},
- ".cm-content": { caretColor: colors.cyan, padding: "16px 0" },
".cm-cursor": { borderLeftColor: colors.cyan, borderLeftWidth: "2px" },
"&.cm-focused .cm-cursor": { borderLeftColor: colors.cyan },
"&.cm-focused .cm-selectionBackground": { backgroundColor: "rgba(51, 177, 255, 0.3)" },
diff --git a/src/editor/themes/oxocarbon-light.ts b/src/editor/themes/oxocarbon-light.ts
index 1bae159..a758b16 100644
--- a/src/editor/themes/oxocarbon-light.ts
+++ b/src/editor/themes/oxocarbon-light.ts
@@ -35,10 +35,15 @@ export const oxocarbonLightTheme = EditorView.theme({
"&": {
backgroundColor: colors.background,
color: colors.text,
- fontSize: "14px",
- fontFamily: "\"IBM Plex Mono\", \"SF Mono\", Monaco, monospace",
+ fontSize: "var(--editor-font-size, 14px)",
+ fontFamily: "var(--editor-font-family, \"IBM Plex Mono\", \"SF Mono\", Monaco, monospace)",
+ },
+ ".cm-scroller": { fontFamily: "var(--editor-font-family, \"IBM Plex Mono\", \"SF Mono\", Monaco, monospace)" },
+ ".cm-content": {
+ fontFamily: "var(--editor-font-family, \"IBM Plex Mono\", \"SF Mono\", Monaco, monospace)",
+ caretColor: colors.cyan,
+ padding: "16px 0",
},
- ".cm-content": { caretColor: colors.cyan, padding: "16px 0" },
".cm-cursor": { borderLeftColor: colors.cyan, borderLeftWidth: "2px" },
"&.cm-focused .cm-cursor": { borderLeftColor: colors.cyan },
"&.cm-focused .cm-selectionBackground": { backgroundColor: "rgba(15, 98, 254, 0.2)" },
diff --git a/src/hooks/app/useSettingsSync.ts b/src/hooks/app/useSettingsSync.ts
index 3ef3cf4..36a259b 100644
--- a/src/hooks/app/useSettingsSync.ts
+++ b/src/hooks/app/useSettingsSync.ts
@@ -30,6 +30,7 @@ export function useSettingsSync(): void {
state.setSidebarCollapsed(settings.sidebar_collapsed);
state.setTopBarsCollapsed(settings.top_bars_collapsed);
state.setStatusBarCollapsed(settings.status_bar_collapsed);
+ state.setReduceMotion(settings.reduce_motion);
state.setLineNumbersVisible(settings.line_numbers_visible);
state.setTextWrappingEnabled(settings.text_wrapping_enabled);
state.setSyntaxHighlightingEnabled(settings.syntax_highlighting_enabled);
diff --git a/src/hooks/controllers/useWorkspaceViewController.ts b/src/hooks/controllers/useWorkspaceViewController.ts
index d9301ef..1882613 100644
--- a/src/hooks/controllers/useWorkspaceViewController.ts
+++ b/src/hooks/controllers/useWorkspaceViewController.ts
@@ -12,7 +12,7 @@ import { usePdfExport, usePdfExportUI } from "$hooks/usePdfExport";
import { usePreview } from "$hooks/usePreview";
import { useWorkspaceSync } from "$hooks/useWorkspaceSync";
import type { PdfExportOptions } from "$pdf/types";
-import { useCalmUiSettings, useEditorPresentationStateRaw } from "$state/selectors";
+import { useEditorPresentationStateRaw } from "$state/selectors";
import type { DocRef, Maybe } from "$types";
import { useMemo } from "react";
@@ -40,7 +40,6 @@ export function useWorkspaceViewController(): WorkspaceViewController {
useWorkspaceSync();
useLayoutHotkeys();
- const calmUiSettings = useCalmUiSettings();
const editorPresentation = useEditorPresentationStateRaw();
const {
locations,
@@ -145,14 +144,6 @@ export function useWorkspaceViewController(): WorkspaceViewController {
useSettingsSync();
- const calmUiEffectiveVisibility = useMemo(() => {
- if (!calmUiSettings.enabled || calmUiSettings.chromeTemporarilyVisible) {
- return { sidebar: true, statusBar: true, tabBar: true };
- }
-
- return { sidebar: false, statusBar: false, tabBar: false };
- }, [calmUiSettings.chromeTemporarilyVisible, calmUiSettings.enabled]);
-
const toolbarProps = useMemo(
() => ({
saveStatus: editorModel.saveStatus,
@@ -222,14 +213,8 @@ export function useWorkspaceViewController(): WorkspaceViewController {
);
const workspacePanelProps = useMemo(
- () => ({
- toolbar: toolbarProps,
- editor: editorProps,
- preview: previewProps,
- statusBar: statusBarProps,
- calmUiVisibility: calmUiEffectiveVisibility,
- }),
- [toolbarProps, editorProps, previewProps, statusBarProps, calmUiEffectiveVisibility],
+ () => ({ toolbar: toolbarProps, editor: editorProps, preview: previewProps, statusBar: statusBarProps }),
+ [toolbarProps, editorProps, previewProps, statusBarProps],
);
return { workspacePanelProps, focusModePanelProps, handleExportPdf };
diff --git a/src/ports/types.ts b/src/ports/types.ts
index e4a8fcb..ad3bd3f 100644
--- a/src/ports/types.ts
+++ b/src/ports/types.ts
@@ -33,6 +33,7 @@ export type UiLayoutSettings = {
sidebar_collapsed: boolean;
top_bars_collapsed: boolean;
status_bar_collapsed: boolean;
+ reduce_motion: boolean;
line_numbers_visible: boolean;
text_wrapping_enabled: boolean;
syntax_highlighting_enabled: boolean;
diff --git a/src/state/helpers.ts b/src/state/helpers.ts
index 015ee58..39a9eb4 100644
--- a/src/state/helpers.ts
+++ b/src/state/helpers.ts
@@ -10,6 +10,7 @@ export const stateToLayoutSettings = (
sidebar_collapsed: layoutChrome.sidebarCollapsed,
top_bars_collapsed: layoutChrome.topBarsCollapsed,
status_bar_collapsed: layoutChrome.statusBarCollapsed,
+ reduce_motion: layoutChrome.reduceMotion,
line_numbers_visible: presentation.lineNumbersVisible,
text_wrapping_enabled: presentation.textWrappingEnabled,
syntax_highlighting_enabled: presentation.syntaxHighlightingEnabled,
diff --git a/src/state/selectors.ts b/src/state/selectors.ts
index 75f4bf0..ae910bd 100644
--- a/src/state/selectors.ts
+++ b/src/state/selectors.ts
@@ -301,6 +301,7 @@ export const useLayoutSettingsWriterToolsState = () =>
export const useAppHeaderBarState = () =>
useLayoutStore(
useShallow((state) => ({
+ sidebarCollapsed: state.sidebarCollapsed,
tabBarCollapsed: state.topBarsCollapsed,
statusBarCollapsed: state.statusBarCollapsed,
toggleSidebarCollapsed: state.toggleSidebarCollapsed,
diff --git a/src/styles/fonts.css b/src/styles/fonts.css
new file mode 100644
index 0000000..34d3c57
--- /dev/null
+++ b/src/styles/fonts.css
@@ -0,0 +1,255 @@
+@font-face {
+ font-family: "Writer IBM Plex Mono";
+ src: url("/fonts/ibm-plex-mono-400-normal.ttf") format("truetype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Mono";
+ src: url("/fonts/ibm-plex-mono-400-italic.ttf") format("truetype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Mono";
+ src: url("/fonts/ibm-plex-mono-700-normal.ttf") format("truetype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Mono";
+ src: url("/fonts/ibm-plex-mono-700-italic.ttf") format("truetype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Sans";
+ src: url("/fonts/ibm-plex-sans-400-normal.ttf") format("truetype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Sans";
+ src: url("/fonts/ibm-plex-sans-400-italic.ttf") format("truetype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Sans";
+ src: url("/fonts/ibm-plex-sans-700-normal.ttf") format("truetype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Sans";
+ src: url("/fonts/ibm-plex-sans-700-italic.ttf") format("truetype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Serif";
+ src: url("/fonts/ibm-plex-serif-400-normal.ttf") format("truetype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Serif";
+ src: url("/fonts/ibm-plex-serif-400-italic.ttf") format("truetype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Serif";
+ src: url("/fonts/ibm-plex-serif-700-normal.ttf") format("truetype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer IBM Plex Serif";
+ src: url("/fonts/ibm-plex-serif-700-italic.ttf") format("truetype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Argon";
+ src: url("/fonts/monaspace-argon-400-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Argon";
+ src: url("/fonts/monaspace-argon-400-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Argon";
+ src: url("/fonts/monaspace-argon-700-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Argon";
+ src: url("/fonts/monaspace-argon-700-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Krypton";
+ src: url("/fonts/monaspace-krypton-400-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Krypton";
+ src: url("/fonts/monaspace-krypton-400-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Krypton";
+ src: url("/fonts/monaspace-krypton-700-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Krypton";
+ src: url("/fonts/monaspace-krypton-700-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Neon";
+ src: url("/fonts/monaspace-neon-400-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Neon";
+ src: url("/fonts/monaspace-neon-400-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Neon";
+ src: url("/fonts/monaspace-neon-700-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Neon";
+ src: url("/fonts/monaspace-neon-700-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Radon";
+ src: url("/fonts/monaspace-radon-400-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Radon";
+ src: url("/fonts/monaspace-radon-400-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Radon";
+ src: url("/fonts/monaspace-radon-700-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Radon";
+ src: url("/fonts/monaspace-radon-700-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Xenon";
+ src: url("/fonts/monaspace-xenon-400-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Xenon";
+ src: url("/fonts/monaspace-xenon-400-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Xenon";
+ src: url("/fonts/monaspace-xenon-700-normal.otf") format("opentype");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Writer Monaspace Xenon";
+ src: url("/fonts/monaspace-xenon-700-italic.otf") format("opentype");
+ font-style: italic;
+ font-weight: 700;
+ font-display: swap;
+}