ui: overhaul files, kanban, calendar, settings, chat modules

- FileBrowser: modernize breadcrumbs, toolbar, list/grid items, empty states
- KanbanColumn: remove fixed height, border-based styling, compact header
- KanbanCard: cleaner border styling, smaller tags, compact footer
- Calendar: compact nav bar, border grid, today circle indicator, day view empty state
- DocumentViewer: remove bg-night rounded-[32px], border-b header pattern
- Settings tags: inline border/rounded-xl cards, icon action buttons
- Chat: create +layout.svelte with PageHeader, overhaul sidebar and main area
- Chat i18n: add nav_chat, chat_title, chat_subtitle keys (en + et)

svelte-check: 0 errors, vitest: 112/112 passed
This commit is contained in:
AlacrisDevs
2026-02-07 11:03:58 +02:00
parent 2913912cb8
commit 819d5b876a
10 changed files with 376 additions and 410 deletions

View File

@@ -123,63 +123,63 @@
});
</script>
<div class="flex flex-col h-full gap-2">
<div class="flex flex-col h-full">
<!-- Navigation bar -->
<div class="flex items-center justify-between px-2">
<div class="flex items-center gap-2">
<div class="flex items-center justify-between px-4 py-2 shrink-0">
<div class="flex items-center gap-1">
<button
class="p-1 text-light/60 hover:text-light hover:bg-dark rounded-full transition-colors"
class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
onclick={prev}
aria-label="Previous"
>
<span
class="material-symbols-rounded"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
style="font-size: 20px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;"
>chevron_left</span
>
</button>
<span
class="font-heading text-h4 text-white min-w-[200px] text-center"
class="font-heading text-body-sm text-white min-w-[180px] text-center"
>{headerTitle}</span
>
<button
class="p-1 text-light/60 hover:text-light hover:bg-dark rounded-full transition-colors"
class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
onclick={next}
aria-label="Next"
>
<span
class="material-symbols-rounded"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
style="font-size: 20px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;"
>chevron_right</span
>
</button>
<button
class="px-3 py-1 text-body-md font-body text-light/60 hover:text-white hover:bg-dark rounded-[32px] transition-colors ml-2"
class="px-2.5 py-1 text-body-sm font-body text-light/50 hover:text-white hover:bg-dark/50 rounded-lg transition-colors ml-1"
onclick={goToToday}
>
Today
</button>
</div>
<div class="flex bg-dark rounded-[32px] p-0.5">
<div class="flex gap-0.5 bg-dark/30 rounded-lg p-0.5">
<button
class="px-3 py-1 text-body-md font-body rounded-[32px] transition-colors {currentView ===
class="px-2.5 py-1 text-[12px] font-body rounded-md transition-colors {currentView ===
'day'
? 'bg-primary text-night'
: 'text-light/60 hover:text-light'}"
? 'bg-primary text-background'
: 'text-light/50 hover:text-white'}"
onclick={() => (currentView = "day")}>Day</button
>
<button
class="px-3 py-1 text-body-md font-body rounded-[32px] transition-colors {currentView ===
class="px-2.5 py-1 text-[12px] font-body rounded-md transition-colors {currentView ===
'week'
? 'bg-primary text-night'
: 'text-light/60 hover:text-light'}"
? 'bg-primary text-background'
: 'text-light/50 hover:text-white'}"
onclick={() => (currentView = "week")}>Week</button
>
<button
class="px-3 py-1 text-body-md font-body rounded-[32px] transition-colors {currentView ===
class="px-2.5 py-1 text-[12px] font-body rounded-md transition-colors {currentView ===
'month'
? 'bg-primary text-night'
: 'text-light/60 hover:text-light'}"
? 'bg-primary text-background'
: 'text-light/50 hover:text-white'}"
onclick={() => (currentView = "month")}>Month</button
>
</div>
@@ -187,48 +187,40 @@
<!-- Month View -->
{#if currentView === "month"}
<div
class="flex flex-col flex-1 gap-2 min-h-0 bg-background rounded-xl p-2"
>
<div class="flex flex-col flex-1 min-h-0">
<!-- Day Headers -->
<div class="grid grid-cols-7 gap-2">
<div class="grid grid-cols-7 border-b border-light/5">
{#each weekDayHeaders as day}
<div class="flex items-center justify-center py-2 px-2">
<span
class="font-heading text-h4 text-white text-center"
>{day}</span
>
<div class="flex items-center justify-center py-2">
<span class="font-body text-[11px] text-light/40 uppercase tracking-wider">{day}</span>
</div>
{/each}
</div>
<!-- Calendar Grid -->
<div
class="flex-1 flex flex-col gap-2 min-h-0 rounded-lg overflow-hidden"
>
<div class="flex-1 flex flex-col min-h-0 overflow-hidden">
{#each weeks as week}
<div class="grid grid-cols-7 gap-2 flex-1">
<div class="grid grid-cols-7 flex-1 border-b border-light/5 last:border-b-0">
{#each week as day}
{@const dayEvents = getEventsForDay(day)}
{@const isToday = isSameDay(day, today)}
{@const inMonth = isCurrentMonth(day)}
<div
class="bg-night rounded-none flex flex-col items-start px-2 py-2.5 overflow-hidden transition-colors hover:bg-dark/50 min-h-0 cursor-pointer
{!inMonth ? 'opacity-50' : ''}"
<button
type="button"
class="flex flex-col items-start px-1.5 py-1 overflow-hidden transition-colors hover:bg-dark/30 min-h-0 cursor-pointer border-r border-light/5 last:border-r-0
{!inMonth ? 'opacity-40' : ''}"
onclick={() => onDateClick?.(day)}
>
<span
class="font-body text-body text-white {isToday
? 'text-primary font-bold'
: ''}"
class="text-[12px] font-body w-6 h-6 flex items-center justify-center rounded-full shrink-0
{isToday ? 'bg-primary text-background font-bold' : 'text-light/60'}"
>
{day.getDate()}
</span>
{#each dayEvents.slice(0, 2) as event}
<button
class="w-full mt-1 px-2 py-0.5 rounded-[4px] text-body-sm font-bold font-body text-night truncate text-left"
style="background-color: {event.color ??
'#00A3E0'}"
class="w-full mt-0.5 px-1.5 py-0.5 rounded text-[11px] font-body text-night truncate text-left font-medium"
style="background-color: {event.color ?? '#00A3E0'}"
onclick={(e) => {
e.stopPropagation();
onEventClick?.(event);
@@ -238,12 +230,9 @@
</button>
{/each}
{#if dayEvents.length > 2}
<span
class="text-body-sm text-light/40 mt-0.5"
>+{dayEvents.length - 2} more</span
>
<span class="text-[10px] text-light/30 mt-0.5 px-1">+{dayEvents.length - 2}</span>
{/if}
</div>
</button>
{/each}
</div>
{/each}
@@ -253,40 +242,25 @@
<!-- Week View -->
{#if currentView === "week"}
<div
class="flex flex-col flex-1 gap-2 min-h-0 bg-background rounded-xl p-2"
>
<div
class="grid grid-cols-7 gap-2 flex-1 rounded-lg overflow-hidden"
>
<div class="flex flex-col flex-1 min-h-0">
<div class="grid grid-cols-7 flex-1 overflow-hidden">
{#each weekDates as day}
{@const dayEvents = getEventsForDay(day)}
{@const isToday = isSameDay(day, today)}
<div class="flex flex-col overflow-hidden">
<div class="px-2 py-2 text-center">
<div
class="font-heading text-h4 {isToday
? 'text-primary'
: 'text-white'}"
>
<div class="flex flex-col overflow-hidden border-r border-light/5 last:border-r-0">
<div class="px-2 py-2 text-center border-b border-light/5">
<div class="text-[11px] font-body uppercase tracking-wider {isToday ? 'text-primary' : 'text-light/40'}">
{weekDayHeaders[(day.getDay() + 6) % 7]}
</div>
<div
class="font-body text-body-md {isToday
? 'text-primary'
: 'text-light/60'}"
>
<div class="text-body-sm font-heading mt-0.5 {isToday ? 'text-primary' : 'text-white'}">
{day.getDate()}
</div>
</div>
<div
class="bg-night flex-1 px-2 pb-2 space-y-1 overflow-y-auto"
>
<div class="flex-1 px-1.5 py-1.5 space-y-1 overflow-y-auto">
{#each dayEvents as event}
<button
class="w-full px-2 py-1.5 rounded-[4px] text-body-sm font-bold font-body text-night truncate text-left"
style="background-color: {event.color ??
'#00A3E0'}"
class="w-full px-2 py-1.5 rounded text-[11px] font-body text-night truncate text-left font-medium"
style="background-color: {event.color ?? '#00A3E0'}"
onclick={() => onEventClick?.(event)}
>
{event.title}
@@ -302,27 +276,24 @@
<!-- Day View -->
{#if currentView === "day"}
{@const dayEvents = getEventsForDay(currentDate)}
<div class="flex-1 bg-night px-4 py-5 min-h-0 overflow-auto">
<div class="flex-1 px-4 py-4 min-h-0 overflow-auto">
{#if dayEvents.length === 0}
<div class="text-center text-light/40 py-12">
<p class="font-body text-body">No events for this day</p>
<div class="flex flex-col items-center justify-center h-full text-light/40">
<span class="material-symbols-rounded mb-3" style="font-size: 48px; font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 48;">event_busy</span>
<p class="text-body-sm">No events for this day</p>
</div>
{:else}
<div class="space-y-2">
{#each dayEvents as event}
<button
class="w-full text-left p-3 rounded-[8px] transition-colors hover:opacity-80"
style="background-color: {event.color ??
'#00A3E0'}20; border-left: 3px solid {event.color ??
'#00A3E0'}"
class="w-full text-left p-3 rounded-xl border border-light/5 hover:border-light/10 transition-all"
style="border-left: 3px solid {event.color ?? '#00A3E0'}"
onclick={() => onEventClick?.(event)}
>
<div class="font-heading text-h5 text-white">
<div class="font-heading text-body-sm text-white">
{event.title}
</div>
<div
class="font-body text-body-md text-light/60 mt-1"
>
<div class="text-[12px] text-light/40 mt-1">
{new Date(event.start_time).toLocaleTimeString(
"en-US",
{ hour: "numeric", minute: "2-digit" },
@@ -333,9 +304,7 @@
)}
</div>
{#if event.description}
<div
class="font-body text-body-md text-light/50 mt-2"
>
<div class="text-[12px] text-light/30 mt-1.5 line-clamp-2">
{event.description}
</div>
{/if}

View File

@@ -42,17 +42,15 @@
}
</script>
<div
class="bg-night rounded-[32px] overflow-hidden flex flex-col min-w-0 h-full"
>
<div class="flex flex-col min-w-0 h-full overflow-hidden">
<!-- Lock Banner -->
{#if locked}
<div
class="flex items-center gap-2 px-4 py-2.5 bg-warning/10 border-b border-warning/20"
class="flex items-center gap-2 px-4 py-2 bg-warning/10 border-b border-warning/20 shrink-0"
>
<span
class="material-symbols-rounded text-warning"
style="font-size: 20px; font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 20;"
style="font-size: 18px; font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
>
lock
</span>
@@ -64,42 +62,35 @@
{/if}
<!-- Header -->
<header class="flex items-center gap-2 px-4 py-5">
<h2 class="flex-1 font-heading text-h1 text-white truncate">
<div class="flex items-center gap-2 px-5 py-3 border-b border-light/5 shrink-0">
<h2 class="flex-1 font-heading text-body-sm text-white truncate">
{document.name}
</h2>
{#if locked}
<Button size="md" disabled>
<span
class="material-symbols-rounded mr-1"
style="font-size: 16px; font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 16;"
>lock</span
>
Locked
</Button>
<Button size="sm" disabled>Locked</Button>
{:else if mode === "edit"}
<Button size="md" onclick={handleEditClick}>
<Button size="sm" onclick={handleEditClick}>
{isEditing ? "Preview" : "Edit"}
</Button>
{:else}
<Button size="md" onclick={handleEditClick}>Edit</Button>
<Button size="sm" onclick={handleEditClick}>Edit</Button>
{/if}
<button
type="button"
class="p-1 hover:bg-dark rounded-full transition-colors"
class="p-1 hover:bg-dark/50 rounded-lg transition-colors"
aria-label="More options"
>
<span
class="material-symbols-rounded text-light"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
class="material-symbols-rounded text-light/40 hover:text-white"
style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
>
more_horiz
</span>
</button>
</header>
</div>
<!-- Editor Area -->
<div class="flex-1 bg-background rounded-[32px] mx-4 mb-4 overflow-auto">
<div class="flex-1 overflow-auto">
<Editor {document} {onSave} editable={isEditing} />
</div>
</div>

View File

@@ -5,9 +5,6 @@
Button,
Modal,
Input,
Avatar,
IconButton,
Icon,
} from "$lib/components/ui";
import { DocumentViewer } from "$lib/components/documents";
import { createLogger } from "$lib/utils/logger";
@@ -490,97 +487,101 @@
}
</script>
<div class="flex h-full gap-4">
<div class="flex h-full gap-0">
<!-- Files Panel -->
<div
class="bg-night rounded-[32px] flex flex-col gap-4 px-4 py-5 overflow-hidden flex-1 min-w-0 h-full"
>
<!-- Header -->
<header class="flex items-center gap-2 p-1">
<Avatar name={title} size="md" />
<h1 class="flex-1 font-heading text-h1 text-white">{title}</h1>
<Button size="md" onclick={handleAdd}>{m.btn_new()}</Button>
<IconButton title={m.files_toggle_view()} onclick={toggleViewMode}>
<Icon
name={viewMode === "list" ? "grid_view" : "view_list"}
size={24}
/>
</IconButton>
</header>
<!-- Breadcrumb Path -->
<nav class="flex items-center gap-2 text-h3 font-heading">
{#each breadcrumbPath as crumb, i}
{#if i > 0}
<span
class="material-symbols-rounded text-light/30"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
>
chevron_right
</span>
{/if}
<a
href={getFolderUrl(crumb.id)}
class="px-3 py-1 rounded-xl transition-colors
{crumb.id === currentFolderId
? 'text-white'
: 'text-light/60 hover:text-primary'}
{dragOverBreadcrumb === (crumb.id ?? '__root__')
? 'ring-2 ring-primary bg-primary/10'
: ''}"
ondragover={(e) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
dragOverBreadcrumb = crumb.id ?? "__root__";
}}
ondragleave={() => {
dragOverBreadcrumb = undefined;
}}
ondrop={async (e) => {
e.preventDefault();
e.stopPropagation();
dragOverBreadcrumb = undefined;
if (!draggedItem) return;
if (draggedItem.parent_id === crumb.id) {
<div class="flex flex-col flex-1 min-w-0 h-full overflow-hidden">
<!-- Toolbar: Breadcrumbs + Actions -->
<div class="flex items-center gap-2 px-6 py-3 border-b border-light/5 shrink-0">
<!-- Breadcrumb Path -->
<nav class="flex items-center gap-1 flex-1 min-w-0 overflow-x-auto">
{#each breadcrumbPath as crumb, i}
{#if i > 0}
<span
class="material-symbols-rounded text-light/20 shrink-0"
style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
>
chevron_right
</span>
{/if}
<a
href={getFolderUrl(crumb.id)}
class="px-2 py-1 rounded-lg text-body-sm font-body whitespace-nowrap transition-colors
{crumb.id === currentFolderId
? 'text-white bg-dark/30'
: 'text-light/50 hover:text-white hover:bg-dark/30'}
{dragOverBreadcrumb === (crumb.id ?? '__root__')
? 'ring-2 ring-primary bg-primary/10'
: ''}"
ondragover={(e) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
dragOverBreadcrumb = crumb.id ?? "__root__";
}}
ondragleave={() => {
dragOverBreadcrumb = undefined;
}}
ondrop={async (e) => {
e.preventDefault();
e.stopPropagation();
dragOverBreadcrumb = undefined;
if (!draggedItem) return;
if (draggedItem.parent_id === crumb.id) {
resetDragState();
return;
}
const draggedName = draggedItem.name;
await handleMove(draggedItem.id, crumb.id);
toasts.success(
`Moved "${draggedName}" to "${crumb.name}"`,
);
resetDragState();
return;
}
const draggedName = draggedItem.name;
await handleMove(draggedItem.id, crumb.id);
toasts.success(
`Moved "${draggedName}" to "${crumb.name}"`,
);
resetDragState();
}}
}}
>
{#if i === 0}
<span class="material-symbols-rounded" style="font-size: 16px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 16;">home</span>
{:else}
{crumb.name}
{/if}
</a>
{/each}
</nav>
<Button size="sm" icon="add" onclick={handleAdd}>{m.btn_new()}</Button>
<button
type="button"
class="p-1.5 rounded-lg text-light/40 hover:text-white hover:bg-dark/50 transition-colors"
title={m.files_toggle_view()}
onclick={toggleViewMode}
>
<span
class="material-symbols-rounded"
style="font-size: 20px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;"
>{viewMode === "list" ? "grid_view" : "view_list"}</span
>
{crumb.name}
</a>
{/each}
</nav>
</button>
</div>
<!-- File List/Grid -->
<div class="flex-1 overflow-auto min-h-0">
<div class="flex-1 overflow-auto min-h-0 p-4">
{#if viewMode === "list"}
<div
class="flex flex-col gap-1"
class="flex flex-col gap-0.5"
ondragover={handleContainerDragOver}
ondrop={handleDropOnEmpty}
role="list"
>
{#if currentFolderItems.length === 0}
<div class="text-center text-light/40 py-8 text-sm">
<p>
No files yet. Drag files here or create a new
one.
</p>
<div class="flex flex-col items-center justify-center text-light/40 py-16">
<span class="material-symbols-rounded mb-3" style="font-size: 48px; font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 48;">folder_open</span>
<p class="text-body-sm">{m.files_empty()}</p>
</div>
{:else}
{#each currentFolderItems as item}
<button
type="button"
class="flex items-center gap-2 h-10 pl-1 pr-2 py-1 rounded-[32px] w-full text-left transition-colors hover:bg-dark
{selectedDoc?.id === item.id ? 'bg-dark' : ''}
class="flex items-center gap-3 px-3 py-2 rounded-xl w-full text-left transition-colors hover:bg-dark/50
{selectedDoc?.id === item.id ? 'bg-dark/50 ring-1 ring-primary/20' : ''}
{draggedItem?.id === item.id ? 'opacity-50' : ''}
{dragOverFolder === item.id ? 'ring-2 ring-primary bg-primary/10' : ''}"
draggable="true"
@@ -595,24 +596,20 @@
oncontextmenu={(e) =>
handleContextMenu(e, item)}
>
<div
class="w-8 h-8 flex items-center justify-center p-1"
>
<span
class="material-symbols-rounded text-light"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
>
{getDocIcon(item)}
</span>
</div>
<span
class="font-body text-body text-white truncate flex-1"
class="material-symbols-rounded shrink-0 {item.type === 'folder' ? 'text-amber-400' : item.type === 'kanban' ? 'text-purple-400' : 'text-light/50'}"
style="font-size: 20px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;"
>
{getDocIcon(item)}
</span>
<span
class="font-body text-body-sm text-white truncate flex-1"
>{item.name}</span
>
{#if item.type === "folder"}
<span
class="material-symbols-rounded text-light/50"
style="font-size: 20px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;"
class="material-symbols-rounded text-light/20"
style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
>
chevron_right
</span>
@@ -624,26 +621,22 @@
{:else}
<!-- Grid View -->
<div
class="grid grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4"
class="grid grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-2"
ondragover={handleContainerDragOver}
ondrop={handleDropOnEmpty}
role="list"
>
{#if currentFolderItems.length === 0}
<div
class="col-span-full text-center text-light/40 py-8 text-sm"
>
<p>
No files yet. Drag files here or create a new
one.
</p>
<div class="col-span-full flex flex-col items-center justify-center text-light/40 py-16">
<span class="material-symbols-rounded mb-3" style="font-size: 48px; font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 48;">folder_open</span>
<p class="text-body-sm">{m.files_empty()}</p>
</div>
{:else}
{#each currentFolderItems as item}
<button
type="button"
class="flex flex-col items-center gap-2 p-4 rounded-xl transition-colors hover:bg-dark
{selectedDoc?.id === item.id ? 'bg-dark' : ''}
class="flex flex-col items-center gap-2 p-3 rounded-xl border border-transparent transition-all hover:bg-dark/50 hover:border-light/5
{selectedDoc?.id === item.id ? 'bg-dark/50 border-primary/20' : ''}
{draggedItem?.id === item.id ? 'opacity-50' : ''}
{dragOverFolder === item.id ? 'ring-2 ring-primary bg-primary/10' : ''}"
draggable="true"
@@ -659,13 +652,13 @@
handleContextMenu(e, item)}
>
<span
class="material-symbols-rounded text-light"
style="font-size: 48px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 48;"
class="material-symbols-rounded {item.type === 'folder' ? 'text-amber-400' : item.type === 'kanban' ? 'text-purple-400' : 'text-light/40'}"
style="font-size: 40px; font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 40;"
>
{getDocIcon(item)}
</span>
<span
class="font-body text-body-md text-white text-center truncate w-full"
class="font-body text-[12px] text-white text-center truncate w-full"
>{item.name}</span
>
</button>
@@ -678,7 +671,7 @@
<!-- Compact Editor Panel (shown when a doc is selected) -->
{#if selectedDoc}
<div class="flex-1 min-w-0 h-full">
<div class="flex-1 min-w-0 h-full border-l border-light/5">
<DocumentViewer
document={selectedDoc}
onSave={handleSave}

View File

@@ -57,7 +57,7 @@
<button
type="button"
class="bg-night rounded-[16px] p-2 cursor-pointer hover:ring-1 hover:ring-primary/30 transition-all group w-full text-left overflow-clip flex flex-col gap-2 relative"
class="bg-night/80 border border-light/5 hover:border-light/10 rounded-xl px-3 py-2.5 cursor-pointer transition-all group w-full text-left flex flex-col gap-1.5 relative"
class:opacity-50={isDragging}
{draggable}
{ondragstart}
@@ -67,25 +67,25 @@
{#if ondelete}
<button
type="button"
class="absolute top-1 right-1 p-1 rounded-full opacity-0 group-hover:opacity-100 hover:bg-error/20 transition-all z-10"
class="absolute top-1.5 right-1.5 p-0.5 rounded-lg opacity-0 group-hover:opacity-100 hover:bg-error/10 transition-all z-10"
onclick={handleDelete}
aria-label="Delete card"
>
<span
class="material-symbols-rounded text-light/40 hover:text-error"
style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
class="material-symbols-rounded text-light/30 hover:text-error"
style="font-size: 16px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 16;"
>
delete
close
</span>
</button>
{/if}
<!-- Tags / Chips -->
{#if card.tags && card.tags.length > 0}
<div class="flex gap-[10px] items-start flex-wrap">
<div class="flex gap-1 items-start flex-wrap">
{#each card.tags as tag}
<span
class="rounded-[4px] px-1 py-[4px] font-body font-bold text-[14px] text-night leading-none overflow-clip"
class="rounded-[4px] px-1.5 py-0.5 font-body font-bold text-[11px] text-night leading-none"
style="background-color: {tag.color || '#00A3E0'}"
>
{tag.name}
@@ -95,55 +95,40 @@
{/if}
<!-- Title -->
<p class="font-body text-body text-white w-full leading-none p-1">
<p class="font-body text-body-sm text-white w-full leading-snug">
{card.title}
</p>
<!-- Bottom row: details + avatar -->
{#if hasFooter}
<div class="flex items-center justify-between w-full">
<div class="flex gap-1 items-center">
<!-- Due date -->
<div class="flex items-center justify-between w-full mt-0.5">
<div class="flex gap-2 items-center text-[11px] text-light/40">
{#if card.due_date}
<div class="flex items-center">
<span class="flex items-center gap-0.5">
<span
class="material-symbols-rounded text-light p-1"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
>
calendar_today
</span>
<span
class="font-body text-[12px] text-light leading-none"
>
{formatDueDate(card.due_date)}
</span>
</div>
class="material-symbols-rounded"
style="font-size: 14px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 14;"
>calendar_today</span>
{formatDueDate(card.due_date)}
</span>
{/if}
<!-- Checklist -->
{#if (card.checklist_total ?? 0) > 0}
<div class="flex items-center">
<span class="flex items-center gap-0.5">
<span
class="material-symbols-rounded text-light p-1"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
>
check_box
</span>
<span
class="font-body text-[12px] text-light leading-none"
>
{card.checklist_done ?? 0}/{card.checklist_total}
</span>
</div>
class="material-symbols-rounded"
style="font-size: 14px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 14;"
>check_box</span>
{card.checklist_done ?? 0}/{card.checklist_total}
</span>
{/if}
</div>
<!-- Assignee avatar -->
{#if card.assignee_id}
<Avatar
name={card.assignee_name || "?"}
src={card.assignee_avatar}
size="sm"
size="xs"
/>
{/if}
</div>

View File

@@ -14,28 +14,25 @@
</script>
<div
class="bg-background flex flex-col gap-4 items-start overflow-hidden px-4 py-5 rounded-[32px] w-64 h-[512px]"
class="bg-dark/20 border border-light/5 flex flex-col overflow-hidden rounded-xl w-[272px] shrink-0 h-full"
>
<!-- Header -->
<div class="flex items-center gap-2 p-1 rounded-[32px] w-full">
<div class="flex items-center gap-2 px-3 py-2.5 border-b border-light/5">
<div class="flex-1 flex items-center gap-2 min-w-0">
<span class="font-heading text-h4 text-white truncate">{title}</span
>
<div
class="bg-dark flex items-center justify-center p-1 rounded-lg shrink-0"
>
<span class="font-heading text-h6 text-white">{count}</span>
</div>
<span class="font-heading text-body-sm text-white truncate">{title}</span>
<span
class="text-[11px] text-light/40 bg-light/5 px-1.5 py-0.5 rounded-md shrink-0"
>{count}</span>
</div>
{#if onMore}
<button
type="button"
class="p-1 flex items-center justify-center hover:bg-dark/50 rounded-full transition-colors"
class="p-0.5 flex items-center justify-center hover:bg-dark/50 rounded-lg transition-colors"
onclick={onMore}
>
<span
class="material-symbols-rounded text-light"
style="font-size: 24px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;"
class="material-symbols-rounded text-light/40 hover:text-white"
style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
>
more_horiz
</span>
@@ -45,7 +42,7 @@
<!-- Cards container -->
<div
class="flex-1 flex flex-col gap-2 items-start overflow-y-auto w-full min-h-0"
class="flex-1 flex flex-col gap-1.5 p-2 overflow-y-auto min-h-0"
>
{#if children}
{@render children()}
@@ -54,8 +51,10 @@
<!-- Add button -->
{#if onAddCard}
<Button variant="secondary" fullWidth icon="add" onclick={onAddCard}>
Add card
</Button>
<div class="px-2 pb-2">
<Button variant="tertiary" fullWidth size="sm" icon="add" onclick={onAddCard}>
Add card
</Button>
</div>
{/if}
</div>