Add foundational components and logic for migration, UI design, and input handling

- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design.
- Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri.
- Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling.
- Updated `ScribeShell` with migration popup logic to prompt users for data migration.
- Integrated `AlertStack` for better alert handling and notification management.
- Enhanced Rust/Tauri services with migration command implementations.
- Added translations and styles for new components.
This commit is contained in:
natreex
2026-04-05 12:52:54 -04:00
parent 2b6d4cc48b
commit d4765e6576
49 changed files with 3115 additions and 2 deletions

View File

@@ -0,0 +1,89 @@
import React, {ReactNode, useEffect, useRef, useState} from "react";
import {LucideIcon} from "lucide-react";
interface DropdownItem {
label: string;
icon?: LucideIcon;
onClick: () => void;
variant?: 'default' | 'danger';
}
interface DropdownProps {
trigger: ReactNode;
items: DropdownItem[];
align?: 'left' | 'right';
}
export default function Dropdown(
{
trigger,
items,
align = 'left',
}: DropdownProps): React.JSX.Element {
const [isOpen, setIsOpen] = useState<boolean>(false);
const dropdownRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
useEffect(function handleClickOutside() {
function onClickOutside(event: MouseEvent): void {
if (dropdownRef.current && event.target instanceof Node && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', onClickOutside);
return (): void => document.removeEventListener('mousedown', onClickOutside);
}, []);
function handleItemClick(item: DropdownItem): void {
item.onClick();
setIsOpen(false);
}
return (
<div ref={dropdownRef} className="relative">
<div onClick={(): void => setIsOpen(!isOpen)}>
{trigger}
</div>
{isOpen && (
<div
className={`
absolute top-full mt-2 z-50 min-w-48
bg-tertiary rounded-xl py-2
border border-secondary backdrop-blur-sm animate-fadeIn
${align === 'right' ? 'right-0' : 'left-0'}
`}
>
{items.map(function renderDropdownItem(item: DropdownItem, index: number) {
const isDanger: boolean = item.variant === 'danger';
const ItemIcon: LucideIcon | undefined = item.icon;
return (
<button
key={index}
onClick={(): void => handleItemClick(item)}
className={`
group w-full flex items-center gap-3 px-4 py-2.5
transition-all hover:pl-5
${isDanger
? 'text-error hover:bg-error/10'
: 'text-text-primary hover:bg-secondary'
}
${index === 0 ? 'rounded-t-xl' : ''}
${index === items.length - 1 ? 'rounded-b-xl' : ''}
`}
>
{ItemIcon && (
<ItemIcon
className={`w-4 h-4 ${isDanger ? 'text-error' : 'text-muted group-hover:text-primary'}`}
strokeWidth={1.75}
/>
)}
<span className="font-medium text-sm">{item.label}</span>
</button>
);
})}
</div>
)}
</div>
);
}