My-Weekly-ToDo-List/src/components/QuickSettingsSidebar.tsx
mARTin-B78 52d160ccea feat: projects tab, priority icons, list/tab visuals
Wave-1 user feedback (points 7–10):
- Settings: new Projects tab with full CRUD (name, color, icon picker)
- Priority Icons render in simple/calendar/list views with style-specific
  visuals (Eisenhower icons, ABCDE letters, Ivy Lee 1–6, Pareto star).
  Toggle + style selector in Settings; Ivy Lee ranks now persist to
  task.priority so cross-view badges stay consistent.
- Someday Lists gain optional icon (left of title) and color (left
  border tint), edited via a pencil-popup with IconPicker + color picker.
- Tabs gain optional icon and color, stored in user.viewSettings JSON
  and edited via the same popup pattern.

Schema: SomedayList.color/icon, User.showPriorityIcons/priorityStyle.

v1.99.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 14:23:56 +02:00

221 lines
8.8 KiB
TypeScript

"use client";
import { X, Type, Space, CheckSquare, Calendar, Minus, Target } from "lucide-react";
interface QuickSettingsProps {
fontSize: string;
onFontSizeChange: (size: string) => void;
spacing: string;
onSpacingChange: (spacing: string) => void;
showCompleted: boolean;
onShowCompletedChange: (show: boolean) => void;
startDayOffset: number;
onStartDayOffsetChange: (offset: number) => void;
showLines: boolean;
onShowLinesChange: (show: boolean) => void;
showPriorityIcons?: boolean;
onShowPriorityIconsChange?: (show: boolean) => void;
isOpen: boolean;
onClose: () => void;
darkMode?: boolean;
}
export default function QuickSettingsSidebar({
fontSize,
onFontSizeChange,
spacing,
onSpacingChange,
showCompleted,
onShowCompletedChange,
startDayOffset,
onStartDayOffsetChange,
showLines,
onShowLinesChange,
showPriorityIcons,
onShowPriorityIconsChange,
isOpen,
onClose,
darkMode,
}: QuickSettingsProps) {
const bg = darkMode ? "#1f2937" : "#ffffff";
const text = darkMode ? "#e5e7eb" : "#333333";
const border = darkMode ? "#374151" : "#e5e7eb";
const accent = "#0ea5e9";
const btnBg = darkMode ? "#374151" : "#f3f4f6";
const btnActiveBg = accent;
const btnActiveText = "#ffffff";
const labelColor = darkMode ? "#9ca3af" : "#6b7280";
const sizes = ["S", "M", "L"];
const SegmentedButton = ({
options,
value,
onChange,
}: {
options: string[];
value: string;
onChange: (v: string) => void;
}) => (
<div style={{ display: "flex", gap: "2px", background: btnBg, borderRadius: "8px", padding: "2px" }}>
{options.map((opt) => (
<button
key={opt}
onClick={() => onChange(opt)}
style={{
flex: 1,
padding: "6px 10px",
border: "none",
borderRadius: "6px",
cursor: "pointer",
fontSize: "0.75rem",
fontWeight: value === opt ? 600 : 400,
background: value === opt ? btnActiveBg : "transparent",
color: value === opt ? btnActiveText : text,
transition: "all 0.15s ease",
}}
>
{opt}
</button>
))}
</div>
);
const Toggle = ({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) => (
<button
onClick={() => onChange(!checked)}
style={{
width: "36px",
height: "20px",
borderRadius: "10px",
border: "none",
background: checked ? accent : (darkMode ? "#4b5563" : "#d1d5db"),
cursor: "pointer",
position: "relative",
transition: "background 0.2s ease",
flexShrink: 0,
}}
>
<div
style={{
width: "16px",
height: "16px",
borderRadius: "50%",
background: "#fff",
position: "absolute",
top: "2px",
left: checked ? "18px" : "2px",
transition: "left 0.2s ease",
boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
}}
/>
</button>
);
return (
<>
{/* Backdrop */}
{isOpen && (
<div
onClick={onClose}
style={{
position: "fixed",
inset: 0,
background: "rgba(0,0,0,0.1)",
zIndex: 998,
}}
/>
)}
{/* Panel */}
<div
style={{
position: "fixed",
right: 0,
top: 0,
bottom: 0,
width: isOpen ? "220px" : "0",
overflow: "hidden",
background: bg,
borderLeft: isOpen ? `1px solid ${border}` : "none",
transition: "width 0.2s ease",
zIndex: 999,
display: "flex",
flexDirection: "column",
}}
>
<div style={{ padding: "16px", display: "flex", flexDirection: "column", gap: "20px", overflowY: "auto", flex: 1 }}>
{/* Header */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontWeight: 600, fontSize: "0.85rem", color: text }}>Quick Settings</span>
<button
onClick={onClose}
style={{ background: "none", border: "none", cursor: "pointer", color: labelColor, padding: "2px" }}
>
<X size={16} />
</button>
</div>
{/* Font Size */}
<div>
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "6px" }}>
<Type size={14} style={{ color: labelColor }} />
<span style={{ fontSize: "0.75rem", color: labelColor }}>Font Size</span>
</div>
<SegmentedButton options={sizes} value={fontSize} onChange={onFontSizeChange} />
</div>
{/* Spacing */}
<div>
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "6px" }}>
<Space size={14} style={{ color: labelColor }} />
<span style={{ fontSize: "0.75rem", color: labelColor }}>Spacing</span>
</div>
<SegmentedButton options={sizes} value={spacing} onChange={onSpacingChange} />
</div>
{/* Show Completed */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
<CheckSquare size={14} style={{ color: labelColor }} />
<span style={{ fontSize: "0.75rem", color: labelColor }}>Show Completed</span>
</div>
<Toggle checked={showCompleted} onChange={onShowCompletedChange} />
</div>
{/* Start Day */}
<div>
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "6px" }}>
<Calendar size={14} style={{ color: labelColor }} />
<span style={{ fontSize: "0.75rem", color: labelColor }}>Start View</span>
</div>
<SegmentedButton
options={["Yesterday", "Today"]}
value={startDayOffset === -1 ? "Yesterday" : "Today"}
onChange={(v) => onStartDayOffsetChange(v === "Yesterday" ? -1 : 0)}
/>
</div>
{/* Show Lines */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
<Minus size={14} style={{ color: labelColor }} />
<span style={{ fontSize: "0.75rem", color: labelColor }}>Show Lines</span>
</div>
<Toggle checked={showLines} onChange={onShowLinesChange} />
</div>
{/* Show Priority Icons */}
{onShowPriorityIconsChange && (
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
<Target size={14} style={{ color: labelColor }} />
<span style={{ fontSize: "0.75rem", color: labelColor }}>Priority Icons</span>
</div>
<Toggle checked={showPriorityIcons ?? true} onChange={onShowPriorityIconsChange} />
</div>
)}
</div>
</div>
</>
);
}