feat(operator): command palette, impersonation, incident, tweaks (O.8)
- CommandPalette + useCommandPalette: ⌘K opens a search-and-jump panel over real tenants/partners + fixture flags + nav + actions. Arrow keys + Enter navigate, Escape/backdrop close. Recents are intentionally omitted for now; add when there's something to recent over. - Impersonation stub: useImpersonation + ImpersonationModal + ImpersonationBanner. Modal opens from tenant detail and from the palette. Banner stays at the top of the shell until exited. No real OBO token is minted — wiring OAuth Token Exchange is tracked as a follow-up. - IncidentModal + useIncidentModal: opened from the Overview and Infrastructure incident banners, renders the mock INCIDENT data with metrics, timeline and draft composer. - TweaksPanel + useTweaks: floating bottom-right panel for theme (dark/light), density (comfy/compact), env badge (prod/staging/dev). Saved to localStorage. - Theme/density apply via [data-theme] + [data-density] overrides in tokens.css. Topbar env badge now reads from useTweaks instead of a prop. - Layout wires ⌘K + ⌘[ at the document level and mounts the palette + modals + banner + tweaks panel once for all pages.
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
|
||||
const route = useRoute()
|
||||
const { toggle } = useSidebar()
|
||||
const { toggle: togglePalette } = useCommandPalette()
|
||||
// Touch useTweaks so the persisted theme/density hydrate on first paint.
|
||||
useTweaks()
|
||||
|
||||
// Derive the active nav row from the route. Matches OpSidebar's `id` keys.
|
||||
const currentNav = computed(() => {
|
||||
@@ -24,13 +27,17 @@ const currentNav = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
// Keyboard shortcut: ⌘[ toggles sidebar. ⌘K palette lands in O.8.
|
||||
// Keyboard shortcuts: ⌘[ toggles sidebar, ⌘K opens the command palette.
|
||||
onMounted(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === '[') {
|
||||
e.preventDefault()
|
||||
toggle()
|
||||
}
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault()
|
||||
togglePalette()
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', onKey)
|
||||
onBeforeUnmount(() => document.removeEventListener('keydown', onKey))
|
||||
@@ -39,23 +46,32 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="shell">
|
||||
<OpSidebar :current="currentNav" />
|
||||
<main>
|
||||
<OpTopbar />
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
<ImpersonationBanner />
|
||||
<div class="cols">
|
||||
<OpSidebar :current="currentNav" />
|
||||
<main>
|
||||
<OpTopbar />
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<CommandPalette />
|
||||
<ImpersonationModal />
|
||||
<IncidentModal />
|
||||
<TweaksPanel />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
.cols { display: flex; flex: 1; min-height: 0; }
|
||||
|
||||
main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
||||
.content { flex: 1; min-width: 0; overflow-y: auto; }
|
||||
|
||||
Reference in New Issue
Block a user