mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 02:54:06 +02:00
Bug/v2/fix rtl (#4958)
This commit is contained in:
@@ -10,9 +10,14 @@ import { handleUnlessSpecialClick } from '@app/utils/clickHandlers';
|
||||
interface AllToolsNavButtonProps {
|
||||
activeButton: string;
|
||||
setActiveButton: (id: string) => void;
|
||||
tooltipPosition?: 'left' | 'right' | 'top' | 'bottom';
|
||||
}
|
||||
|
||||
const AllToolsNavButton: React.FC<AllToolsNavButtonProps> = ({ activeButton, setActiveButton }) => {
|
||||
const AllToolsNavButton: React.FC<AllToolsNavButtonProps> = ({
|
||||
activeButton,
|
||||
setActiveButton,
|
||||
tooltipPosition = 'right'
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { handleReaderToggle, handleBackToTools, selectedToolKey, leftPanelView } = useToolWorkflow();
|
||||
const { getHomeNavigation } = useSidebarNavigation();
|
||||
@@ -40,7 +45,13 @@ const AllToolsNavButton: React.FC<AllToolsNavButtonProps> = ({ activeButton, set
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip content={t("quickAccess.allTools", "Tools")} position="right" arrow containerStyle={{ marginTop: "-1rem" }} maxWidth={200}>
|
||||
<Tooltip
|
||||
content={t("quickAccess.allTools", "Tools")}
|
||||
position={tooltipPosition}
|
||||
arrow
|
||||
containerStyle={{ marginTop: "-1rem" }}
|
||||
maxWidth={200}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-1 mt-4 mb-2">
|
||||
<ActionIcon
|
||||
component="a"
|
||||
@@ -70,4 +81,3 @@ const AllToolsNavButton: React.FC<AllToolsNavButtonProps> = ({ activeButton, set
|
||||
|
||||
export default AllToolsNavButton;
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,11 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
|
||||
|
||||
// Clear ripple effect
|
||||
setTimeout(() => setRippleEffect(null), 100);
|
||||
|
||||
// Force a full reload so RTL/LTR layout and tooltips re-evaluate correctly
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.reload();
|
||||
}
|
||||
}, 300);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@@ -39,6 +39,8 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const isOverflow = useIsOverflowing(scrollableRef);
|
||||
|
||||
const isRTL = typeof document !== 'undefined' && document.documentElement.dir === 'rtl';
|
||||
|
||||
// Open modal if URL is at /settings/*
|
||||
useEffect(() => {
|
||||
const isSettings = location.pathname.startsWith('/settings');
|
||||
@@ -189,9 +191,6 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
ref={ref}
|
||||
data-sidebar="quick-access"
|
||||
className={`h-screen flex flex-col w-16 quick-access-bar-main ${isRainbowMode ? 'rainbow-mode' : ''}`}
|
||||
style={{
|
||||
borderRight: '1px solid var(--border-default)'
|
||||
}}
|
||||
>
|
||||
{/* Fixed header outside scrollable area */}
|
||||
<div className="quick-access-header">
|
||||
@@ -278,7 +277,7 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
// If admin, show menu with both options
|
||||
return (
|
||||
<div key={buttonConfig.id} data-tour="help-button">
|
||||
<Menu position="right" offset={10} zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}>
|
||||
<Menu position={isRTL ? 'left' : 'right'} offset={10} zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}>
|
||||
<Menu.Target>
|
||||
<div>{renderNavButton(buttonConfig, index)}</div>
|
||||
</Menu.Target>
|
||||
|
||||
@@ -19,19 +19,22 @@ import LightModeIcon from '@mui/icons-material/LightMode';
|
||||
|
||||
import { useSidebarContext } from '@app/contexts/SidebarContext';
|
||||
import { RightRailButtonConfig, RightRailRenderContext, RightRailSection } from '@app/types/rightRail';
|
||||
import { useRightRailTooltipSide } from '@app/hooks/useRightRailTooltipSide';
|
||||
|
||||
const SECTION_ORDER: RightRailSection[] = ['top', 'middle', 'bottom'];
|
||||
|
||||
function renderWithTooltip(
|
||||
node: React.ReactNode,
|
||||
tooltip: React.ReactNode | undefined
|
||||
tooltip: React.ReactNode | undefined,
|
||||
position: 'left' | 'right',
|
||||
offset: number
|
||||
) {
|
||||
if (!tooltip) return node;
|
||||
|
||||
const portalTarget = typeof document !== 'undefined' ? document.body : undefined;
|
||||
|
||||
return (
|
||||
<Tooltip content={tooltip} position="left" offset={12} arrow portalTarget={portalTarget}>
|
||||
<Tooltip content={tooltip} position={position} offset={offset} arrow portalTarget={portalTarget}>
|
||||
<div className="right-rail-tooltip-wrapper">{node}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -39,6 +42,7 @@ function renderWithTooltip(
|
||||
|
||||
export default function RightRail() {
|
||||
const { sidebarRefs } = useSidebarContext();
|
||||
const { position: tooltipPosition, offset: tooltipOffset } = useRightRailTooltipSide(sidebarRefs);
|
||||
const { t } = useTranslation();
|
||||
const viewerContext = React.useContext(ViewerContext);
|
||||
const { toggleTheme, themeMode } = useRainbowThemeContext();
|
||||
@@ -117,9 +121,9 @@ export default function RightRail() {
|
||||
</ActionIcon>
|
||||
);
|
||||
|
||||
return renderWithTooltip(buttonNode, btn.tooltip);
|
||||
return renderWithTooltip(buttonNode, btn.tooltip, tooltipPosition, tooltipOffset);
|
||||
},
|
||||
[actions, allButtonsDisabled, disableForFullscreen]
|
||||
[actions, allButtonsDisabled, disableForFullscreen, tooltipPosition, tooltipOffset]
|
||||
);
|
||||
|
||||
const handleExportAll = useCallback(async () => {
|
||||
@@ -203,14 +207,18 @@ export default function RightRail() {
|
||||
<DarkModeIcon sx={{ fontSize: '1.5rem' }} />
|
||||
)}
|
||||
</ActionIcon>,
|
||||
t('rightRail.toggleTheme', 'Toggle Theme')
|
||||
t('rightRail.toggleTheme', 'Toggle Theme'),
|
||||
tooltipPosition,
|
||||
tooltipOffset
|
||||
)}
|
||||
|
||||
{renderWithTooltip(
|
||||
<div style={{ display: 'inline-flex' }}>
|
||||
<LanguageSelector position="left-start" offset={6} compact />
|
||||
</div>,
|
||||
t('rightRail.language', 'Language')
|
||||
t('rightRail.language', 'Language'),
|
||||
tooltipPosition,
|
||||
tooltipOffset
|
||||
)}
|
||||
|
||||
{renderWithTooltip(
|
||||
@@ -226,7 +234,9 @@ export default function RightRail() {
|
||||
>
|
||||
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>,
|
||||
downloadTooltip
|
||||
downloadTooltip,
|
||||
tooltipPosition,
|
||||
tooltipOffset
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface TooltipProps {
|
||||
|
||||
export const Tooltip: React.FC<TooltipProps> = ({
|
||||
sidebarTooltip = false,
|
||||
position = 'right',
|
||||
position,
|
||||
content,
|
||||
tips,
|
||||
children,
|
||||
@@ -81,6 +81,16 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
const isControlled = controlledOpen !== undefined;
|
||||
const open = (isControlled ? !!controlledOpen : internalOpen) && !disabled;
|
||||
|
||||
const resolvedPosition: NonNullable<TooltipProps['position']> = useMemo(() => {
|
||||
const htmlDir = typeof document !== 'undefined' ? document.documentElement.dir : 'ltr';
|
||||
const isRTL = htmlDir === 'rtl';
|
||||
const base = position ?? 'right';
|
||||
if (!isRTL) return base as NonNullable<TooltipProps['position']>;
|
||||
if (base === 'left') return 'right';
|
||||
if (base === 'right') return 'left';
|
||||
return base as NonNullable<TooltipProps['position']>;
|
||||
}, [position]);
|
||||
|
||||
const setOpen = useCallback(
|
||||
(newOpen: boolean) => {
|
||||
if (newOpen === open) return; // avoid churn
|
||||
@@ -94,7 +104,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
const { coords, positionReady } = useTooltipPosition({
|
||||
open,
|
||||
sidebarTooltip,
|
||||
position,
|
||||
position: resolvedPosition,
|
||||
gap,
|
||||
triggerRef,
|
||||
tooltipRef,
|
||||
@@ -145,8 +155,8 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
left: 'tooltip-arrow-left',
|
||||
right: 'tooltip-arrow-right',
|
||||
};
|
||||
return map[position] || map.right;
|
||||
}, [position, sidebarTooltip]);
|
||||
return map[resolvedPosition] || map.right;
|
||||
}, [resolvedPosition, sidebarTooltip]);
|
||||
|
||||
const getArrowStyleClass = useCallback(
|
||||
(key: string) =>
|
||||
@@ -332,7 +342,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
className={`${styles['tooltip-arrow']} ${getArrowStyleClass(arrowClass!)}`}
|
||||
style={
|
||||
coords.arrowOffset !== null
|
||||
? { [position === 'top' || position === 'bottom' ? 'left' : 'top']: coords.arrowOffset }
|
||||
? { [resolvedPosition === 'top' || resolvedPosition === 'bottom' ? 'left' : 'top']: coords.arrowOffset }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -24,11 +24,12 @@ import { Tooltip } from '@app/components/shared/Tooltip';
|
||||
interface ActiveToolButtonProps {
|
||||
activeButton: string;
|
||||
setActiveButton: (id: string) => void;
|
||||
tooltipPosition?: 'left' | 'right' | 'top' | 'bottom';
|
||||
}
|
||||
|
||||
const NAV_IDS = ['read', 'sign', 'automate'];
|
||||
|
||||
const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ setActiveButton }) => {
|
||||
const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ setActiveButton, tooltipPosition = 'right' }) => {
|
||||
const { selectedTool, selectedToolKey, leftPanelView, handleBackToTools } = useToolWorkflow();
|
||||
const { getHomeNavigation } = useSidebarNavigation();
|
||||
|
||||
@@ -139,7 +140,12 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ setActiveButton })
|
||||
{indicatorTool && (
|
||||
<div className="current-tool-content">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<Tooltip content={isBackHover ? 'Back to all tools' : indicatorTool.name} position="right" arrow maxWidth={140}>
|
||||
<Tooltip
|
||||
content={isBackHover ? 'Back to all tools' : indicatorTool.name}
|
||||
position={tooltipPosition}
|
||||
arrow
|
||||
maxWidth={140}
|
||||
>
|
||||
<ActionIcon
|
||||
component="a"
|
||||
href={getHomeNavigation().href}
|
||||
@@ -189,4 +195,3 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ setActiveButton })
|
||||
|
||||
export default ActiveToolButton;
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
max-width: 4rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
border-right: 1px solid var(--border-default);
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
direction: ltr; /* keep layout stable when document is rtl */
|
||||
}
|
||||
|
||||
/* Rainbow mode container */
|
||||
@@ -53,6 +57,17 @@
|
||||
max-width: 4rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
border-right: 1px solid var(--border-default);
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* RTL adjustments keep the bar on-screen and separated from content */
|
||||
:root[dir='rtl'] .quick-access-bar-main,
|
||||
:root[dir='rtl'] .quick-access-bar-main.rainbow-mode {
|
||||
border-right: none;
|
||||
border-left: 1px solid var(--border-default);
|
||||
}
|
||||
|
||||
/* Header padding */
|
||||
@@ -276,4 +291,4 @@
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { generateThumbnailWithMetadata } from '@app/utils/thumbnailUtils';
|
||||
import { createProcessedFile } from '@app/contexts/file/fileActions';
|
||||
import { createStirlingFile, createNewStirlingFileStub } from '@app/types/fileContext';
|
||||
import { useNavigationState } from '@app/contexts/NavigationContext';
|
||||
import { useSidebarContext } from '@app/contexts/SidebarContext';
|
||||
import { useRightRailTooltipSide } from '@app/hooks/useRightRailTooltipSide';
|
||||
|
||||
interface ViewerAnnotationControlsProps {
|
||||
currentView: string;
|
||||
@@ -19,6 +21,8 @@ interface ViewerAnnotationControlsProps {
|
||||
|
||||
export default function ViewerAnnotationControls({ currentView, disabled = false }: ViewerAnnotationControlsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { sidebarRefs } = useSidebarContext();
|
||||
const { position: tooltipPosition, offset: tooltipOffset } = useRightRailTooltipSide(sidebarRefs);
|
||||
const [selectedColor, setSelectedColor] = useState('#000000');
|
||||
const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
|
||||
const [isHoverColorPickerOpen, setIsHoverColorPickerOpen] = useState(false);
|
||||
@@ -53,7 +57,7 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
return (
|
||||
<>
|
||||
{/* Annotation Visibility Toggle */}
|
||||
<Tooltip content={t('rightRail.toggleAnnotations', 'Toggle Annotations Visibility')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<Tooltip content={t('rightRail.toggleAnnotations', 'Toggle Annotations Visibility')} position={tooltipPosition} offset={tooltipOffset} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@@ -130,7 +134,7 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
</div>
|
||||
) : (
|
||||
// When inactive: Show "Draw" tooltip
|
||||
<Tooltip content={t('rightRail.draw', 'Draw')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<Tooltip content={t('rightRail.draw', 'Draw')} position={tooltipPosition} offset={tooltipOffset} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@@ -156,7 +160,7 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
)}
|
||||
|
||||
{/* Save PDF with Annotations */}
|
||||
<Tooltip content={t('rightRail.save', 'Save')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<Tooltip content={t('rightRail.save', 'Save')} position={tooltipPosition} offset={tooltipOffset} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@@ -230,4 +234,4 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user