mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Improved language select (#5062)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { supportedLanguages } from '@app/i18n';
|
import { supportedLanguages } from '@app/i18n';
|
||||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||||
@@ -11,6 +12,7 @@ interface LanguageSelectorProps {
|
|||||||
position?: React.ComponentProps<typeof Menu>['position'];
|
position?: React.ComponentProps<typeof Menu>['position'];
|
||||||
offset?: number;
|
offset?: number;
|
||||||
compact?: boolean; // icon-only trigger
|
compact?: boolean; // icon-only trigger
|
||||||
|
tooltip?: string; // tooltip text for compact mode
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LanguageOption {
|
interface LanguageOption {
|
||||||
@@ -51,7 +53,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const label = disabled ? (
|
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>
|
<p>{option.label}</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
@@ -64,7 +66,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
opacity: animationTriggered ? 1 : 0,
|
opacity: animationTriggered ? 1 : 0,
|
||||||
transform: animationTriggered ? 'translateY(0px)' : 'translateY(8px)',
|
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
|
<Button
|
||||||
@@ -90,7 +92,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
|
|||||||
: isSelected
|
: isSelected
|
||||||
? 'light-dark(var(--mantine-color-blue-9), var(--mantine-color-white))'
|
? 'light-dark(var(--mantine-color-blue-9), var(--mantine-color-white))'
|
||||||
: 'light-dark(var(--mantine-color-gray-7), 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',
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||||
'&:hover': !disabled ? {
|
'&:hover': !disabled ? {
|
||||||
backgroundColor: isSelected
|
backgroundColor: isSelected
|
||||||
@@ -126,7 +128,7 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
|
|||||||
backgroundColor: 'var(--mantine-color-blue-4)',
|
backgroundColor: 'var(--mantine-color-blue-4)',
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
transform: 'translate(-50%, -50%)',
|
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,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -149,7 +151,12 @@ const RippleStyles: React.FC = () => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Main component
|
// 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 { i18n } = useTranslation();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [animationTriggered, setAnimationTriggered] = useState(false);
|
const [animationTriggered, setAnimationTriggered] = useState(false);
|
||||||
@@ -184,14 +191,14 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
|
|||||||
setOpened(false);
|
setOpened(false);
|
||||||
|
|
||||||
// Clear ripple effect
|
// Clear ripple effect
|
||||||
setTimeout(() => setRippleEffect(null), 100);
|
setTimeout(() => setRippleEffect(null), 50);
|
||||||
|
|
||||||
// Force a full reload so RTL/LTR layout and tooltips re-evaluate correctly
|
// Force a full reload so RTL/LTR layout and tooltips re-evaluate correctly
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 150);
|
||||||
}, 200);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
|
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
|
||||||
@@ -202,7 +209,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
|
|||||||
if (opened) {
|
if (opened) {
|
||||||
setAnimationTriggered(false);
|
setAnimationTriggered(false);
|
||||||
// Small delay to ensure DOM is ready
|
// Small delay to ensure DOM is ready
|
||||||
setTimeout(() => setAnimationTriggered(true), 50);
|
setTimeout(() => setAnimationTriggered(true), 20);
|
||||||
}
|
}
|
||||||
}, [opened]);
|
}, [opened]);
|
||||||
|
|
||||||
@@ -218,7 +225,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
|
|||||||
zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}
|
zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}
|
||||||
transitionProps={{
|
transitionProps={{
|
||||||
transition: 'scale-y',
|
transition: 'scale-y',
|
||||||
duration: 200,
|
duration: 120,
|
||||||
timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -227,8 +234,8 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
radius="md"
|
radius="md"
|
||||||
title={currentLanguage}
|
|
||||||
className="right-rail-icon"
|
className="right-rail-icon"
|
||||||
|
title={!opened && tooltip ? tooltip : undefined}
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
color: 'var(--right-rail-icon)',
|
color: 'var(--right-rail-icon)',
|
||||||
@@ -274,8 +281,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
|
|||||||
zIndex: Z_INDEX_OVER_FULLSCREEN_SURFACE,
|
zIndex: Z_INDEX_OVER_FULLSCREEN_SURFACE,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ScrollArea h={190} type="scroll">
|
<div className={styles.languageGrid}>
|
||||||
<div className={styles.languageGrid}>
|
|
||||||
{languageOptions.map((option, index) => {
|
{languageOptions.map((option, index) => {
|
||||||
const enabledLanguages = [
|
const enabledLanguages = [
|
||||||
'en-GB', 'zh-CN', 'zh-TW', 'ar-AR', 'fa-IR', 'tr-TR', 'uk-UA', 'zh-BO', 'sl-SI',
|
'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>
|
</div>
|
||||||
</ScrollArea>
|
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -216,14 +216,12 @@ export default function RightRail() {
|
|||||||
tooltipOffset
|
tooltipOffset
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{renderWithTooltip(
|
<LanguageSelector
|
||||||
<div style={{ display: 'inline-flex' }}>
|
position="left-start"
|
||||||
<LanguageSelector position="left-start" offset={6} compact />
|
offset={6}
|
||||||
</div>,
|
compact
|
||||||
t('rightRail.language', 'Language'),
|
tooltip={t('rightRail.language', 'Language')}
|
||||||
tooltipPosition,
|
/>
|
||||||
tooltipOffset
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderWithTooltip(
|
{renderWithTooltip(
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
|
|||||||
Reference in New Issue
Block a user