Init Repo

This commit is contained in:
2026-02-28 03:22:04 +08:00
commit de59b57ee7
883 changed files with 156857 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
import { ref, computed, onMounted, onUnmounted, type Ref, type ComputedRef } from 'vue'
export interface UseDetailPanelOptions {
/**
* Function to check if any dialogs are currently open
* This prevents keyboard shortcuts from triggering when dialogs are active
*/
isDialogOpen?: () => boolean
/**
* Session storage key for persisting auto-enable state
* If not provided, state won't be persisted
*/
sessionStorageKey?: string
}
export interface UseDetailPanelReturn<T> {
/** Auto-enable toggle state - controls whether panel shows automatically on selection */
isDetailPanelEnabled: Ref<boolean>
/** Manual visibility control - controlled by keyboard shortcuts */
isDetailPanelVisible: Ref<boolean>
/** Currently selected entity */
selectedEntity: Ref<T | null>
/** Mobile detail sheet visibility */
showMobileDetail: Ref<boolean>
/** Computed property that determines if panel should be shown */
showPanel: ComputedRef<boolean>
/** Toggle the auto-enable state */
toggleDetailPanelEnabled: () => void
/** Close the detail panel and clear selection */
closeDetailPanel: () => void
/** Select an entity and optionally show mobile detail */
selectEntity: (entity: T) => void
/** Handle row click with proper selection logic */
handleRowClick: (entity: T, event?: MouseEvent) => void
}
/**
* Composable for consistent detail panel behavior across entity browsers
*
* Provides:
* - Auto-enable toggle functionality
* - Manual keyboard control ('i' key)
* - Mobile sheet support
* - Session persistence
* - Consistent selection behavior
*/
export function useDetailPanel<T extends { id: number }>(
options: UseDetailPanelOptions = {}
): UseDetailPanelReturn<T> {
const { isDialogOpen, sessionStorageKey } = options
// State variables
const isDetailPanelEnabled = ref(true) // Auto-enable toggle state
const isDetailPanelVisible = ref(false) // Manual visibility control
const selectedEntity = ref<T | null>(null)
const showMobileDetail = ref(false)
// Load persisted auto-enable state from session storage
if (sessionStorageKey) {
const stored = sessionStorage.getItem(sessionStorageKey)
if (stored !== null) {
try {
isDetailPanelEnabled.value = JSON.parse(stored)
} catch {
// Fall back to default if parsing fails
isDetailPanelEnabled.value = true
}
}
}
// Combined panel visibility logic (OR condition)
const showPanel = computed(() =>
selectedEntity.value && (isDetailPanelEnabled.value || isDetailPanelVisible.value)
)
// Keyboard shortcut handler
const handleKeyDown = (event: KeyboardEvent) => {
// Only handle 'i' key when an entity is selected
if (
event.key.toLowerCase() === 'i' &&
selectedEntity.value
) {
// Don't trigger if dialogs are open
if (isDialogOpen && isDialogOpen()) {
return
}
// Don't trigger if user is typing in an input field
const target = event.target as HTMLElement
if (target && (
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.contentEditable === 'true'
)) {
return
}
event.preventDefault()
// Toggle detail panel visibility
if (window.innerWidth >= 1024) {
// Desktop: toggle manual visibility
isDetailPanelVisible.value = !isDetailPanelVisible.value
} else {
// Mobile: toggle mobile detail sheet
showMobileDetail.value = !showMobileDetail.value
}
}
}
// Methods
const toggleDetailPanelEnabled = () => {
isDetailPanelEnabled.value = !isDetailPanelEnabled.value
// Persist to session storage if key provided
if (sessionStorageKey) {
sessionStorage.setItem(sessionStorageKey, JSON.stringify(isDetailPanelEnabled.value))
}
}
const closeDetailPanel = () => {
selectedEntity.value = null
showMobileDetail.value = false
isDetailPanelVisible.value = false
}
const selectEntity = (entity: T) => {
selectedEntity.value = entity
// Show mobile detail sheet on small screens if auto-enabled
if (isDetailPanelEnabled.value && window.innerWidth < 1024) {
showMobileDetail.value = true
}
// Don't reset manual visibility when selecting a new entity
// This allows the panel to stay open when manually activated with 'i' key
}
const handleRowClick = (entity: T, event?: MouseEvent) => {
// Don't handle row clicks if any dialog is open
if (isDialogOpen && isDialogOpen()) {
return
}
// Single click selects the entity
// If detail panel is auto-enabled, it will automatically show due to the reactive condition
selectedEntity.value = entity
// Don't reset manual visibility when clicking a row
// This preserves manual panel state when activated with 'i' key
// Show mobile detail sheet on small screens if auto-enabled
if (isDetailPanelEnabled.value && window.innerWidth < 1024) {
showMobileDetail.value = true
}
}
// Lifecycle hooks for keyboard event handling
onMounted(() => {
document.addEventListener('keydown', handleKeyDown)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeyDown)
})
return {
isDetailPanelEnabled,
isDetailPanelVisible,
selectedEntity,
showMobileDetail,
showPanel,
toggleDetailPanelEnabled,
closeDetailPanel,
selectEntity,
handleRowClick
}
}