feat: mobile UX polish — FA icons, weather fix, header swap, sticky fix
- Replace emoji project icons with FontAwesome solid icons (46 icons) with proper icon picker grid and legacy emoji fallback - Fix weather temperature unreadable on mobile (white on white) by adding explicit dark/light color - Fix weekday sticky header on mobile: correct top offset for time-grid scroll container, use overflow-x: clip to not break sticky - Swap mobile header: quick settings (PanelLeftOpen) on left, burger menu (settings) on right, remove search icon (available in left sidebar) v1.46.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
183fd6ea48
commit
c84fd4c60e
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.45.0",
|
||||
"version": "1.46.0",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -3879,10 +3879,14 @@ h3 {
|
||||
.weekly-day-header {
|
||||
position: sticky;
|
||||
top: calc(48px + env(safe-area-inset-top, 0px));
|
||||
z-index: 10;
|
||||
z-index: 40;
|
||||
background: var(--weekly-bg, white);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
/* In time-grid mode, the scroll container is time-grid-wrapper, so top=0 */
|
||||
.time-grid-on .weekly-day-header {
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@ -4308,7 +4312,7 @@ h3 {
|
||||
@media (max-width: 768px) {
|
||||
.time-grid-on .weekly-days-grid {
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-x: clip !important;
|
||||
}
|
||||
.time-grid-on .weekly-day-column {
|
||||
overflow-y: visible !important;
|
||||
@ -4358,8 +4362,12 @@ h3 {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Sticky day header: top: 0 relative to the scroll container (time-grid-wrapper) */
|
||||
.time-grid-on .weekly-day-header {
|
||||
top: calc(48px + env(safe-area-inset-top, 0px)) !important;
|
||||
position: sticky !important;
|
||||
top: 0 !important;
|
||||
z-index: 40 !important;
|
||||
background-color: var(--weekly-bg, white);
|
||||
}
|
||||
/* On mobile, time-column-header should NOT be sticky — it wastes vertical space */
|
||||
.time-grid-on .time-column-header {
|
||||
|
||||
@ -14,7 +14,17 @@ import TaskRecurrenceModal from "./RecurrenceModal";
|
||||
import { GridTaskBlock } from "./GridTaskBlock";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faApple, faGoogle, faMicrosoft } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faServer } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faServer, faFolder, faBriefcase, faBullseye, faRocket, faStar,
|
||||
faLightbulb, faFire, faPalette, faMusic, faMobileScreen, faLaptop,
|
||||
faGlobe, faHouse, faBuilding, faChartBar, faChartLine, faWrench,
|
||||
faBolt, faGamepad, faPen, faBook, faGraduationCap, faFlask,
|
||||
faMicroscope, faDumbbell, faUtensils, faPlane, faLeaf, faHeart,
|
||||
faCartShopping, faCoins, faGift, faCamera, faFilm, faBroom,
|
||||
faPaw, faEarthAmericas, faLock, faCheck, faCode, faCube,
|
||||
faUsers, faCar, faMountain, faUmbrella, faClock, faTag,
|
||||
IconDefinition,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import FocusModeOverlay from "./FocusModeOverlay";
|
||||
import {
|
||||
@ -5762,9 +5772,9 @@ export default function WeeklyView() {
|
||||
{/* Mobile Header */}
|
||||
{isMobile && (
|
||||
<header className="mobile-header" style={{ background: darkMode ? "#111827" : "#ffffff", borderBottom: `1px solid ${darkMode ? "#374151" : "#e5e7eb"}` }}>
|
||||
{/* Left: Menu button */}
|
||||
<button className="mobile-header-btn" onClick={() => setShowSettings(true)} title="Menu" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||
<Menu size={22} />
|
||||
{/* Left: Quick settings */}
|
||||
<button className="mobile-header-btn" onClick={() => setShowQuickSettings(true)} title="Quick Settings" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||
<PanelLeftOpen size={22} />
|
||||
</button>
|
||||
|
||||
{/* Center: Date display (tap to jump to date) */}
|
||||
@ -5802,9 +5812,9 @@ export default function WeeklyView() {
|
||||
<ChevronDown size={12} style={{ opacity: 0.5 }} />
|
||||
</button>
|
||||
|
||||
{/* Right: Search */}
|
||||
<button className="mobile-header-btn" onClick={() => setIsSearchOpen(true)} title="Search" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||
<Search size={20} />
|
||||
{/* Right: Settings menu */}
|
||||
<button className="mobile-header-btn" onClick={() => setShowSettings(true)} title="Settings" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||
<Menu size={22} />
|
||||
</button>
|
||||
</header>
|
||||
)}
|
||||
@ -6345,7 +6355,7 @@ export default function WeeklyView() {
|
||||
>
|
||||
<option value="">{t.filterByProject}</option>
|
||||
{projects.map(p => (
|
||||
<option key={p.id} value={p.id}>{p.icon || "📁"} {p.name}</option>
|
||||
<option key={p.id} value={p.id}>● {p.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
@ -6861,9 +6871,9 @@ export default function WeeklyView() {
|
||||
if (!w) return null;
|
||||
const icon = getWeatherIcon(w.code);
|
||||
return (
|
||||
<div style={{ position: "absolute", top: "1px", right: "3px", display: "flex", alignItems: "center", gap: "2px", fontSize: "10px", opacity: 0.6, pointerEvents: "none", zIndex: 1, lineHeight: 1 }}>
|
||||
<div style={{ position: "absolute", top: "1px", right: "3px", display: "flex", alignItems: "center", gap: "2px", fontSize: "10px", opacity: 0.7, pointerEvents: "none", zIndex: 1, lineHeight: 1, color: "#555" }}>
|
||||
<span>{icon}</span>
|
||||
<span style={{ fontWeight: 500 }}>{w.temp}°</span>
|
||||
<span style={{ fontWeight: 600, color: darkMode ? "#aaa" : "#555" }}>{w.temp}°</span>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
@ -9274,7 +9284,7 @@ function TaskItem({
|
||||
setShowProjectPicker(false);
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: "12px" }}>{p.icon || "📁"}</span>
|
||||
<span style={{ fontSize: "12px" }}><ProjectIcon icon={p.icon} size={12} color={p.color || "#999"} /></span>
|
||||
<Circle size={8} fill={p.color || "#999"} stroke={p.color || "#999"} strokeWidth={0} />
|
||||
{p.name}
|
||||
</button>
|
||||
@ -9500,13 +9510,43 @@ function TaskItem({
|
||||
}
|
||||
|
||||
// Projects Sidebar Component
|
||||
const projectEmojisGlobal = [
|
||||
"📁", "📂", "💼", "🎯", "🚀", "⭐", "💡", "🔥", "🎨", "🎵",
|
||||
"📱", "💻", "🌐", "🏠", "🏢", "📊", "📈", "🔧", "⚡", "🎮",
|
||||
"📝", "📖", "🎓", "🧪", "🔬", "🏋️", "🍽️", "✈️", "🌿", "❤️",
|
||||
"🛒", "💰", "🎁", "📸", "🎬", "🧹", "🐾", "🌍", "🔒", "✅",
|
||||
const projectIconsGlobal: { name: string; icon: IconDefinition }[] = [
|
||||
{ name: "folder", icon: faFolder }, { name: "briefcase", icon: faBriefcase },
|
||||
{ name: "bullseye", icon: faBullseye }, { name: "rocket", icon: faRocket },
|
||||
{ name: "star", icon: faStar }, { name: "lightbulb", icon: faLightbulb },
|
||||
{ name: "fire", icon: faFire }, { name: "palette", icon: faPalette },
|
||||
{ name: "music", icon: faMusic }, { name: "mobile", icon: faMobileScreen },
|
||||
{ name: "laptop", icon: faLaptop }, { name: "globe", icon: faGlobe },
|
||||
{ name: "house", icon: faHouse }, { name: "building", icon: faBuilding },
|
||||
{ name: "chart-bar", icon: faChartBar }, { name: "chart-line", icon: faChartLine },
|
||||
{ name: "wrench", icon: faWrench }, { name: "bolt", icon: faBolt },
|
||||
{ name: "gamepad", icon: faGamepad }, { name: "pen", icon: faPen },
|
||||
{ name: "book", icon: faBook }, { name: "graduation-cap", icon: faGraduationCap },
|
||||
{ name: "flask", icon: faFlask }, { name: "microscope", icon: faMicroscope },
|
||||
{ name: "dumbbell", icon: faDumbbell }, { name: "utensils", icon: faUtensils },
|
||||
{ name: "plane", icon: faPlane }, { name: "leaf", icon: faLeaf },
|
||||
{ name: "heart", icon: faHeart }, { name: "cart-shopping", icon: faCartShopping },
|
||||
{ name: "coins", icon: faCoins }, { name: "gift", icon: faGift },
|
||||
{ name: "camera", icon: faCamera }, { name: "film", icon: faFilm },
|
||||
{ name: "broom", icon: faBroom }, { name: "paw", icon: faPaw },
|
||||
{ name: "earth", icon: faEarthAmericas }, { name: "lock", icon: faLock },
|
||||
{ name: "check", icon: faCheck }, { name: "code", icon: faCode },
|
||||
{ name: "cube", icon: faCube }, { name: "users", icon: faUsers },
|
||||
{ name: "car", icon: faCar }, { name: "mountain", icon: faMountain },
|
||||
{ name: "umbrella", icon: faUmbrella }, { name: "clock", icon: faClock },
|
||||
{ name: "tag", icon: faTag },
|
||||
];
|
||||
|
||||
// Helper to render a project icon (FA name or legacy emoji)
|
||||
function ProjectIcon({ icon, size = 18, color }: { icon?: string | null; size?: number; color?: string }) {
|
||||
if (!icon) return <FontAwesomeIcon icon={faFolder} style={{ fontSize: size, color }} />;
|
||||
// Check if it's an FA icon name
|
||||
const found = projectIconsGlobal.find((i) => i.name === icon);
|
||||
if (found) return <FontAwesomeIcon icon={found.icon} style={{ fontSize: size, color }} />;
|
||||
// Legacy emoji fallback
|
||||
return <span style={{ fontSize: size, lineHeight: 1 }}>{icon}</span>;
|
||||
}
|
||||
|
||||
function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onClose }: {
|
||||
darkMode: boolean;
|
||||
language: string;
|
||||
@ -9516,12 +9556,12 @@ function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onCl
|
||||
}) {
|
||||
const [newProjectName, setNewProjectName] = useState("");
|
||||
const [newProjectColor, setNewProjectColor] = useState("#3b82f6");
|
||||
const [newProjectIcon, setNewProjectIcon] = useState("📁");
|
||||
const [newProjectIcon, setNewProjectIcon] = useState("folder");
|
||||
const [showNewIconPicker, setShowNewIconPicker] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editName, setEditName] = useState("");
|
||||
const [editColor, setEditColor] = useState("");
|
||||
const [editIcon, setEditIcon] = useState("");
|
||||
const [editIcon, setEditIcon] = useState("folder");
|
||||
const [showEditIconPicker, setShowEditIconPicker] = useState(false);
|
||||
|
||||
const t = {
|
||||
@ -9539,7 +9579,7 @@ function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onCl
|
||||
const createProject = () => {
|
||||
if (!newProjectName.trim()) return;
|
||||
fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newProjectName.trim(), color: newProjectColor, icon: newProjectIcon }) })
|
||||
.then(() => { onProjectsChanged(); setNewProjectName(""); setNewProjectIcon("📁"); });
|
||||
.then(() => { onProjectsChanged(); setNewProjectName(""); setNewProjectIcon("folder"); });
|
||||
};
|
||||
|
||||
const updateProject = (id: string) => {
|
||||
@ -9579,7 +9619,7 @@ function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onCl
|
||||
|
||||
{projects.length === 0 ? (
|
||||
<div style={{ textAlign: "center", padding: "32px 16px" }}>
|
||||
<div style={{ width: "56px", height: "56px", borderRadius: "50%", background: darkMode ? "#2a2a3a" : "#f3f4f6", display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto 12px", fontSize: "24px" }}>📁</div>
|
||||
<div style={{ width: "56px", height: "56px", borderRadius: "50%", background: darkMode ? "#2a2a3a" : "#f3f4f6", display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto 12px" }}><ProjectIcon icon="folder" size={24} color={darkMode ? "#9ca3af" : "#999"} /></div>
|
||||
<p style={{ fontSize: "0.9rem", color: "#aaa", fontStyle: "italic" }}>{t.noProjects}</p>
|
||||
</div>
|
||||
) : (
|
||||
@ -9589,15 +9629,15 @@ function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onCl
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px", width: "100%" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<div style={{ position: "relative" }}>
|
||||
<button onClick={() => setShowEditIconPicker(!showEditIconPicker)} style={{ width: "40px", height: "40px", borderRadius: "10px", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, background: darkMode ? "#1e1e2e" : "#fff", cursor: "pointer", fontSize: "20px", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
{editIcon || "📁"}
|
||||
<button onClick={() => setShowEditIconPicker(!showEditIconPicker)} style={{ width: "40px", height: "40px", borderRadius: "10px", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, background: darkMode ? "#1e1e2e" : "#fff", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
<ProjectIcon icon={editIcon} size={18} color={darkMode ? "#d1d5db" : "#555"} />
|
||||
</button>
|
||||
{showEditIconPicker && (
|
||||
<div style={{ position: "absolute", top: "100%", left: 0, marginTop: "4px", zIndex: 50, background: darkMode ? "#2a2a3a" : "#fff", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, borderRadius: "12px", padding: "10px", boxShadow: "0 8px 24px rgba(0,0,0,0.2)", width: "240px" }}>
|
||||
<div style={{ position: "absolute", top: "100%", left: 0, marginTop: "4px", zIndex: 50, background: darkMode ? "#2a2a3a" : "#fff", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, borderRadius: "12px", padding: "10px", boxShadow: "0 8px 24px rgba(0,0,0,0.2)", width: "280px", maxHeight: "240px", overflowY: "auto" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: "4px" }}>
|
||||
{projectEmojisGlobal.map((emoji) => (
|
||||
<button key={emoji} onClick={() => { setEditIcon(emoji); setShowEditIconPicker(false); }} style={{ width: "28px", height: "28px", border: "none", background: editIcon === emoji ? (darkMode ? "#374151" : "#f3f4f6") : "transparent", borderRadius: "6px", cursor: "pointer", fontSize: "16px", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
{emoji}
|
||||
{projectIconsGlobal.map((item) => (
|
||||
<button key={item.name} onClick={() => { setEditIcon(item.name); setShowEditIconPicker(false); }} style={{ width: "30px", height: "30px", border: "none", background: editIcon === item.name ? (darkMode ? "#374151" : "#e0e7ff") : "transparent", borderRadius: "6px", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", color: darkMode ? "#d1d5db" : "#555" }}>
|
||||
<FontAwesomeIcon icon={item.icon} style={{ fontSize: "14px" }} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@ -9616,11 +9656,11 @@ function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onCl
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span style={{ fontSize: "22px", lineHeight: 1 }}>{p.icon || "📁"}</span>
|
||||
<ProjectIcon icon={p.icon} size={20} color={p.color || "#999"} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<span style={{ fontSize: "0.9rem", fontWeight: 600, display: "block", color: darkMode ? "#f0f0f0" : "#333" }}>{p.name}</span>
|
||||
</div>
|
||||
<button onClick={() => { setEditingId(p.id); setEditName(p.name); setEditColor(p.color || "#999"); setEditIcon(p.icon || "📁"); setShowEditIconPicker(false); }} style={{ padding: "6px", opacity: 0.5, cursor: "pointer", background: "none", border: "none", borderRadius: "6px", color: darkMode ? "#ccc" : "#333" }} title="Edit">
|
||||
<button onClick={() => { setEditingId(p.id); setEditName(p.name); setEditColor(p.color || "#999"); setEditIcon(p.icon || "folder"); setShowEditIconPicker(false); }} style={{ padding: "6px", opacity: 0.5, cursor: "pointer", background: "none", border: "none", borderRadius: "6px", color: darkMode ? "#ccc" : "#333" }} title="Edit">
|
||||
<Pencil size={15} />
|
||||
</button>
|
||||
<button onClick={() => deleteProject(p.id, p.name)} style={{ padding: "6px", opacity: 0.5, cursor: "pointer", color: "#ef4444", background: "none", border: "none", borderRadius: "6px" }} title="Delete">
|
||||
@ -9638,15 +9678,15 @@ function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onCl
|
||||
<p style={{ fontSize: "0.8rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#888", marginBottom: "10px" }}>{t.addProject}</p>
|
||||
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
|
||||
<div style={{ position: "relative" }}>
|
||||
<button onClick={() => setShowNewIconPicker(!showNewIconPicker)} style={{ width: "40px", height: "40px", borderRadius: "10px", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, background: darkMode ? "#2a2a3a" : "#fff", cursor: "pointer", fontSize: "20px", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
{newProjectIcon}
|
||||
<button onClick={() => setShowNewIconPicker(!showNewIconPicker)} style={{ width: "40px", height: "40px", borderRadius: "10px", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, background: darkMode ? "#2a2a3a" : "#fff", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
<ProjectIcon icon={newProjectIcon} size={18} color={darkMode ? "#d1d5db" : "#555"} />
|
||||
</button>
|
||||
{showNewIconPicker && (
|
||||
<div style={{ position: "absolute", bottom: "100%", left: 0, marginBottom: "4px", zIndex: 50, background: darkMode ? "#2a2a3a" : "#fff", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, borderRadius: "12px", padding: "10px", boxShadow: "0 8px 24px rgba(0,0,0,0.2)", width: "240px" }}>
|
||||
<div style={{ position: "absolute", bottom: "100%", left: 0, marginBottom: "4px", zIndex: 50, background: darkMode ? "#2a2a3a" : "#fff", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, borderRadius: "12px", padding: "10px", boxShadow: "0 8px 24px rgba(0,0,0,0.2)", width: "280px", maxHeight: "240px", overflowY: "auto" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: "4px" }}>
|
||||
{projectEmojisGlobal.map((emoji) => (
|
||||
<button key={emoji} onClick={() => { setNewProjectIcon(emoji); setShowNewIconPicker(false); }} style={{ width: "28px", height: "28px", border: "none", background: newProjectIcon === emoji ? (darkMode ? "#374151" : "#f3f4f6") : "transparent", borderRadius: "6px", cursor: "pointer", fontSize: "16px", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
{emoji}
|
||||
{projectIconsGlobal.map((item) => (
|
||||
<button key={item.name} onClick={() => { setNewProjectIcon(item.name); setShowNewIconPicker(false); }} style={{ width: "30px", height: "30px", border: "none", background: newProjectIcon === item.name ? (darkMode ? "#374151" : "#e0e7ff") : "transparent", borderRadius: "6px", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", color: darkMode ? "#d1d5db" : "#555" }}>
|
||||
<FontAwesomeIcon icon={item.icon} style={{ fontSize: "14px" }} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@ -10128,13 +10168,6 @@ function SettingsSidebar({
|
||||
const [showEditProjectIconPicker, setShowEditProjectIconPicker] = useState(false);
|
||||
const [weatherSearchResults, setWeatherSearchResults] = useState<any[]>([]);
|
||||
|
||||
const projectEmojis = [
|
||||
"📁", "📂", "💼", "🎯", "🚀", "⭐", "💡", "🔥", "🎨", "🎵",
|
||||
"📱", "💻", "🌐", "🏠", "🏢", "📊", "📈", "🔧", "⚡", "🎮",
|
||||
"📝", "📖", "🎓", "🧪", "🔬", "🏋️", "🍽️", "✈️", "🌿", "❤️",
|
||||
"🛒", "💰", "🎁", "📸", "🎬", "🧹", "🐾", "🌍", "🔒", "✅",
|
||||
];
|
||||
|
||||
// Fetch lists when the calendar tab is selected
|
||||
useEffect(() => {
|
||||
if (activeTab === "calendar") {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user