From e8ec273355ad636f3949e84cda5a3d3b6bd1aa74 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:57:28 +0100 Subject: [PATCH] feat(calendar): make toolbar content horizontally scrollable on mobile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added overflow-x: auto with hidden scrollbar - Max-width constraint to prevent overflow - Smooth touch scrolling for mobile devices 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../calendar/CalendarToolbar.svelte | 9 + .../lib/components/calendar/DateStrip.svelte | 8 +- .../src/lib/stores/eventContextMenu.svelte.ts | 45 ++++ .../src/context-menu/ContextMenu.svelte | 208 ++++++++++++++++++ packages/shared-ui/src/context-menu/index.ts | 3 + packages/shared-ui/src/context-menu/types.ts | 41 ++++ packages/shared-ui/src/index.ts | 4 + 7 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 apps/calendar/apps/web/src/lib/stores/eventContextMenu.svelte.ts create mode 100644 packages/shared-ui/src/context-menu/ContextMenu.svelte create mode 100644 packages/shared-ui/src/context-menu/index.ts create mode 100644 packages/shared-ui/src/context-menu/types.ts diff --git a/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte b/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte index 0e979f07b..ef7444e83 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte @@ -153,6 +153,15 @@ box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.08); border-radius: 1rem; white-space: nowrap; + max-width: calc(100vw - 2rem); + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ + } + + .toolbar-content::-webkit-scrollbar { + display: none; /* Chrome/Safari */ } :global(.dark) .toolbar-content { diff --git a/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte b/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte index 82a4a57ce..7b0a3b73a 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte @@ -293,8 +293,8 @@ .date-strip-wrapper { position: fixed; bottom: calc(140px + env(safe-area-inset-bottom, 0px)); /* Above InputBar + PillNav */ - left: 1rem; - right: 1rem; + left: 0; + right: 0; z-index: 48; display: flex; flex-direction: column; @@ -513,8 +513,8 @@ /* Responsive */ @media (max-width: 640px) { .date-strip-wrapper { - left: 0.5rem; - right: 0.5rem; + left: 0; + right: 0; } .date-strip-container { diff --git a/apps/calendar/apps/web/src/lib/stores/eventContextMenu.svelte.ts b/apps/calendar/apps/web/src/lib/stores/eventContextMenu.svelte.ts new file mode 100644 index 000000000..8c337064b --- /dev/null +++ b/apps/calendar/apps/web/src/lib/stores/eventContextMenu.svelte.ts @@ -0,0 +1,45 @@ +/** + * Event Context Menu Store - Manages context menu state for calendar events + */ + +import type { CalendarEvent } from '@calendar/shared'; + +// State +let visible = $state(false); +let x = $state(0); +let y = $state(0); +let targetEvent = $state(null); + +export const eventContextMenuStore = { + // Getters + get visible() { + return visible; + }, + get x() { + return x; + }, + get y() { + return y; + }, + get targetEvent() { + return targetEvent; + }, + + /** + * Show the context menu for an event + */ + show(event: CalendarEvent, clientX: number, clientY: number) { + targetEvent = event; + x = clientX; + y = clientY; + visible = true; + }, + + /** + * Hide the context menu + */ + hide() { + visible = false; + targetEvent = null; + }, +}; diff --git a/packages/shared-ui/src/context-menu/ContextMenu.svelte b/packages/shared-ui/src/context-menu/ContextMenu.svelte new file mode 100644 index 000000000..6cbf91d8e --- /dev/null +++ b/packages/shared-ui/src/context-menu/ContextMenu.svelte @@ -0,0 +1,208 @@ + + +{#if visible} + + +{/if} + + diff --git a/packages/shared-ui/src/context-menu/index.ts b/packages/shared-ui/src/context-menu/index.ts new file mode 100644 index 000000000..59acefbf5 --- /dev/null +++ b/packages/shared-ui/src/context-menu/index.ts @@ -0,0 +1,3 @@ +export { default as ContextMenu } from './ContextMenu.svelte'; +export type { ContextMenuItem, ContextMenuState } from './types'; +export { createContextMenuState } from './types'; diff --git a/packages/shared-ui/src/context-menu/types.ts b/packages/shared-ui/src/context-menu/types.ts new file mode 100644 index 000000000..6b38ec590 --- /dev/null +++ b/packages/shared-ui/src/context-menu/types.ts @@ -0,0 +1,41 @@ +import type { Snippet } from 'svelte'; + +export interface ContextMenuItem { + /** Unique identifier for the item */ + id: string; + /** Display label */ + label: string; + /** Icon snippet to render */ + icon?: Snippet; + /** Keyboard shortcut hint */ + shortcut?: string; + /** Whether the item is disabled */ + disabled?: boolean; + /** Visual variant */ + variant?: 'default' | 'danger'; + /** Item type - use 'divider' for separator */ + type?: 'item' | 'divider'; + /** Action to perform when clicked */ + action?: () => void; + /** Additional data attached to the item */ + data?: unknown; +} + +export interface ContextMenuState { + visible: boolean; + x: number; + y: number; + target: T | null; +} + +/** + * Creates a context menu state object + */ +export function createContextMenuState(): ContextMenuState { + return { + visible: false, + x: 0, + y: 0, + target: null, + }; +} diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 1915e966c..36eb00f7d 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -143,3 +143,7 @@ export type { DonutSegment, ProgressItem, } from './charts'; + +// Context Menu +export { ContextMenu, createContextMenuState } from './context-menu'; +export type { ContextMenuItem, ContextMenuState } from './context-menu';