Add RTL support for JoyToggleButtonGroup and implement i18n for search actions

This commit is contained in:
Mo Tarbin
2026-08-11 02:09:56 -04:00
parent 7e4b4b8fd6
commit 6ace462797
2 changed files with 44 additions and 36 deletions

View File

@@ -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) : {},
},
},
},
}

View File

@@ -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: <CheckCircleOutline />,
@@ -58,33 +50,33 @@ const ICONS = {
actions: <AddRounded />,
}
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 (
<Box
@@ -165,7 +159,7 @@ const SearchContainer = ({ children, onClose, presentation }) => {
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={<SearchRounded />}
endDecorator={
isLoading ? (
<CircularProgress size='sm' />
) : presentation === 'modal' ? (
<Chip size='sm' variant='outlined'>
Esc
{t('search.escape')}
</Chip>
) : 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')}
</Typography>
</Box>
<Divider />
@@ -350,9 +344,9 @@ const GlobalSearchPalette = ({
<InboxOutlined
sx={{ fontSize: 36, color: 'text.tertiary', mb: 1 }}
/>
<Typography level='title-md'>No direct matches</Typography>
<Typography level='title-md'>{t('search.empty.title')}</Typography>
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
You can still filter the task list with this search.
{t('search.empty.subtitle')}
</Typography>
</Box>
)}
@@ -381,8 +375,8 @@ const GlobalSearchPalette = ({
}}
>
{!query.trim() && result.provider !== 'actions'
? 'Recent'
: GROUP_LABELS[result.provider]}
? t('search.recent')
: t(`search.groups.${result.provider}`)}
</Typography>
)}
<ListItemButton
@@ -434,12 +428,16 @@ const GlobalSearchPalette = ({
color: 'text.tertiary',
}}
>
<Typography level='body-xs'> Navigate</Typography>
<Typography level='body-xs'> Open</Typography>
<Typography level='body-xs'>
{t('search.footer.navigate')}
</Typography>
<Typography level='body-xs'> {t('search.footer.open')}</Typography>
<Typography level='body-xs' sx={{ ml: 'auto' }}>
{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')}
</Typography>
</Box>
</SearchContainer>