add overflow menu support to pane stack
This commit is contained in:
parent
100f506c44
commit
db71e6dc80
3 changed files with 121 additions and 26 deletions
|
|
@ -1,6 +1,7 @@
|
|||
<svelte:options runes={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui'
|
||||
import {
|
||||
type PaneConfig,
|
||||
type PaneStackStore,
|
||||
|
|
@ -105,10 +106,39 @@
|
|||
element={pane.action.element}
|
||||
elementStyle={!!pane.action.element}
|
||||
onclick={pane.action.handler}
|
||||
disabled={pane.action.disabled}
|
||||
>
|
||||
{pane.action.label}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if pane.overflowMenu && pane.overflowMenu.length > 0}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
{...props}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
iconOnly
|
||||
icon="ellipsis"
|
||||
aria-label="More options"
|
||||
/>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content class="overflow-menu" side="bottom" align="end" sideOffset={4}>
|
||||
{#each pane.overflowMenu as item}
|
||||
<DropdownMenu.Item
|
||||
class="overflow-menu-item {item.variant === 'danger' ? 'danger' : ''}"
|
||||
onSelect={item.handler}
|
||||
>
|
||||
{item.label}
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SidebarHeader>
|
||||
|
||||
|
|
@ -256,4 +286,38 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overflow menu styles
|
||||
:global(.overflow-menu) {
|
||||
background: var(--menu-bg, white);
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
border-radius: $card-corner;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: $unit-half;
|
||||
min-width: calc($unit * 20);
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
:global(.overflow-menu-item) {
|
||||
padding: $unit $unit-2x;
|
||||
border-radius: $item-corner-small;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: var(--button-bg-hover, #f5f5f5);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: var(--danger, #dc3545);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: var(--danger-bg, #fff5f5);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,19 @@ export type ElementType = 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light'
|
|||
/** Context key for pane stack */
|
||||
const PANE_STACK_CONTEXT_KEY = Symbol('pane-stack')
|
||||
|
||||
export interface PaneAction {
|
||||
label: string
|
||||
handler: () => void
|
||||
element?: ElementType
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface OverflowMenuItem {
|
||||
label: string
|
||||
handler: () => void
|
||||
variant?: 'default' | 'danger'
|
||||
}
|
||||
|
||||
export interface PaneConfig {
|
||||
/** Unique identifier for this pane */
|
||||
id: string
|
||||
|
|
@ -25,11 +38,9 @@ export interface PaneConfig {
|
|||
/** Optional callback when back is clicked (for root pane) */
|
||||
onback?: () => void
|
||||
/** Optional save/action button configuration */
|
||||
action?: {
|
||||
label: string
|
||||
handler: () => void
|
||||
element?: ElementType
|
||||
}
|
||||
action?: PaneAction
|
||||
/** Optional overflow menu items */
|
||||
overflowMenu?: OverflowMenuItem[]
|
||||
/** Whether this pane's content should scroll */
|
||||
scrollable?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { Snippet, Component } from 'svelte'
|
|||
import {
|
||||
PaneStackStore,
|
||||
type PaneConfig,
|
||||
type OverflowMenuItem,
|
||||
type ElementType
|
||||
} from '$lib/stores/paneStack.svelte'
|
||||
|
||||
|
|
@ -128,30 +129,28 @@ class SidebarStore {
|
|||
|
||||
/**
|
||||
* Update the action button for the current pane
|
||||
* @param handler - Click handler, or undefined to show disabled button
|
||||
* @param label - Button label (defaults to 'Done')
|
||||
* @param element - Element type for styling
|
||||
* @param show - Whether to show the button at all (defaults to true if label provided)
|
||||
*/
|
||||
setAction(
|
||||
onsave: (() => void) | undefined,
|
||||
saveLabel?: string,
|
||||
element?: ElementType
|
||||
handler: (() => void) | undefined,
|
||||
label?: string,
|
||||
element?: ElementType,
|
||||
show: boolean = true
|
||||
) {
|
||||
const currentPane = this.paneStack.currentPane
|
||||
if (currentPane) {
|
||||
this.paneStack.updateCurrentProps({})
|
||||
// Update action on current pane
|
||||
const panes = this.paneStack.panes
|
||||
const currentIndex = panes.length - 1
|
||||
if (currentIndex >= 0 && panes[currentIndex]) {
|
||||
panes[currentIndex] = {
|
||||
...panes[currentIndex],
|
||||
action:
|
||||
onsave ?
|
||||
{
|
||||
label: saveLabel ?? 'Done',
|
||||
handler: onsave,
|
||||
element
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
const panes = this.paneStack.panes
|
||||
const currentIndex = panes.length - 1
|
||||
if (currentIndex >= 0 && panes[currentIndex]) {
|
||||
panes[currentIndex] = {
|
||||
...panes[currentIndex],
|
||||
action: show && label ? {
|
||||
label,
|
||||
handler: handler ?? (() => {}),
|
||||
element,
|
||||
disabled: !handler
|
||||
} : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,6 +162,27 @@ class SidebarStore {
|
|||
this.setAction(undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the overflow menu items for the current pane
|
||||
*/
|
||||
setOverflowMenu(items: OverflowMenuItem[] | undefined) {
|
||||
const panes = this.paneStack.panes
|
||||
const currentIndex = panes.length - 1
|
||||
if (currentIndex >= 0 && panes[currentIndex]) {
|
||||
panes[currentIndex] = {
|
||||
...panes[currentIndex],
|
||||
overflowMenu: items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the overflow menu for the current pane
|
||||
*/
|
||||
clearOverflowMenu() {
|
||||
this.setOverflowMenu(undefined)
|
||||
}
|
||||
|
||||
// Getters for reactive access
|
||||
get isOpen() {
|
||||
return this.state.open
|
||||
|
|
|
|||
Loading…
Reference in a new issue