From 6ace4627976cfe3a1570317dc221553e0e43af74 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Tue, 11 Aug 2026 02:09:56 -0400 Subject: [PATCH] Add RTL support for JoyToggleButtonGroup and implement i18n for search actions --- src/contexts/ThemeContext.jsx | 10 +++++ src/search/GlobalSearchPalette.jsx | 70 +++++++++++++++--------------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/contexts/ThemeContext.jsx b/src/contexts/ThemeContext.jsx index d0de31c..955056f 100644 --- a/src/contexts/ThemeContext.jsx +++ b/src/contexts/ThemeContext.jsx @@ -153,6 +153,16 @@ const themeConfig = { }), }, }, + // ToggleButtonGroup is styled(StyledButtonGroup) under its own slot name, so + // it inherits the same geometry — and the same RTL problem — but not the + // JoyButtonGroup override. Its radius is left at Joy's default on purpose; + // only the direction-sensitive geometry needs correcting. + JoyToggleButtonGroup: { + styleOverrides: { + root: ({ ownerState, theme }) => + theme.direction === 'rtl' ? buttonGroupRtlGeometry(ownerState) : {}, + }, + }, }, } diff --git a/src/search/GlobalSearchPalette.jsx b/src/search/GlobalSearchPalette.jsx index ec2b520..dd7125b 100644 --- a/src/search/GlobalSearchPalette.jsx +++ b/src/search/GlobalSearchPalette.jsx @@ -24,6 +24,7 @@ import { import Fuse from 'fuse.js' import PropTypes from 'prop-types' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import AppModal from '../components/common/AppModal' @@ -38,15 +39,6 @@ const GROUPS = [ 'settings', 'actions', ] -const GROUP_LABELS = { - tasks: 'Tasks', - history: 'Notes', - projects: 'Projects', - labels: 'Labels', - people: 'People', - settings: 'Settings', - actions: 'Quick actions', -} const ICONS = { tasks: , @@ -58,33 +50,33 @@ const ICONS = { actions: , } -const QUICK_ACTIONS = [ +const buildQuickActions = t => [ { id: 'action:create', provider: 'actions', - title: 'Create a task', - subtitle: 'Quick action', + title: t('search.actions.createTask'), + subtitle: t('search.actions.quickAction'), route: '/chores/create', }, { id: 'action:tasks', provider: 'actions', - title: 'View all tasks', - subtitle: 'Navigation', + title: t('search.actions.viewAllTasks'), + subtitle: t('search.actions.navigation'), route: '/chores', }, { id: 'action:archived', provider: 'actions', - title: 'View archived tasks', - subtitle: 'Navigation', + title: t('search.actions.viewArchivedTasks'), + subtitle: t('search.actions.navigation'), route: '/archived', }, { id: 'action:settings', provider: 'actions', - title: 'Open settings', - subtitle: 'Navigation', + title: t('search.actions.openSettings'), + subtitle: t('search.actions.navigation'), route: '/settings', }, ] @@ -142,6 +134,8 @@ const Highlight = ({ query, text }) => { } const SearchContainer = ({ children, onClose, presentation }) => { + const { t } = useTranslation() + if (presentation === 'page') { return ( { open onClose={onClose} disableRestoreFocus - title='Search' + title={t('search.title')} size='lg' maxHeight='min(720px, calc(100dvh - 48px))' contentSx={{ @@ -189,6 +183,7 @@ const GlobalSearchPalette = ({ presentation = 'modal', }) => { const navigate = useNavigate() + const { t } = useTranslation() const focusInputRef = useCallback(node => { if (node) requestAnimationFrame(() => node.focus()) }, []) @@ -232,7 +227,7 @@ const GlobalSearchPalette = ({ const recentResults = recents .map(item => currentById.get(item.id) || item) .filter(item => item.provider !== 'history' || currentById.has(item.id)) - return [...recentResults, ...QUICK_ACTIONS] + return [...recentResults, ...buildQuickActions(t)] } const grouped = GROUPS.filter(group => group !== 'actions').flatMap(group => @@ -258,12 +253,12 @@ const GlobalSearchPalette = ({ grouped.push({ id: 'action:filter-tasks', provider: 'actions', - title: `Show tasks matching “${query.trim()}”`, - subtitle: 'Filter the task list', + title: t('search.actions.filterTasks', { query: query.trim() }), + subtitle: t('search.actions.filterTasksSubtitle'), route: `/chores?search=${encodeURIComponent(query.trim())}`, }) return grouped - }, [documents, query, recents, searchIndexes]) + }, [documents, query, recents, searchIndexes, t]) useEffect(() => { selectedResultRef.current?.scrollIntoView({ @@ -302,8 +297,7 @@ const GlobalSearchPalette = ({ slotProps={{ input: { ref: focusInputRef, - 'aria-label': - 'Search tasks, history, projects, labels and settings', + 'aria-label': t('search.inputAriaLabel'), }, }} value={query} @@ -312,14 +306,14 @@ const GlobalSearchPalette = ({ setSelectedIndex(0) }} onKeyDown={onInputKeyDown} - placeholder='Search Donetick' + placeholder={t('search.placeholder')} startDecorator={} endDecorator={ isLoading ? ( ) : presentation === 'modal' ? ( - Esc + {t('search.escape')} ) : null } @@ -333,7 +327,7 @@ const GlobalSearchPalette = ({ level='body-xs' sx={{ color: 'text.tertiary', mt: 1, px: 0.5 }} > - Searching content available on this device + {t('search.deviceNote')} @@ -350,9 +344,9 @@ const GlobalSearchPalette = ({ - No direct matches + {t('search.empty.title')} - You can still filter the task list with this search. + {t('search.empty.subtitle')} )} @@ -381,8 +375,8 @@ const GlobalSearchPalette = ({ }} > {!query.trim() && result.provider !== 'actions' - ? 'Recent' - : GROUP_LABELS[result.provider]} + ? t('search.recent') + : t(`search.groups.${result.provider}`)} )} - ↑↓ Navigate - ↵ Open + + ↑↓ {t('search.footer.navigate')} + + ↵ {t('search.footer.open')} {query.trim() - ? `${Math.max(0, results.length - 1)} results` - : 'Type to search'} + ? t('search.footer.results', { + count: Math.max(0, results.length - 1), + }) + : t('search.footer.typeToSearch')}