mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Shorten onbaording (#5198)
Also added `enableDesktopInstallSlide` flag in `settings.yml` to hide the download for desktop page in the onboarding. --------- Co-authored-by: James Brunton <[email protected]>
This commit is contained in:
co-authored by
James Brunton
parent
371d816ce7
commit
0064c1866e
@@ -1,139 +0,0 @@
|
||||
import {
|
||||
Modal,
|
||||
Stack,
|
||||
Button,
|
||||
Text,
|
||||
Title,
|
||||
Anchor,
|
||||
useMantineTheme,
|
||||
useComputedColorScheme,
|
||||
} from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useState } from 'react';
|
||||
import { Z_ANALYTICS_MODAL } from '@app/styles/zIndex';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
interface AdminAnalyticsChoiceModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function AdminAnalyticsChoiceModal({ opened, onClose }: AdminAnalyticsChoiceModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { refetch } = useAppConfig();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const theme = useMantineTheme();
|
||||
const computedColorScheme = useComputedColorScheme('light', { getInitialValueInEffect: true });
|
||||
const isDark = computedColorScheme === 'dark';
|
||||
const privacyHighlightStyles = {
|
||||
color: isDark ? '#FFFFFF' : theme.colors.blue[7],
|
||||
padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
|
||||
borderRadius: theme.radius.md,
|
||||
fontWeight: 700,
|
||||
textAlign: 'center' as const,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: theme.spacing.xs,
|
||||
letterSpacing: 0.3,
|
||||
};
|
||||
|
||||
const handleChoice = async (enableAnalytics: boolean) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('enabled', enableAnalytics.toString());
|
||||
|
||||
await apiClient.post('/api/v1/settings/update-enable-analytics', formData);
|
||||
|
||||
// Refetch config to apply new settings without page reload
|
||||
await refetch();
|
||||
|
||||
// Close the modal after successful save
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error occurred');
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnable = () => {
|
||||
handleChoice(true);
|
||||
};
|
||||
|
||||
const handleDisable = () => {
|
||||
handleChoice(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={() => {}} // Prevent closing
|
||||
closeOnClickOutside={false}
|
||||
closeOnEscape={false}
|
||||
withCloseButton={false}
|
||||
size="lg"
|
||||
centered
|
||||
zIndex={Z_ANALYTICS_MODAL}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Title order={2}>{t('analytics.title', 'Do you want make Stirling PDF better?')}</Title>
|
||||
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('analytics.paragraph1', 'Stirling PDF has opt in analytics to help us improve the product.')}
|
||||
</Text>
|
||||
<Text size="sm" style={privacyHighlightStyles}>
|
||||
• {t('analytics.privacyAssurance', 'We do not track any personal information or the contents of your files.')} •
|
||||
</Text>
|
||||
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('analytics.paragraph2', 'Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.')}{' '}
|
||||
<Anchor
|
||||
href="https://docs.stirlingpdf.com/analytics-telemetry"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="sm"
|
||||
>
|
||||
{t('analytics.learnMore', 'Learn more')}
|
||||
</Anchor>
|
||||
</Text>
|
||||
|
||||
{error && (
|
||||
<Text c="red" size="sm">
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Stack gap="sm">
|
||||
<Button
|
||||
onClick={handleEnable}
|
||||
loading={loading}
|
||||
fullWidth
|
||||
size="md"
|
||||
>
|
||||
{t('analytics.enable', 'Enable analytics')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleDisable}
|
||||
loading={loading}
|
||||
fullWidth
|
||||
size="md"
|
||||
variant="subtle"
|
||||
c="gray"
|
||||
>
|
||||
{t('analytics.disable', 'Disable analytics')}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Text size="xs" c="dimmed" ta="center">
|
||||
{t('analytics.settings', 'You can change the settings for analytics in the config/settings.yml file')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -95,6 +95,22 @@ export default function Footer({
|
||||
>
|
||||
{t('legal.terms', 'Terms and Conditions')}
|
||||
</a>
|
||||
<a
|
||||
className="footer-link px-3"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://discord.gg/Cn8pWhQRxZ"
|
||||
>
|
||||
{t('footer.discord', 'Discord')}
|
||||
</a>
|
||||
<a
|
||||
className="footer-link px-3"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/Stirling-Tools/Stirling-PDF"
|
||||
>
|
||||
{t('footer.issues', 'GitHub')}
|
||||
</a>
|
||||
<a
|
||||
className="footer-link px-3"
|
||||
target="_blank"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useSidebarNavigation } from '@app/hooks/useSidebarNavigation';
|
||||
import { handleUnlessSpecialClick } from '@app/utils/clickHandlers';
|
||||
import { ButtonConfig } from '@app/types/sidebar';
|
||||
import '@app/components/shared/quickAccessBar/QuickAccessBar.css';
|
||||
import { Tooltip } from '@app/components/shared/Tooltip';
|
||||
import AllToolsNavButton from '@app/components/shared/AllToolsNavButton';
|
||||
import ActiveToolButton from "@app/components/shared/quickAccessBar/ActiveToolButton";
|
||||
import AppConfigModal from '@app/components/shared/AppConfigModal';
|
||||
@@ -18,6 +19,7 @@ import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import { useLicenseAlert } from "@app/hooks/useLicenseAlert";
|
||||
import { requestStartTour } from '@app/constants/events';
|
||||
import QuickAccessButton from '@app/components/shared/quickAccessBar/QuickAccessButton';
|
||||
import { useToursTooltip } from '@app/components/shared/quickAccessBar/useToursTooltip';
|
||||
|
||||
import {
|
||||
isNavButtonActive,
|
||||
@@ -41,6 +43,14 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
const [configModalOpen, setConfigModalOpen] = useState(false);
|
||||
const [activeButton, setActiveButton] = useState<string>('tools');
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
tooltipOpen,
|
||||
manualCloseOnly,
|
||||
showCloseButton,
|
||||
toursMenuOpen,
|
||||
setToursMenuOpen,
|
||||
handleTooltipOpenChange,
|
||||
} = useToursTooltip();
|
||||
|
||||
const isRTL = typeof document !== 'undefined' && document.documentElement.dir === 'rtl';
|
||||
|
||||
@@ -166,8 +176,8 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
const bottomButtons: ButtonConfig[] = [
|
||||
{
|
||||
id: 'help',
|
||||
name: t("quickAccess.help", "Help"),
|
||||
icon: <LocalIcon icon="help-rounded" width="1.25rem" height="1.25rem" />,
|
||||
name: t("quickAccess.tours", "Tours"),
|
||||
icon: <LocalIcon icon="explore-rounded" width="1.25rem" height="1.25rem" />,
|
||||
isRound: true,
|
||||
size: 'md',
|
||||
type: 'action',
|
||||
@@ -192,6 +202,7 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
<div
|
||||
ref={ref}
|
||||
data-sidebar="quick-access"
|
||||
data-tour="quick-access-bar"
|
||||
className={`h-screen flex flex-col w-16 quick-access-bar-main ${isRainbowMode ? 'rainbow-mode' : ''}`}
|
||||
>
|
||||
{/* Fixed header outside scrollable area */}
|
||||
@@ -247,58 +258,83 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
// Handle help button with menu or direct action
|
||||
if (buttonConfig.id === 'help') {
|
||||
const isAdmin = config?.isAdmin === true;
|
||||
const toursTooltipContent = isAdmin
|
||||
? t('quickAccess.toursTooltip.admin', 'Watch walkthroughs here: Tools tour, New V2 layout tour, and the Admin tour.')
|
||||
: t('quickAccess.toursTooltip.user', 'Watch walkthroughs here: Tools tour and the New V2 layout tour.');
|
||||
const tourItems = [
|
||||
{
|
||||
key: 'whatsnew',
|
||||
icon: <LocalIcon icon="auto-awesome-rounded" width="1.25rem" height="1.25rem" />,
|
||||
title: t("quickAccess.helpMenu.whatsNewTour", "See what's new in V2"),
|
||||
description: t("quickAccess.helpMenu.whatsNewTourDesc", "Tour the updated layout"),
|
||||
onClick: () => requestStartTour('whatsnew'),
|
||||
},
|
||||
{
|
||||
key: 'tools',
|
||||
icon: <LocalIcon icon="view-carousel-rounded" width="1.25rem" height="1.25rem" />,
|
||||
title: t("quickAccess.helpMenu.toolsTour", "Tools Tour"),
|
||||
description: t("quickAccess.helpMenu.toolsTourDesc", "Learn what the tools can do"),
|
||||
onClick: () => requestStartTour('tools'),
|
||||
},
|
||||
...(isAdmin ? [{
|
||||
key: 'admin',
|
||||
icon: <LocalIcon icon="admin-panel-settings-rounded" width="1.25rem" height="1.25rem" />,
|
||||
title: t("quickAccess.helpMenu.adminTour", "Admin Tour"),
|
||||
description: t("quickAccess.helpMenu.adminTourDesc", "Explore admin settings & features"),
|
||||
onClick: () => requestStartTour('admin'),
|
||||
}] : []),
|
||||
];
|
||||
|
||||
// If not admin, just show button that starts tools tour directly
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div
|
||||
key={buttonConfig.id}
|
||||
data-tour="help-button"
|
||||
onClick={() => requestStartTour('tools')}
|
||||
const helpButtonNode = (
|
||||
<div data-tour="help-button">
|
||||
<Menu
|
||||
position={isRTL ? 'left' : 'right'}
|
||||
offset={10}
|
||||
zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}
|
||||
opened={toursMenuOpen}
|
||||
onChange={setToursMenuOpen}
|
||||
>
|
||||
{renderNavButton(buttonConfig, index)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If admin, show menu with both options
|
||||
return (
|
||||
<div key={buttonConfig.id} data-tour="help-button">
|
||||
<Menu position={isRTL ? 'left' : 'right'} offset={10} zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE}>
|
||||
<Menu.Target>
|
||||
<div>{renderNavButton(buttonConfig, index)}</div>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
leftSection={<LocalIcon icon="view-carousel-rounded" width="1.25rem" height="1.25rem" />}
|
||||
onClick={() => requestStartTour('tools')}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>
|
||||
{t("quickAccess.helpMenu.toolsTour", "Tools Tour")}
|
||||
{tourItems.map((item) => (
|
||||
<Menu.Item
|
||||
key={item.key}
|
||||
leftSection={item.icon}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>
|
||||
{item.title}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.875rem', opacity: 0.7 }}>
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.875rem', opacity: 0.7 }}>
|
||||
{t("quickAccess.helpMenu.toolsTourDesc", "Learn what the tools can do")}
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<LocalIcon icon="admin-panel-settings-rounded" width="1.25rem" height="1.25rem" />}
|
||||
onClick={() => requestStartTour('admin')}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>
|
||||
{t("quickAccess.helpMenu.adminTour", "Admin Tour")}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.875rem', opacity: 0.7 }}>
|
||||
{t("quickAccess.helpMenu.adminTourDesc", "Explore admin settings & features")}
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
position="right"
|
||||
arrow
|
||||
offset={8}
|
||||
open={tooltipOpen}
|
||||
manualCloseOnly={manualCloseOnly}
|
||||
showCloseButton={showCloseButton}
|
||||
closeOnOutside={false}
|
||||
openOnFocus={false}
|
||||
content={toursTooltipContent}
|
||||
onOpenChange={handleTooltipOpenChange}
|
||||
>
|
||||
{helpButtonNode}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const buttonNode = renderNavButton(buttonConfig, index);
|
||||
|
||||
@@ -33,6 +33,10 @@ export interface TooltipProps {
|
||||
disabled?: boolean;
|
||||
/** If false, tooltip will not open on focus (hover only) */
|
||||
openOnFocus?: boolean;
|
||||
/** If true, tooltip stays open until explicitly closed (ignores hover/blur/esc/outside) */
|
||||
manualCloseOnly?: boolean;
|
||||
/** Show a close button even when not pinned */
|
||||
showCloseButton?: boolean;
|
||||
}
|
||||
|
||||
export const Tooltip: React.FC<TooltipProps> = ({
|
||||
@@ -55,6 +59,8 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
closeOnOutside = true,
|
||||
disabled = false,
|
||||
openOnFocus = true,
|
||||
manualCloseOnly = false,
|
||||
showCloseButton = false,
|
||||
}) => {
|
||||
const [internalOpen, setInternalOpen] = useState(false);
|
||||
const [isPinned, setIsPinned] = useState(false);
|
||||
@@ -81,6 +87,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
|
||||
const isControlled = controlledOpen !== undefined;
|
||||
const open = (isControlled ? !!controlledOpen : internalOpen) && !disabled;
|
||||
const allowAutoClose = !manualCloseOnly;
|
||||
|
||||
const resolvedPosition: NonNullable<TooltipProps['position']> = useMemo(() => {
|
||||
const htmlDir = typeof document !== 'undefined' ? document.documentElement.dir : 'ltr';
|
||||
@@ -132,11 +139,11 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
}
|
||||
|
||||
// Not pinned and configured to close on outside
|
||||
if (closeOnOutside && !insideTooltip && !insideTrigger) {
|
||||
if (allowAutoClose && closeOnOutside && !insideTooltip && !insideTrigger) {
|
||||
setOpen(false);
|
||||
}
|
||||
},
|
||||
[isPinned, closeOnOutside, setOpen]
|
||||
[isPinned, closeOnOutside, setOpen, allowAutoClose]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -200,10 +207,10 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
}
|
||||
|
||||
clearTimers();
|
||||
if (!isPinned) setOpen(false);
|
||||
if (allowAutoClose && !isPinned) setOpen(false);
|
||||
(children.props as any)?.onPointerLeave?.(e);
|
||||
},
|
||||
[clearTimers, isPinned, setOpen, children.props]
|
||||
[clearTimers, isPinned, setOpen, children.props, allowAutoClose]
|
||||
);
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
@@ -257,15 +264,16 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
return;
|
||||
}
|
||||
clearTimers();
|
||||
if (!isPinned) setOpen(false);
|
||||
if (allowAutoClose && !isPinned) setOpen(false);
|
||||
(children.props as any)?.onBlur?.(e);
|
||||
},
|
||||
[isPinned, setOpen, children.props, clearTimers]
|
||||
[isPinned, setOpen, children.props, allowAutoClose, clearTimers]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (manualCloseOnly) return;
|
||||
if (e.key === 'Escape') setOpen(false);
|
||||
}, [setOpen]);
|
||||
}, [setOpen, manualCloseOnly]);
|
||||
|
||||
// Keep open while pointer is over the tooltip; close when leaving it (if not pinned)
|
||||
const handleTooltipPointerEnter = useCallback(() => {
|
||||
@@ -276,9 +284,9 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
(e: React.PointerEvent) => {
|
||||
const related = e.relatedTarget as Node | null;
|
||||
if (isDomNode(related) && triggerRef.current && triggerRef.current.contains(related)) return;
|
||||
if (!isPinned) setOpen(false);
|
||||
if (allowAutoClose && !isPinned) setOpen(false);
|
||||
},
|
||||
[isPinned, setOpen]
|
||||
[isPinned, setOpen, allowAutoClose]
|
||||
);
|
||||
|
||||
// Enhance child with handlers and ref
|
||||
@@ -301,6 +309,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
});
|
||||
|
||||
const shouldShowTooltip = open;
|
||||
const shouldShowCloseButton = showCloseButton || isPinned;
|
||||
|
||||
const tooltipElement = shouldShowTooltip ? (
|
||||
<div
|
||||
@@ -325,7 +334,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
className={`${styles['tooltip-container']} ${isPinned ? styles.pinned : ''}`}
|
||||
onClick={pinOnClick ? (e) => { e.stopPropagation(); setIsPinned(true); } : undefined}
|
||||
>
|
||||
{isPinned && (
|
||||
{shouldShowCloseButton && (
|
||||
<button
|
||||
className={styles['tooltip-pin-button']}
|
||||
onClick={(e) => {
|
||||
@@ -363,7 +372,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
<span className={styles['tooltip-title']}>{header.title}</span>
|
||||
</div>
|
||||
)}
|
||||
<TooltipContent content={content} tips={tips} />
|
||||
<TooltipContent content={content} tips={tips} extraRightPadding={shouldShowCloseButton ? 48 : 0} />
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { TOUR_STATE_EVENT, type TourStatePayload } from '@app/constants/events';
|
||||
import { isOnboardingCompleted, hasShownToursTooltip, markToursTooltipShown } from '@app/components/onboarding/orchestrator/onboardingStorage';
|
||||
|
||||
export interface ToursTooltipState {
|
||||
tooltipOpen: boolean | undefined;
|
||||
manualCloseOnly: boolean;
|
||||
showCloseButton: boolean;
|
||||
toursMenuOpen: boolean;
|
||||
setToursMenuOpen: (open: boolean) => void;
|
||||
handleTooltipOpenChange: (next: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates all the logic for the tours tooltip:
|
||||
* - Shows automatically after onboarding/tour completes (once per user)
|
||||
* - Hides while the tours menu is open
|
||||
* - After dismissal, reverts to hover-only tooltip
|
||||
*/
|
||||
export function useToursTooltip(): ToursTooltipState {
|
||||
const [showToursTooltip, setShowToursTooltip] = useState(false);
|
||||
const [toursMenuOpen, setToursMenuOpen] = useState(false);
|
||||
const tourWasOpenRef = useRef(false);
|
||||
|
||||
// Auto-show when a tour ends (fires once per user)
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const handleTourStateChange = (event: Event) => {
|
||||
const { detail } = event as CustomEvent<TourStatePayload>;
|
||||
const wasOpen = tourWasOpenRef.current;
|
||||
tourWasOpenRef.current = detail.isOpen;
|
||||
|
||||
if (wasOpen && !detail.isOpen && !hasShownToursTooltip()) {
|
||||
setShowToursTooltip(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener(TOUR_STATE_EVENT, handleTourStateChange);
|
||||
return () => window.removeEventListener(TOUR_STATE_EVENT, handleTourStateChange);
|
||||
}, []);
|
||||
|
||||
// Show once after onboarding is complete
|
||||
useEffect(() => {
|
||||
if (isOnboardingCompleted() && !hasShownToursTooltip()) {
|
||||
setShowToursTooltip(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDismissToursTooltip = useCallback(() => {
|
||||
markToursTooltipShown();
|
||||
setShowToursTooltip(false);
|
||||
}, []);
|
||||
|
||||
const hasBeenDismissed = hasShownToursTooltip();
|
||||
|
||||
const handleTooltipOpenChange = useCallback(
|
||||
(next: boolean) => {
|
||||
if (!next) {
|
||||
if (!hasBeenDismissed) {
|
||||
handleDismissToursTooltip();
|
||||
}
|
||||
} else if (!hasBeenDismissed && !toursMenuOpen) {
|
||||
setShowToursTooltip(true);
|
||||
}
|
||||
},
|
||||
[hasBeenDismissed, toursMenuOpen, handleDismissToursTooltip]
|
||||
);
|
||||
|
||||
const tooltipOpen = toursMenuOpen ? false : hasBeenDismissed ? undefined : showToursTooltip;
|
||||
|
||||
return {
|
||||
tooltipOpen,
|
||||
manualCloseOnly: !hasBeenDismissed,
|
||||
showCloseButton: !hasBeenDismissed,
|
||||
toursMenuOpen,
|
||||
setToursMenuOpen,
|
||||
handleTooltipOpenChange,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
background: var(--bg-raised);
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 0.0625rem solid var(--primary-color, #3b82f6);
|
||||
border: 0.0625rem solid var(--border-default);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
z-index: 1;
|
||||
@@ -60,6 +60,13 @@
|
||||
border-color: #ef4444 !important;
|
||||
}
|
||||
|
||||
.tooltip-pin-button:focus,
|
||||
.tooltip-pin-button:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--border-default) !important;
|
||||
background-color: var(--bg-raised) !important;
|
||||
}
|
||||
|
||||
/* Tooltip Header */
|
||||
.tooltip-header {
|
||||
display: flex;
|
||||
@@ -91,7 +98,7 @@
|
||||
|
||||
/* Tooltip Body */
|
||||
.tooltip-body {
|
||||
padding: 1rem !important;
|
||||
padding: 1rem;
|
||||
color: var(--text-primary) !important;
|
||||
font-size: 0.875rem !important;
|
||||
line-height: 1.6 !important;
|
||||
|
||||
@@ -5,18 +5,20 @@ import { TooltipTip } from '@app/types/tips';
|
||||
interface TooltipContentProps {
|
||||
content?: React.ReactNode;
|
||||
tips?: TooltipTip[];
|
||||
extraRightPadding?: number;
|
||||
}
|
||||
|
||||
export const TooltipContent: React.FC<TooltipContentProps> = ({
|
||||
content,
|
||||
tips,
|
||||
extraRightPadding = 0,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`${styles['tooltip-body']}`}
|
||||
style={{
|
||||
color: 'var(--text-primary)',
|
||||
padding: '16px',
|
||||
padding: `16px ${16 + extraRightPadding}px 16px 16px`,
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.6'
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user