mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
feat(calendar): add search highlighting to calendar views
- Add searchStore for managing search state and event highlighting - Integrate InputBar search with calendar view highlighting - Dim non-matching events and highlight matching events during search - Add search-highlighted and search-dimmed CSS classes to all views - Adjust toolbar position for DateStrip component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7ff8213bd6
commit
51912a285d
7 changed files with 221 additions and 18 deletions
|
|
@ -55,7 +55,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PillToolbar position="bottom" bottomOffset="70px">
|
<PillToolbar position="bottom" bottomOffset="140px">
|
||||||
<!-- Calendar selector -->
|
<!-- Calendar selector -->
|
||||||
<PillCalendarSelector direction="up" embedded={true} />
|
<PillCalendarSelector direction="up" embedded={true} />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { eventsStore } from '$lib/stores/events.svelte';
|
import { eventsStore } from '$lib/stores/events.svelte';
|
||||||
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
||||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
import { todosStore, type Task } from '$lib/stores/todos.svelte';
|
import { todosStore, type Task } from '$lib/stores/todos.svelte';
|
||||||
import TaskBlock from './TaskBlock.svelte';
|
import TaskBlock from './TaskBlock.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
@ -677,6 +678,8 @@
|
||||||
{#each headerAllDayEvents as event}
|
{#each headerAllDayEvents as event}
|
||||||
<button
|
<button
|
||||||
class="all-day-event"
|
class="all-day-event"
|
||||||
|
class:search-highlighted={searchStore.isEventHighlighted(event.id)}
|
||||||
|
class:search-dimmed={searchStore.isEventDimmed(event.id)}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onclick={(e) => handleEventClick(event, e)}
|
onclick={(e) => handleEventClick(event, e)}
|
||||||
>
|
>
|
||||||
|
|
@ -719,6 +722,8 @@
|
||||||
{#each blockAllDayEvents as event}
|
{#each blockAllDayEvents as event}
|
||||||
<button
|
<button
|
||||||
class="all-day-block-event"
|
class="all-day-block-event"
|
||||||
|
class:search-highlighted={searchStore.isEventHighlighted(event.id)}
|
||||||
|
class:search-dimmed={searchStore.isEventDimmed(event.id)}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onclick={(e) => handleEventClick(event, e)}
|
onclick={(e) => handleEventClick(event, e)}
|
||||||
>
|
>
|
||||||
|
|
@ -731,12 +736,16 @@
|
||||||
{@const isBeingDragged = isDragging && draggedEvent?.id === event.id}
|
{@const isBeingDragged = isDragging && draggedEvent?.id === event.id}
|
||||||
{@const isBeingResized = isResizing && resizeEvent?.id === event.id}
|
{@const isBeingResized = isResizing && resizeEvent?.id === event.id}
|
||||||
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
||||||
|
{@const isSearchHighlighted = searchStore.isEventHighlighted(event.id)}
|
||||||
|
{@const isSearchDimmed = searchStore.isEventDimmed(event.id)}
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div
|
<div
|
||||||
class="event-card"
|
class="event-card"
|
||||||
class:dragging={isBeingDragged}
|
class:dragging={isBeingDragged}
|
||||||
class:resizing={isBeingResized}
|
class:resizing={isBeingResized}
|
||||||
class:draft={isDraft}
|
class:draft={isDraft}
|
||||||
|
class:search-highlighted={isSearchHighlighted}
|
||||||
|
class:search-dimmed={isSearchDimmed}
|
||||||
data-event-id={event.id}
|
data-event-id={event.id}
|
||||||
style={isBeingDragged
|
style={isBeingDragged
|
||||||
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
||||||
|
|
@ -844,6 +853,18 @@
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-event.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-event.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Block-style all-day events (displayed as full-day blocks in the grid) */
|
/* Block-style all-day events (displayed as full-day blocks in the grid) */
|
||||||
|
|
@ -870,6 +891,17 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.all-day-block-event.search-highlighted {
|
||||||
|
opacity: 0.6;
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-block-event.search-dimmed {
|
||||||
|
opacity: 0.15;
|
||||||
|
filter: grayscale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.all-day-block-event .event-title {
|
.all-day-block-event .event-title {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
@ -969,6 +1001,21 @@
|
||||||
animation: pulse-outline 1.5s ease-in-out infinite;
|
animation: pulse-outline 1.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search highlighting */
|
||||||
|
.event-card.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 4px hsl(var(--color-primary) / 0.3),
|
||||||
|
0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse-outline {
|
@keyframes pulse-outline {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { eventsStore } from '$lib/stores/events.svelte';
|
import { eventsStore } from '$lib/stores/events.svelte';
|
||||||
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
||||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
import { todosStore } from '$lib/stores/todos.svelte';
|
import { todosStore } from '$lib/stores/todos.svelte';
|
||||||
import TodoDayCell from './TodoDayCell.svelte';
|
import TodoDayCell from './TodoDayCell.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
@ -286,11 +287,15 @@
|
||||||
{#each getEventsForDay(day) as event}
|
{#each getEventsForDay(day) as event}
|
||||||
{@const isBeingDragged = isDragging && draggedEvent?.id === event.id}
|
{@const isBeingDragged = isDragging && draggedEvent?.id === event.id}
|
||||||
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
||||||
|
{@const isSearchHighlighted = searchStore.isEventHighlighted(event.id)}
|
||||||
|
{@const isSearchDimmed = searchStore.isEventDimmed(event.id)}
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div
|
<div
|
||||||
class="event-pill"
|
class="event-pill"
|
||||||
class:dragging={isBeingDragged}
|
class:dragging={isBeingDragged}
|
||||||
class:draft={isDraft}
|
class:draft={isDraft}
|
||||||
|
class:search-highlighted={isSearchHighlighted}
|
||||||
|
class:search-dimmed={isSearchDimmed}
|
||||||
data-event-id={event.id}
|
data-event-id={event.id}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onpointerdown={(e) => startDrag(event, e)}
|
onpointerdown={(e) => startDrag(event, e)}
|
||||||
|
|
@ -458,6 +463,20 @@
|
||||||
animation: pulse-outline 1.5s ease-in-out infinite;
|
animation: pulse-outline 1.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search highlighting */
|
||||||
|
.event-pill.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.3);
|
||||||
|
z-index: 10;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-pill.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse-outline {
|
@keyframes pulse-outline {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { eventsStore } from '$lib/stores/events.svelte';
|
import { eventsStore } from '$lib/stores/events.svelte';
|
||||||
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
||||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
import { todosStore, type Task } from '$lib/stores/todos.svelte';
|
import { todosStore, type Task } from '$lib/stores/todos.svelte';
|
||||||
import TaskBlock from './TaskBlock.svelte';
|
import TaskBlock from './TaskBlock.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
@ -781,6 +782,8 @@
|
||||||
{#each getHeaderAllDayEventsForDay(day) as event}
|
{#each getHeaderAllDayEventsForDay(day) as event}
|
||||||
<button
|
<button
|
||||||
class="all-day-event"
|
class="all-day-event"
|
||||||
|
class:search-highlighted={searchStore.isEventHighlighted(event.id)}
|
||||||
|
class:search-dimmed={searchStore.isEventDimmed(event.id)}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onclick={(e) => handleEventClick(event, e)}
|
onclick={(e) => handleEventClick(event, e)}
|
||||||
title={event.title}
|
title={event.title}
|
||||||
|
|
@ -841,6 +844,8 @@
|
||||||
{#each getBlockAllDayEventsForDay(day) as event (event.id)}
|
{#each getBlockAllDayEventsForDay(day) as event (event.id)}
|
||||||
<button
|
<button
|
||||||
class="all-day-block-event"
|
class="all-day-block-event"
|
||||||
|
class:search-highlighted={searchStore.isEventHighlighted(event.id)}
|
||||||
|
class:search-dimmed={searchStore.isEventDimmed(event.id)}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onclick={(e) => handleEventClick(event, e)}
|
onclick={(e) => handleEventClick(event, e)}
|
||||||
title={event.title}
|
title={event.title}
|
||||||
|
|
@ -856,12 +861,16 @@
|
||||||
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
||||||
{@const isCrossDayDrag =
|
{@const isCrossDayDrag =
|
||||||
isBeingDragged && dragTargetDay && !isSameDay(day, dragTargetDay)}
|
isBeingDragged && dragTargetDay && !isSameDay(day, dragTargetDay)}
|
||||||
|
{@const isSearchHighlighted = searchStore.isEventHighlighted(event.id)}
|
||||||
|
{@const isSearchDimmed = searchStore.isEventDimmed(event.id)}
|
||||||
<div
|
<div
|
||||||
class="event-card"
|
class="event-card"
|
||||||
class:dragging={isBeingDragged && !isCrossDayDrag}
|
class:dragging={isBeingDragged && !isCrossDayDrag}
|
||||||
class:dragging-source={isCrossDayDrag}
|
class:dragging-source={isCrossDayDrag}
|
||||||
class:resizing={isBeingResized}
|
class:resizing={isBeingResized}
|
||||||
class:draft={isDraft}
|
class:draft={isDraft}
|
||||||
|
class:search-highlighted={isSearchHighlighted}
|
||||||
|
class:search-dimmed={isSearchDimmed}
|
||||||
data-event-id={event.id}
|
data-event-id={event.id}
|
||||||
style={isBeingDragged && !isCrossDayDrag
|
style={isBeingDragged && !isCrossDayDrag
|
||||||
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
||||||
|
|
@ -995,6 +1004,18 @@
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-event.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-event.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.compact .all-day-event,
|
.compact .all-day-event,
|
||||||
|
|
@ -1034,6 +1055,17 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.all-day-block-event.search-highlighted {
|
||||||
|
opacity: 0.6;
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-block-event.search-dimmed {
|
||||||
|
opacity: 0.15;
|
||||||
|
filter: grayscale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.all-day-block-event .event-title {
|
.all-day-block-event .event-title {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
@ -1214,6 +1246,21 @@
|
||||||
animation: pulse-outline 1.5s ease-in-out infinite;
|
animation: pulse-outline 1.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search highlighting */
|
||||||
|
.event-card.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 4px hsl(var(--color-primary) / 0.3),
|
||||||
|
0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse-outline {
|
@keyframes pulse-outline {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { eventsStore } from '$lib/stores/events.svelte';
|
import { eventsStore } from '$lib/stores/events.svelte';
|
||||||
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
||||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
import { todosStore, type Task } from '$lib/stores/todos.svelte';
|
import { todosStore, type Task } from '$lib/stores/todos.svelte';
|
||||||
import TaskBlock from './TaskBlock.svelte';
|
import TaskBlock from './TaskBlock.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
@ -818,6 +819,8 @@
|
||||||
{#each getHeaderAllDayEventsForDay(day) as event}
|
{#each getHeaderAllDayEventsForDay(day) as event}
|
||||||
<button
|
<button
|
||||||
class="all-day-event"
|
class="all-day-event"
|
||||||
|
class:search-highlighted={searchStore.isEventHighlighted(event.id)}
|
||||||
|
class:search-dimmed={searchStore.isEventDimmed(event.id)}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onclick={() => goto(`/?event=${event.id}`)}
|
onclick={() => goto(`/?event=${event.id}`)}
|
||||||
>
|
>
|
||||||
|
|
@ -875,6 +878,8 @@
|
||||||
{#each getBlockAllDayEventsForDay(day) as event (event.id)}
|
{#each getBlockAllDayEventsForDay(day) as event (event.id)}
|
||||||
<button
|
<button
|
||||||
class="all-day-block-event"
|
class="all-day-block-event"
|
||||||
|
class:search-highlighted={searchStore.isEventHighlighted(event.id)}
|
||||||
|
class:search-dimmed={searchStore.isEventDimmed(event.id)}
|
||||||
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
style="background-color: {calendarsStore.getColor(event.calendarId)}"
|
||||||
onclick={() => goto(`/?event=${event.id}`)}
|
onclick={() => goto(`/?event=${event.id}`)}
|
||||||
>
|
>
|
||||||
|
|
@ -889,12 +894,16 @@
|
||||||
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
{@const isDraft = eventsStore.isDraftEvent(event.id)}
|
||||||
{@const isCrossDayDrag =
|
{@const isCrossDayDrag =
|
||||||
isBeingDragged && dragTargetDay && !isSameDay(day, dragTargetDay)}
|
isBeingDragged && dragTargetDay && !isSameDay(day, dragTargetDay)}
|
||||||
|
{@const isSearchHighlighted = searchStore.isEventHighlighted(event.id)}
|
||||||
|
{@const isSearchDimmed = searchStore.isEventDimmed(event.id)}
|
||||||
<div
|
<div
|
||||||
class="event-card"
|
class="event-card"
|
||||||
class:dragging={isBeingDragged && !isCrossDayDrag}
|
class:dragging={isBeingDragged && !isCrossDayDrag}
|
||||||
class:dragging-source={isCrossDayDrag}
|
class:dragging-source={isCrossDayDrag}
|
||||||
class:resizing={isBeingResized}
|
class:resizing={isBeingResized}
|
||||||
class:draft={isDraft}
|
class:draft={isDraft}
|
||||||
|
class:search-highlighted={isSearchHighlighted}
|
||||||
|
class:search-dimmed={isSearchDimmed}
|
||||||
data-event-id={event.id}
|
data-event-id={event.id}
|
||||||
style={isBeingDragged && !isCrossDayDrag
|
style={isBeingDragged && !isCrossDayDrag
|
||||||
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
||||||
|
|
@ -1028,6 +1037,18 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-event.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-event.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Block-style all-day events (displayed as full-day blocks in the grid) */
|
/* Block-style all-day events (displayed as full-day blocks in the grid) */
|
||||||
|
|
@ -1054,6 +1075,17 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.all-day-block-event.search-highlighted {
|
||||||
|
opacity: 0.6;
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-day-block-event.search-dimmed {
|
||||||
|
opacity: 0.15;
|
||||||
|
filter: grayscale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.all-day-block-event .event-title {
|
.all-day-block-event .event-title {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
@ -1225,6 +1257,21 @@
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search highlighting */
|
||||||
|
.event-card.search-highlighted {
|
||||||
|
outline: 2px solid hsl(var(--color-primary));
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 4px hsl(var(--color-primary) / 0.3),
|
||||||
|
0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card.search-dimmed {
|
||||||
|
opacity: 0.35;
|
||||||
|
filter: grayscale(0.3);
|
||||||
|
}
|
||||||
|
|
||||||
/* Task drag ghost */
|
/* Task drag ghost */
|
||||||
.task-drag-ghost {
|
.task-drag-ghost {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
45
apps/calendar/apps/web/src/lib/stores/search.svelte.ts
Normal file
45
apps/calendar/apps/web/src/lib/stores/search.svelte.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* Search Store - manages search state for highlighting events in calendar views
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface SearchItem {
|
||||||
|
id: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchStore {
|
||||||
|
// Current search query
|
||||||
|
query = $state('');
|
||||||
|
|
||||||
|
// Event IDs that match the search
|
||||||
|
matchingEventIds = $state<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// Whether search is active (user is typing in InputBar)
|
||||||
|
isSearching = $state(false);
|
||||||
|
|
||||||
|
// Set search query and matching items (events or any items with an id)
|
||||||
|
setSearch(query: string, matchingItems: SearchItem[]) {
|
||||||
|
this.query = query;
|
||||||
|
this.matchingEventIds = new Set(matchingItems.map((item) => item.id));
|
||||||
|
this.isSearching = query.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear search
|
||||||
|
clear() {
|
||||||
|
this.query = '';
|
||||||
|
this.matchingEventIds = new Set();
|
||||||
|
this.isSearching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an event matches the search
|
||||||
|
isEventHighlighted(eventId: string): boolean {
|
||||||
|
return this.isSearching && this.matchingEventIds.has(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an event should be dimmed (search active but event doesn't match)
|
||||||
|
isEventDimmed(eventId: string): boolean {
|
||||||
|
return this.isSearching && !this.matchingEventIds.has(eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const searchStore = new SearchStore();
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
PillNavItem,
|
PillNavItem,
|
||||||
PillDropdownItem,
|
PillDropdownItem,
|
||||||
QuickInputItem,
|
QuickInputItem,
|
||||||
QuickAction,
|
|
||||||
CreatePreview,
|
CreatePreview,
|
||||||
} from '@manacore/shared-ui';
|
} from '@manacore/shared-ui';
|
||||||
import { theme } from '$lib/stores/theme';
|
import { theme } from '$lib/stores/theme';
|
||||||
|
|
@ -34,6 +33,7 @@
|
||||||
import { getPillAppItems } from '@manacore/shared-branding';
|
import { getPillAppItems } from '@manacore/shared-branding';
|
||||||
import { setLocale, supportedLocales } from '$lib/i18n';
|
import { setLocale, supportedLocales } from '$lib/i18n';
|
||||||
import { searchEvents } from '$lib/api/events';
|
import { searchEvents } from '$lib/api/events';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { de } from 'date-fns/locale';
|
import { de } from 'date-fns/locale';
|
||||||
import {
|
import {
|
||||||
|
|
@ -49,19 +49,7 @@
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
// QuickInputBar quick actions
|
// InputBar search - search events
|
||||||
const quickActions: QuickAction[] = [
|
|
||||||
{
|
|
||||||
id: 'today',
|
|
||||||
label: 'Heute',
|
|
||||||
icon: 'calendar',
|
|
||||||
onclick: () => viewStore.goToToday(),
|
|
||||||
},
|
|
||||||
{ id: 'agenda', label: 'Agenda', icon: 'list', href: '/agenda' },
|
|
||||||
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// QuickInputBar search - search events
|
|
||||||
async function handleSearch(query: string): Promise<QuickInputItem[]> {
|
async function handleSearch(query: string): Promise<QuickInputItem[]> {
|
||||||
if (!query.trim()) return [];
|
if (!query.trim()) return [];
|
||||||
|
|
||||||
|
|
@ -76,9 +64,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelect(item: QuickInputItem) {
|
function handleSelect(item: QuickInputItem) {
|
||||||
|
searchStore.clear();
|
||||||
goto(`/event/${item.id}`);
|
goto(`/event/${item.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update search store when search changes (for calendar view highlighting)
|
||||||
|
function handleSearchChange(query: string, results: QuickInputItem[]) {
|
||||||
|
if (!query.trim()) {
|
||||||
|
searchStore.clear();
|
||||||
|
} else {
|
||||||
|
searchStore.setSearch(query, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// QuickInputBar Quick-Create handlers
|
// QuickInputBar Quick-Create handlers
|
||||||
function handleParseCreate(query: string): CreatePreview | null {
|
function handleParseCreate(query: string): CreatePreview | null {
|
||||||
if (!query.trim()) return null;
|
if (!query.trim()) return null;
|
||||||
|
|
@ -347,11 +345,11 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Global Quick Input Bar -->
|
<!-- Global Input Bar -->
|
||||||
<QuickInputBar
|
<QuickInputBar
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
{quickActions}
|
onSearchChange={handleSearchChange}
|
||||||
placeholder="Neuer Termin oder suchen..."
|
placeholder="Neuer Termin oder suchen..."
|
||||||
emptyText="Keine Termine gefunden"
|
emptyText="Keine Termine gefunden"
|
||||||
searchingText="Suche..."
|
searchingText="Suche..."
|
||||||
|
|
@ -360,7 +358,7 @@
|
||||||
createText="Erstellen"
|
createText="Erstellen"
|
||||||
appIcon="calendar"
|
appIcon="calendar"
|
||||||
primaryColor="#3b82f6"
|
primaryColor="#3b82f6"
|
||||||
autoFocus={false}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue