fix: add ShortcutTooltip component and integrate it for keyboard shortcuts in CalendarEventPanel and Calendar pages

This commit is contained in:
Shariq Ansari 2025-09-02 22:35:03 +05:30
parent f2ce3165dd
commit 346643bc6d
4 changed files with 139 additions and 32 deletions

View File

@ -254,6 +254,7 @@ declare module 'vue' {
SettingsIcon: typeof import('./src/components/Icons/SettingsIcon.vue')['default']
SettingsIcon2: typeof import('./src/components/Icons/SettingsIcon2.vue')['default']
SettingsPage: typeof import('./src/components/Settings/SettingsPage.vue')['default']
ShortcutTooltip: typeof import('./src/components/ShortcutTooltip.vue')['default']
SidebarLink: typeof import('./src/components/SidebarLink.vue')['default']
SidePanelLayout: typeof import('./src/components/SidePanelLayout.vue')['default']
SidePanelLayoutEditor: typeof import('./src/components/SidePanelLayoutEditor.vue')['default']

View File

@ -13,33 +13,31 @@
{{ __(title) }}
</div>
<div class="flex items-center gap-x-1">
<Button
<ShortcutTooltip
v-if="mode == 'details'"
:icon="EditIcon"
variant="ghost"
:tooltip="__('Edit event')"
@click="editDetails"
/>
<Button
:label="__('Edit event')"
combo="Enter"
>
<Button :icon="EditIcon" variant="ghost" @click="editDetails" />
</ShortcutTooltip>
<ShortcutTooltip
v-if="mode === 'edit' || mode === 'details'"
icon="trash-2"
variant="ghost"
:tooltip="__('Delete event')"
@click="deleteEvent"
/>
<Button
:label="__('Delete event')"
combo="Delete"
:alt-combos="['Backspace']"
>
<Button icon="trash-2" variant="ghost" @click="deleteEvent" />
</ShortcutTooltip>
<ShortcutTooltip
v-if="mode === 'edit' || mode === 'details'"
icon="copy"
variant="ghost"
:tooltip="__('Duplicate event')"
@click="duplicateEvent"
/>
<Button
icon="x"
variant="ghost"
:tooltip="__('Close panel')"
@click="close"
/>
:label="__('Duplicate event')"
combo="Mod+D"
>
<Button icon="copy" variant="ghost" @click="duplicateEvent" />
</ShortcutTooltip>
<ShortcutTooltip :label="__('Close panel')" combo="Esc">
<Button icon="x" variant="ghost" @click="close" />
</ShortcutTooltip>
</div>
</div>
@ -377,6 +375,7 @@ import {
CalendarActiveEvent as activeEvent,
createDocumentResource,
} from 'frappe-ui'
import ShortcutTooltip from '@/components/ShortcutTooltip.vue'
import { ref, computed, watch, h } from 'vue'
import { useRouter } from 'vue-router'

View File

@ -0,0 +1,104 @@
<template>
<Tooltip v-if="!disabled">
<template #body>
<div
class="rounded bg-surface-gray-7 py-1.5 px-2 text-xs text-ink-white shadow-xl"
>
<span class="flex items-center gap-1">
<span>{{ label }}</span>
<!-- Primary combos (one or many) -->
<template
v-for="(combo, idx) in primaryCombosDisplay"
:key="'prim-' + idx + combo"
>
<KeyboardShortcut
bg
class="!bg-surface-gray-5 !text-ink-gray-2 px-1"
:combo="combo"
/>
</template>
<!-- Alternate combos / equivalents -->
<template
v-for="(alt, idx) in altCombosDisplay"
:key="'alt-' + idx + alt"
>
<KeyboardShortcut
bg
class="!bg-surface-gray-5 !text-ink-gray-2 px-1"
:combo="alt"
/>
</template>
</span>
</div>
</template>
<slot />
</Tooltip>
<slot v-else />
</template>
<script setup lang="ts">
import { Tooltip, KeyboardShortcut } from 'frappe-ui'
import { computed } from 'vue'
interface Props {
label: string
combo?: string | string[]
altCombos?: string[]
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
combo: '',
altCombos: () => [],
disabled: false,
})
const isMac = computed(() => {
if (typeof navigator === 'undefined') return false
const platform =
(navigator as any).userAgentData?.platform || navigator.platform || ''
if (/Mac|iPod|iPhone|iPad/i.test(platform)) return true
return /Mac OS X|Macintosh|iPhone|iPad|iPod/i.test(navigator.userAgent || '')
})
function normalizeCombo(raw: string): string {
if (!raw) return ''
if (/^mod\+/i.test(raw)) {
const rest = raw.split('+').slice(1).join('+')
return (isMac.value ? 'Cmd' : 'Ctrl') + '+' + rest
}
return raw
}
function normalizeList(list: string | string[]): string[] {
const arr = Array.isArray(list) ? list : list ? [list] : []
return arr.map(normalizeCombo)
}
// Dedupe Backspace/Delete (prefer Backspace) on macOS
function dedupeMacDeleteVariants(primary: string[], alts: string[]) {
if (!isMac.value) return { primary, alts }
const all = [...primary, ...alts]
if (all.includes('Delete') && all.includes('Backspace')) {
return {
primary: primary.filter((k) => k !== 'Delete'),
alts: alts.filter((k) => k !== 'Delete'),
}
}
return { primary, alts }
}
// Base normalized lists
const normalizedPrimary = computed(() => normalizeList(props.combo))
const normalizedAlt = computed(() => props.altCombos.map(normalizeCombo))
// Apply dedupe once to both arrays to avoid circular dependency
const deduped = computed(() =>
dedupeMacDeleteVariants(normalizedPrimary.value, normalizedAlt.value),
)
const primaryCombosDisplay = computed(() => deduped.value.primary)
const altCombosDisplay = computed(() => deduped.value.alts)
defineOptions({ name: 'ShortcutTooltip' })
</script>

View File

@ -4,14 +4,16 @@
<ViewBreadcrumbs routeName="Calendar" />
</template>
<template #right-header>
<Button
variant="solid"
:label="__('Create')"
:disabled="isCreateDisabled"
@click="newEvent"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
<ShortcutTooltip :label="__('Create event')" combo="Mod+E">
<Button
variant="solid"
:label="__('Create')"
:disabled="isCreateDisabled"
@click="newEvent"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</ShortcutTooltip>
</template>
</LayoutHeader>
<div class="flex h-screen overflow-hidden">
@ -129,6 +131,7 @@
import CalendarEventPanel from '@/components/Calendar/CalendarEventPanel.vue'
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import ShortcutTooltip from '@/components/ShortcutTooltip.vue'
import { sessionStore } from '@/stores/session'
import { globalStore } from '@/stores/global'
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'