Files
donetick/src/components/animations/LoadingScreen.jsx
everysingletear bdac10ac57 i18n: extract shared strings into the existing common namespace
Part of #145.

Fourteen files whose user-facing strings are generic enough to belong in
`common` — loading and empty states, the confirmation modal, the shared
input modals (text/date/user/attachment viewer), the mobile nav bar, the
autocomplete input, the error screen and the file-upload error paths.

Extends the namespace that already exists, so `src/i18n/config.js` is
untouched and this cannot collide with any other extraction PR over the
`ns:` array. 28 keys added to `public/locales/en/common.json`.

English only — no translations, no behaviour change. Every t() value is
checked against this branch's base: the string must appear
character-for-character in the code it replaces (36 call sites).

One value is matched loosely and worth naming: `errorScreen.hideDetails`.
The base renders `{showDetails ? 'Hide' : 'Show'} error details`, so
neither full phrase exists contiguously in the source — only one branch of
the ternary can. Both keys hold exactly what each branch renders. The
sentence is kept whole rather than split around the ternary, because a
split sentence cannot be reordered by a translator.
2026-08-13 10:16:14 +08:00

109 lines
2.3 KiB
JavaScript

import { Box, CircularProgress, Typography } from '@mui/joy'
import { styled } from '@mui/joy/styles'
// Styled components with keyframe animations
const LoadingContainer = styled(Box)({
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
backdropFilter: 'blur(8px)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999,
animation: 'fadeIn 0.3s ease-out',
'@keyframes fadeIn': {
from: {
opacity: 0,
},
to: {
opacity: 1,
},
},
})
const LoadingContent = styled(Box)({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '24px',
animation: 'slideUp 0.5s ease-out 0.2s both',
'@keyframes slideUp': {
from: {
opacity: 0,
transform: 'translateY(20px)',
},
to: {
opacity: 1,
transform: 'translateY(0)',
},
},
})
const PulsingText = styled(Typography)({
animation: 'pulse 2s ease-in-out infinite',
'@keyframes pulse': {
'0%, 100%': {
opacity: 1,
},
'50%': {
opacity: 0.5,
},
},
})
const LogoContainer = styled(Box)({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
marginBottom: '24px',
})
import { useTranslation } from 'react-i18next'
const LoadingScreen = ({
message = null,
showLogo = true,
size = 'lg',
}) => {
const { t } = useTranslation('common')
return (
<LoadingContainer>
<LoadingContent>
{showLogo && (
<LogoContainer>
<Typography
level='h1'
sx={{
fontSize: { xs: '2rem', sm: '2.5rem' },
fontWeight: 700,
color: 'primary.500',
mb: 1,
}}
>
{t('done')}
<span style={{ color: '#06b6d4' }}>tick</span>
</Typography>
</LogoContainer>
)}
<CircularProgress
size={size}
sx={{
'--CircularProgress-size': size === 'lg' ? '60px' : '40px',
mb: 2,
}}
/>
<PulsingText level='body-md'>{message ?? t('loading')}</PulsingText>
</LoadingContent>
</LoadingContainer>
)
}
export default LoadingScreen