From 272502e0a30edfdde57d0aa2eb5be60e986778b4 Mon Sep 17 00:00:00 2001 From: Steven RYDELL Date: Wed, 29 Jul 2026 08:52:37 +0200 Subject: [PATCH] Add a square corners toggle for a border-radius-free interface --- src/components/layout/TopBar.tsx | 11 ++++++++++- src/i18n/en.json | 1 + src/index.css | 12 +++++++++--- src/stores/uiStore.ts | 22 ++++++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 67e1405..b32da5a 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -7,7 +7,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import * as LucideIcons from 'lucide-react'; -const { Sun, Moon, User, LogOut, Check, Menu, Sparkles, Search } = LucideIcons; +const { Sun, Moon, User, LogOut, Check, Menu, Sparkles, Search, Radius } = LucideIcons; import { Button } from '@/components/ui/button'; import { CommandPalette } from '@/components/common/CommandPalette'; import { @@ -43,6 +43,8 @@ export function TopBar() { const navigate = useNavigate(); const theme = useUIStore((s) => s.theme); const toggleTheme = useUIStore((s) => s.toggleTheme); + const radius = useUIStore((s) => s.radius); + const toggleRadius = useUIStore((s) => s.toggleRadius); const toggleSidebar = useUIStore((s) => s.toggleSidebar); const setActiveSection = useUIStore((s) => s.setActiveSection); const accounts = useAuthStore((s) => s.accounts); @@ -189,6 +191,13 @@ export function TopBar() { )} + + + {t('squareCorners', 'Square corners')} + {radius === 'square' && } + + + { const endSessionEndpoint = useAuthStore.getState().endSessionEndpoint; diff --git a/src/i18n/en.json b/src/i18n/en.json index 3eeb1fd..efe6b32 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -337,6 +337,7 @@ "periodValue": "Period value" }, "sections": "Sections", + "squareCorners": "Square corners", "toggleTheme": "Toggle theme", "tracing": { "addFilter": "Add filter", diff --git a/src/index.css b/src/index.css index 67a2012..208ecdb 100644 --- a/src/index.css +++ b/src/index.css @@ -90,6 +90,12 @@ --chart-5: 340 75% 60%; } +/* Square mode: every radius token derives multiplicatively from --radius, + so zeroing it here flattens all corners (rounded-full stays untouched). */ +:root[data-radius='square'] { + --radius: 0px; +} + @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); @@ -127,10 +133,10 @@ --color-chart-4: hsl(var(--chart-4)); --color-chart-5: hsl(var(--chart-5)); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) * 0.5); + --radius-md: calc(var(--radius) * 0.75); --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); + --radius-xl: calc(var(--radius) * 1.5); --animate-collapsible-down: collapsible-down 0.2s ease-out; --animate-collapsible-up: collapsible-up 0.2s ease-out; diff --git a/src/stores/uiStore.ts b/src/stores/uiStore.ts index 8bfd068..ee0d44b 100644 --- a/src/stores/uiStore.ts +++ b/src/stores/uiStore.ts @@ -8,14 +8,18 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; type Theme = 'light' | 'dark'; +type Radius = 'rounded' | 'square'; interface UIState { theme: Theme; + radius: Radius; sidebarOpen: boolean; activeSection: string; toggleTheme: () => void; setTheme: (theme: Theme) => void; + toggleRadius: () => void; + setRadius: (radius: Radius) => void; toggleSidebar: () => void; setSidebarOpen: (open: boolean) => void; setActiveSection: (section: string) => void; @@ -29,11 +33,16 @@ function applyThemeClass(theme: Theme) { } } +function applyRadiusAttribute(radius: Radius) { + document.documentElement.dataset.radius = radius; +} + export const useUIStore = create()( persist( (set, get) => ({ theme: typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', + radius: 'rounded', sidebarOpen: typeof window !== 'undefined' ? (window.matchMedia?.('(min-width: 768px)').matches ?? true) : true, activeSection: '', @@ -48,6 +57,17 @@ export const useUIStore = create()( set({ theme }); }, + toggleRadius: () => { + const next = get().radius === 'rounded' ? 'square' : 'rounded'; + applyRadiusAttribute(next); + set({ radius: next }); + }, + + setRadius: (radius) => { + applyRadiusAttribute(radius); + set({ radius }); + }, + toggleSidebar: () => { set({ sidebarOpen: !get().sidebarOpen }); }, @@ -64,11 +84,13 @@ export const useUIStore = create()( name: 'stalwart-ui', partialize: (state) => ({ theme: state.theme, + radius: state.radius, }), onRehydrateStorage: () => { return (state) => { if (state) { applyThemeClass(state.theme); + applyRadiusAttribute(state.radius ?? 'rounded'); } }; },