feat: move someday controls to horizontal tab bar, remove label column
Replace the someday-label-column with a unified horizontal tab bar containing: collapse chevron, "any day" label, new list icon (ListPlus), new tab icon (FolderPlus), divider, then tab buttons. All icons have tooltips. Inline input for creating new tabs directly in the bar. v1.27.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f05a7c9035
commit
8959aa7f5a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.26.2",
|
"version": "1.27.0",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1509,6 +1509,33 @@ h3 {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.someday-bar-icon-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--weekly-text-light, #999);
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.someday-bar-icon-btn:hover {
|
||||||
|
background: var(--weekly-hover-bg, rgba(0, 0, 0, 0.06));
|
||||||
|
color: var(--weekly-text, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.someday-tabs-bar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--weekly-border, #e5e7eb);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Someday Tab Buttons (horizontal) */
|
/* Someday Tab Buttons (horizontal) */
|
||||||
.someday-tab-btn-h {
|
.someday-tab-btn-h {
|
||||||
background: none;
|
background: none;
|
||||||
|
|||||||
@ -54,6 +54,8 @@ import {
|
|||||||
PanelLeftOpen,
|
PanelLeftOpen,
|
||||||
Type,
|
Type,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
|
FolderPlus,
|
||||||
|
ListPlus,
|
||||||
Circle,
|
Circle,
|
||||||
X,
|
X,
|
||||||
Cable,
|
Cable,
|
||||||
@ -256,6 +258,7 @@ const translations: Record<string, any> = {
|
|||||||
confirmPassword: "Confirm Password",
|
confirmPassword: "Confirm Password",
|
||||||
someday: "SOMEDAY",
|
someday: "SOMEDAY",
|
||||||
lists: "Lists",
|
lists: "Lists",
|
||||||
|
newList: "New list",
|
||||||
allTabs: "All",
|
allTabs: "All",
|
||||||
newTab: "New tab",
|
newTab: "New tab",
|
||||||
newTabName: "New tab name:",
|
newTabName: "New tab name:",
|
||||||
@ -449,6 +452,7 @@ const translations: Record<string, any> = {
|
|||||||
confirmPassword: "Passwort bestätigen",
|
confirmPassword: "Passwort bestätigen",
|
||||||
someday: "IRGENDWANN",
|
someday: "IRGENDWANN",
|
||||||
lists: "Listen",
|
lists: "Listen",
|
||||||
|
newList: "Neue Liste",
|
||||||
allTabs: "Alle",
|
allTabs: "Alle",
|
||||||
newTab: "Neuer Tab",
|
newTab: "Neuer Tab",
|
||||||
newTabName: "Neuer Tab-Name:",
|
newTabName: "Neuer Tab-Name:",
|
||||||
@ -641,6 +645,7 @@ const translations: Record<string, any> = {
|
|||||||
confirmPassword: "Confirmer le mot de passe",
|
confirmPassword: "Confirmer le mot de passe",
|
||||||
someday: "UN JOUR",
|
someday: "UN JOUR",
|
||||||
lists: "Listes",
|
lists: "Listes",
|
||||||
|
newList: "Nouvelle liste",
|
||||||
allTabs: "Tous",
|
allTabs: "Tous",
|
||||||
newTab: "Nouvel onglet",
|
newTab: "Nouvel onglet",
|
||||||
newTabName: "Nom du nouvel onglet :",
|
newTabName: "Nom du nouvel onglet :",
|
||||||
@ -833,6 +838,7 @@ const translations: Record<string, any> = {
|
|||||||
confirmPassword: "Confirmar contraseña",
|
confirmPassword: "Confirmar contraseña",
|
||||||
someday: "ALGÚN DÍA",
|
someday: "ALGÚN DÍA",
|
||||||
lists: "Listas",
|
lists: "Listas",
|
||||||
|
newList: "Nueva lista",
|
||||||
allTabs: "Todas",
|
allTabs: "Todas",
|
||||||
newTab: "Nueva pestaña",
|
newTab: "Nueva pestaña",
|
||||||
newTabName: "Nombre de nueva pestaña:",
|
newTabName: "Nombre de nueva pestaña:",
|
||||||
@ -1025,6 +1031,7 @@ const translations: Record<string, any> = {
|
|||||||
confirmPassword: "Conferma password",
|
confirmPassword: "Conferma password",
|
||||||
someday: "UN GIORNO",
|
someday: "UN GIORNO",
|
||||||
lists: "Liste",
|
lists: "Liste",
|
||||||
|
newList: "Nuova lista",
|
||||||
allTabs: "Tutte",
|
allTabs: "Tutte",
|
||||||
newTab: "Nuova scheda",
|
newTab: "Nuova scheda",
|
||||||
newTabName: "Nome nuova scheda:",
|
newTabName: "Nome nuova scheda:",
|
||||||
@ -1461,6 +1468,8 @@ export default function WeeklyView() {
|
|||||||
const [renamingTabValue, setRenamingTabValue] = useState("");
|
const [renamingTabValue, setRenamingTabValue] = useState("");
|
||||||
const [newTabForListId, setNewTabForListId] = useState<string | null>(null);
|
const [newTabForListId, setNewTabForListId] = useState<string | null>(null);
|
||||||
const [newTabNameValue, setNewTabNameValue] = useState("");
|
const [newTabNameValue, setNewTabNameValue] = useState("");
|
||||||
|
const [creatingNewTab, setCreatingNewTab] = useState(false);
|
||||||
|
const [creatingNewTabName, setCreatingNewTabName] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
@ -6362,203 +6371,134 @@ export default function WeeklyView() {
|
|||||||
ref={somedaySectionRefCb}
|
ref={somedaySectionRefCb}
|
||||||
className={`weekly-someday ${somedayExpanded ? "expanded" : "collapsed"} transition-colors duration-200`}
|
className={`weekly-someday ${somedayExpanded ? "expanded" : "collapsed"} transition-colors duration-200`}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", flexDirection: "row", maxWidth: "100%", width: "100%" }}>
|
{/* Someday tabs bar */}
|
||||||
{showTimeGrid && (
|
<div className="someday-tabs-bar">
|
||||||
<div
|
<div
|
||||||
className="someday-label-column"
|
onClick={() => setSomedayExpanded(!somedayExpanded)}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
marginRight: "4px",
|
||||||
|
}}
|
||||||
|
title={somedayExpanded ? "Collapse" : "Expand"}
|
||||||
|
>
|
||||||
|
<ChevronRight
|
||||||
|
size={14}
|
||||||
style={{
|
style={{
|
||||||
width: "50px",
|
transform: somedayExpanded ? "rotate(90deg)" : "rotate(0deg)",
|
||||||
|
transition: "transform 0.15s",
|
||||||
|
color: "var(--weekly-text-light)",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
borderRight: "1px solid var(--weekly-border)",
|
|
||||||
padding: "4px 4px",
|
|
||||||
gap: "4px",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<div
|
<span style={{
|
||||||
onClick={() => setSomedayExpanded(!somedayExpanded)}
|
fontSize: "0.7rem",
|
||||||
style={{
|
fontWeight: 600,
|
||||||
cursor: "pointer",
|
color: "var(--weekly-text-light)",
|
||||||
display: "flex",
|
textTransform: "uppercase",
|
||||||
flexDirection: "column",
|
letterSpacing: "0.05em",
|
||||||
alignItems: "center",
|
whiteSpace: "nowrap",
|
||||||
gap: "0px",
|
}}>
|
||||||
}}
|
any day
|
||||||
title={somedayExpanded ? "Collapse" : "Expand"}
|
</span>
|
||||||
>
|
</div>
|
||||||
<span
|
<button
|
||||||
style={{
|
className="someday-bar-icon-btn"
|
||||||
fontSize: "0.6rem",
|
onClick={() => handleStartAddSomedayList()}
|
||||||
fontWeight: 600,
|
title={t.newList}
|
||||||
color: "var(--weekly-text-light)",
|
>
|
||||||
textTransform: "uppercase",
|
<ListPlus size={14} />
|
||||||
letterSpacing: "0.05em",
|
</button>
|
||||||
lineHeight: 1.1,
|
<button
|
||||||
textAlign: "center",
|
className="someday-bar-icon-btn"
|
||||||
}}
|
onClick={() => { setCreatingNewTab(true); setCreatingNewTabName(""); }}
|
||||||
>
|
title={t.newTab}
|
||||||
any day
|
>
|
||||||
</span>
|
<FolderPlus size={14} />
|
||||||
<span
|
</button>
|
||||||
style={{
|
{creatingNewTab && (
|
||||||
fontSize: "0.55rem",
|
<input
|
||||||
color: "#888",
|
className="someday-tab-rename-input"
|
||||||
marginTop: "1px",
|
value={creatingNewTabName}
|
||||||
}}
|
onChange={(e) => setCreatingNewTabName(e.target.value)}
|
||||||
>
|
onBlur={() => {
|
||||||
{somedayLists.length}{" "}
|
if (creatingNewTabName.trim()) {
|
||||||
{translations[language]?.lists || translations["en"].lists}
|
setSomedayTab(creatingNewTabName.trim());
|
||||||
</span>
|
}
|
||||||
</div>
|
setCreatingNewTab(false);
|
||||||
<button
|
}}
|
||||||
className="someday-add-btn dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:border-white transition-colors"
|
onKeyDown={(e) => {
|
||||||
onClick={(e) => {
|
if (e.key === "Enter") e.currentTarget.blur();
|
||||||
e.stopPropagation();
|
if (e.key === "Escape") setCreatingNewTab(false);
|
||||||
handleStartAddSomedayList();
|
}}
|
||||||
}}
|
placeholder={t.newTab}
|
||||||
title="Add new list"
|
autoFocus
|
||||||
style={{
|
|
||||||
background: "none",
|
|
||||||
border: "1px dashed #ccc",
|
|
||||||
borderRadius: "50%",
|
|
||||||
width: "18px",
|
|
||||||
height: "18px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#888",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
lineHeight: 1,
|
|
||||||
padding: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!showTimeGrid && (
|
|
||||||
<div
|
|
||||||
className="someday-label-column"
|
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
fontSize: "0.75rem",
|
||||||
alignItems: "center",
|
border: "1px solid var(--weekly-border)",
|
||||||
padding: "4px 8px",
|
borderRadius: "3px",
|
||||||
gap: "6px",
|
padding: "2px 6px",
|
||||||
|
background: "var(--weekly-bg)",
|
||||||
|
color: "var(--weekly-text)",
|
||||||
|
width: "80px",
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<div
|
|
||||||
onClick={() => setSomedayExpanded(!somedayExpanded)}
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "6px",
|
|
||||||
}}
|
|
||||||
title={somedayExpanded ? "Collapse" : "Expand"}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: "0.6rem",
|
|
||||||
fontWeight: 600,
|
|
||||||
color: "var(--weekly-text-light)",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
letterSpacing: "0.05em",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
any day
|
|
||||||
</span>
|
|
||||||
<span style={{ fontSize: "0.55rem", color: "#888" }}>
|
|
||||||
{somedayLists.length}{" "}
|
|
||||||
{translations[language]?.lists || translations["en"].lists}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="someday-add-btn dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:border-white transition-colors"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleStartAddSomedayList();
|
|
||||||
}}
|
|
||||||
title="Add new list"
|
|
||||||
style={{
|
|
||||||
background: "none",
|
|
||||||
border: "1px dashed #ccc",
|
|
||||||
borderRadius: "50%",
|
|
||||||
width: "18px",
|
|
||||||
height: "18px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#888",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
lineHeight: 1,
|
|
||||||
padding: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<div ref={somedayGridRef} style={{ flex: 1, minWidth: 0, overflowX: "auto" }}>
|
<div className="someday-tabs-bar-divider" />
|
||||||
{/* Horizontal tab bar above grid */}
|
<button
|
||||||
{somedayExpanded && somedayTabs.length > 0 && (
|
className={`someday-tab-btn-h ${activeSomedayTab === null ? "active" : ""}`}
|
||||||
<div className="someday-tabs-bar">
|
onClick={() => setSomedayTab(null)}
|
||||||
|
>{t.allTabs} <span className="someday-tab-count">{somedayLists.length}</span></button>
|
||||||
|
{somedayTabs.map(tab => (
|
||||||
|
editingTabName === tab ? (
|
||||||
|
<input
|
||||||
|
key={tab}
|
||||||
|
className="someday-tab-rename-input"
|
||||||
|
value={renamingTabValue}
|
||||||
|
onChange={(e) => setRenamingTabValue(e.target.value)}
|
||||||
|
onBlur={() => {
|
||||||
|
renameTab(tab, renamingTabValue);
|
||||||
|
setEditingTabName(null);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") e.currentTarget.blur();
|
||||||
|
if (e.key === "Escape") setEditingTabName(null);
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
style={{
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
border: "1px solid var(--weekly-border)",
|
||||||
|
borderRadius: "3px",
|
||||||
|
padding: "2px 6px",
|
||||||
|
background: "var(--weekly-bg)",
|
||||||
|
color: "var(--weekly-text)",
|
||||||
|
width: "80px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div key={tab} className="someday-tab-wrapper-h">
|
||||||
<button
|
<button
|
||||||
className={`someday-tab-btn-h ${activeSomedayTab === null ? "active" : ""}`}
|
className={`someday-tab-btn-h ${activeSomedayTab === tab ? "active" : ""}`}
|
||||||
onClick={() => setSomedayTab(null)}
|
onClick={() => setSomedayTab(tab)}
|
||||||
>{t.allTabs} <span className="someday-tab-count">{somedayLists.length}</span></button>
|
onDoubleClick={() => {
|
||||||
{somedayTabs.map(tab => (
|
setEditingTabName(tab);
|
||||||
editingTabName === tab ? (
|
setRenamingTabValue(tab);
|
||||||
<input
|
}}
|
||||||
key={tab}
|
title={t.renameTab}
|
||||||
className="someday-tab-rename-input"
|
>{tab} <span className="someday-tab-count">{somedayLists.filter(l => l.tab === tab).length}</span></button>
|
||||||
value={renamingTabValue}
|
<button
|
||||||
onChange={(e) => setRenamingTabValue(e.target.value)}
|
className="someday-tab-dissolve-h"
|
||||||
onBlur={() => {
|
onClick={(e) => { e.stopPropagation(); dissolveTab(tab); }}
|
||||||
renameTab(tab, renamingTabValue);
|
title={t.dissolveTab}
|
||||||
setEditingTabName(null);
|
><X size={10} /></button>
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") e.currentTarget.blur();
|
|
||||||
if (e.key === "Escape") setEditingTabName(null);
|
|
||||||
}}
|
|
||||||
autoFocus
|
|
||||||
style={{
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
border: "1px solid var(--weekly-border)",
|
|
||||||
borderRadius: "3px",
|
|
||||||
padding: "2px 6px",
|
|
||||||
background: "var(--weekly-bg)",
|
|
||||||
color: "var(--weekly-text)",
|
|
||||||
width: "80px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div key={tab} className="someday-tab-wrapper-h">
|
|
||||||
<button
|
|
||||||
className={`someday-tab-btn-h ${activeSomedayTab === tab ? "active" : ""}`}
|
|
||||||
onClick={() => setSomedayTab(tab)}
|
|
||||||
onDoubleClick={() => {
|
|
||||||
setEditingTabName(tab);
|
|
||||||
setRenamingTabValue(tab);
|
|
||||||
}}
|
|
||||||
title={t.renameTab}
|
|
||||||
>{tab} <span className="someday-tab-count">{somedayLists.filter(l => l.tab === tab).length}</span></button>
|
|
||||||
<button
|
|
||||||
className="someday-tab-dissolve-h"
|
|
||||||
onClick={(e) => { e.stopPropagation(); dissolveTab(tab); }}
|
|
||||||
title={t.dissolveTab}
|
|
||||||
><X size={10} /></button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row", maxWidth: "100%", width: "100%" }}>
|
||||||
|
<div ref={somedayGridRef} style={{ flex: 1, minWidth: 0, overflowX: "auto" }}>
|
||||||
{somedayExpanded && (
|
{somedayExpanded && (
|
||||||
<div
|
<div
|
||||||
className={`weekly-someday-lists-grid cols-${Math.min(7, Math.max(1, viewDays))}`}
|
className={`weekly-someday-lists-grid cols-${Math.min(7, Math.max(1, viewDays))}`}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user