fix: look for filter back to old look

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-26 07:42:11 +02:00
parent fd1b8e7bd4
commit 05ef128c71
5 changed files with 440 additions and 131 deletions

View File

@@ -97,14 +97,38 @@ defmodule BDS.Desktop.ShellLive do
end
def handle_event("toggle_sidebar_filters", _params, socket) do
view_id = Atom.to_string(socket.assigns.workbench.active_view)
sidebar_filter_panels =
Map.update(socket.assigns.sidebar_filter_panels, view_id, false, &not &1)
socket =
put_sidebar_filter_panel_state(socket, fn state ->
if state.visible do
%{state | visible: false}
else
%{default_sidebar_filter_panel_state() | visible: true}
end
end)
{:noreply,
socket
|> assign(:sidebar_filter_panels, sidebar_filter_panels)
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("toggle_sidebar_archive", _params, socket) do
{:noreply,
socket
|> put_sidebar_filter_panel_state(fn state -> %{state | archive_collapsed: not state.archive_collapsed} end)
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("toggle_sidebar_tags", _params, socket) do
{:noreply,
socket
|> put_sidebar_filter_panel_state(fn state -> %{state | tags_collapsed: not state.tags_collapsed} end)
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("toggle_sidebar_categories", _params, socket) do
{:noreply,
socket
|> put_sidebar_filter_panel_state(fn state -> %{state | categories_collapsed: not state.categories_collapsed} end)
|> reload_shell(socket.assigns.workbench)}
end
@@ -122,6 +146,20 @@ defmodule BDS.Desktop.ShellLive do
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("clear_sidebar_tags", _params, socket) do
{:noreply,
socket
|> put_sidebar_filters(fn filters -> Map.put(filters, :tags, []) end)
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("clear_sidebar_categories", _params, socket) do
{:noreply,
socket
|> put_sidebar_filters(fn filters -> Map.put(filters, :categories, []) end)
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("toggle_sidebar_tag", %{"tag" => tag}, socket) do
{:noreply,
socket
@@ -136,9 +174,31 @@ defmodule BDS.Desktop.ShellLive do
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("select_sidebar_year", %{"year" => year}, socket) do
parsed_year = parse_optional_integer(year)
{:noreply,
socket
|> put_sidebar_filter_panel_state(fn state ->
%{state |
archive_collapsed: false,
expanded_year: if(state.expanded_year == parsed_year, do: nil, else: parsed_year)
}
end)
|> put_sidebar_filters(fn filters ->
filters
|> Map.put(:year, parsed_year)
|> Map.put(:month, nil)
end)
|> reload_shell(socket.assigns.workbench)}
end
def handle_event("select_sidebar_month", %{"year" => year, "month" => month}, socket) do
{:noreply,
socket
|> put_sidebar_filter_panel_state(fn state ->
%{state | archive_collapsed: false, expanded_year: parse_optional_integer(year)}
end)
|> put_sidebar_filters(fn filters ->
filters
|> Map.put(:year, parse_optional_integer(year))
@@ -150,6 +210,7 @@ defmodule BDS.Desktop.ShellLive do
def handle_event("clear_sidebar_month", _params, socket) do
{:noreply,
socket
|> put_sidebar_filter_panel_state(fn state -> %{state | archive_collapsed: false} end)
|> put_sidebar_filters(fn filters -> filters |> Map.put(:year, nil) |> Map.put(:month, nil) end)
|> reload_shell(socket.assigns.workbench)}
end
@@ -346,34 +407,29 @@ defmodule BDS.Desktop.ShellLive do
assigns
|> assign(:sidebar_filters_config, filters)
|> assign(:selected_filters, selected)
|> assign(:filter_panel_visible, Map.get(filters, :filter_panel_visible, true))
|> assign(:filter_panel_visible, Map.get(filters, :filter_panel_visible, false))
|> assign(:archive_collapsed, Map.get(filters, :archive_collapsed, true))
|> assign(:tags_collapsed, Map.get(filters, :tags_collapsed, true))
|> assign(:categories_collapsed, Map.get(filters, :categories_collapsed, true))
|> assign(:expanded_year, Map.get(filters, :expanded_year))
|> assign(:year_groups, group_year_month_counts(Map.get(filters, :year_month_counts, [])))
~H"""
<form class="search-box" data-testid="sidebar-search-form" phx-change="update_sidebar_search">
<form class="search-box" data-testid="sidebar-search-form" phx-change="update_sidebar_search" phx-submit="update_sidebar_search">
<input
type="text"
name="sidebar_filters[search]"
value={Map.get(@selected_filters, :search) || ""}
placeholder={translated(@sidebar_filters_config.search_placeholder)}
/>
<button type="submit" title={translated("sidebar.search")}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
<path d="M15.7 14.3l-4.2-4.2c-.2-.2-.5-.3-.8-.3.9-1.1 1.5-2.5 1.5-4C12.2 2.6 9.6 0 6.4 0S.6 2.6.6 5.8s2.6 5.8 5.8 5.8c1.5 0 2.9-.5 4-1.4 0 .3.1.6.3.8l4.2 4.2c.2.2.5.3.7.3s.5-.1.7-.3c.4-.4.4-1 0-1.4zm-9.3-4c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5z"/>
</svg>
</button>
<%= if Map.get(@selected_filters, :search) do %>
<button class="clear-search" data-testid="sidebar-clear-search" type="button" phx-click="clear_sidebar_search">×</button>
<% end %>
<div class="sidebar-actions">
<button
class={[
"sidebar-action",
if(@filter_panel_visible, do: "active")
]}
data-testid="sidebar-filter-toggle"
type="button"
phx-click="toggle_sidebar_filters"
aria-label={translated(@sidebar_filters_config.toggle_filters_label)}
title={translated(@sidebar_filters_config.toggle_filters_label)}
>
</button>
</div>
</form>
<%= if Map.get(@sidebar_filters_config, :has_active_filters) do %>
@@ -388,79 +444,144 @@ defmodule BDS.Desktop.ShellLive do
<% end %>
<%= if @filter_panel_visible do %>
<%= if Enum.any?(Map.get(@sidebar_filters_config, :year_month_counts, [])) do %>
<%= if Enum.any?(@year_groups) do %>
<div class="calendar-view">
<div class="calendar-header">
<div
class={[
"calendar-header",
"collapsible-header",
if(@archive_collapsed, do: "collapsed", else: "expanded")
]}
data-testid="sidebar-filter-archive-header"
phx-click="toggle_sidebar_archive"
>
<span class="collapse-icon"><%= if @archive_collapsed, do: "▶", else: "▼" %></span>
<span><%= translated(@sidebar_filters_config.archive_label) %></span>
<%= if Map.get(@selected_filters, :year) do %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_month">×</button>
<% end %>
</div>
<div class="calendar-years">
<%= for entry <- Map.get(@sidebar_filters_config, :year_month_counts, []) do %>
<button
class={[
"calendar-month",
if(Map.get(@selected_filters, :year) == entry.year and Map.get(@selected_filters, :month) == entry.month, do: "selected")
]}
data-testid="sidebar-filter-month"
type="button"
phx-click="select_sidebar_month"
phx-value-year={entry.year}
phx-value-month={entry.month}
>
<span class="month-label"><%= ShellData.format_dashboard_month(entry.year, entry.month) %> <%= entry.year %></span>
<span class="month-count"><%= entry.count %></span>
</button>
<button class="clear-filter" type="button" phx-click="clear_sidebar_month" phx-stop-propagation>✕</button>
<% end %>
</div>
<%= unless @archive_collapsed do %>
<div class="calendar-years">
<%= for year_group <- @year_groups do %>
<div class="calendar-year">
<div
class={[
"calendar-year-header",
if(Map.get(@selected_filters, :year) == year_group.year and is_nil(Map.get(@selected_filters, :month)), do: "selected")
]}
phx-click="select_sidebar_year"
phx-value-year={year_group.year}
>
<span class="expand-icon"><%= if @expanded_year == year_group.year, do: "▼", else: "▶" %></span>
<span class="year-label"><%= year_group.year %></span>
<span class="year-count"><%= year_group.count %></span>
</div>
<%= if @expanded_year == year_group.year do %>
<div class="calendar-months">
<%= for month_entry <- year_group.months do %>
<button
class={[
"calendar-month",
if(Map.get(@selected_filters, :year) == year_group.year and Map.get(@selected_filters, :month) == month_entry.month, do: "selected")
]}
data-testid="sidebar-filter-month"
type="button"
phx-click="select_sidebar_month"
phx-value-year={year_group.year}
phx-value-month={month_entry.month}
>
<span class="month-label"><%= ShellData.format_dashboard_month(year_group.year, month_entry.month) %></span>
<span class="month-count"><%= month_entry.count %></span>
</button>
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
<% end %>
<div class="filter-panel">
<%= if Enum.any?(Map.get(@sidebar_filters_config, :available_tags, [])) do %>
<section class="filter-section">
<div class="filter-header"><%= translated(@sidebar_filters_config.tags_label) %></div>
<div class="filter-chips">
<%= for tag <- Map.get(@sidebar_filters_config, :available_tags, []) do %>
<button
class={[
"filter-chip",
if(tag in Map.get(@selected_filters, :tags, []), do: "active")
]}
data-testid="sidebar-filter-tag"
data-filter-tag={tag}
type="button"
phx-click="toggle_sidebar_tag"
phx-value-tag={tag}
>
<%= tag %>
</button>
<div
class={[
"filter-header",
"collapsible-header",
if(@tags_collapsed, do: "collapsed", else: "expanded")
]}
data-testid="sidebar-filter-tags-header"
phx-click="toggle_sidebar_tags"
>
<span class="collapse-icon"><%= if @tags_collapsed, do: "▶", else: "▼" %></span>
<span><%= translated(@sidebar_filters_config.tags_label) %></span>
<%= if Enum.any?(Map.get(@selected_filters, :tags, [])) do %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_tags" phx-stop-propagation title={translated(@sidebar_filters_config.clear_tags_label)}>✕</button>
<% end %>
</div>
<%= unless @tags_collapsed do %>
<div class="filter-chips">
<%= for tag <- Map.get(@sidebar_filters_config, :available_tags, []) do %>
<button
class={[
"filter-chip",
if(tag in Map.get(@selected_filters, :tags, []), do: "active"),
if(sidebar_filter_tag_color(@sidebar_filters_config, tag), do: "has-color")
]}
style={sidebar_filter_chip_style(@sidebar_filters_config, tag)}
data-testid="sidebar-filter-tag"
data-filter-tag={tag}
type="button"
phx-click="toggle_sidebar_tag"
phx-value-tag={tag}
>
<%= tag %>
</button>
<% end %>
</div>
<% end %>
</section>
<% end %>
<%= if Enum.any?(Map.get(@sidebar_filters_config, :available_categories, [])) do %>
<section class="filter-section">
<div class="filter-header"><%= translated(@sidebar_filters_config.categories_label) %></div>
<div class="filter-chips">
<%= for category <- Map.get(@sidebar_filters_config, :available_categories, []) do %>
<button
class={[
"filter-chip",
if(category in Map.get(@selected_filters, :categories, []), do: "active")
]}
data-testid="sidebar-filter-category"
data-filter-category={category}
type="button"
phx-click="toggle_sidebar_category"
phx-value-category={category}
>
<%= category %>
</button>
<div
class={[
"filter-header",
"collapsible-header",
if(@categories_collapsed, do: "collapsed", else: "expanded")
]}
data-testid="sidebar-filter-categories-header"
phx-click="toggle_sidebar_categories"
>
<span class="collapse-icon"><%= if @categories_collapsed, do: "▶", else: "▼" %></span>
<span><%= translated(@sidebar_filters_config.categories_label) %></span>
<%= if Enum.any?(Map.get(@selected_filters, :categories, [])) do %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_categories" phx-stop-propagation title={translated(@sidebar_filters_config.clear_categories_label)}>✕</button>
<% end %>
</div>
<%= unless @categories_collapsed do %>
<div class="filter-chips">
<%= for category <- Map.get(@sidebar_filters_config, :available_categories, []) do %>
<button
class={[
"filter-chip",
if(category in Map.get(@selected_filters, :categories, []), do: "active")
]}
data-testid="sidebar-filter-category"
data-filter-category={category}
type="button"
phx-click="toggle_sidebar_category"
phx-value-category={category}
>
<%= category %>
</button>
<% end %>
</div>
<% end %>
</section>
<% end %>
</div>
@@ -925,13 +1046,46 @@ defmodule BDS.Desktop.ShellLive do
filters = Map.get(sidebar_data, :filters)
if is_map(filters) and Map.get(filters, :enabled) do
filter_panel_visible = Map.get(socket.assigns.sidebar_filter_panels, view_id, true)
Map.put(sidebar_data, :filters, Map.put(filters, :filter_panel_visible, filter_panel_visible))
panel_state = sidebar_filter_panel_state(socket, view_id)
Map.put(sidebar_data, :filters, Map.merge(filters, %{
filter_panel_visible: panel_state.visible,
archive_collapsed: panel_state.archive_collapsed,
tags_collapsed: panel_state.tags_collapsed,
categories_collapsed: panel_state.categories_collapsed,
expanded_year: panel_state.expanded_year
}))
else
sidebar_data
end
end
defp sidebar_filter_panel_state(socket, view_id) do
default_state = default_sidebar_filter_panel_state()
case Map.get(socket.assigns.sidebar_filter_panels, view_id) do
state when is_map(state) -> Map.merge(default_state, state)
visible when is_boolean(visible) -> Map.put(default_state, :visible, visible)
_other -> default_state
end
end
defp put_sidebar_filter_panel_state(socket, updater) do
view_id = Atom.to_string(socket.assigns.workbench.active_view)
state = socket |> sidebar_filter_panel_state(view_id) |> updater.()
assign(socket, :sidebar_filter_panels, Map.put(socket.assigns.sidebar_filter_panels, view_id, state))
end
defp default_sidebar_filter_panel_state do
%{
visible: false,
archive_collapsed: true,
tags_collapsed: true,
categories_collapsed: true,
expanded_year: nil
}
end
defp current_sidebar_filters(socket, view_id) do
socket.assigns.sidebar_filters_by_view
|> Map.get(view_id, %{})
@@ -970,6 +1124,65 @@ defmodule BDS.Desktop.ShellLive do
Map.put(filters, key, next_values)
end
defp group_year_month_counts(entries) do
entries
|> Enum.group_by(& &1.year)
|> Enum.map(fn {year, months} ->
%{
year: year,
count: Enum.reduce(months, 0, fn entry, acc -> acc + (entry.count || 0) end),
months: Enum.sort_by(months, &-&1.month)
}
end)
|> Enum.sort_by(&-&1.year)
end
defp sidebar_filter_tag_color(filters_config, tag) do
filters_config
|> Map.get(:available_tag_colors, %{})
|> Map.get(tag)
|> normalize_sidebar_filter_color()
end
defp sidebar_filter_chip_style(filters_config, tag) do
case sidebar_filter_tag_color(filters_config, tag) do
nil -> nil
color -> "background-color: #{color}; color: #{sidebar_filter_contrast_color(color)}; border-color: #{color};"
end
end
defp normalize_sidebar_filter_color(nil), do: nil
defp normalize_sidebar_filter_color(""), do: nil
defp normalize_sidebar_filter_color("#" <> rest = color) when byte_size(rest) == 6 do
if String.match?(rest, ~r/\A[0-9a-fA-F]{6}\z/), do: color, else: nil
end
defp normalize_sidebar_filter_color(_color), do: nil
defp sidebar_filter_contrast_color("#" <> rgb) do
<<r::binary-size(2), g::binary-size(2), b::binary-size(2)>> = rgb
{red, _} = Integer.parse(r, 16)
{green, _} = Integer.parse(g, 16)
{blue, _} = Integer.parse(b, 16)
luminance = (red * 299 + green * 587 + blue * 114) / 1000
if luminance > 150, do: "#1e1e1e", else: "#ffffff"
end
defp sidebar_filter_contrast_color(_color), do: "#ffffff"
defp sidebar_filters_enabled?(sidebar_data) do
sidebar_data
|> Map.get(:filters)
|> then(&(is_map(&1) and Map.get(&1, :enabled, false)))
end
defp sidebar_filters_visible?(sidebar_data) do
sidebar_data
|> Map.get(:filters, %{})
|> Map.get(:filter_panel_visible, false)
end
defp normalize_filter_string(nil), do: nil
defp normalize_filter_string(value) do

View File

@@ -97,6 +97,25 @@
<div class="sidebar-section">
<div class="sidebar-section-header">
<span><%= String.upcase(sidebar_header_label(@sidebar_header)) %></span>
<%= if sidebar_filters_enabled?(@sidebar_data) do %>
<div class="sidebar-actions">
<button
class={[
"sidebar-action",
if(sidebar_filters_visible?(@sidebar_data), do: "active")
]}
data-testid="sidebar-filter-toggle"
type="button"
phx-click="toggle_sidebar_filters"
aria-label={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))}
title={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))}
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"/>
</svg>
</button>
</div>
<% end %>
</div>
</div>
<%= render_sidebar_filters(assigns) %>

View File

@@ -69,9 +69,9 @@ defmodule BDS.UI.Sidebar do
}
end
defp empty_view("posts"), do: posts_view_data([], [], %{}, false, empty_filter_params())
defp empty_view("pages"), do: posts_view_data([], [], %{}, true, empty_filter_params())
defp empty_view("media"), do: media_view_data([], [], empty_filter_params())
defp empty_view("posts"), do: posts_view_data([], [], %{}, false, empty_filter_params(), %{})
defp empty_view("pages"), do: posts_view_data([], [], %{}, true, empty_filter_params(), %{})
defp empty_view("media"), do: media_view_data([], [], empty_filter_params(), %{})
defp empty_view("scripts"), do: entity_list_view("Scripts", "Automation helpers", "scripts", [])
defp empty_view("templates"), do: entity_list_view("Templates", "Site rendering", "templates", [])
defp empty_view("tags"), do: tags_nav_view([])
@@ -84,16 +84,19 @@ defmodule BDS.UI.Sidebar do
defp posts_view(project_id, params, pages?) do
posts = list_posts(project_id)
translation_counts = translation_counts(project_id)
tag_colors = tag_color_map(project_id)
filters = normalize_filter_params(params)
base_posts = Enum.filter(posts, &(page_post?(&1) == pages?))
filtered_posts = apply_post_filters(base_posts, filters)
posts_view_data(base_posts, filtered_posts, translation_counts, pages?, filters)
posts_view_data(base_posts, filtered_posts, translation_counts, pages?, filters, tag_colors)
end
defp posts_view_data(base_posts, filtered_posts, translation_counts, pages?, filters) do
defp posts_view_data(base_posts, filtered_posts, translation_counts, pages?, filters, tag_colors) do
limited_posts = Enum.take(filtered_posts, filters.display_limit)
grouped_posts = group_posts(limited_posts)
available_tags = available_tags(base_posts, & &1.tags)
available_categories = available_categories(base_posts, pages?)
%{
title: if(pages?, do: "Pages", else: "Posts"),
@@ -114,8 +117,9 @@ defmodule BDS.UI.Sidebar do
results_for_label: "sidebar.resultsFor",
no_results_label: "sidebar.noMatchingPosts",
year_month_counts: year_month_counts(base_posts, &post_filter_timestamp/1),
available_tags: available_tags(base_posts, & &1.tags),
available_categories: available_categories(base_posts, pages?),
available_tags: available_tags,
available_tag_colors: Map.take(tag_colors, available_tags),
available_categories: available_categories,
max_items: @default_page_size,
display_limit: filters.display_limit,
loaded_count: length(limited_posts),
@@ -140,14 +144,16 @@ defmodule BDS.UI.Sidebar do
defp media_view(project_id, params) do
media_items = list_media(project_id)
tag_colors = tag_color_map(project_id)
filters = normalize_filter_params(params)
filtered_media = apply_media_filters(media_items, filters)
media_view_data(media_items, filtered_media, filters)
media_view_data(media_items, filtered_media, filters, tag_colors)
end
defp media_view_data(base_media, filtered_media, filters) do
defp media_view_data(base_media, filtered_media, filters, tag_colors) do
limited_media = Enum.take(filtered_media, filters.display_limit)
available_tags = available_tags(base_media, & &1.tags)
%{
title: "Media",
@@ -166,7 +172,8 @@ defmodule BDS.UI.Sidebar do
results_for_label: "sidebar.resultsFor",
no_results_label: "sidebar.noMediaFiles",
year_month_counts: year_month_counts(base_media, &Map.get(&1, :updated_at)),
available_tags: available_tags(base_media, & &1.tags),
available_tags: available_tags,
available_tag_colors: Map.take(tag_colors, available_tags),
available_categories: [],
max_items: @default_page_size,
display_limit: filters.display_limit,
@@ -493,6 +500,16 @@ defmodule BDS.UI.Sidebar do
|> Enum.sort_by(&String.downcase/1)
end
defp tag_color_map(project_id) do
Repo.all(from tag in Tag, where: tag.project_id == ^project_id, select: {tag.name, tag.color})
|> Enum.reduce(%{}, fn {name, color}, acc ->
case String.trim(to_string(color || "")) do
"" -> acc
trimmed -> Map.put(acc, to_string(name), trimmed)
end
end)
end
defp filtered_categories(categories) do
Enum.reject(categories || [], &(normalize_term(&1) == @page_category))
end

View File

@@ -2460,18 +2460,6 @@ button {
border-color: var(--vscode-focusBorder);
}
.search-box button,
.clear-filter,
.filter-status button,
.load-more-button,
.calendar-year-header,
.calendar-month,
.filter-header,
.filter-chip {
border: none;
cursor: pointer;
}
.search-box button[type="submit"] {
position: absolute;
right: 40px;
@@ -2485,7 +2473,6 @@ button {
.search-box button[type="submit"]:hover {
opacity: 1;
background: transparent;
}
.search-box .clear-search {
@@ -2502,7 +2489,6 @@ button {
.search-box .clear-search:hover {
opacity: 1;
background: transparent;
}
.calendar-view {
@@ -2522,42 +2508,29 @@ button {
margin-bottom: 8px;
}
.collapsible-header {
width: 100%;
display: flex;
align-items: center;
text-align: left;
}
.calendar-header.collapsible-header,
.filter-header.collapsible-header {
.calendar-header.collapsible-header {
cursor: pointer;
padding: 4px 6px;
margin: 0 -6px 8px -6px;
border-radius: 3px;
user-select: none;
background: transparent;
}
.calendar-header.collapsible-header:hover,
.filter-header.collapsible-header:hover {
.calendar-header.collapsible-header:hover {
background-color: var(--vscode-list-hoverBackground);
}
.calendar-header.collapsible-header.collapsed,
.filter-header.collapsible-header.collapsed {
.calendar-header.collapsible-header.collapsed {
margin-bottom: 0;
}
.collapse-icon {
.calendar-header .collapse-icon {
font-size: 9px;
margin-right: 4px;
opacity: 0.7;
color: var(--vscode-descriptionForeground);
}
.calendar-header .clear-filter,
.filter-header .clear-filter {
.calendar-header .clear-filter {
background: transparent;
border: none;
color: var(--vscode-descriptionForeground);
@@ -2565,11 +2538,9 @@ button {
font-size: 10px;
padding: 2px 4px;
opacity: 0.7;
margin-left: auto;
}
.calendar-header .clear-filter:hover,
.filter-header .clear-filter:hover {
.calendar-header .clear-filter:hover {
opacity: 1;
}
@@ -2592,13 +2563,11 @@ button {
text-align: left;
}
.calendar-year-header:hover,
.calendar-month:hover {
.calendar-year-header:hover {
background-color: var(--vscode-list-hoverBackground);
}
.calendar-year-header.selected,
.calendar-month.selected {
.calendar-year-header.selected {
background-color: var(--vscode-list-activeSelectionBackground);
}
@@ -2608,8 +2577,7 @@ button {
width: 10px;
}
.year-label,
.month-label {
.calendar-year-header .year-label {
flex: 1;
}
@@ -2642,6 +2610,26 @@ button {
text-align: left;
}
.calendar-month:hover {
background-color: var(--vscode-list-hoverBackground);
}
.calendar-month.selected {
background-color: var(--vscode-list-activeSelectionBackground);
}
.calendar-month .month-count {
font-size: 10px;
color: var(--vscode-descriptionForeground);
}
.calendar-empty {
font-size: 12px;
color: var(--vscode-descriptionForeground);
padding: 8px;
text-align: center;
}
.month-count,
.sidebar-section-count {
font-size: 10px;
@@ -2672,6 +2660,43 @@ button {
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 {
display: flex;
flex-wrap: wrap;
@@ -2698,6 +2723,18 @@ button {
color: var(--vscode-button-foreground);
}
.filter-chip.has-color {
border: 1px solid transparent;
}
.filter-chip.has-color:hover {
opacity: 0.85;
}
.filter-chip.has-color.active {
box-shadow: 0 0 0 2px var(--vscode-focusBorder, #007fd4);
}
.filter-status {
display: flex;
align-items: center;

View File

@@ -9,6 +9,7 @@ defmodule BDS.Desktop.ShellLiveTest do
alias BDS.Posts.Post
alias BDS.Projects
alias BDS.Repo
alias BDS.Tags
@endpoint BDS.Desktop.Endpoint
@@ -218,15 +219,37 @@ defmodule BDS.Desktop.ShellLiveTest do
test "sidebar filters and load more are server-driven", %{project: project} do
seed_sidebar_posts(project.id)
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "tech", color: "#112233"})
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
assert html =~ ~s(data-testid="sidebar-search-form")
assert html =~ ~s(data-testid="sidebar-filter-toggle")
assert html =~ ~s(data-testid="sidebar-filter-tag")
assert html =~ ~s(class="sidebar-section-header")
assert html =~ ~s(class="sidebar-actions")
assert html =~ ~s(data-testid="sidebar-load-more")
refute html =~ ~s(data-testid="sidebar-filter-tag")
assert html =~ "Alpha Post"
refute html =~ "Overflow Post"
html =
view
|> element("[data-testid='sidebar-filter-toggle']")
|> render_click()
assert html =~ ~s(class="calendar-header collapsible-header collapsed")
assert html =~ ~s(class="filter-header collapsible-header collapsed")
refute html =~ ~s(class="calendar-year-header")
refute html =~ ~s(data-testid="sidebar-filter-tag")
html =
view
|> element("[data-testid='sidebar-filter-tags-header']")
|> render_click()
assert html =~ ~s(class="filter-chip has-color")
assert html =~ ~s(data-testid="sidebar-filter-tag")
html =
view
|> form("[data-testid='sidebar-search-form']", %{sidebar_filters: %{search: "Alpha"}})
@@ -354,8 +377,8 @@ defmodule BDS.Desktop.ShellLiveTest do
|> render_click()
refute html =~ ~s(class="panel-shell is-hidden")
assert html =~ ~s(class="panel-tab active")
assert html =~ "No background tasks running"
assert html =~ ~s(<button class="panel-tab active" type="button" phx-click="select_panel_tab" phx-value-tab="tasks">)
assert html =~ ~s(class="task-list") or html =~ "No background tasks running"
end
defp seed_sidebar_posts(project_id) do