mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 08:21:23 +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
|
|
@ -72,13 +72,21 @@
|
|||
openSubmenuId = openSubmenuId === itemId ? null : itemId;
|
||||
}
|
||||
|
||||
function handleItemClick(item: PillDropdownItem) {
|
||||
function handleItemClick(item: PillDropdownItem, event: MouseEvent) {
|
||||
if (item.submenu && item.submenu.length > 0) {
|
||||
toggleSubmenu(item.id);
|
||||
return;
|
||||
}
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
item.onClick(event);
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
function handleSplitClick(item: PillDropdownItem, event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
if (item.onSplitClick) {
|
||||
item.onSplitClick();
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
|
@ -186,58 +194,79 @@
|
|||
style="animation-delay: {(header ? i + 1 : i) * 15}ms"
|
||||
></div>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => handleItemClick(item)}
|
||||
class="pill glass-pill fan-pill"
|
||||
class:danger-pill={item.danger}
|
||||
class:active-pill={item.active}
|
||||
class:has-submenu={item.submenu && item.submenu.length > 0}
|
||||
class:submenu-open={openSubmenuId === item.id}
|
||||
<div
|
||||
class="fan-pill-wrapper"
|
||||
class:has-split-button={item.showSplitButton}
|
||||
style="animation-delay: {(header ? i + 1 : i) * 15}ms"
|
||||
>
|
||||
{#if item.imageUrl}
|
||||
<img src={item.imageUrl} alt="" class="pill-image-icon" />
|
||||
{:else if item.icon === 'mana'}
|
||||
<svg class="pill-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d={getIcon('mana')} />
|
||||
</svg>
|
||||
{:else if item.icon}
|
||||
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIcon(item.icon)}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<span class="pill-label">{item.label}</span>
|
||||
{#if item.active}
|
||||
<svg class="check-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIcon('check')}
|
||||
/>
|
||||
</svg>
|
||||
{:else if item.submenu && item.submenu.length > 0}
|
||||
<svg
|
||||
class="chevron-submenu"
|
||||
class:rotated={openSubmenuId === item.id}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
<button
|
||||
onclick={(e) => handleItemClick(item, e)}
|
||||
class="pill glass-pill fan-pill"
|
||||
class:danger-pill={item.danger}
|
||||
class:active-pill={item.active}
|
||||
class:has-submenu={item.submenu && item.submenu.length > 0}
|
||||
class:submenu-open={openSubmenuId === item.id}
|
||||
>
|
||||
{#if item.imageUrl}
|
||||
<img src={item.imageUrl} alt="" class="pill-image-icon" />
|
||||
{:else if item.icon === 'mana'}
|
||||
<svg class="pill-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d={getIcon('mana')} />
|
||||
</svg>
|
||||
{:else if item.icon}
|
||||
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIcon(item.icon)}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<span class="pill-label">{item.label}</span>
|
||||
{#if item.active}
|
||||
<svg class="check-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIcon('check')}
|
||||
/>
|
||||
</svg>
|
||||
{:else if item.submenu && item.submenu.length > 0}
|
||||
<svg
|
||||
class="chevron-submenu"
|
||||
class:rotated={openSubmenuId === item.id}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIcon('chevronDown')}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{#if item.showSplitButton && item.onSplitClick}
|
||||
<button
|
||||
onclick={(e) => handleSplitClick(item, e)}
|
||||
class="split-button glass-pill"
|
||||
title="Open in split panel (Ctrl/Cmd+Click)"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIcon('chevronDown')}
|
||||
/>
|
||||
</svg>
|
||||
<svg class="split-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 4H5a1 1 0 00-1 1v14a1 1 0 001 1h4a1 1 0 001-1V5a1 1 0 00-1-1zM19 4h-4a1 1 0 00-1 1v14a1 1 0 001 1h4a1 1 0 001-1V5a1 1 0 00-1-1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Submenu items -->
|
||||
{#if item.submenu && item.submenu.length > 0 && openSubmenuId === item.id}
|
||||
<div class="submenu-container">
|
||||
|
|
@ -516,6 +545,61 @@
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
/* Split button wrapper */
|
||||
.fan-pill-wrapper {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 2px;
|
||||
animation: fanIn 0.15s ease-out forwards;
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
.fan-up .fan-pill-wrapper {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
.fan-pill-wrapper .fan-pill {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.fan-pill-wrapper.has-split-button .fan-pill {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.split-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 9999px;
|
||||
border-bottom-right-radius: 9999px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.split-button:hover {
|
||||
background: var(--color-primary-100, rgba(59, 130, 246, 0.15));
|
||||
border-color: var(--color-primary-200, rgba(59, 130, 246, 0.3));
|
||||
}
|
||||
|
||||
:global(.dark) .split-button:hover {
|
||||
background: var(--color-primary-900, rgba(59, 130, 246, 0.2));
|
||||
border-color: var(--color-primary-800, rgba(59, 130, 246, 0.4));
|
||||
}
|
||||
|
||||
.split-icon {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
||||
/* Footer for custom content (e.g., a11y toggles) */
|
||||
.dropdown-footer {
|
||||
animation: fanIn 0.15s ease-out forwards;
|
||||
|
|
|
|||
|
|
@ -109,7 +109,8 @@
|
|||
function createAppDropdownItems(
|
||||
apps: PillAppItem[],
|
||||
allAppsUrl?: string,
|
||||
allAppsText?: string
|
||||
allAppsText?: string,
|
||||
openInPanelHandler?: (appId: string, url: string) => void
|
||||
): PillDropdownItem[] {
|
||||
const items: PillDropdownItem[] = apps.map((app) => ({
|
||||
id: app.id,
|
||||
|
|
@ -117,7 +118,19 @@
|
|||
// Use image icon if available, otherwise use grid as fallback
|
||||
imageUrl: app.icon,
|
||||
icon: app.icon ? undefined : 'grid',
|
||||
onClick: () => {
|
||||
onClick: (event?: MouseEvent) => {
|
||||
// Check for modifier keys (Ctrl/Cmd + Click opens in panel)
|
||||
if (
|
||||
event &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
openInPanelHandler &&
|
||||
app.url &&
|
||||
!app.isCurrent
|
||||
) {
|
||||
openInPanelHandler(app.id, app.url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.isCurrent) {
|
||||
// Navigate to home route for current app
|
||||
window.location.href = '/';
|
||||
|
|
@ -127,6 +140,10 @@
|
|||
},
|
||||
active: app.isCurrent,
|
||||
disabled: false,
|
||||
// Show split button if handler is provided and app is not current
|
||||
showSplitButton: !!openInPanelHandler && !app.isCurrent && !!app.url,
|
||||
onSplitClick:
|
||||
openInPanelHandler && app.url ? () => openInPanelHandler(app.id, app.url!) : undefined,
|
||||
}));
|
||||
|
||||
// Add "All Apps" link at the end if href is provided
|
||||
|
|
@ -228,6 +245,10 @@
|
|||
showA11yQuickToggles?: boolean;
|
||||
/** Desktop navigation position (mobile always at bottom) */
|
||||
desktopPosition?: 'top' | 'bottom';
|
||||
/** Called when an app should be opened in a split panel */
|
||||
onOpenInPanel?: (appId: string, url: string) => void;
|
||||
/** Toolbar content snippet (shown in sidebar mode) */
|
||||
toolbarContent?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
|
|
@ -270,6 +291,8 @@
|
|||
onA11yReduceMotionChange,
|
||||
showA11yQuickToggles = false,
|
||||
desktopPosition = 'top',
|
||||
onOpenInPanel,
|
||||
toolbarContent,
|
||||
}: Props = $props();
|
||||
|
||||
// Type guards for elements
|
||||
|
|
@ -320,8 +343,15 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Dropdown direction: up on mobile (nav at bottom), down on desktop/sidebar
|
||||
const dropdownDirection = $derived<'up' | 'down'>(isMobile && !isSidebarMode ? 'up' : 'down');
|
||||
// Dropdown direction: up when nav is at bottom (mobile or desktop-bottom), down otherwise
|
||||
const dropdownDirection = $derived<'up' | 'down'>(
|
||||
// Mobile: always up (nav at bottom) unless in sidebar mode
|
||||
(isMobile && !isSidebarMode) ||
|
||||
// Desktop with bottom position: up unless in sidebar mode
|
||||
(!isMobile && desktopPosition === 'bottom' && !isSidebarMode)
|
||||
? 'up'
|
||||
: 'down'
|
||||
);
|
||||
|
||||
function toggleSidebarMode() {
|
||||
const newValue = !isSidebarMode;
|
||||
|
|
@ -412,7 +442,14 @@
|
|||
chevronDown: 'M19 9l-7 7-7-7',
|
||||
chevronUp: 'M5 15l7-7 7 7',
|
||||
chevronLeft: 'M15 19l-7-7 7-7',
|
||||
chevronRight: 'M9 5l7 7-7 7',
|
||||
menu: 'M4 6h16M4 12h16M4 18h16',
|
||||
// Layout icons
|
||||
sidebar: 'M3 3h7v18H3V3zm9 0h9v18h-9V3z', // Sidebar layout icon
|
||||
layoutBottom: 'M3 3h18v9H3V3zm0 12h18v6H3v-6z', // Bottom bar layout icon
|
||||
panelRight: 'M9 3h12v18H9V3zM3 3h3v18H3V3z', // Panel right icon
|
||||
minimize: 'M4 12h16', // Minimize (minus) icon
|
||||
maximize: 'M4 8h16M4 16h16', // Two lines for expand
|
||||
fire: 'M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z',
|
||||
grid: 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z',
|
||||
gridSmall:
|
||||
|
|
@ -442,7 +479,7 @@
|
|||
<!-- Logo pill / App Switcher -->
|
||||
{#if showAppSwitcher && appItems.length > 0}
|
||||
<PillDropdown
|
||||
items={createAppDropdownItems(appItems, allAppsHref, allAppsLabel)}
|
||||
items={createAppDropdownItems(appItems, allAppsHref, allAppsLabel, onOpenInPanel)}
|
||||
direction={dropdownDirection}
|
||||
label={appName}
|
||||
icon="grid"
|
||||
|
|
@ -770,28 +807,24 @@
|
|||
<!-- Control Button (right position in horizontal mode, bottom in sidebar mode) -->
|
||||
{#if !isSidebarMode}
|
||||
<div class="pill glass-pill segmented-control">
|
||||
<button onclick={collapseNav} class="segment-btn" title="Collapse navigation">
|
||||
<button onclick={collapseNav} class="segment-btn" title="Navigation minimieren">
|
||||
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIconPath('chevronLeft')}
|
||||
d={getIconPath('chevronRight')}
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="segment-divider"></div>
|
||||
<button
|
||||
onclick={toggleSidebarMode}
|
||||
class="segment-btn"
|
||||
title="Switch to sidebar navigation"
|
||||
>
|
||||
<button onclick={toggleSidebarMode} class="segment-btn" title="Zur Sidebar wechseln">
|
||||
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIconPath('chevronDown')}
|
||||
d={getIconPath('sidebar')}
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -800,26 +833,37 @@
|
|||
|
||||
<!-- Control Button (bottom position in sidebar mode) -->
|
||||
{#if isSidebarMode}
|
||||
<!-- Toolbar content (if provided) -->
|
||||
{#if toolbarContent}
|
||||
<div class="pill-divider sidebar-divider"></div>
|
||||
<div class="sidebar-toolbar-content">
|
||||
{@render toolbarContent()}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="sidebar-spacer"></div>
|
||||
<div class="pill glass-pill segmented-control sidebar-segmented">
|
||||
<button onclick={toggleSidebarMode} class="segment-btn" title="Switch to top navigation">
|
||||
<button
|
||||
onclick={toggleSidebarMode}
|
||||
class="segment-btn"
|
||||
title="Zur Bottom-Navigation wechseln"
|
||||
>
|
||||
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIconPath('chevronUp')}
|
||||
d={getIconPath('layoutBottom')}
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="segment-divider"></div>
|
||||
<button onclick={collapseNav} class="segment-btn" title="Collapse navigation">
|
||||
<button onclick={collapseNav} class="segment-btn" title="Sidebar minimieren">
|
||||
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={getIconPath('chevronLeft')}
|
||||
d={getIconPath('chevronRight')}
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -1097,6 +1141,89 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
/* Toolbar content in sidebar mode */
|
||||
.sidebar-toolbar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0;
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:global(.dark) .sidebar-toolbar-content {
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
}
|
||||
|
||||
:global(.dark) .sidebar-toolbar-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content :global(.toolbar-bar) {
|
||||
flex-direction: column;
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content :global(.toolbar-content) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content :global(.pill-toolbar-btn),
|
||||
.sidebar-toolbar-content :global(.pill-dropdown .trigger-button) {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content :global(.pill-toolbar-btn:hover),
|
||||
.sidebar-toolbar-content :global(.pill-dropdown .trigger-button:hover) {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:global(.dark) .sidebar-toolbar-content :global(.pill-toolbar-btn:hover),
|
||||
:global(.dark) .sidebar-toolbar-content :global(.pill-dropdown .trigger-button:hover) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Style for PillViewSwitcher in sidebar */
|
||||
.sidebar-toolbar-content :global(.pill-view-switcher) {
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.sidebar-toolbar-content :global(.pill-view-switcher .view-option) {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
/* Mobile: Sidebar container adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar-container {
|
||||
|
|
@ -1286,17 +1413,17 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
/* FAB for collapsed state */
|
||||
/* FAB for collapsed state - positioned at right */
|
||||
.nav-fab {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.875rem;
|
||||
border-radius: 0 0 1rem 0;
|
||||
border-radius: 0 0 0 1rem;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
|
@ -1306,17 +1433,17 @@
|
|||
.nav-fab.desktop-bottom {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
border-radius: 0 1rem 0 0;
|
||||
border-radius: 1rem 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile: FAB always at bottom left */
|
||||
/* Mobile: FAB always at bottom right */
|
||||
@media (max-width: 768px) {
|
||||
.nav-fab {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-radius: 0 1rem 0 0;
|
||||
right: 0;
|
||||
border-radius: 1rem 0 0 0;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.875rem);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ export interface PillDropdownItem {
|
|||
icon?: string;
|
||||
/** Image URL for icon (data URL or regular URL) */
|
||||
imageUrl?: string;
|
||||
/** Click handler */
|
||||
onClick?: () => void;
|
||||
/** Click handler (receives optional MouseEvent for modifier key detection) */
|
||||
onClick?: (event?: MouseEvent) => void;
|
||||
/** Whether item is disabled */
|
||||
disabled?: boolean;
|
||||
/** Whether item should be styled as danger/destructive */
|
||||
|
|
@ -43,6 +43,10 @@ export interface PillDropdownItem {
|
|||
divider?: boolean;
|
||||
/** Nested submenu items */
|
||||
submenu?: PillDropdownItem[];
|
||||
/** Whether to show a split button for opening in panel */
|
||||
showSplitButton?: boolean;
|
||||
/** Click handler for split button */
|
||||
onSplitClick?: () => void;
|
||||
}
|
||||
|
||||
export interface PillAppItem {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue