Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions VERSION_LOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Version Log

## v0.8.2 - The Antigravity Styling Update

### 🛠 Improvements

- **Antigravity Design Language:** Applied a cleaner, VS Code-inspired design across the application:
- Sharper corners (`rounded-sm`) on buttons, modals, tooltips, and dropdowns.
- Reduced focus ring intensity for a more subtle, native appearance.
- Removed shadows from modals, tooltips, and context menus for a flatter look.
- Status bar controls (LLM selectors, database dropdown, zoom buttons) now use text-only hover/focus instead of visible rings.
- **Improved Markdown Preview Zoom:** Text now reflows properly when zooming in or out, eliminating whitespace issues at different zoom levels.
- **Better Tree Selection Visibility:** Fixed the tree item selection highlight in light mode to be clearly visible instead of nearly transparent.

### 🐛 Fixes

- Disabled the scroll synchronization between the code editor and Markdown preview panes, as the sync behavior was unreliable. Panes now scroll independently.
- Fixed search box styling to be more professional with a subtle background and no visible border.

## v0.8.1 - The Native Fonts & Focus Fix Update

### 🛠 Improvements

- Updated the application font stack to match VS Code on Windows, using **Segoe UI** for the interface and **Consolas** for code. This provides a more native and consistent look for Windows users.

### 🐛 Fixes

- Resolved an issue where the "Create Document from Template" dialog would lose focus or reset the cursor position while typing in variable inputs. The focus trap logic has been optimized to handle dynamic content updates correctly.

## v0.8.0 - The Emoji & Command Palette Update

