Improved language select (#5062)

This commit is contained in:
Reece Browne 2025-11-28 22:49:23 +00:00 committed by GitHub
parent e4c6ce5836
commit 058a81d554
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 27 additions and 24 deletions

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Menu, Button, ScrollArea, ActionIcon, Tooltip } from '@mantine/core';
import { Menu, Button, ActionIcon } from '@mantine/core';
import { Tooltip } from '@app/components/shared/Tooltip';
import { useTranslation } from 'react-i18next';
import { supportedLanguages } from '@app/i18n';
import LocalIcon from '@app/components/shared/LocalIcon';
@ -11,6 +12,7 @@ interface LanguageSelectorProps {
position?: React.ComponentProps<typeof Menu>['position'];
offset?: number;
compact?: boolean; // icon-only trigger
tooltip?: string; // tooltip text for compact mode
}
interface LanguageOption {
@ -51,7 +53,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
const { t } = useTranslation();
const label = disabled ? (
<Tooltip label={t('comingSoon', 'Coming soon')} position="left" withArrow>
<Tooltip content={t('comingSoon', 'Coming soon')} position="left" arrow>
<p>{option.label}</p>
</Tooltip>
) : (
@ -64,7 +66,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
style={{
opacity: animationTriggered ? 1 : 0,
transform: animationTriggered ? 'translateY(0px)' : 'translateY(8px)',
transition: `opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) ${index * 0.02}s, transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) ${index * 0.02}s`,
transition: `opacity 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94) ${index * 0.01}s, transform 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94) ${index * 0.01}s`,
}}
>
<Button
@ -90,7 +92,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
: isSelected
? 'light-dark(var(--mantine-color-blue-9), var(--mantine-color-white))'
: 'light-dark(var(--mantine-color-gray-7), var(--mantine-color-white))',
transition: 'all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
transition: 'all 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
cursor: disabled ? 'not-allowed' : 'pointer',
'&:hover': !disabled ? {
backgroundColor: isSelected
@ -126,7 +128,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
backgroundColor: 'var(--mantine-color-blue-4)',
opacity: 0.6,
transform: 'translate(-50%, -50%)',
animation: 'ripple-expand 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
animation: 'ripple-expand 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
zIndex: 1,
}}
/>
@ -149,7 +151,12 @@ const RippleStyles: React.FC = () => (
);
// Main component
const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-start', offset = 8, compact = false }) => {
const LanguageSelector: React.FC<LanguageSelectorProps> = ({
position = 'bottom-start',
offset = 8,
compact = false,
tooltip
}) => {
const { i18n } = useTranslation();
const [opened, setOpened] = useState(false);
const [animationTriggered, setAnimationTriggered] = useState(false);
@ -184,14 +191,14 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
setOpened(false);
// Clear ripple effect
setTimeout(() => setRippleEffect(null), 100);
setTimeout(() => setRippleEffect(null), 50);
// Force a full reload so RTL/LTR layout and tooltips re-evaluate correctly
if (typeof window !== 'undefined') {
window.location.reload();
}
}, 300);
}, 200);
}, 150);
}, 100);
};
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
@ -202,7 +209,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
if (opened) {
setAnimationTriggered(false);
// Small delay to ensure DOM is ready
setTimeout(() => setAnimationTriggered(true), 50);
setTimeout(() => setAnimationTriggered(true), 20);
}
}, [opened]);
@ -218,7 +225,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}
transitionProps={{
transition: 'scale-y',
duration: 200,
duration: 120,
timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
}}
>
@ -227,8 +234,8 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
<ActionIcon
variant="subtle"
radius="md"
title={currentLanguage}
className="right-rail-icon"
title={!opened && tooltip ? tooltip : undefined}
styles={{
root: {
color: 'var(--right-rail-icon)',
@ -274,8 +281,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
zIndex: Z_INDEX_OVER_FULLSCREEN_SURFACE,
}}
>
<ScrollArea h={190} type="scroll">
<div className={styles.languageGrid}>
<div className={styles.languageGrid}>
{languageOptions.map((option, index) => {
const enabledLanguages = [
'en-GB', 'zh-CN', 'zh-TW', 'ar-AR', 'fa-IR', 'tr-TR', 'uk-UA', 'zh-BO', 'sl-SI',
@ -301,8 +307,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
/>
);
})}
</div>
</ScrollArea>
</div>
</Menu.Dropdown>
</Menu>
</>

View File

@ -216,14 +216,12 @@ export default function RightRail() {
tooltipOffset
)}
{renderWithTooltip(
<div style={{ display: 'inline-flex' }}>
<LanguageSelector position="left-start" offset={6} compact />
</div>,
t('rightRail.language', 'Language'),
tooltipPosition,
tooltipOffset
)}
<LanguageSelector
position="left-start"
offset={6}
compact
tooltip={t('rightRail.language', 'Language')}
/>
{renderWithTooltip(
<ActionIcon