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>
221 lines
8.8 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|