mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
refactor(todo): move ViewSelector behind Layout pill, simplify homepage
- Move board view management (ViewSelector, activeViewId, ViewEditorModal) from +page.svelte to +layout.svelte - Layout pill in PillNav now toggles ViewSelector strip visibility - +page.svelte reduced to minimal BoardViewRenderer with context-provided view - Provide activeView via Svelte context from layout - Fix broken import in TaskItem.svelte (linter artifact) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
504e7756a7
commit
59e535af94
3 changed files with 135 additions and 451 deletions
|
|
@ -19,8 +19,8 @@
|
|||
import { contactsStore } from '$lib/stores/contacts.svelte';
|
||||
import { ContactAvatar, ContactSelector } from '@manacore/shared-ui';
|
||||
import SubtaskList from './SubtaskList.svelte';
|
||||
import {
|
||||
import { Check, CheckSquare, DotsSixVertical } from '@manacore/shared-icons';
|
||||
import {
|
||||
PrioritySelector,
|
||||
StorypointsSelector,
|
||||
DurationPicker,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,15 @@
|
|||
import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import { TodoEvents } from '@manacore/shared-utils/analytics';
|
||||
import { todoStore, taskCollection } from '$lib/data/local-store';
|
||||
import { useAllTasks, useAllProjects, getActiveProjects } from '$lib/data/task-queries';
|
||||
import type { LocalBoardView } from '$lib/data/local-store';
|
||||
import {
|
||||
useAllTasks,
|
||||
useAllProjects,
|
||||
useAllBoardViews,
|
||||
getActiveProjects,
|
||||
} from '$lib/data/task-queries';
|
||||
import { boardViewsStore } from '$lib/stores/board-views.svelte';
|
||||
import { ViewSelector, ViewEditorModal } from '$lib/components/board-views';
|
||||
import SyncIndicator from '$lib/components/SyncIndicator.svelte';
|
||||
import { List, X } from '@manacore/shared-icons';
|
||||
|
||||
|
|
@ -61,10 +69,90 @@
|
|||
const allProjects = useAllProjects();
|
||||
const allTags = useAllSharedTags();
|
||||
|
||||
// ─── Board View Management ──────────────────────────────
|
||||
const boardViews = useAllBoardViews();
|
||||
const ACTIVE_VIEW_KEY = 'todo:activeViewId';
|
||||
let activeViewId = $state<string | null>(null);
|
||||
|
||||
// Auto-select first view when views load and nothing is selected
|
||||
$effect(() => {
|
||||
if (boardViews.value.length > 0 && !activeViewId) {
|
||||
const stored =
|
||||
typeof localStorage !== 'undefined' ? localStorage.getItem(ACTIVE_VIEW_KEY) : null;
|
||||
activeViewId =
|
||||
stored && boardViews.value.find((v) => v.id === stored) ? stored : boardViews.value[0].id;
|
||||
}
|
||||
if (
|
||||
activeViewId &&
|
||||
boardViews.value.length > 0 &&
|
||||
!boardViews.value.find((v) => v.id === activeViewId)
|
||||
) {
|
||||
activeViewId = boardViews.value[0].id;
|
||||
}
|
||||
});
|
||||
|
||||
let activeView = $derived(boardViews.value.find((v) => v.id === activeViewId) ?? null);
|
||||
|
||||
function handleSelectView(viewId: string) {
|
||||
activeViewId = viewId;
|
||||
localStorage.setItem(ACTIVE_VIEW_KEY, viewId);
|
||||
}
|
||||
|
||||
// ViewSelector visibility (toggled via Layout pill)
|
||||
let isViewSelectorVisible = $state(false);
|
||||
|
||||
// View Editor Modal
|
||||
let showViewEditor = $state(false);
|
||||
let editingView = $state<LocalBoardView | null>(null);
|
||||
|
||||
function handleCreateView() {
|
||||
editingView = null;
|
||||
showViewEditor = true;
|
||||
}
|
||||
|
||||
function handleEditView(view: LocalBoardView) {
|
||||
editingView = view;
|
||||
showViewEditor = true;
|
||||
}
|
||||
|
||||
async function handleSaveView(data: Partial<LocalBoardView>) {
|
||||
if (editingView) {
|
||||
await boardViewsStore.updateView(editingView.id, data);
|
||||
} else {
|
||||
const newView = await boardViewsStore.createView({
|
||||
name: data.name ?? 'Neue View',
|
||||
icon: data.icon ?? 'columns',
|
||||
groupBy: data.groupBy ?? 'status',
|
||||
layout: data.layout ?? 'kanban',
|
||||
columns: data.columns ?? [],
|
||||
order: boardViews.value.length,
|
||||
});
|
||||
if (newView?.id) handleSelectView(newView.id);
|
||||
}
|
||||
showViewEditor = false;
|
||||
editingView = null;
|
||||
}
|
||||
|
||||
async function handleDeleteView() {
|
||||
if (!editingView) return;
|
||||
await boardViewsStore.deleteView(editingView.id);
|
||||
showViewEditor = false;
|
||||
editingView = null;
|
||||
}
|
||||
|
||||
async function handleReorderViews(viewIds: string[]) {
|
||||
await boardViewsStore.reorderViews(viewIds);
|
||||
}
|
||||
|
||||
// Provide data to child components via Svelte context
|
||||
setContext('projects', allProjects);
|
||||
setContext('tasks', allTasks);
|
||||
setContext('tags', allTags);
|
||||
setContext('activeView', {
|
||||
get value() {
|
||||
return activeView;
|
||||
},
|
||||
});
|
||||
|
||||
// Edit mode state — shared between layout (PillNav button) and page (editor)
|
||||
let editMode = $state(false);
|
||||
|
|
@ -249,12 +337,12 @@
|
|||
// Keep navRoutes for keyboard shortcuts (Ctrl+1-3)
|
||||
const viewRoutes: Record<string, string> = { fokus: '/', uebersicht: '/', matrix: '/' };
|
||||
|
||||
// Handle edit mode toggle
|
||||
function handleEditToggle() {
|
||||
editMode = !editMode;
|
||||
// Handle view selector toggle (Layout pill)
|
||||
function handleViewSelectorToggle() {
|
||||
isViewSelectorVisible = !isViewSelectorVisible;
|
||||
}
|
||||
|
||||
// Filter, Tags, and Edit stay as standalone pills (toggle behavior, not navigation)
|
||||
// Filter, Tags, and Layout stay as standalone pills (toggle behavior, not navigation)
|
||||
let baseNavItems = $derived<PillNavItem[]>([
|
||||
{
|
||||
href: '/',
|
||||
|
|
@ -274,10 +362,10 @@
|
|||
? [
|
||||
{
|
||||
href: '/',
|
||||
label: editMode ? 'Fertig' : 'Layout',
|
||||
icon: editMode ? 'check' : 'grid',
|
||||
onClick: handleEditToggle,
|
||||
active: editMode,
|
||||
label: activeView?.name ?? 'Layout',
|
||||
icon: 'grid',
|
||||
onClick: handleViewSelectorToggle,
|
||||
active: isViewSelectorVisible,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
|
@ -462,6 +550,18 @@
|
|||
ariaLabel="Hauptnavigation"
|
||||
/>
|
||||
|
||||
<!-- ViewSelector strip (toggled via Layout pill) -->
|
||||
{#if isViewSelectorVisible && ($page.url.pathname === '/' || $page.url.pathname === '')}
|
||||
<ViewSelector
|
||||
views={boardViews.value}
|
||||
{activeViewId}
|
||||
onSelect={handleSelectView}
|
||||
onCreate={handleCreateView}
|
||||
onEdit={handleEditView}
|
||||
onReorder={handleReorderViews}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- TagStrip (above PillNav, toggled via Tags pill) -->
|
||||
{#if isTagStripVisible}
|
||||
<TagStrip
|
||||
|
|
@ -509,8 +609,8 @@
|
|||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Global Quick Input Bar - only on list and kanban views -->
|
||||
{#if $page.url.pathname === '/' || $page.url.pathname === '/kanban' || $page.url.pathname === '/statistics'}
|
||||
<!-- Global Quick Input Bar -->
|
||||
{#if $page.url.pathname === '/' || $page.url.pathname === '/statistics'}
|
||||
<QuickInputBar
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSelect}
|
||||
|
|
@ -590,6 +690,18 @@
|
|||
{#if authStore.isAuthenticated}
|
||||
<SessionExpiredBanner locale={$locale || 'de'} loginHref="/login" />
|
||||
{/if}
|
||||
|
||||
<!-- View Editor Modal -->
|
||||
<ViewEditorModal
|
||||
open={showViewEditor}
|
||||
view={editingView}
|
||||
onSave={handleSaveView}
|
||||
onDelete={handleDeleteView}
|
||||
onClose={() => {
|
||||
showViewEditor = false;
|
||||
editingView = null;
|
||||
}}
|
||||
/>
|
||||
</AuthGate>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -1,45 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import type { TaskPriority } from '@todo/shared';
|
||||
import { getContext } from 'svelte';
|
||||
import type { LocalBoardView } from '$lib/data/local-store';
|
||||
import { useAllBoardViews } from '$lib/data/task-queries';
|
||||
import { ViewSelector, BoardViewRenderer, ViewEditorModal } from '$lib/components/board-views';
|
||||
import { boardViewsStore } from '$lib/stores/board-views.svelte';
|
||||
import { BoardViewRenderer } from '$lib/components/board-views';
|
||||
import { todoSettings } from '$lib/stores/settings.svelte';
|
||||
import TaskFilters from '$lib/components/TaskFilters.svelte';
|
||||
import { Funnel, FloppyDisk } from '@manacore/shared-icons';
|
||||
|
||||
// Live query for board views
|
||||
const boardViews = useAllBoardViews();
|
||||
|
||||
// Active view — persisted in localStorage
|
||||
const STORAGE_KEY = 'todo:activeViewId';
|
||||
let activeViewId = $state<string | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
activeViewId = stored;
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-select first view when views load and nothing is selected
|
||||
$effect(() => {
|
||||
if (boardViews.value.length > 0 && !activeViewId) {
|
||||
activeViewId = boardViews.value[0].id;
|
||||
}
|
||||
// If stored view no longer exists, fall back to first
|
||||
if (
|
||||
activeViewId &&
|
||||
boardViews.value.length > 0 &&
|
||||
!boardViews.value.find((v) => v.id === activeViewId)
|
||||
) {
|
||||
activeViewId = boardViews.value[0].id;
|
||||
}
|
||||
});
|
||||
|
||||
let activeView = $derived(boardViews.value.find((v) => v.id === activeViewId) ?? null);
|
||||
let pageTitle = $derived(activeView?.name ?? 'Aufgaben');
|
||||
// Get active view from layout context
|
||||
const activeViewCtx: { readonly value: LocalBoardView | null } = getContext('activeView');
|
||||
|
||||
// Map layout mode to BoardViewRenderer layoutOverride
|
||||
const LAYOUT_MAP = {
|
||||
|
|
@ -49,121 +15,8 @@
|
|||
} as const;
|
||||
|
||||
let layoutOverride = $derived(LAYOUT_MAP[todoSettings.activeLayoutMode]);
|
||||
|
||||
function handleSelectView(viewId: string) {
|
||||
activeViewId = viewId;
|
||||
localStorage.setItem(STORAGE_KEY, viewId);
|
||||
}
|
||||
|
||||
// ─── Editor Modal ──────────────────────────────────────
|
||||
let showEditor = $state(false);
|
||||
let editingView = $state<LocalBoardView | null>(null);
|
||||
|
||||
function handleCreateView() {
|
||||
editingView = null;
|
||||
showEditor = true;
|
||||
}
|
||||
|
||||
function handleEditView(view: LocalBoardView) {
|
||||
editingView = view;
|
||||
showEditor = true;
|
||||
}
|
||||
|
||||
async function handleSaveView(data: Partial<LocalBoardView>) {
|
||||
if (editingView) {
|
||||
await boardViewsStore.updateView(editingView.id, data);
|
||||
} else {
|
||||
const newView = await boardViewsStore.createView({
|
||||
name: data.name ?? 'Neue View',
|
||||
icon: data.icon ?? 'columns',
|
||||
groupBy: data.groupBy ?? 'status',
|
||||
layout: data.layout ?? 'kanban',
|
||||
columns: data.columns ?? [],
|
||||
order: boardViews.value.length,
|
||||
});
|
||||
if (newView?.id) {
|
||||
handleSelectView(newView.id);
|
||||
}
|
||||
}
|
||||
showEditor = false;
|
||||
editingView = null;
|
||||
}
|
||||
|
||||
async function handleDeleteView() {
|
||||
if (!editingView) return;
|
||||
await boardViewsStore.deleteView(editingView.id);
|
||||
showEditor = false;
|
||||
editingView = null;
|
||||
}
|
||||
|
||||
async function handleReorderViews(viewIds: string[]) {
|
||||
await boardViewsStore.reorderViews(viewIds);
|
||||
}
|
||||
|
||||
// ─── Filter state ──────────────────────────────────────
|
||||
let filterPriorities = $state<TaskPriority[]>([]);
|
||||
let filterProjectId = $state<string | null>(null);
|
||||
let filterLabelIds = $state<string[]>([]);
|
||||
let filterSearchQuery = $state('');
|
||||
let showFilters = $state(false);
|
||||
|
||||
let previousViewId = $state<string | null>(null);
|
||||
$effect(() => {
|
||||
if (activeView && activeView.id !== previousViewId) {
|
||||
previousViewId = activeView.id;
|
||||
if (activeView.filter) {
|
||||
filterPriorities = (activeView.filter.priorities ?? []) as TaskPriority[];
|
||||
filterProjectId = activeView.filter.projectId ?? null;
|
||||
filterLabelIds = activeView.filter.tagIds ?? [];
|
||||
} else {
|
||||
filterPriorities = [];
|
||||
filterProjectId = null;
|
||||
filterLabelIds = [];
|
||||
}
|
||||
filterSearchQuery = '';
|
||||
}
|
||||
});
|
||||
|
||||
function clearFilters() {
|
||||
filterPriorities = [];
|
||||
filterProjectId = null;
|
||||
filterLabelIds = [];
|
||||
filterSearchQuery = '';
|
||||
}
|
||||
|
||||
async function saveFiltersToView() {
|
||||
if (!activeViewId) return;
|
||||
const filter: { projectId?: string; tagIds?: string[]; priorities?: string[] } = {};
|
||||
if (filterProjectId) filter.projectId = filterProjectId;
|
||||
if (filterLabelIds.length > 0) filter.tagIds = filterLabelIds;
|
||||
if (filterPriorities.length > 0) filter.priorities = filterPriorities;
|
||||
const hasFilter = Object.keys(filter).length > 0;
|
||||
await boardViewsStore.updateView(activeViewId, { filter: hasFilter ? filter : undefined });
|
||||
}
|
||||
|
||||
let hasActiveFilters = $derived(
|
||||
filterPriorities.length > 0 ||
|
||||
filterProjectId !== null ||
|
||||
filterLabelIds.length > 0 ||
|
||||
filterSearchQuery.trim() !== ''
|
||||
);
|
||||
|
||||
let isMobile = $state(false);
|
||||
|
||||
function checkMobile() {
|
||||
isMobile = window.innerWidth < 768;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
}
|
||||
});
|
||||
let activeView = $derived(activeViewCtx.value);
|
||||
let pageTitle = $derived(activeView?.name ?? 'Aufgaben');
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -171,305 +24,24 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="board-page">
|
||||
<!-- View Selector - Top on Desktop -->
|
||||
{#if !isMobile}
|
||||
<ViewSelector
|
||||
views={boardViews.value}
|
||||
{activeViewId}
|
||||
onSelect={handleSelectView}
|
||||
onCreate={handleCreateView}
|
||||
onEdit={handleEditView}
|
||||
onReorder={handleReorderViews}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<h1 class="page-title">{pageTitle}</h1>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showFilters = !showFilters)}
|
||||
class="filter-button px-4 py-2 text-sm font-medium transition-all flex items-center gap-2 {showFilters ||
|
||||
hasActiveFilters
|
||||
? 'active'
|
||||
: ''}"
|
||||
>
|
||||
<Funnel size={16} />
|
||||
Filter
|
||||
{#if hasActiveFilters}
|
||||
<span
|
||||
class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold rounded-full bg-primary-foreground text-primary"
|
||||
>
|
||||
{filterPriorities.length +
|
||||
(filterProjectId ? 1 : 0) +
|
||||
filterLabelIds.length +
|
||||
(filterSearchQuery ? 1 : 0)}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Collapsible Filters -->
|
||||
{#if showFilters}
|
||||
<div class="mb-6 px-4 sm:px-6 lg:px-8 animate-in slide-in-from-top-2 duration-200">
|
||||
<TaskFilters
|
||||
variant="bar"
|
||||
selectedPriorities={filterPriorities}
|
||||
selectedProjectId={filterProjectId}
|
||||
selectedLabelIds={filterLabelIds}
|
||||
searchQuery={filterSearchQuery}
|
||||
onPrioritiesChange={(priorities: TaskPriority[]) => (filterPriorities = priorities)}
|
||||
onProjectChange={(projectId: string | null) => (filterProjectId = projectId)}
|
||||
onLabelsChange={(labelIds: string[]) => (filterLabelIds = labelIds)}
|
||||
onSearchChange={(query: string) => (filterSearchQuery = query)}
|
||||
onClearFilters={clearFilters}
|
||||
showSearch={true}
|
||||
showLabels={true}
|
||||
/>
|
||||
{#if hasActiveFilters}
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<button type="button" class="save-filter-btn" onclick={saveFiltersToView}>
|
||||
<FloppyDisk size={14} />
|
||||
Filter speichern
|
||||
</button>
|
||||
{#if activeView?.filter}
|
||||
<button
|
||||
type="button"
|
||||
class="clear-saved-filter-btn"
|
||||
onclick={async () => {
|
||||
clearFilters();
|
||||
if (activeViewId) {
|
||||
await boardViewsStore.updateView(activeViewId, { filter: undefined });
|
||||
}
|
||||
}}
|
||||
>
|
||||
Gespeicherten Filter entfernen
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Board Content -->
|
||||
<div class="board-container" class:mobile-bottom-padding={isMobile}>
|
||||
{#if activeView}
|
||||
<BoardViewRenderer
|
||||
view={{
|
||||
...activeView,
|
||||
filter: hasActiveFilters
|
||||
? {
|
||||
projectId: filterProjectId ?? undefined,
|
||||
tagIds: filterLabelIds.length > 0 ? filterLabelIds : undefined,
|
||||
priorities: filterPriorities.length > 0 ? filterPriorities : undefined,
|
||||
}
|
||||
: activeView.filter,
|
||||
}}
|
||||
{layoutOverride}
|
||||
/>
|
||||
{:else if boardViews.value.length === 0}
|
||||
<div class="empty-state">
|
||||
<p class="text-muted-foreground">Views werden geladen...</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- View Selector - Bottom on Mobile -->
|
||||
{#if isMobile}
|
||||
<div class="mobile-selector">
|
||||
<ViewSelector
|
||||
views={boardViews.value}
|
||||
{activeViewId}
|
||||
onSelect={handleSelectView}
|
||||
onCreate={handleCreateView}
|
||||
onEdit={handleEditView}
|
||||
/>
|
||||
{#if activeView}
|
||||
<BoardViewRenderer view={activeView} {layoutOverride} />
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
<p class="text-muted-foreground">Views werden geladen...</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- View Editor Modal -->
|
||||
<ViewEditorModal
|
||||
open={showEditor}
|
||||
view={editingView}
|
||||
onSave={handleSaveView}
|
||||
onDelete={handleDeleteView}
|
||||
onClose={() => {
|
||||
showEditor = false;
|
||||
editingView = null;
|
||||
}}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.board-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 140px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
color: var(--foreground);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.board-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.board-container.mobile-bottom-padding {
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Mobile selector fixed at bottom */
|
||||
.mobile-selector {
|
||||
position: fixed;
|
||||
bottom: 70px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 40;
|
||||
background: linear-gradient(to top, var(--background) 0%, transparent 100%);
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* Glass-Pill filter button */
|
||||
.filter-button {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 9999px;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
:global(.dark) .filter-button {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.filter-button:hover {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-1px);
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
:global(.dark) .filter-button:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.filter-button.active {
|
||||
background: #8b5cf6;
|
||||
border-color: #8b5cf6;
|
||||
color: white;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(139, 92, 246, 0.3),
|
||||
0 2px 4px -1px rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
.filter-button.active:hover {
|
||||
background: #7c3aed;
|
||||
border-color: #7c3aed;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.save-filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: #8b5cf6;
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.save-filter-btn:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
border-color: rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
:global(.dark) .save-filter-btn {
|
||||
color: #a78bfa;
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
border-color: rgba(139, 92, 246, 0.25);
|
||||
}
|
||||
|
||||
:global(.dark) .save-filter-btn:hover {
|
||||
background: rgba(139, 92, 246, 0.25);
|
||||
border-color: rgba(139, 92, 246, 0.35);
|
||||
}
|
||||
|
||||
.clear-saved-filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.clear-saved-filter-btn:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #ef4444;
|
||||
border-color: rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
:global(.dark) .clear-saved-filter-btn {
|
||||
color: #9ca3af;
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
:global(.dark) .clear-saved-filter-btn:hover {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.animate-in {
|
||||
animation: animateIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.slide-in-from-top-2 {
|
||||
--tw-enter-translate-y: -0.5rem;
|
||||
}
|
||||
|
||||
@keyframes animateIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(var(--tw-enter-translate-y, 0));
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue