mirror of
https://github.com/umami-software/umami.git
synced 2026-02-12 16:45:35 +01:00
hide next overlay; ctrl+shift+d to hide dials; move dials to bottom left
This commit is contained in:
parent
e611e42542
commit
90eb038cd7
3 changed files with 45 additions and 50 deletions
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": ["Bash(pnpm --filter @niteshift/dials build:*)"],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -166,6 +166,7 @@ if (cloudMode) {
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
export default {
|
export default {
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
|
devIndicators: false,
|
||||||
env: {
|
env: {
|
||||||
basePath,
|
basePath,
|
||||||
cloudMode,
|
cloudMode,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import { BooleanControl } from '../controls/BooleanControl';
|
||||||
import { NumberControl } from '../controls/NumberControl';
|
import { NumberControl } from '../controls/NumberControl';
|
||||||
import type { DialRegistration } from '../types';
|
import type { DialRegistration } from '../types';
|
||||||
|
|
||||||
|
type OverlayState = 'hidden' | 'collapsed' | 'expanded';
|
||||||
|
|
||||||
export interface DialsOverlayProps {
|
export interface DialsOverlayProps {
|
||||||
/** Initial visibility state */
|
/** Initial visibility state */
|
||||||
defaultVisible?: boolean;
|
defaultVisible?: boolean;
|
||||||
|
|
@ -35,47 +37,27 @@ export function DialsOverlay({
|
||||||
defaultVisible = true,
|
defaultVisible = true,
|
||||||
position = 'bottom-left',
|
position = 'bottom-left',
|
||||||
}: DialsOverlayProps) {
|
}: DialsOverlayProps) {
|
||||||
// Load visibility state from localStorage (avoiding hydration mismatch)
|
// Three states: hidden (nothing), collapsed (button only), expanded (full panel)
|
||||||
const [isVisible, setIsVisible] = useState(defaultVisible);
|
const [overlayState, setOverlayState] = useState<OverlayState>(
|
||||||
|
defaultVisible ? 'expanded' : 'collapsed',
|
||||||
|
);
|
||||||
|
|
||||||
// Load from localStorage after mount to avoid hydration issues
|
// Load from localStorage after mount to avoid hydration issues
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stored = localStorage.getItem('niteshift-dials-visible');
|
const stored = localStorage.getItem('niteshift-dials-state');
|
||||||
if (stored !== null) {
|
if (stored !== null && ['hidden', 'collapsed', 'expanded'].includes(stored)) {
|
||||||
setIsVisible(stored === 'true');
|
setOverlayState(stored as OverlayState);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [dials, setDials] = useState<DialRegistration[]>([]);
|
const [dials, setDials] = useState<DialRegistration[]>([]);
|
||||||
const [hasNextOverlay, setHasNextOverlay] = useState(false);
|
|
||||||
const [isMacLike, setIsMacLike] = useState(false);
|
const [isMacLike, setIsMacLike] = useState(false);
|
||||||
const [shortcutLabel, setShortcutLabel] = useState('Ctrl+D (macOS) / Ctrl+Alt+D (Win/Linux)');
|
|
||||||
const registry = getDialRegistry();
|
const registry = getDialRegistry();
|
||||||
|
|
||||||
// Persist visibility state to localStorage
|
// Persist state to localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('niteshift-dials-visible', String(isVisible));
|
localStorage.setItem('niteshift-dials-state', overlayState);
|
||||||
}, [isVisible]);
|
}, [overlayState]);
|
||||||
|
|
||||||
// Detect Next.js error overlay
|
|
||||||
useEffect(() => {
|
|
||||||
const checkNextOverlay = () => {
|
|
||||||
// Next.js error overlay has specific identifiers
|
|
||||||
const nextjsOverlay =
|
|
||||||
document.querySelector('nextjs-portal') ||
|
|
||||||
document.querySelector('[data-nextjs-dialog-overlay]') ||
|
|
||||||
document.querySelector('[data-nextjs-toast]');
|
|
||||||
setHasNextOverlay(!!nextjsOverlay);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check on mount and set up observer
|
|
||||||
checkNextOverlay();
|
|
||||||
|
|
||||||
const observer = new MutationObserver(checkNextOverlay);
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Subscribe to registry changes
|
// Subscribe to registry changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -99,22 +81,28 @@ export function DialsOverlay({
|
||||||
setIsMacLike(isMac);
|
setIsMacLike(isMac);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setShortcutLabel(isMacLike ? 'Ctrl+D (macOS)' : 'Ctrl+Alt+D (Windows/Linux)');
|
|
||||||
}, [isMacLike]);
|
|
||||||
|
|
||||||
// Keyboard shortcut to toggle visibility
|
// Keyboard shortcut to toggle visibility
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyPress = (e: KeyboardEvent) => {
|
const handleKeyPress = (e: KeyboardEvent) => {
|
||||||
|
// Guard against undefined or null key (can happen with some special key events)
|
||||||
|
if (!e.key) return;
|
||||||
|
|
||||||
const key = e.key.toLowerCase();
|
const key = e.key.toLowerCase();
|
||||||
if (key !== 'd') return;
|
if (key !== 'd') return;
|
||||||
|
|
||||||
const macCombo = isMacLike && e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey;
|
const macCombo = isMacLike && e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey;
|
||||||
const otherCombo = !isMacLike && e.ctrlKey && e.altKey && !e.metaKey && !e.shiftKey;
|
const otherCombo = !isMacLike && e.ctrlKey && e.altKey && !e.metaKey && !e.shiftKey;
|
||||||
|
// Ctrl+Shift+D to hide entirely on any platform
|
||||||
|
const hideCombo = e.ctrlKey && e.shiftKey && !e.metaKey && !e.altKey;
|
||||||
|
|
||||||
if (macCombo || otherCombo) {
|
if (hideCombo) {
|
||||||
|
// Ctrl+Shift+D: toggle between hidden and collapsed
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsVisible(prev => !prev);
|
setOverlayState(prev => (prev === 'hidden' ? 'collapsed' : 'hidden'));
|
||||||
|
} else if (macCombo || otherCombo) {
|
||||||
|
// Ctrl+D / Ctrl+Alt+D: toggle between collapsed and expanded
|
||||||
|
e.preventDefault();
|
||||||
|
setOverlayState(prev => (prev === 'expanded' ? 'collapsed' : 'expanded'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -163,20 +151,21 @@ export function DialsOverlay({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate bottom position based on Next.js overlay presence
|
// Hidden state: render nothing
|
||||||
const bottomPosition = hasNextOverlay ? '140px' : '20px';
|
if (overlayState === 'hidden') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isVisible) {
|
// Collapsed state: render button only
|
||||||
|
if (overlayState === 'collapsed') {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="dials-toggle-button"
|
className="dials-toggle-button"
|
||||||
onClick={() => setIsVisible(true)}
|
onClick={() => setOverlayState('expanded')}
|
||||||
title={`Show Dials (${shortcutLabel})`}
|
title="Show Dials (Ctrl+D)"
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
[position.includes('bottom') ? 'bottom' : 'top']: position.includes('bottom')
|
[position.includes('bottom') ? 'bottom' : 'top']: '20px',
|
||||||
? bottomPosition
|
|
||||||
: '20px',
|
|
||||||
[position.includes('right') ? 'right' : 'left']: '20px',
|
[position.includes('right') ? 'right' : 'left']: '20px',
|
||||||
width: '48px',
|
width: '48px',
|
||||||
height: '48px',
|
height: '48px',
|
||||||
|
|
@ -202,9 +191,7 @@ export function DialsOverlay({
|
||||||
className="dials-overlay"
|
className="dials-overlay"
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
[position.includes('bottom') ? 'bottom' : 'top']: position.includes('bottom')
|
[position.includes('bottom') ? 'bottom' : 'top']: '20px',
|
||||||
? bottomPosition
|
|
||||||
: '20px',
|
|
||||||
[position.includes('right') ? 'right' : 'left']: '20px',
|
[position.includes('right') ? 'right' : 'left']: '20px',
|
||||||
width: '320px',
|
width: '320px',
|
||||||
maxHeight: '80vh',
|
maxHeight: '80vh',
|
||||||
|
|
@ -236,12 +223,12 @@ export function DialsOverlay({
|
||||||
Design Dials
|
Design Dials
|
||||||
</h3>
|
</h3>
|
||||||
<div style={{ fontSize: '10px', color: '#8c92a4', marginTop: '2px' }}>
|
<div style={{ fontSize: '10px', color: '#8c92a4', marginTop: '2px' }}>
|
||||||
{shortcutLabel}
|
Ctrl+D toggle · Ctrl+Shift+D hide
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsVisible(false)}
|
onClick={() => setOverlayState('collapsed')}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
|
|
@ -251,7 +238,7 @@ export function DialsOverlay({
|
||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
color: '#8c92a4',
|
color: '#8c92a4',
|
||||||
}}
|
}}
|
||||||
title="Close (Shift+Cmd/Ctrl+D)"
|
title="Collapse (Ctrl+D)"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue