Files
VoiceStudio/frontend/src/components/NavRail.jsx
T
5c9b7ca313 chore(format): adopt oxfmt for JS/TS/JSX + CI format gate (#774)
Adds oxfmt (Rust formatter, Prettier-conformant) — the repo had no formatter, so
this is a one-time normalization of the JS/TS/JSX code (257 files; purely
cosmetic — full suite stays 638/638).

Scope is deliberately narrowed in .oxfmtrc.json to JS/TS/JSX only:
- singleQuote:true + jsxSingleQuote:false — preserve the project's existing
  style (single-quoted JS, double-quoted JSX attrs), not oxfmt's double-quote
  default. (Flipping quotes globally also broke a source-string-parsing test;
  preserving them keeps featureCoverage green.)
- Excludes **/*.css (the CSS→Tailwind migration will rewrite those — formatting
  them now is wasted churn), **/*.json (avoids reformatting 20 i18n locale
  files + config), **/*.toml, and src-tauri/** (Rust/Tauri config — out of scope
  for a frontend JS formatter; oxfmt was reformatting Cargo.toml/tauri.conf.json).

Tooling:
- `bun run format` (write) / `bun run format:check` (verify).
- ci.yml: new "Frontend format check (oxfmt)" gate after the oxlint gate.

Verified: format:check clean; oxlint 0 errors; vite build; full suite 638/638;
bun install --frozen-lockfile in sync.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:18:01 +05:30

92 lines
3.0 KiB
React

import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Globe,
Fingerprint,
Film,
FolderOpen,
Settings2,
ArrowLeftRight,
Library,
FileText,
BookOpen,
BookMarked,
} from 'lucide-react';
const ITEM_DEFS = [
{ id: 'launchpad', Icon: Globe, tKey: 'launchpad', accent: '#f3a5b6' },
{ id: 'studio', Icon: Fingerprint, tKey: 'voice', accent: '#d3869b' },
{ id: 'dub', Icon: Film, tKey: 'dub', accent: '#fe8019' },
{ id: 'stories', Icon: BookOpen, tKey: 'stories', accent: '#fabd2f' },
{ id: 'audiobook', Icon: BookMarked, tKey: 'audiobook', accent: '#8ec07c' },
{ id: 'gallery', Icon: Library, tKey: 'gallery', accent: '#b8bb26' },
{ id: 'transcriptions', Icon: FileText, tKey: 'transcripts', accent: '#d3869b' },
{ id: 'projects', Icon: FolderOpen, tKey: 'omnidrive', accent: '#83a598' },
];
const FOOTER_DEFS = [{ id: 'settings', Icon: Settings2, tKey: 'settings', accent: '#fabd2f' }];
function RailBtn({ active, Icon, label, accent, onClick }) {
return (
<button
onClick={onClick}
title={label}
aria-label={label}
className={`rail-btn ${active ? 'active' : ''}`}
style={{ '--rail-accent': accent }}
>
<Icon size={18} />
<span className="rail-label">{label}</span>
</button>
);
}
export default function NavRail({ mode, setMode, side = 'left', onFlipSide }) {
const { t } = useTranslation();
const items = React.useMemo(
() => ITEM_DEFS.map((d) => ({ ...d, label: t(`nav.${d.tKey}`) })),
[t],
);
const footerItems = React.useMemo(
() => FOOTER_DEFS.map((d) => ({ ...d, label: t(`nav.${d.tKey}`) })),
[t],
);
return (
<aside className={`nav-rail rail-${side}`}>
<div className="rail-top">
{items.map((it) => (
<RailBtn key={it.id} {...it} active={mode === it.id} onClick={() => setMode(it.id)} />
))}
</div>
<div className="rail-bottom">
{/* Quiet "Support" pill — warms to the accent on hover, opens the
donate page. Sits with the footer nav (Settings / flip). (#007) */}
<button
onClick={() => setMode('donate')}
title={t('donate.pill', { defaultValue: 'Support OmniVoice' })}
aria-label={t('donate.pill', { defaultValue: 'Support OmniVoice' })}
className={`rail-btn donate-pill ${mode === 'donate' ? 'active' : ''}`}
>
<span className="donate-pill__heart" aria-hidden="true">
🩷
</span>
<span className="rail-label">
{t('donate.pill', { defaultValue: 'Support OmniVoice' })}
</span>
</button>
{footerItems.map((it) => (
<RailBtn key={it.id} {...it} active={mode === it.id} onClick={() => setMode(it.id)} />
))}
<button
onClick={onFlipSide}
title={side === 'left' ? t('nav.move_rail_right') : t('nav.move_rail_left')}
aria-label={t('nav.flip_rail')}
className="rail-btn rail-flip"
>
<ArrowLeftRight size={15} />
</button>
</div>
</aside>
);
}