toolbar family keyed (57 -> 33)

Toolbar, ActionButtons, SelectionActions and DefaultActions. Almost every string
here reused an existing files.menu.* key from the last two commits rather than
adding a duplicate — cut/copy/paste/download/delete/newFolder/uploadFiles/
uploadFolder/hideHiddenFiles were all already there. Three new: gitClone,
searchFiles, clearSelection, refresh, selected.

The clone-URL placeholder here is the same hint as NewEntryDialog's, reused rather
than restated with different example names (user/repo vs owner/repo) — one key per
meaning, not per occurrence.

The "N selected" count splits the number and the word across two spans (the word
hidden on mobile); only the word needed a key, so files.menu.selected is standalone
prose rather than an interpolated frame.
This commit is contained in:
2026-08-23 16:26:36 +00:00
parent 2e69b15de6
commit ad0fd12262
6 changed files with 40 additions and 22 deletions
@@ -1,5 +1,6 @@
import { RefreshCw } from 'lucide-react';
import type { UseFileBrowserAppType } from '../../useFileBrowserApp';
import { useTranslation } from '../../../../../locales';
import { SelectionActions } from './SelectionActions';
import { DefaultActions } from './DefaultActions';
@@ -8,13 +9,14 @@ type ActionButtonsProps = {
};
export const ActionButtons = ({ fileBrowserManager }: ActionButtonsProps) => {
const { t } = useTranslation();
const { refresh, selected } = fileBrowserManager;
return (
<div className="flex items-center gap-1">
<button
onClick={refresh}
title="Refresh"
title={t('files.menu.refresh')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<RefreshCw className="h-4 w-4" />
@@ -1,12 +1,14 @@
import { useRef } from 'react';
import { FolderPlus, Upload, FolderUp, ClipboardPaste } from 'lucide-react';
import type { UseFileBrowserAppType } from '../../useFileBrowserApp';
import { useTranslation } from '../../../../../locales';
type DefaultActionsProps = {
fileBrowserManager: UseFileBrowserAppType;
};
export const DefaultActions = ({ fileBrowserManager }: DefaultActionsProps) => {
const { t } = useTranslation();
const { setNewEntry, handleUpload, clipboard, handlePaste } = fileBrowserManager;
const fileInputRef = useRef<HTMLInputElement | null>(null);
const folderInputRef = useRef<HTMLInputElement | null>(null);
@@ -22,7 +24,7 @@ export const DefaultActions = ({ fileBrowserManager }: DefaultActionsProps) => {
<>
<button
onClick={() => setNewEntry('folder')}
title="New folder"
title={t('files.menu.newFolder')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<FolderPlus className="h-4 w-4" />
@@ -30,7 +32,7 @@ export const DefaultActions = ({ fileBrowserManager }: DefaultActionsProps) => {
<button
onClick={() => fileInputRef.current?.click()}
title="Upload files"
title={t('files.menu.uploadFiles')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<Upload className="h-4 w-4" />
@@ -39,7 +41,7 @@ export const DefaultActions = ({ fileBrowserManager }: DefaultActionsProps) => {
<button
onClick={() => folderInputRef.current?.click()}
title="Upload folder"
title={t('files.menu.uploadFolder')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<FolderUp className="h-4 w-4" />
@@ -57,7 +59,7 @@ export const DefaultActions = ({ fileBrowserManager }: DefaultActionsProps) => {
{clipboard !== null && (
<button
onClick={handlePaste}
title="Paste"
title={t('files.menu.paste')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<ClipboardPaste className="h-4 w-4" />
@@ -1,11 +1,13 @@
import { Scissors, Copy, ClipboardPaste, Download, Trash2, X } from 'lucide-react';
import type { UseFileBrowserAppType } from '../../useFileBrowserApp';
import { useTranslation } from '../../../../../locales';
type SelectionActionsProps = {
fileBrowserManager: UseFileBrowserAppType;
};
export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps) => {
const { t } = useTranslation();
const {
selected,
clipboard,
@@ -21,18 +23,18 @@ export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps)
<>
<span className="text-sm font-medium text-duck-dark/70 mr-1">
{selected.size}
<span className="hidden md:inline"> selected</span>
<span className="hidden md:inline"> {t('files.menu.selected')}</span>
</span>
<button
onClick={handleCut}
title="Cut"
title={t('files.menu.cut')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<Scissors className="h-4 w-4" />
</button>
<button
onClick={handleCopy}
title="Copy"
title={t('files.menu.copy')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<Copy className="h-4 w-4" />
@@ -40,7 +42,7 @@ export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps)
{clipboard !== null && (
<button
onClick={handlePaste}
title="Paste"
title={t('files.menu.paste')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<ClipboardPaste className="h-4 w-4" />
@@ -48,21 +50,21 @@ export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps)
)}
<button
onClick={handleDownloadSelected}
title="Download"
title={t('files.menu.download')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<Download className="h-4 w-4" />
</button>
<button
onClick={handleDeleteSelected}
title="Delete"
title={t('files.menu.delete')}
className="p-1.5 rounded-md text-destructive hover:bg-destructive cursor-pointer transition-colors"
>
<Trash2 className="h-4 w-4" />
</button>
<button
onClick={() => setSelected(new Set())}
title="Clear selection"
title={t('files.menu.clearSelection')}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<X className="h-4 w-4" />
@@ -1,5 +1,6 @@
import { Loader2, LayoutGrid, List, Search, X, Check, GitBranch, Eye, EyeOff, Download, Mic } from 'lucide-react';
import type { UseFileBrowserAppType } from '../../useFileBrowserApp';
import { useTranslation } from '../../../../../locales';
import { ActionButtons } from './ActionButtons';
type ToolbarProps = {
@@ -7,6 +8,7 @@ type ToolbarProps = {
};
export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
const { t } = useTranslation();
const {
showCloneInput,
setShowCloneInput,
@@ -41,7 +43,7 @@ export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
autoFocus
value={cloneUrl}
onChange={(ev) => setCloneUrl(ev.target.value)}
placeholder="https://github.com/user/repo.git"
placeholder={t('newEntry.clone.placeholder')}
className="h-8 w-40 md:w-64 text-sm rounded-md border border-duck-dark/20 bg-background/60 text-duck-dark placeholder:text-duck-dark/40 outline-none focus:border-duck-teal/50 px-2"
disabled={cloning}
onKeyDown={(ev) => {
@@ -55,7 +57,7 @@ export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
type="submit"
disabled={cloning}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors disabled:opacity-40"
title="Clone"
title={t('files.menu.cloneRepository')}
>
{cloning ? <Loader2 className="h-4 w-4 animate-spin" /> : <Check className="h-4 w-4" />}
</button>
@@ -67,7 +69,7 @@ export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
setCloneUrl('');
}}
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors disabled:opacity-40"
title="Cancel"
title={t('newEntry.cancel')}
>
<X className="h-4 w-4" />
</button>
@@ -76,21 +78,21 @@ export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
<>
<button
onClick={() => setShowVideoDownload(true)}
title="Download video"
title={t('files.menu.downloadVideo')}
className="hidden md:block p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<Download className="h-4 w-4" />
</button>
<button
onClick={() => setShowDictate(true)}
title="Dictate"
title={t('files.menu.dictate')}
className="hidden md:block p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<Mic className="h-4 w-4" />
</button>
<button
onClick={() => setShowCloneInput(true)}
title="Git clone"
title={t('files.menu.gitClone')}
className="hidden md:block p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
>
<GitBranch className="h-4 w-4" />
@@ -109,7 +111,7 @@ export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
searchInputRef.current?.blur();
}
}}
placeholder="Search files..."
placeholder={t('files.menu.searchFiles')}
className="h-8 w-28 focus:w-40 md:w-40 md:focus:w-56 transition-all pl-8 pr-7 text-base md:text-sm rounded-md border border-duck-dark/20 bg-background/60 text-duck-dark placeholder:text-duck-dark/40 outline-none focus:border-duck-teal/50"
/>
{searchQuery && (
@@ -124,7 +126,7 @@ export const Toolbar = ({ fileBrowserManager }: ToolbarProps) => {
<button
onClick={() => setShowHidden((v) => !v)}
className={`hidden md:block p-1.5 rounded-md cursor-pointer transition-colors ${showHidden ? 'bg-duck-teal text-duck-yellow' : 'text-duck-teal hover:bg-duck-dark/5'}`}
title={showHidden ? 'Hide hidden files' : 'Show hidden files'}
title={showHidden ? t('files.menu.hideHiddenFiles') : t('files.menu.showHiddenFiles')}
>
{showHidden ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
</button>
@@ -521,7 +521,12 @@
"unpin": "Unpin",
"pin": "Pin",
"chat": "Chat...",
"delete": "Delete"
"delete": "Delete",
"gitClone": "Git clone",
"searchFiles": "Search files...",
"clearSelection": "Clear selection",
"selected": "selected",
"refresh": "Refresh"
}
},
"taskRunner": {
@@ -521,7 +521,12 @@
"unpin": "Remover fixação",
"pin": "Fixar",
"chat": "Chat...",
"delete": "Eliminar"
"delete": "Eliminar",
"gitClone": "Clonar via Git",
"searchFiles": "Pesquisar ficheiros...",
"clearSelection": "Limpar seleção",
"selected": "selecionados",
"refresh": "Atualizar"
}
},
"taskRunner": {