feat: collapsible sidebar filter widgets
This commit is contained in:
@@ -412,6 +412,28 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-header.collapsible-header {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin: 0 -6px 8px -6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header.collapsible-header:hover {
|
||||||
|
background-color: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header.collapsible-header.collapsed {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header .collapse-icon {
|
||||||
|
font-size: 9px;
|
||||||
|
margin-right: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-header .clear-filter {
|
.calendar-header .clear-filter {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -523,6 +545,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-header {
|
.filter-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -531,6 +555,43 @@
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-header.collapsible-header {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin: 0 -6px 6px -6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header.collapsible-header:hover {
|
||||||
|
background-color: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header.collapsible-header.collapsed {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header .collapse-icon {
|
||||||
|
font-size: 9px;
|
||||||
|
margin-right: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header .clear-filter {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
margin-left: auto;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header .clear-filter:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-chips {
|
.filter-chips {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ interface CalendarViewProps {
|
|||||||
const CalendarView: React.FC<CalendarViewProps> = ({ onDateSelect, selectedYear, selectedMonth }) => {
|
const CalendarView: React.FC<CalendarViewProps> = ({ onDateSelect, selectedYear, selectedMonth }) => {
|
||||||
const [yearMonthData, setYearMonthData] = useState<{ year: number; month: number; count: number }[]>([]);
|
const [yearMonthData, setYearMonthData] = useState<{ year: number; month: number; count: number }[]>([]);
|
||||||
const [expandedYear, setExpandedYear] = useState<number | null>(null);
|
const [expandedYear, setExpandedYear] = useState<number | null>(null);
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -107,15 +108,23 @@ const CalendarView: React.FC<CalendarViewProps> = ({ onDateSelect, selectedYear,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="calendar-view">
|
<div className="calendar-view">
|
||||||
<div className="calendar-header">
|
<div
|
||||||
|
className={`calendar-header collapsible-header ${isCollapsed ? 'collapsed' : 'expanded'}`}
|
||||||
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||||
|
>
|
||||||
|
<span className="collapse-icon">{isCollapsed ? '▶' : '▼'}</span>
|
||||||
<span>ARCHIVE</span>
|
<span>ARCHIVE</span>
|
||||||
{(selectedYear || selectedMonth !== undefined) && (
|
{(selectedYear || selectedMonth !== undefined) && (
|
||||||
<button className="clear-filter" onClick={() => onDateSelect(0)} title="Clear filter">
|
<button
|
||||||
|
className="clear-filter"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onDateSelect(0); }}
|
||||||
|
title="Clear filter"
|
||||||
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="calendar-years">
|
{!isCollapsed && <div className="calendar-years">
|
||||||
{years.map(year => (
|
{years.map(year => (
|
||||||
<div key={year} className="calendar-year">
|
<div key={year} className="calendar-year">
|
||||||
<div
|
<div
|
||||||
@@ -151,7 +160,7 @@ const CalendarView: React.FC<CalendarViewProps> = ({ onDateSelect, selectedYear,
|
|||||||
{years.length === 0 && (
|
{years.length === 0 && (
|
||||||
<div className="calendar-empty">No posts yet</div>
|
<div className="calendar-empty">No posts yet</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -175,12 +184,30 @@ const FilterPanel: React.FC<FilterPanelProps> = ({
|
|||||||
onTagSelect,
|
onTagSelect,
|
||||||
onCategorySelect,
|
onCategorySelect,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [tagsCollapsed, setTagsCollapsed] = useState(true);
|
||||||
|
const [categoriesCollapsed, setCategoriesCollapsed] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="filter-panel">
|
<div className="filter-panel">
|
||||||
{tags.length > 0 && (
|
{tags.length > 0 && (
|
||||||
<div className="filter-section">
|
<div className="filter-section">
|
||||||
<div className="filter-header">TAGS</div>
|
<div
|
||||||
<div className="filter-chips">
|
className={`filter-header collapsible-header ${tagsCollapsed ? 'collapsed' : 'expanded'}`}
|
||||||
|
onClick={() => setTagsCollapsed(!tagsCollapsed)}
|
||||||
|
>
|
||||||
|
<span className="collapse-icon">{tagsCollapsed ? '▶' : '▼'}</span>
|
||||||
|
<span>TAGS</span>
|
||||||
|
{selectedTags.length > 0 && (
|
||||||
|
<button
|
||||||
|
className="clear-filter"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onTagSelect([]); }}
|
||||||
|
title="Clear tags"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!tagsCollapsed && <div className="filter-chips">
|
||||||
{tags.map(tag => {
|
{tags.map(tag => {
|
||||||
const color = tagColors.get(tag);
|
const color = tagColors.get(tag);
|
||||||
const hasColor = !!color;
|
const hasColor = !!color;
|
||||||
@@ -208,13 +235,28 @@ const FilterPanel: React.FC<FilterPanelProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{categories.length > 0 && (
|
{categories.length > 0 && (
|
||||||
<div className="filter-section">
|
<div className="filter-section">
|
||||||
<div className="filter-header">CATEGORIES</div>
|
<div
|
||||||
<div className="filter-chips">
|
className={`filter-header collapsible-header ${categoriesCollapsed ? 'collapsed' : 'expanded'}`}
|
||||||
|
onClick={() => setCategoriesCollapsed(!categoriesCollapsed)}
|
||||||
|
>
|
||||||
|
<span className="collapse-icon">{categoriesCollapsed ? '▶' : '▼'}</span>
|
||||||
|
<span>CATEGORIES</span>
|
||||||
|
{selectedCategories.length > 0 && (
|
||||||
|
<button
|
||||||
|
className="clear-filter"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onCategorySelect([]); }}
|
||||||
|
title="Clear categories"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!categoriesCollapsed && <div className="filter-chips">
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
<button
|
<button
|
||||||
key={cat}
|
key={cat}
|
||||||
@@ -230,7 +272,7 @@ const FilterPanel: React.FC<FilterPanelProps> = ({
|
|||||||
{cat}
|
{cat}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -247,6 +289,7 @@ interface MediaCalendarViewProps {
|
|||||||
const MediaCalendarView: React.FC<MediaCalendarViewProps> = ({ onDateSelect, selectedYear, selectedMonth }) => {
|
const MediaCalendarView: React.FC<MediaCalendarViewProps> = ({ onDateSelect, selectedYear, selectedMonth }) => {
|
||||||
const [yearMonthData, setYearMonthData] = useState<{ year: number; month: number; count: number }[]>([]);
|
const [yearMonthData, setYearMonthData] = useState<{ year: number; month: number; count: number }[]>([]);
|
||||||
const [expandedYear, setExpandedYear] = useState<number | null>(null);
|
const [expandedYear, setExpandedYear] = useState<number | null>(null);
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -270,15 +313,23 @@ const MediaCalendarView: React.FC<MediaCalendarViewProps> = ({ onDateSelect, sel
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="calendar-view">
|
<div className="calendar-view">
|
||||||
<div className="calendar-header">
|
<div
|
||||||
|
className={`calendar-header collapsible-header ${isCollapsed ? 'collapsed' : 'expanded'}`}
|
||||||
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||||
|
>
|
||||||
|
<span className="collapse-icon">{isCollapsed ? '▶' : '▼'}</span>
|
||||||
<span>ARCHIVE</span>
|
<span>ARCHIVE</span>
|
||||||
{(selectedYear || selectedMonth !== undefined) && (
|
{(selectedYear || selectedMonth !== undefined) && (
|
||||||
<button className="clear-filter" onClick={() => onDateSelect(0)} title="Clear filter">
|
<button
|
||||||
|
className="clear-filter"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onDateSelect(0); }}
|
||||||
|
title="Clear filter"
|
||||||
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="calendar-years">
|
{!isCollapsed && <div className="calendar-years">
|
||||||
{years.map(year => (
|
{years.map(year => (
|
||||||
<div key={year} className="calendar-year">
|
<div key={year} className="calendar-year">
|
||||||
<div
|
<div
|
||||||
@@ -314,7 +365,7 @@ const MediaCalendarView: React.FC<MediaCalendarViewProps> = ({ onDateSelect, sel
|
|||||||
{years.length === 0 && (
|
{years.length === 0 && (
|
||||||
<div className="calendar-empty">No media yet</div>
|
<div className="calendar-empty">No media yet</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -333,12 +384,29 @@ const MediaFilterPanel: React.FC<MediaFilterPanelProps> = ({
|
|||||||
selectedTags,
|
selectedTags,
|
||||||
onTagSelect,
|
onTagSelect,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [tagsCollapsed, setTagsCollapsed] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="filter-panel">
|
<div className="filter-panel">
|
||||||
{tags.length > 0 && (
|
{tags.length > 0 && (
|
||||||
<div className="filter-section">
|
<div className="filter-section">
|
||||||
<div className="filter-header">TAGS</div>
|
<div
|
||||||
<div className="filter-chips">
|
className={`filter-header collapsible-header ${tagsCollapsed ? 'collapsed' : 'expanded'}`}
|
||||||
|
onClick={() => setTagsCollapsed(!tagsCollapsed)}
|
||||||
|
>
|
||||||
|
<span className="collapse-icon">{tagsCollapsed ? '▶' : '▼'}</span>
|
||||||
|
<span>TAGS</span>
|
||||||
|
{selectedTags.length > 0 && (
|
||||||
|
<button
|
||||||
|
className="clear-filter"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onTagSelect([]); }}
|
||||||
|
title="Clear tags"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!tagsCollapsed && <div className="filter-chips">
|
||||||
{tags.map(tag => {
|
{tags.map(tag => {
|
||||||
const color = tagColors.get(tag);
|
const color = tagColors.get(tag);
|
||||||
const hasColor = !!color;
|
const hasColor = !!color;
|
||||||
@@ -366,7 +434,7 @@ const MediaFilterPanel: React.FC<MediaFilterPanelProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user