diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d879e5e..b507e0c18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ Format: weekly entries grouped by feature area. --- +## 2026-04-21 — Calendar Sidebar Typed Indicator Dots + +### Added +- Show typed indicator dots on the right-sidebar mini-calendar when viewing the Calendar tab. Each day cell renders up to 3 side-by-side colored dots, one per item, with colors matching the item's visual type: purple for events, green for imported events, blue for tasks, pink for reminders, orange for snoozes. Days with more items than available slots prioritize diversity — one dot per unique type first, then filling remaining slots with duplicates. For example, a day with 3 events, 2 tasks, and 1 snooze shows one purple, one blue, and one orange dot rather than three purple dots. The Journal tab keeps its single emerald/amber activity-intensity dot unchanged, so switching tabs visibly changes what the sidebar tells you. + +--- + ## 2026-04-20 — Calendar Sync Triggers ### Fixed diff --git a/apps/desktop/src/renderer/src/components/calendar/calendar-item-chip.tsx b/apps/desktop/src/renderer/src/components/calendar/calendar-item-chip.tsx index ba6e21f9d..643a566e6 100644 --- a/apps/desktop/src/renderer/src/components/calendar/calendar-item-chip.tsx +++ b/apps/desktop/src/renderer/src/components/calendar/calendar-item-chip.tsx @@ -1,27 +1,21 @@ -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' +import { AlarmClock, Calendar, CalendarDays, CheckSquare3, NotificationSnooze } from '@/lib/icons' +import { getEventBaseColor, getEventBgColor, getEventTextColor } from '@/lib/event-type-colors' import { formatTimeOfDay } from '@/lib/time-format' import type { ClockFormat } from '@/lib/time-format' import { cn } from '@/lib/utils' import type { CalendarProjectionItem } from '@/services/calendar-service' import type { AnchorRect } from './types' -const CHIP_STYLES: Record = { - event: - 'border-[#D8B4FE] bg-[#FAF5FF] text-violet-800 dark:border-violet-500/30 dark:bg-violet-950/30 dark:text-violet-200', - task: 'border-[#BEDBFF] bg-[#EFF6FF] text-blue-800 dark:border-blue-500/30 dark:bg-blue-950/30 dark:text-blue-200', - reminder: - 'border-[#B9F8CF] bg-[#F0FDF4] text-green-800 dark:border-green-500/30 dark:bg-green-950/30 dark:text-green-200', - snooze: - 'border-[#FFD6A7] bg-[#FFF7ED] text-orange-800 dark:border-orange-500/30 dark:bg-orange-950/30 dark:text-orange-200', - external_event: 'border-border bg-surface text-muted-foreground' -} - -const INVERTED_CHIP_STYLES: Record = { - event: 'bg-[#9810FA] text-white dark:bg-[#C4B5FD] dark:text-[#1a1625]', - task: 'bg-[#155DFC] text-white dark:bg-[#93C5FD] dark:text-[#051833]', - reminder: 'bg-[#FCCEE8] text-white dark:bg-[#FCA5A5] dark:text-[#5c1a2f]', - snooze: 'bg-[#F54900] text-white dark:bg-[#FDBA74] dark:text-[#6b2e0f]', - external_event: 'bg-[#00A63E] text-white dark:bg-[#86EFAC] dark:text-[#051a0a]' +const VISUAL_TYPE_ICONS: Record< + CalendarProjectionItem['visualType'], + React.ComponentType<{ className?: string }> +> = { + event: CalendarDays, + task: CheckSquare3, + reminder: AlarmClock, + snooze: NotificationSnooze, + external_event: Calendar } interface CalendarItemChipProps { @@ -44,11 +38,24 @@ export function CalendarItemChip({ onDeleteItem }: CalendarItemChipProps): React.JSX.Element { const timeLabel = item.isAllDay ? 'All day' : formatTimeOfDay(new Date(item.startAt), clockFormat) + const VisualIcon = VISUAL_TYPE_ICONS[item.visualType] const deletable = Boolean(onDeleteItem) && canDeleteEvent(item) const cls = cn( - 'flex h-full w-full items-start justify-between gap-0.5 rounded-[6px] border px-1 py-0.5 text-left transition-colors @xl:px-2 @xl:py-1', - isSelected ? INVERTED_CHIP_STYLES[item.visualType] : CHIP_STYLES[item.visualType], - (onClick || deletable) && 'cursor-pointer hover:brightness-95' + 'flex h-full w-full items-start justify-between gap-0.5 rounded-[6px] px-1 py-0.5 text-left transition-[filter] @xl:px-2 @xl:py-1', + (onClick || deletable) && 'cursor-pointer hover:brightness-100' + ) + const chipStyle = useMemo( + () => + isSelected + ? { + backgroundColor: getEventBaseColor(item.visualType), + color: '#FFFFFF' + } + : { + backgroundColor: getEventBgColor(item.visualType), + color: getEventTextColor(item.visualType) + }, + [item.visualType, isSelected] ) const handleContextMenu = useCallback( @@ -69,6 +76,7 @@ export function CalendarItemChip({ const content = ( <> + {item.title} {timeLabel} @@ -81,6 +89,7 @@ export function CalendarItemChip({ ) diff --git a/apps/desktop/src/renderer/src/lib/color.ts b/apps/desktop/src/renderer/src/lib/color.ts new file mode 100644 index 000000000..a3eab0afb --- /dev/null +++ b/apps/desktop/src/renderer/src/lib/color.ts @@ -0,0 +1,6 @@ +export function withAlpha(hex: string, opacity: number): string { + const alpha = Math.round(opacity * 255) + .toString(16) + .padStart(2, '0') + return `${hex}${alpha}` +} diff --git a/apps/desktop/src/renderer/src/lib/event-type-colors.ts b/apps/desktop/src/renderer/src/lib/event-type-colors.ts new file mode 100644 index 000000000..048933e03 --- /dev/null +++ b/apps/desktop/src/renderer/src/lib/event-type-colors.ts @@ -0,0 +1,26 @@ +import type { CalendarProjectionItem } from '@/services/calendar-service' +import { withAlpha } from './color' + +type VisualType = CalendarProjectionItem['visualType'] + +export const EVENT_BG_OPACITY = 0.2 + +export const EVENT_TYPE_COLORS: Record = { + event: '#9FE1E7', + task: '#1EB06D', + reminder: '#1BADF8', + snooze: '#7BD148', + external_event: '#9A9CFF' +} + +export function getEventBaseColor(type: VisualType): string { + return EVENT_TYPE_COLORS[type] +} + +export function getEventBgColor(type: VisualType): string { + return withAlpha(EVENT_TYPE_COLORS[type], EVENT_BG_OPACITY) +} + +export function getEventTextColor(type: VisualType): string { + return EVENT_TYPE_COLORS[type] +} diff --git a/apps/desktop/src/renderer/src/lib/icons/icon-map.ts b/apps/desktop/src/renderer/src/lib/icons/icon-map.ts index b9571f64a..315c5cc59 100644 --- a/apps/desktop/src/renderer/src/lib/icons/icon-map.ts +++ b/apps/desktop/src/renderer/src/lib/icons/icon-map.ts @@ -152,6 +152,7 @@ import { CheckmarkCircle01Icon, CheckmarkCircle02Icon, CheckmarkSquare01Icon, + CheckmarkSquare03Icon, ClipboardPasteIcon, CloudAngledRainIcon, ColorsIcon, @@ -197,6 +198,7 @@ import { NeuralNetworkIcon, NextIcon, Notification01Icon, + NotificationSnooze01Icon, NodeMoveDownIcon, NodeMoveUpIcon, PanelLeftIcon, @@ -329,6 +331,7 @@ export const Timer = createIcon(Timer01Icon) export const AlarmClock = createIcon(AlarmClockIcon) export const Bell = createIcon(Notification01Icon) export const BellRing = createIcon(BellDotIcon) +export const NotificationSnooze = createIcon(NotificationSnooze01Icon) export const Tag = createIcon(Tag01Icon) export const Tags = createIcon(TagsIcon) export const Flag = createIcon(Flag01Icon) @@ -447,6 +450,7 @@ export const Check = createIcon(Tick01Icon) export const CheckCircle = createIcon(CheckmarkCircle01Icon) export const CheckCircle2 = createIcon(CheckmarkCircle02Icon) export const CheckSquare = createIcon(CheckmarkSquare01Icon) +export const CheckSquare3 = createIcon(CheckmarkSquare03Icon) export const X = createIcon(Cancel01Icon) export const XCircle = createIcon(CancelCircleIcon) export const AlertCircle = createIcon(AlertCircleIcon)