feat(editor): menu Help, homogeneo con la app de escritorio

Mismo contenido y orden que el Help del menu nativo de escritorio
(pro/desktop menu.rs): Documentation, Examples, Pricing | Velxio Home,
Blog, About Velxio | Discord Community, GitHub Repository. Los enlaces que
el header del editor dejo de mostrar como nav de marketing recuperan aqui
un sitio ordenado, y todo abre en pestana nueva para que el editor (y el
trabajo sin guardar) se quede donde esta — el mismo criterio que la app
nativa, que abre el navegador del sistema.
This commit is contained in:
David Montero Crespo 2026-07-30 17:31:31 +02:00
parent 3cc1510cae
commit 7d78f87bf0
1 changed files with 40 additions and 2 deletions

View File

@ -32,11 +32,19 @@ import './EditorMenuBar.css';
type Item = type Item =
| { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string } | { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string }
| { kind: 'link'; href: string; label: string }
| { kind: 'separator' }; | { kind: 'separator' };
// Same links the desktop app's Help menu opens (pro/desktop menu.rs) — the
// web editor mirrors that structure so both feel like one product. They
// replace the marketing nav this header no longer shows, opening in a new
// tab so the editor (and any unsaved work) stays put.
const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio';
const DISCORD_URL = 'https://discord.gg/3mARjJrh4E';
export const EditorMenuBar: React.FC = () => { export const EditorMenuBar: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState<'file' | 'edit' | null>(null); const [open, setOpen] = useState<'file' | 'edit' | 'help' | null>(null);
const rootRef = useRef<HTMLDivElement>(null); const rootRef = useRef<HTMLDivElement>(null);
// Re-render when owners (un)register their commands. // Re-render when owners (un)register their commands.
@ -85,6 +93,19 @@ export const EditorMenuBar: React.FC = () => {
}, },
]; ];
const helpItems: Item[] = [
{ kind: 'link', href: '/docs', label: t('header.nav.documentation', 'Documentation') },
{ kind: 'link', href: '/examples', label: t('header.nav.examples', 'Examples') },
{ kind: 'link', href: '/pricing', label: t('header.nav.pricing', 'Pricing') },
{ kind: 'separator' },
{ kind: 'link', href: '/', label: t('editor.menu.home', 'Velxio Home') },
{ kind: 'link', href: '/blog/', label: t('header.nav.blog', 'Blog') },
{ kind: 'link', href: '/about', label: t('editor.menu.about', 'About Velxio') },
{ kind: 'separator' },
{ kind: 'link', href: DISCORD_URL, label: t('editor.menu.discord', 'Discord Community') },
{ kind: 'link', href: GITHUB_URL, label: t('editor.menu.github', 'GitHub Repository') },
];
const editItems: Item[] = [ const editItems: Item[] = [
{ kind: 'separator' }, // placeholder: undo/redo render specially above { kind: 'separator' }, // placeholder: undo/redo render specially above
{ kind: 'command', id: 'view.reset', label: t('editor.menu.centerView', 'Center canvas view') }, { kind: 'command', id: 'view.reset', label: t('editor.menu.centerView', 'Center canvas view') },
@ -92,6 +113,20 @@ export const EditorMenuBar: React.FC = () => {
{ kind: 'command', id: 'view.zoomOut', label: t('editor.canvas.zoomOut', 'Zoom out') }, { kind: 'command', id: 'view.zoomOut', label: t('editor.canvas.zoomOut', 'Zoom out') },
]; ];
const renderLink = (item: Extract<Item, { kind: 'link' }>): React.ReactNode => (
<a
key={item.href}
role="menuitem"
className="emb-item"
href={item.href}
target="_blank"
rel="noopener noreferrer"
onClick={() => setOpen(null)}
>
<span>{item.label}</span>
</a>
);
const renderCommand = (item: Extract<Item, { kind: 'command' }>): React.ReactNode => ( const renderCommand = (item: Extract<Item, { kind: 'command' }>): React.ReactNode => (
<button <button
key={item.id} key={item.id}
@ -108,7 +143,7 @@ export const EditorMenuBar: React.FC = () => {
</button> </button>
); );
const menu = (which: 'file' | 'edit', label: string, items: Item[]): React.ReactNode => ( const menu = (which: 'file' | 'edit' | 'help', label: string, items: Item[]): React.ReactNode => (
<div className="emb-root" key={which}> <div className="emb-root" key={which}>
<button <button
className={`emb-trigger${open === which ? ' emb-trigger-open' : ''}`} className={`emb-trigger${open === which ? ' emb-trigger-open' : ''}`}
@ -152,6 +187,8 @@ export const EditorMenuBar: React.FC = () => {
{items.map((item, i) => {items.map((item, i) =>
item.kind === 'separator' ? ( item.kind === 'separator' ? (
<div key={`sep-${i}`} className="emb-separator" /> <div key={`sep-${i}`} className="emb-separator" />
) : item.kind === 'link' ? (
renderLink(item)
) : ( ) : (
renderCommand(item) renderCommand(item)
), ),
@ -165,6 +202,7 @@ export const EditorMenuBar: React.FC = () => {
<div className="editor-menubar" ref={rootRef}> <div className="editor-menubar" ref={rootRef}>
{menu('file', t('editor.menu.file', 'File'), fileItems)} {menu('file', t('editor.menu.file', 'File'), fileItems)}
{menu('edit', t('editor.menu.edit', 'Edit'), editItems)} {menu('edit', t('editor.menu.edit', 'Edit'), editItems)}
{menu('help', t('editor.menu.help', 'Help'), helpItems)}
</div> </div>
); );
}; };