mirror of
https://github.com/immich-app/static-pages.git
synced 2026-09-30 13:23:05 +08:00
feat(ui)!: propagate action trigger events to action handlers (#818)
This commit is contained in:
@@ -208,7 +208,7 @@ export const components: ComponentItem[] = [
|
||||
{ name: 'Tooltip', icon: mdiInformationSlabCircleOutline, activeIcon: mdiInformationSlabCircle },
|
||||
];
|
||||
|
||||
const onAction: ActionItemHandler = (item) => {
|
||||
const onAction: ActionItemHandler = ({ action: item }) => {
|
||||
toastManager.show({
|
||||
title: `Clicked ${item.title}`,
|
||||
color: item.color ?? 'primary',
|
||||
|
||||
@@ -1,6 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { ExampleActions } from '$lib/constants.js';
|
||||
import { ActionButton, Heading } from '@immich/ui';
|
||||
import {
|
||||
ActionButton,
|
||||
type ActionEvent,
|
||||
type ActionItem,
|
||||
CommandPaletteDefaultProvider,
|
||||
Heading,
|
||||
Kbd,
|
||||
Text,
|
||||
toastManager,
|
||||
} from '@immich/ui';
|
||||
import { mdiDeleteForeverOutline, mdiTrashCanOutline } from '@mdi/js';
|
||||
|
||||
const onAction = ({ action, event }: ActionEvent) => {
|
||||
if (event.type === 'click') {
|
||||
toastManager.show({
|
||||
title: `Clicked ${action.title}…`,
|
||||
description: '…using the mouse',
|
||||
icon: action.icon,
|
||||
});
|
||||
} else {
|
||||
const { key, ctrlKey: force } = event as KeyboardEvent;
|
||||
|
||||
toastManager.show({
|
||||
title: `Used shortcut for ${action.title}…`,
|
||||
description: `…using ${force ? 'Ctrl+' : ''}${key}`,
|
||||
color: force ? 'danger' : undefined,
|
||||
icon: force ? mdiDeleteForeverOutline : action.icon,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const DeleteWithShortcuts: ActionItem = {
|
||||
title: 'Delete',
|
||||
icon: mdiTrashCanOutline,
|
||||
color: 'danger',
|
||||
shortcuts: [{ key: 'Delete' }, { key: 'Delete', ctrl: true }],
|
||||
onAction,
|
||||
};
|
||||
</script>
|
||||
|
||||
<Heading size="small" class="mb-2">Icon buttons</Heading>
|
||||
@@ -20,3 +57,13 @@
|
||||
<ActionButton type="button" action={ExampleActions.Delete} />
|
||||
<ActionButton type="button" action={ExampleActions.Download} variant="filled" color="primary" />
|
||||
</div>
|
||||
|
||||
<CommandPaletteDefaultProvider name="Example action" actions={[DeleteWithShortcuts]} />
|
||||
|
||||
<Heading size="small" class="mt-6 mb-2">Event info</Heading>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Text>Use the mouse or keyboard shortcuts <Kbd>Del</Kbd> or <Kbd>Ctrl Del</Kbd></Text>
|
||||
<div class="w-fit">
|
||||
<ActionButton action={DeleteWithShortcuts} type="button" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { Markdown, Text } from '@immich/ui';
|
||||
import BasicExample from './BasicExample.svelte';
|
||||
import basicExample from './BasicExample.svelte?raw';
|
||||
import EventExample from './EventExample.svelte';
|
||||
import eventExample from './EventExample.svelte?raw';
|
||||
</script>
|
||||
|
||||
<ComponentPage name="CommandPalette" description="A command menu that can be used to search, filter, and select items">
|
||||
@@ -12,4 +14,5 @@
|
||||
<Text>See <ComponentLink name="CommandPaletteButton" /> for a dedicated button to open the component</Text>
|
||||
</Markdown.Alert>
|
||||
<ComponentExamples examples={[{ title: 'Basic', code: basicExample, component: BasicExample }]} />
|
||||
<ComponentExamples examples={[{ title: 'Event types', code: eventExample, component: EventExample }]} />
|
||||
</ComponentPage>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Button,
|
||||
CommandPaletteDefaultProvider,
|
||||
commandPaletteManager,
|
||||
Text,
|
||||
toastManager,
|
||||
type ActionEvent,
|
||||
type ActionItem,
|
||||
} from '@immich/ui';
|
||||
import { mdiCallMerge } from '@mdi/js';
|
||||
|
||||
const onAction = ({ event }: ActionEvent) => {
|
||||
toastManager.info(`Event type: ${event.type}`);
|
||||
if (event instanceof KeyboardEvent) {
|
||||
toastManager.info(`Key used: ${event.key}`);
|
||||
}
|
||||
};
|
||||
|
||||
const action: ActionItem = {
|
||||
title: 'Trigger me',
|
||||
description: 'Trigger me using your mouse or keyboard',
|
||||
icon: mdiCallMerge,
|
||||
onAction,
|
||||
};
|
||||
</script>
|
||||
|
||||
<CommandPaletteDefaultProvider actions={[action]} types={['custom']} />
|
||||
|
||||
<Text>Get the event that triggered the action</Text>
|
||||
<Button onclick={() => commandPaletteManager.open('type:custom')}>Open</Button>
|
||||
@@ -11,7 +11,7 @@
|
||||
mdiTrashCanOutline,
|
||||
} from '@mdi/js';
|
||||
|
||||
const onAction: ActionItemHandler = (item) => {
|
||||
const onAction: ActionItemHandler = ({ action: item }) => {
|
||||
toastManager.show({
|
||||
title: `Clicked ${item.title}`,
|
||||
color: item.color ?? 'primary',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const common = $derived({
|
||||
variant: variantOverride ?? 'ghost',
|
||||
color: colorOverride ?? action.color ?? 'secondary',
|
||||
onclick: () => onAction(action),
|
||||
onclick: (event: MouseEvent) => onAction({ action, event }),
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
type Props = {
|
||||
item: ActionItem;
|
||||
selected: boolean;
|
||||
onSelect: () => void;
|
||||
onSelect: (event: MouseEvent) => void;
|
||||
};
|
||||
|
||||
const { item, selected, onSelect }: Props = $props();
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<div bind:this={ref} class="p-1">
|
||||
<Button
|
||||
onclick={() => onSelect()}
|
||||
onclick={(event: MouseEvent) => onSelect(event)}
|
||||
fullWidth
|
||||
variant={selected ? 'outline' : 'ghost'}
|
||||
color="secondary"
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
<DropdownMenu.Item
|
||||
textValue={item.title}
|
||||
closeOnSelect
|
||||
onSelect={() => item.onAction(item)}
|
||||
onSelect={(event) => item.onAction({ action: item, event })}
|
||||
class={itemStyles({ color: item.color, inset: true })}
|
||||
>
|
||||
{@render itemText(item)}
|
||||
@@ -168,7 +168,7 @@
|
||||
<DropdownMenu.Item
|
||||
textValue={item.title}
|
||||
closeOnSelect
|
||||
onSelect={() => item.onAction(item)}
|
||||
onSelect={(event) => item.onAction({ action: item, event })}
|
||||
title={item.title}
|
||||
class={itemStyles({ color: item.color })}
|
||||
>
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
type CommandPaletteTranslations,
|
||||
} from '$lib/services/command-palette-manager.svelte.js';
|
||||
import { t } from '$lib/services/translation.svelte.js';
|
||||
import type { ActionItem } from '$lib/types.js';
|
||||
import type { ActionEvent, ActionItem } from '$lib/types.js';
|
||||
import { mdiArrowDown, mdiArrowUp, mdiKeyboardEsc, mdiKeyboardReturn, mdiMagnify } from '@mdi/js';
|
||||
|
||||
type Props = {
|
||||
onClose: (action?: ActionItem) => void;
|
||||
onClose: (actionEvent?: ActionEvent) => void;
|
||||
translations?: CommandPaletteTranslations;
|
||||
initialQuery?: string;
|
||||
};
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
onClose(commandPaletteManager.selectedItem);
|
||||
onClose({ action: commandPaletteManager.selectedItem, event });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@
|
||||
<CommandPaletteItem
|
||||
{item}
|
||||
selected={commandPaletteManager.isSelected(item)}
|
||||
onSelect={() => onClose(item)}
|
||||
onSelect={(event) => onClose({ action: item, event })}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { matchesShortcut, shortcuts, shouldIgnoreEvent } from '$lib/actions/shor
|
||||
import CommandPaletteModal from '$lib/internal/CommandPaletteModal.svelte';
|
||||
import { modalManager } from '$lib/services/modal-manager.svelte.js';
|
||||
import { isModalOpen } from '$lib/state/modal-state.svelte.js';
|
||||
import type { ActionItem, MaybePromise, TranslationProps } from '$lib/types.js';
|
||||
import type { ActionEvent, ActionItem, MaybePromise, TranslationProps } from '$lib/types.js';
|
||||
import { isEnabled } from '$lib/utilities/common.js';
|
||||
import { asArray, generateId } from '$lib/utilities/internal.js';
|
||||
import Fuse, { type FuseResult, type FuseResultMatch, type IFuseOptions } from 'fuse.js';
|
||||
@@ -220,13 +220,13 @@ class CommandPaletteManager {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
action?.onAction(action);
|
||||
action?.onAction({ action, event });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async #onClose(action?: ActionItem) {
|
||||
await action?.onAction(action);
|
||||
async #onClose(actionEvent?: ActionEvent) {
|
||||
await actionEvent?.action.onAction(actionEvent);
|
||||
this.#isOpen = false;
|
||||
this.#results = [];
|
||||
}
|
||||
|
||||
@@ -370,7 +370,17 @@ export type DatePickerProps = {
|
||||
|
||||
export type IfLike = { $if?: () => boolean };
|
||||
|
||||
export type ActionItemHandler<T extends ActionItem = ActionItem> = (item: T) => unknown | Promise<unknown>;
|
||||
/**
|
||||
* The event will be a `KeyboardEvent | MouseEvent` for actions triggered via
|
||||
* normal (icon) buttons and the command palette (including shortcuts), and
|
||||
* a custom event type for actions triggered via bits-ui components like the
|
||||
* context menu.
|
||||
*/
|
||||
export type ActionEvent<T extends ActionItem = ActionItem> = { action: T; event: Event };
|
||||
|
||||
export type ActionItemHandler<T extends ActionItem = ActionItem> = (
|
||||
actionEvent: ActionEvent<T>,
|
||||
) => unknown | Promise<unknown>;
|
||||
|
||||
export type LinkItem = {
|
||||
title: string;
|
||||
|
||||
Reference in New Issue
Block a user