mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 10:36:41 +02:00
feat(splitscreen): add split-screen feature for multi-app side-by-side view
Add new @manacore/shared-splitscreen package enabling iFrame-based
split-screen functionality across Calendar, Todo, and Contacts apps.
Features:
- SplitPaneContainer with CSS Grid layout
- AppPanel with iFrame sandbox permissions and loading/error states
- ResizeHandle with mouse, touch, and keyboard support (20-80% range)
- PanelControls for swap and close actions
- Svelte 5 runes-based store with Context API
- URL persistence (?panel=todo&split=60)
- localStorage persistence with versioning
- Mobile auto-disable (<1024px breakpoint)
Integration:
- PillNavigation: added onOpenInPanel prop and Ctrl/Cmd+click support
- PillDropdown: added split button per app item
- Calendar, Todo, Contacts layouts wrapped with SplitPaneContainer
Also fixes:
- WeekView.svelte: fixed {@const} placement error
- MultiDayView.svelte: fixed {@const} placement error
🤖 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
f51708d75a
commit
f2ac3e245e
27 changed files with 2770 additions and 531 deletions
|
|
@ -0,0 +1,112 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* SplitPaneContainer Component
|
||||
* Main container that handles split-screen layout.
|
||||
*/
|
||||
|
||||
import type { Snippet } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { getSplitPanelContext } from '../stores/split-panel.svelte.js';
|
||||
import AppPanel from './AppPanel.svelte';
|
||||
import PanelControls from './PanelControls.svelte';
|
||||
import ResizeHandle from './ResizeHandle.svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { children, class: className = '' }: Props = $props();
|
||||
|
||||
const splitPanel = getSplitPanelContext();
|
||||
|
||||
// Grid template based on divider position
|
||||
let gridTemplate = $derived(
|
||||
splitPanel.isActive && splitPanel.rightPanel ? `${splitPanel.dividerPosition}% 6px 1fr` : '1fr'
|
||||
);
|
||||
|
||||
function handleResize(position: number) {
|
||||
splitPanel.setDividerPosition(position);
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
splitPanel.resetDividerPosition();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="split-pane-container {className}"
|
||||
class:split-active={splitPanel.isActive && splitPanel.rightPanel}
|
||||
style:--grid-template={gridTemplate}
|
||||
>
|
||||
<div class="main-panel">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
{#if splitPanel.isActive && splitPanel.rightPanel}
|
||||
<ResizeHandle
|
||||
position={splitPanel.dividerPosition}
|
||||
onResize={handleResize}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
|
||||
<div class="side-panel">
|
||||
<AppPanel panel={splitPanel.rightPanel} />
|
||||
<PanelControls
|
||||
panelName={splitPanel.rightPanel.name || splitPanel.rightPanel.appId}
|
||||
onSwap={() => splitPanel.swapPanels()}
|
||||
onClose={() => splitPanel.closePanel()}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.split-pane-container {
|
||||
display: grid;
|
||||
grid-template-columns: var(--grid-template, 1fr);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-panel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Ensure proper stacking */
|
||||
.split-active .main-panel {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.split-active .side-panel {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Hide side panel on mobile via media query as fallback */
|
||||
@media (max-width: 1023px) {
|
||||
.split-pane-container {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.side-panel,
|
||||
.split-pane-container :global(.resize-handle) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue