add reduce motion toggle and bump to v1.0.1
Add in-app reduce motion setting under Settings > Appearance so users can disable animations without changing their OS preference. Applies a .reduce-motion CSS class to kill all CSS transitions/animations and wraps the app in MotionConfig to globally disable Framer Motion springs, layout animations, and enter/exit transitions. Setting persists to disk. Also removes leftover default Square*.png icons and bumps version to 1.0.1.
1282
package-lock.json
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "openpylon",
|
"name": "openpylon",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -45,7 +45,10 @@
|
|||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
"png-to-ico": "^3.0.1",
|
||||||
|
"puppeteer-core": "^24.37.3",
|
||||||
"shadcn": "^3.8.4",
|
"shadcn": "^3.8.4",
|
||||||
|
"sharp": "^0.34.5",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
@@ -2360,7 +2360,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openpylon"
|
name = "openpylon"
|
||||||
version = "0.1.0"
|
version = "1.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "openpylon"
|
name = "openpylon"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 903 B |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "openpylon",
|
"productName": "openpylon",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"identifier": "com.openpylon.app",
|
"identifier": "com.openpylon.app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { getCurrentWindow, LogicalSize, LogicalPosition } from "@tauri-apps/api/window";
|
import { getCurrentWindow, LogicalSize, LogicalPosition } from "@tauri-apps/api/window";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion, MotionConfig } from "framer-motion";
|
||||||
import { springs, fadeSlideLeft, fadeSlideRight } from "@/lib/motion";
|
import { springs, fadeSlideLeft, fadeSlideRight } from "@/lib/motion";
|
||||||
import { useAppStore } from "@/stores/app-store";
|
import { useAppStore } from "@/stores/app-store";
|
||||||
import { useBoardStore } from "@/stores/board-store";
|
import { useBoardStore } from "@/stores/board-store";
|
||||||
@@ -18,6 +18,7 @@ export default function App() {
|
|||||||
const initialized = useAppStore((s) => s.initialized);
|
const initialized = useAppStore((s) => s.initialized);
|
||||||
const init = useAppStore((s) => s.init);
|
const init = useAppStore((s) => s.init);
|
||||||
const view = useAppStore((s) => s.view);
|
const view = useAppStore((s) => s.view);
|
||||||
|
const reduceMotion = useAppStore((s) => s.settings.reduceMotion);
|
||||||
|
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false);
|
const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false);
|
||||||
@@ -127,7 +128,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<MotionConfig reducedMotion={reduceMotion ? "always" : "user"}>
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{view.type === "board-list" ? (
|
{view.type === "board-list" ? (
|
||||||
@@ -161,6 +162,6 @@ export default function App() {
|
|||||||
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<ShortcutHelpModal open={shortcutHelpOpen} onOpenChange={setShortcutHelpOpen} />
|
<ShortcutHelpModal open={shortcutHelpOpen} onOpenChange={setShortcutHelpOpen} />
|
||||||
</>
|
</MotionConfig>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
|||||||
const setAccentColor = useAppStore((s) => s.setAccentColor);
|
const setAccentColor = useAppStore((s) => s.setAccentColor);
|
||||||
const setUiZoom = useAppStore((s) => s.setUiZoom);
|
const setUiZoom = useAppStore((s) => s.setUiZoom);
|
||||||
const setDensity = useAppStore((s) => s.setDensity);
|
const setDensity = useAppStore((s) => s.setDensity);
|
||||||
|
const setReduceMotion = useAppStore((s) => s.setReduceMotion);
|
||||||
const setDefaultColumnWidth = useAppStore((s) => s.setDefaultColumnWidth);
|
const setDefaultColumnWidth = useAppStore((s) => s.setDefaultColumnWidth);
|
||||||
|
|
||||||
const roRef = useRef<ResizeObserver | null>(null);
|
const roRef = useRef<ResizeObserver | null>(null);
|
||||||
@@ -268,6 +269,30 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{/* Reduce Motion */}
|
||||||
|
<div>
|
||||||
|
<SectionLabel>Reduce Motion</SectionLabel>
|
||||||
|
<p className="mb-2 text-xs text-pylon-text-secondary">
|
||||||
|
Reduces animations and transitions for accessibility.
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{([false, true] as const).map((value) => (
|
||||||
|
<Button
|
||||||
|
key={String(value)}
|
||||||
|
type="button"
|
||||||
|
variant={settings.reduceMotion === value ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setReduceMotion(value)}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
{value ? "Reduced" : "Normal"}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -200,3 +200,12 @@
|
|||||||
scroll-behavior: auto !important;
|
scroll-behavior: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reduce-motion *,
|
||||||
|
.reduce-motion *::before,
|
||||||
|
.reduce-motion *::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,4 +87,5 @@ export const appSettingsSchema = z.object({
|
|||||||
boardSortOrder: z.enum(["manual", "title", "created", "updated"]).default("updated"),
|
boardSortOrder: z.enum(["manual", "title", "created", "updated"]).default("updated"),
|
||||||
boardManualOrder: z.array(z.string()).default([]),
|
boardManualOrder: z.array(z.string()).default([]),
|
||||||
lastNotificationCheck: z.string().nullable().default(null),
|
lastNotificationCheck: z.string().nullable().default(null),
|
||||||
|
reduceMotion: z.boolean().default(false),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface AppState {
|
|||||||
setView: (view: View) => void;
|
setView: (view: View) => void;
|
||||||
refreshBoards: () => Promise<void>;
|
refreshBoards: () => Promise<void>;
|
||||||
addRecentBoard: (boardId: string) => void;
|
addRecentBoard: (boardId: string) => void;
|
||||||
|
setReduceMotion: (reduceMotion: boolean) => void;
|
||||||
setBoardSortOrder: (order: BoardSortOrder) => void;
|
setBoardSortOrder: (order: BoardSortOrder) => void;
|
||||||
setBoardManualOrder: (ids: string[]) => void;
|
setBoardManualOrder: (ids: string[]) => void;
|
||||||
getSortedBoards: () => BoardMeta[];
|
getSortedBoards: () => BoardMeta[];
|
||||||
@@ -36,6 +37,10 @@ function applyTheme(theme: AppSettings["theme"]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyReduceMotion(on: boolean): void {
|
||||||
|
document.documentElement.classList.toggle("reduce-motion", on);
|
||||||
|
}
|
||||||
|
|
||||||
function applyAppearance(settings: AppSettings): void {
|
function applyAppearance(settings: AppSettings): void {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
root.style.fontSize = `${settings.uiZoom * 16}px`;
|
root.style.fontSize = `${settings.uiZoom * 16}px`;
|
||||||
@@ -70,6 +75,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|||||||
boardSortOrder: "updated",
|
boardSortOrder: "updated",
|
||||||
boardManualOrder: [],
|
boardManualOrder: [],
|
||||||
lastNotificationCheck: null,
|
lastNotificationCheck: null,
|
||||||
|
reduceMotion: false,
|
||||||
},
|
},
|
||||||
boards: [],
|
boards: [],
|
||||||
view: { type: "board-list" },
|
view: { type: "board-list" },
|
||||||
@@ -82,6 +88,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|||||||
set({ settings, boards, initialized: true });
|
set({ settings, boards, initialized: true });
|
||||||
applyTheme(settings.theme);
|
applyTheme(settings.theme);
|
||||||
applyAppearance(settings);
|
applyAppearance(settings);
|
||||||
|
applyReduceMotion(settings.reduceMotion);
|
||||||
|
|
||||||
// Due date notifications (once per hour)
|
// Due date notifications (once per hour)
|
||||||
const lastCheck = settings.lastNotificationCheck;
|
const lastCheck = settings.lastNotificationCheck;
|
||||||
@@ -148,6 +155,11 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|||||||
updateAndSave(get, set, { defaultColumnWidth });
|
updateAndSave(get, set, { defaultColumnWidth });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setReduceMotion: (reduceMotion) => {
|
||||||
|
updateAndSave(get, set, { reduceMotion });
|
||||||
|
applyReduceMotion(reduceMotion);
|
||||||
|
},
|
||||||
|
|
||||||
setView: (view) => set({ view }),
|
setView: (view) => set({ view }),
|
||||||
|
|
||||||
refreshBoards: async () => {
|
refreshBoards: async () => {
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ export interface AppSettings {
|
|||||||
boardSortOrder: BoardSortOrder;
|
boardSortOrder: BoardSortOrder;
|
||||||
boardManualOrder: string[];
|
boardManualOrder: string[];
|
||||||
lastNotificationCheck: string | null;
|
lastNotificationCheck: string | null;
|
||||||
|
reduceMotion: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||