mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 19:59:20 +02:00
Compare commits
1 Commits
fix/email-
...
agent/lamb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f7ff444b6 |
7
packages/core/appearance/index.ts
Normal file
7
packages/core/appearance/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
useTypographyStore,
|
||||
DEFAULT_UI_FONT_SIZE,
|
||||
DEFAULT_CODE_FONT_SIZE,
|
||||
MIN_FONT_SIZE,
|
||||
MAX_FONT_SIZE,
|
||||
} from "./store";
|
||||
49
packages/core/appearance/store.ts
Normal file
49
packages/core/appearance/store.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
import { defaultStorage } from "../platform/storage";
|
||||
|
||||
export const DEFAULT_UI_FONT_SIZE = 14;
|
||||
export const DEFAULT_CODE_FONT_SIZE = 13;
|
||||
export const MIN_FONT_SIZE = 10;
|
||||
export const MAX_FONT_SIZE = 24;
|
||||
|
||||
interface TypographyState {
|
||||
uiFontSize: number;
|
||||
codeFontSize: number;
|
||||
uiFontFamily: string;
|
||||
codeFontFamily: string;
|
||||
fontSmoothing: boolean;
|
||||
setUiFontSize: (size: number) => void;
|
||||
setCodeFontSize: (size: number) => void;
|
||||
setUiFontFamily: (family: string) => void;
|
||||
setCodeFontFamily: (family: string) => void;
|
||||
setFontSmoothing: (enabled: boolean) => void;
|
||||
resetUiFontSize: () => void;
|
||||
resetCodeFontSize: () => void;
|
||||
}
|
||||
|
||||
const clampFontSize = (value: number): number =>
|
||||
Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, Math.round(value)));
|
||||
|
||||
export const useTypographyStore = create<TypographyState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
uiFontSize: DEFAULT_UI_FONT_SIZE,
|
||||
codeFontSize: DEFAULT_CODE_FONT_SIZE,
|
||||
uiFontFamily: "",
|
||||
codeFontFamily: "",
|
||||
fontSmoothing: false,
|
||||
setUiFontSize: (size) => set({ uiFontSize: clampFontSize(size) }),
|
||||
setCodeFontSize: (size) => set({ codeFontSize: clampFontSize(size) }),
|
||||
setUiFontFamily: (family) => set({ uiFontFamily: family }),
|
||||
setCodeFontFamily: (family) => set({ codeFontFamily: family }),
|
||||
setFontSmoothing: (enabled) => set({ fontSmoothing: enabled }),
|
||||
resetUiFontSize: () => set({ uiFontSize: DEFAULT_UI_FONT_SIZE }),
|
||||
resetCodeFontSize: () => set({ codeFontSize: DEFAULT_CODE_FONT_SIZE }),
|
||||
}),
|
||||
{
|
||||
name: "multica_typography",
|
||||
storage: createJSONStorage(() => defaultStorage),
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -67,7 +67,8 @@
|
||||
"./utils": "./utils.ts",
|
||||
"./constants/*": "./constants/*.ts",
|
||||
"./platform": "./platform/index.ts",
|
||||
"./analytics": "./analytics/index.ts"
|
||||
"./analytics": "./analytics/index.ts",
|
||||
"./appearance": "./appearance/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "catalog:",
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { Minus, Plus, RotateCcw } from "lucide-react";
|
||||
import {
|
||||
DEFAULT_CODE_FONT_SIZE,
|
||||
DEFAULT_UI_FONT_SIZE,
|
||||
MAX_FONT_SIZE,
|
||||
MIN_FONT_SIZE,
|
||||
useTypographyStore,
|
||||
} from "@multica/core/appearance";
|
||||
import { useTheme } from "@multica/ui/components/common/theme-provider";
|
||||
import { Input } from "@multica/ui/components/ui/input";
|
||||
import { Switch } from "@multica/ui/components/ui/switch";
|
||||
import { cn } from "@multica/ui/lib/utils";
|
||||
|
||||
const LIGHT_COLORS = {
|
||||
@@ -30,7 +40,6 @@ function WindowMockup({
|
||||
|
||||
return (
|
||||
<div className={cn("flex h-full w-full flex-col", className)}>
|
||||
{/* Title bar */}
|
||||
<div
|
||||
className="flex items-center gap-[3px] px-2 py-1.5"
|
||||
style={{ backgroundColor: colors.titleBar }}
|
||||
@@ -39,12 +48,10 @@ function WindowMockup({
|
||||
<span className="size-[6px] rounded-full bg-[#febc2e]" />
|
||||
<span className="size-[6px] rounded-full bg-[#28c840]" />
|
||||
</div>
|
||||
{/* Content area */}
|
||||
<div
|
||||
className="flex flex-1"
|
||||
style={{ backgroundColor: colors.content }}
|
||||
>
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className="w-[30%] space-y-1 p-2"
|
||||
style={{ backgroundColor: colors.sidebar }}
|
||||
@@ -58,7 +65,6 @@ function WindowMockup({
|
||||
style={{ backgroundColor: colors.bar }}
|
||||
/>
|
||||
</div>
|
||||
{/* Main */}
|
||||
<div className="flex-1 space-y-1.5 p-2">
|
||||
<div
|
||||
className="h-1.5 w-4/5 rounded-full"
|
||||
@@ -84,8 +90,95 @@ const themeOptions = [
|
||||
{ value: "system" as const, label: "System" },
|
||||
];
|
||||
|
||||
function SettingRow({
|
||||
label,
|
||||
description,
|
||||
control,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
control: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-6 py-3 first:pt-0 last:pb-0">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-foreground">{label}</div>
|
||||
<div className="text-xs text-muted-foreground">{description}</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center">{control}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FontSizeControl({
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
onReset,
|
||||
ariaLabel,
|
||||
}: {
|
||||
value: number;
|
||||
defaultValue: number;
|
||||
onChange: (next: number) => void;
|
||||
onReset: () => void;
|
||||
ariaLabel: string;
|
||||
}) {
|
||||
const isDefault = value === defaultValue;
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
disabled={isDefault}
|
||||
aria-label={`Reset ${ariaLabel}`}
|
||||
className="flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
|
||||
>
|
||||
<RotateCcw className="size-3.5" />
|
||||
</button>
|
||||
<div className="flex h-8 items-center rounded-lg border border-input">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(value - 1)}
|
||||
disabled={value <= MIN_FONT_SIZE}
|
||||
aria-label={`Decrease ${ariaLabel}`}
|
||||
className="flex size-8 items-center justify-center text-muted-foreground transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
|
||||
>
|
||||
<Minus className="size-3.5" />
|
||||
</button>
|
||||
<span
|
||||
aria-label={ariaLabel}
|
||||
className="min-w-8 text-center text-sm tabular-nums"
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(value + 1)}
|
||||
disabled={value >= MAX_FONT_SIZE}
|
||||
aria-label={`Increase ${ariaLabel}`}
|
||||
className="flex size-8 items-center justify-center text-muted-foreground transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppearanceTab() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const uiFontSize = useTypographyStore((s) => s.uiFontSize);
|
||||
const codeFontSize = useTypographyStore((s) => s.codeFontSize);
|
||||
const uiFontFamily = useTypographyStore((s) => s.uiFontFamily);
|
||||
const codeFontFamily = useTypographyStore((s) => s.codeFontFamily);
|
||||
const fontSmoothing = useTypographyStore((s) => s.fontSmoothing);
|
||||
const setUiFontSize = useTypographyStore((s) => s.setUiFontSize);
|
||||
const setCodeFontSize = useTypographyStore((s) => s.setCodeFontSize);
|
||||
const setUiFontFamily = useTypographyStore((s) => s.setUiFontFamily);
|
||||
const setCodeFontFamily = useTypographyStore((s) => s.setCodeFontFamily);
|
||||
const setFontSmoothing = useTypographyStore((s) => s.setFontSmoothing);
|
||||
const resetUiFontSize = useTypographyStore((s) => s.resetUiFontSize);
|
||||
const resetCodeFontSize = useTypographyStore((s) => s.resetCodeFontSize);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -141,6 +234,75 @@ export function AppearanceTab() {
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-sm font-semibold">Typography</h2>
|
||||
<div className="divide-y divide-border rounded-lg border border-border bg-card px-4">
|
||||
<SettingRow
|
||||
label="UI Font Size"
|
||||
description="Font size for the Multica user interface"
|
||||
control={
|
||||
<FontSizeControl
|
||||
value={uiFontSize}
|
||||
defaultValue={DEFAULT_UI_FONT_SIZE}
|
||||
onChange={setUiFontSize}
|
||||
onReset={resetUiFontSize}
|
||||
ariaLabel="UI font size"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="Code Font Size"
|
||||
description="Font size for code editors and diffs"
|
||||
control={
|
||||
<FontSizeControl
|
||||
value={codeFontSize}
|
||||
defaultValue={DEFAULT_CODE_FONT_SIZE}
|
||||
onChange={setCodeFontSize}
|
||||
onReset={resetCodeFontSize}
|
||||
ariaLabel="Code font size"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="UI Font Family"
|
||||
description="Override the Multica user interface typeface"
|
||||
control={
|
||||
<Input
|
||||
aria-label="UI font family"
|
||||
placeholder="System font"
|
||||
value={uiFontFamily}
|
||||
onChange={(e) => setUiFontFamily(e.target.value)}
|
||||
className="h-8 w-56"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="Code Font Family"
|
||||
description="Override the font for code editors and diffs"
|
||||
control={
|
||||
<Input
|
||||
aria-label="Code font family"
|
||||
placeholder="System monospace"
|
||||
value={codeFontFamily}
|
||||
onChange={(e) => setCodeFontFamily(e.target.value)}
|
||||
className="h-8 w-56"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="Font Smoothing"
|
||||
description="Use native macOS font anti-aliasing"
|
||||
control={
|
||||
<Switch
|
||||
aria-label="Font smoothing"
|
||||
checked={fontSmoothing}
|
||||
onCheckedChange={setFontSmoothing}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user