### ✨ Features
Expand Down
14 changes: 7 additions & 7 deletions components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ children, variant = 'primary', isLoading = false, className, ...props }, ref) => {
const baseClasses = 'inline-flex items-center justify-center px-3 py-1.5 border text-xs font-semibold rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-background disabled:opacity-60 disabled:cursor-not-allowed transition-colors duration-150';
const baseClasses = 'inline-flex items-center justify-center px-3 py-1.5 border text-xs font-semibold rounded-sm focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-100';

const variantClasses = {
primary: 'bg-primary text-primary-text border-transparent hover:bg-primary-hover focus:ring-primary',
secondary: 'bg-secondary text-text-main border-border-color hover:bg-border-color focus:ring-primary',
destructive: 'bg-destructive-bg text-destructive-text border-destructive-border hover:bg-destructive-bg-hover focus:ring-destructive-text',
ghost: 'bg-transparent text-text-main border-transparent hover:bg-border-color focus:ring-primary',
primary: 'bg-primary text-primary-text border-transparent hover:bg-primary-hover focus:ring-primary/50',
secondary: 'bg-secondary text-text-main border-border-color hover:bg-border-color/50 focus:ring-primary/50',
destructive: 'bg-destructive-bg text-destructive-text border-destructive-border hover:bg-destructive-bg-hover focus:ring-destructive-text/50',
ghost: 'bg-transparent text-text-secondary border-transparent hover:text-text-main focus:ring-primary/30',
};

const disabled = props.disabled || isLoading;

return (
Expand Down
273 changes: 273 additions & 0 deletions components/ContextMenu.tsx.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';

export type MenuItem =
| {
label: string;
action: () => void;
icon?: React.FC<{ className?: string }>;
disabled?: boolean;
shortcut?: string;
submenu?: never;
}
| {
label: string;
submenu: MenuItem[];
icon?: React.FC<{ className?: string }>;
disabled?: boolean;
shortcut?: string;
action?: never;
}
| { type: 'separator' };

interface ContextMenuProps {
isOpen: boolean;
position: { x: number; y: number };
items: MenuItem[];
onClose: () => void;
}

const EDGE_MARGIN = 8;

const ContextMenu: React.FC<ContextMenuProps> = ({ isOpen, position, items, onClose }) => {
const menuRef = useRef<HTMLDivElement>(null);
const [openSubmenu, setOpenSubmenu] = useState<string | null>(null);
const [menuStyle, setMenuStyle] = useState<{ top: number; left: number; maxHeight: number; overflowY: React.CSSProperties['overflowY'] }>({
top: position.y,
left: position.x,
maxHeight: 0,
overflowY: 'visible',
});

const recalculatePosition = useCallback(() => {
const menu = menuRef.current;
if (!menu) return;

const { innerWidth, innerHeight } = window;
const rect = menu.getBoundingClientRect();
const maxHeight = Math.max(innerHeight - EDGE_MARGIN * 2, 0);

let left = rect.left;
let top = rect.top;

if (rect.right > innerWidth - EDGE_MARGIN) {
left = Math.max(EDGE_MARGIN, innerWidth - rect.width - EDGE_MARGIN);
}
if (left < EDGE_MARGIN) {
left = EDGE_MARGIN;
}

if (rect.bottom > innerHeight - EDGE_MARGIN) {
top = Math.max(EDGE_MARGIN, innerHeight - rect.height - EDGE_MARGIN);
}
if (top < EDGE_MARGIN) {
top = EDGE_MARGIN;
}

const overflowY: React.CSSProperties['overflowY'] = rect.height > maxHeight ? 'auto' : 'visible';

setMenuStyle((previous) => {
if (
previous.top === top &&
previous.left === left &&
previous.maxHeight === maxHeight &&
previous.overflowY === overflowY
) {
return previous;
}

return {
top,
left,
maxHeight,
overflowY,
};
});
}, []);

useEffect(() => {
if (!isOpen) return;

setOpenSubmenu(null);

const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
onClose();
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
}

document.addEventListener('mousedown', handleClickOutside);
window.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
window.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, onClose]);

useLayoutEffect(() => {
if (!isOpen) return;

setMenuStyle((previous) => {
if (previous.top === position.y && previous.left === position.x) {
return previous;
}

return {
top: position.y,
left: position.x,
maxHeight: previous.maxHeight,
overflowY: previous.overflowY,
};
});

const frame = requestAnimationFrame(() => {
recalculatePosition();
});

return () => cancelAnimationFrame(frame);
}, [isOpen, position.x, position.y, recalculatePosition]);

useLayoutEffect(() => {
if (!isOpen) return;

const frame = requestAnimationFrame(() => {
recalculatePosition();
});

return () => cancelAnimationFrame(frame);
}, [isOpen, items, recalculatePosition]);

useEffect(() => {
if (!isOpen) return;

const handleResize = () => {
recalculatePosition();
};

window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [isOpen, recalculatePosition]);

if (!isOpen) return null;

const overlayRoot = document.getElementById('overlay-root');
if (!overlayRoot) return null;

const hasEnabledItem = (menuItems: MenuItem[]): boolean => {
return menuItems.some(item => {
if ('type' in item) {
return false;
}

if ('submenu' in item) {
return !item.disabled && hasEnabledItem(item.submenu);
}

return !item.disabled;
});
};

const renderItems = (menuItems: MenuItem[], depth = 0) => {
return menuItems.map((item, index) => {
if ('type' in item) {
return <li key={`separator-${depth}-${index}`} className="h-px bg-border-color my-1.5" />;
}

if ('submenu' in item) {
const menuKey = `${depth}-${index}`;
const isOpen = openSubmenu === menuKey;
const hasEnabledSubitem = hasEnabledItem(item.submenu);
const Icon = item.icon;

return (
<li
key={`${item.label}-${depth}-${index}`}
className="relative"
onMouseEnter={() => setOpenSubmenu(menuKey)}
onMouseLeave={() => setOpenSubmenu(null)}
>
<button
onClick={() => {
if (!item.disabled && hasEnabledSubitem) {
setOpenSubmenu(menuKey);
}
}}
disabled={item.disabled || !hasEnabledSubitem}
className="w-full flex items-center justify-between text-left px-2 py-1.5 text-xs rounded-md transition-colors text-text-main disabled:text-text-secondary/50 disabled:cursor-not-allowed hover:bg-primary hover:text-primary-text focus:bg-primary focus:text-primary-text focus:outline-none"
>
<div className="flex items-center gap-3">
{Icon && <Icon className="w-4 h-4" />}
<span>{item.label}</span>
</div>
<span className="text-text-secondary">›</span>
</button>
{isOpen && item.submenu.length > 0 && (
<div className="absolute top-0 left-full ml-1 z-10 w-[16.8rem] rounded-md bg-secondary p-1.5 shadow-2xl border border-border-color animate-fade-in-fast">
<ul className="space-y-1">{renderItems(item.submenu, depth + 1)}</ul>
</div>
)}
</li>
);
}

const { label, action, icon: Icon, disabled, shortcut } = item;

return (
<li key={`${label}-${depth}-${index}`}>
<button
onClick={() => {
if (!disabled) {
action();
onClose();
}
}}
disabled={disabled}
className="w-full flex items-center justify-between text-left px-2 py-1.5 text-xs rounded-md transition-colors text-text-main disabled:text-text-secondary/50 disabled:cursor-not-allowed hover:bg-primary hover:text-primary-text focus:bg-primary focus:text-primary-text focus:outline-none"
>
<div className="flex items-center gap-3">
{Icon && <Icon className="w-4 h-4" />}
<span>{label}</span>
</div>
{shortcut && <span className="text-xs text-text-secondary">{shortcut}</span>}
</button>
</li>
);
});
};

return ReactDOM.createPortal(
<div
ref={menuRef}
style={{
top: menuStyle.top,
left: menuStyle.left,
maxHeight: menuStyle.maxHeight ? menuStyle.maxHeight : undefined,
overflowY: menuStyle.overflowY,
}}
className="fixed z-50 w-[16.8rem] rounded-md bg-secondary p-1.5 shadow-2xl border border-border-color animate-fade-in-fast"
>
<ul className="space-y-1">
{renderItems(items)}
</ul>
<style>{`
@keyframes fade-in-fast {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.animate-fade-in-fast {
animation: fade-in-fast 0.1s ease-out forwards;
}
`}</style>
</div>,
overlayRoot
);
};

export default ContextMenu;
48 changes: 24 additions & 24 deletions components/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,34 @@ interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
tooltipPosition?: 'top' | 'bottom';
}

const IconButton: React.FC<IconButtonProps> = ({ children, tooltip, className, variant = 'primary', size='md', tooltipPosition = 'top', ...props }) => {
const [isHovered, setIsHovered] = useState(false);
const wrapperRef = useRef<HTMLSpanElement>(null);
const { ['aria-label']: ariaLabel, ...buttonProps } = props;
const computedAriaLabel = ariaLabel ?? tooltip;
const IconButton: React.FC<IconButtonProps> = ({ children, tooltip, className, variant = 'primary', size = 'md', tooltipPosition = 'top', ...props }) => {
const [isHovered, setIsHovered] = useState(false);
const wrapperRef = useRef<HTMLSpanElement>(null);
const { ['aria-label']: ariaLabel, ...buttonProps } = props;
const computedAriaLabel = ariaLabel ?? tooltip;

const handleMouseEnter = useCallback(() => {
if (tooltip) setIsHovered(true);
}, [tooltip]);
const handleMouseEnter = useCallback(() => {
if (tooltip) setIsHovered(true);
}, [tooltip]);

const handleMouseLeave = useCallback(() => {
setIsHovered(false);
}, []);
const handleMouseLeave = useCallback(() => {
setIsHovered(false);
}, []);

const baseClasses = "flex items-center justify-center rounded-md focus:outline-none transition-colors";

const variantClasses = {
primary: 'text-text-secondary hover:bg-border-color hover:text-text-main',
ghost: 'text-text-secondary/80 hover:bg-border-color hover:text-text-main',
destructive: 'text-destructive-text bg-transparent hover:bg-destructive-bg'
};
const baseClasses = "flex items-center justify-center rounded-sm focus:outline-none transition-colors duration-100";

const variantClasses = {
primary: 'text-text-secondary hover:text-text-main hover:bg-border-color/30',
ghost: 'text-text-secondary hover:text-text-main',
destructive: 'text-destructive-text bg-transparent hover:bg-destructive-bg/50'
};

const sizeClasses = {
xs: 'w-6 h-6',
sm: 'w-7 h-7',
md: 'w-8 h-8'
};

const sizeClasses = {
xs: 'w-6 h-6',
sm: 'w-7 h-7',
md: 'w-8 h-8'
};

return (
<>
<span
Expand Down
Loading
Loading