Fix info tooltip partially hidden and gone too quickly on mobile
This commit is contained in:
@@ -126,6 +126,27 @@ const Settings = ({
|
|||||||
const [isPharmacokineticExpanded, setIsPharmacokineticExpanded] = React.useState(true);
|
const [isPharmacokineticExpanded, setIsPharmacokineticExpanded] = React.useState(true);
|
||||||
const [isAdvancedExpanded, setIsAdvancedExpanded] = React.useState(false);
|
const [isAdvancedExpanded, setIsAdvancedExpanded] = React.useState(false);
|
||||||
|
|
||||||
|
// Track which tooltip is currently open (for mobile touch interaction)
|
||||||
|
const [openTooltipId, setOpenTooltipId] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
// Track window width for responsive tooltip positioning
|
||||||
|
const [isNarrowScreen, setIsNarrowScreen] = React.useState(
|
||||||
|
typeof window !== 'undefined' ? window.innerWidth < 640 : false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update narrow screen state on window resize
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsNarrowScreen(window.innerWidth < 640);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Determine tooltip side based on screen width
|
||||||
|
const tooltipSide = isNarrowScreen ? 'top' : 'right';
|
||||||
|
|
||||||
// Load and persist settings card expansion states
|
// Load and persist settings card expansion states
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const savedStates = localStorage.getItem('settingsCardStates_v1');
|
const savedStates = localStorage.getItem('settingsCardStates_v1');
|
||||||
@@ -142,6 +163,37 @@ const Settings = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Close tooltip when clicking outside
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!openTooltipId) return;
|
||||||
|
|
||||||
|
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
// Check if click is outside tooltip content and button
|
||||||
|
if (!target.closest('[role="tooltip"]') && !target.closest('button[aria-label*="Tooltip"]')) {
|
||||||
|
setOpenTooltipId(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Small delay to prevent immediate closure
|
||||||
|
setTimeout(() => {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
document.addEventListener('touchstart', handleClickOutside);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
document.removeEventListener('touchstart', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [openTooltipId]);
|
||||||
|
|
||||||
|
// Helper to toggle tooltip (for mobile click interaction)
|
||||||
|
const handleTooltipToggle = (tooltipId: string) => (e: React.MouseEvent<HTMLButtonElement> | React.TouchEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setOpenTooltipId(current => current === tooltipId ? null : tooltipId);
|
||||||
|
};
|
||||||
|
|
||||||
const updateDiagramExpanded = (value: boolean) => {
|
const updateDiagramExpanded = (value: boolean) => {
|
||||||
setIsDiagramExpanded(value);
|
setIsDiagramExpanded(value);
|
||||||
saveCardStates({ diagram: value, simulation: isSimulationExpanded, pharmacokinetic: isPharmacokineticExpanded, advanced: isAdvancedExpanded });
|
saveCardStates({ diagram: value, simulation: isSimulationExpanded, pharmacokinetic: isPharmacokineticExpanded, advanced: isAdvancedExpanded });
|
||||||
@@ -218,17 +270,19 @@ const Settings = ({
|
|||||||
{t('showTemplateDayInChart')}
|
{t('showTemplateDayInChart')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'showTemplateDay'} onOpenChange={(open) => setOpenTooltipId(open ? 'showTemplateDay' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('showTemplateDay')}
|
||||||
|
onTouchStart={handleTooltipToggle('showTemplateDay')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('showTemplateDayTooltip')}
|
aria-label={t('showTemplateDayTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showTemplateDayTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showTemplateDayTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -247,17 +301,19 @@ const Settings = ({
|
|||||||
{t('showDayReferenceLines')}
|
{t('showDayReferenceLines')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'showDayReferenceLines'} onOpenChange={(open) => setOpenTooltipId(open ? 'showDayReferenceLines' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('showDayReferenceLines')}
|
||||||
|
onTouchStart={handleTooltipToggle('showDayReferenceLines')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('showDayReferenceLinesTooltip')}
|
aria-label={t('showDayReferenceLinesTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showDayReferenceLinesTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showDayReferenceLinesTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -276,17 +332,19 @@ const Settings = ({
|
|||||||
{t('showTherapeuticRangeLines')}
|
{t('showTherapeuticRangeLines')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'showTherapeuticRangeLines'} onOpenChange={(open) => setOpenTooltipId(open ? 'showTherapeuticRangeLines' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('showTherapeuticRangeLines')}
|
||||||
|
onTouchStart={handleTooltipToggle('showTherapeuticRangeLines')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('showTherapeuticRangeLinesTooltip')}
|
aria-label={t('showTherapeuticRangeLinesTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showTherapeuticRangeLinesTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showTherapeuticRangeLinesTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -299,17 +357,19 @@ const Settings = ({
|
|||||||
{t('therapeuticRange')}
|
{t('therapeuticRange')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'therapeuticRange'} onOpenChange={(open) => setOpenTooltipId(open ? 'therapeuticRange' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
onClick={handleTooltipToggle('therapeuticRange')}
|
||||||
aria-label={t('therapeuticRangeTooltip')}
|
onTouchStart={handleTooltipToggle('therapeuticRange')}
|
||||||
>
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
|
aria-label={t('therapeuticRangeTooltip')}
|
||||||
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'therapeuticRangeTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'therapeuticRangeTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -347,17 +407,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('displayedDays')}</Label>
|
<Label className="font-medium">{t('displayedDays')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'displayedDays'} onOpenChange={(open) => setOpenTooltipId(open ? 'displayedDays' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('displayedDays')}
|
||||||
|
onTouchStart={handleTooltipToggle('displayedDays')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('displayedDaysTooltip')}
|
aria-label={t('displayedDaysTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'displayedDaysTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'displayedDaysTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -379,17 +441,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('yAxisRange')}</Label>
|
<Label className="font-medium">{t('yAxisRange')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'yAxisRange'} onOpenChange={(open) => setOpenTooltipId(open ? 'yAxisRange' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('yAxisRange')}
|
||||||
|
onTouchStart={handleTooltipToggle('yAxisRange')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('yAxisRangeTooltip')}
|
aria-label={t('yAxisRangeTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'yAxisRangeTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'yAxisRangeTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -438,7 +502,7 @@ const Settings = ({
|
|||||||
{t('xAxisFormatContinuous')}
|
{t('xAxisFormatContinuous')}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs">{t('xAxisFormatContinuousDesc')}</p>
|
<p className="text-xs">{t('xAxisFormatContinuousDesc')}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -448,7 +512,7 @@ const Settings = ({
|
|||||||
{t('xAxisFormat24h')}
|
{t('xAxisFormat24h')}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs">{t('xAxisFormat24hDesc')}</p>
|
<p className="text-xs">{t('xAxisFormat24hDesc')}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -458,7 +522,7 @@ const Settings = ({
|
|||||||
{t('xAxisFormat12h')}
|
{t('xAxisFormat12h')}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs">{t('xAxisFormat12hDesc')}</p>
|
<p className="text-xs">{t('xAxisFormat12hDesc')}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -483,17 +547,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('simulationDuration')}</Label>
|
<Label className="font-medium">{t('simulationDuration')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'simulationDuration'} onOpenChange={(open) => setOpenTooltipId(open ? 'simulationDuration' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('simulationDuration')}
|
||||||
|
onTouchStart={handleTooltipToggle('simulationDuration')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('simulationDurationTooltip')}
|
aria-label={t('simulationDurationTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'simulationDurationTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'simulationDurationTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -533,17 +599,19 @@ const Settings = ({
|
|||||||
{t('steadyStateDays')}
|
{t('steadyStateDays')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'steadyStateDays'} onOpenChange={(open) => setOpenTooltipId(open ? 'steadyStateDays' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('steadyStateDays')}
|
||||||
|
onTouchStart={handleTooltipToggle('steadyStateDays')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('steadyStateDaysTooltip')}
|
aria-label={t('steadyStateDaysTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'steadyStateDaysTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'steadyStateDaysTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -581,17 +649,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('halfLife')}</Label>
|
<Label className="font-medium">{t('halfLife')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'halfLife'} onOpenChange={(open) => setOpenTooltipId(open ? 'halfLife' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('halfLife')}
|
||||||
|
onTouchStart={handleTooltipToggle('halfLife')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('halfLifeTooltip')}
|
aria-label={t('halfLifeTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'halfLifeTooltip', defaultsForT))}</p>
|
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'halfLifeTooltip', defaultsForT))}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -619,17 +689,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('conversionHalfLife')}</Label>
|
<Label className="font-medium">{t('conversionHalfLife')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'conversionHalfLife'} onOpenChange={(open) => setOpenTooltipId(open ? 'conversionHalfLife' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('conversionHalfLife')}
|
||||||
|
onTouchStart={handleTooltipToggle('conversionHalfLife')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('conversionHalfLifeTooltip')}
|
aria-label={t('conversionHalfLifeTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'conversionHalfLifeTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'conversionHalfLifeTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -653,17 +725,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('absorptionHalfLife')}</Label>
|
<Label className="font-medium">{t('absorptionHalfLife')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'absorptionHalfLife'} onOpenChange={(open) => setOpenTooltipId(open ? 'absorptionHalfLife' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('absorptionHalfLife')}
|
||||||
|
onTouchStart={handleTooltipToggle('absorptionHalfLife')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('absorptionHalfLifeTooltip')}
|
aria-label={t('absorptionHalfLifeTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'absorptionHalfLifeTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'absorptionHalfLifeTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -711,17 +785,19 @@ const Settings = ({
|
|||||||
{t('weightBasedVdScaling')}
|
{t('weightBasedVdScaling')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'weightBasedVd'} onOpenChange={(open) => setOpenTooltipId(open ? 'weightBasedVd' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('weightBasedVd')}
|
||||||
|
onTouchStart={handleTooltipToggle('weightBasedVd')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('weightBasedVdTooltip')}
|
aria-label={t('weightBasedVdTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'weightBasedVdTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'weightBasedVdTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -732,17 +808,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="text-sm font-medium">{t('bodyWeight')}</Label>
|
<Label className="text-sm font-medium">{t('bodyWeight')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'bodyWeight'} onOpenChange={(open) => setOpenTooltipId(open ? 'bodyWeight' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
onClick={handleTooltipToggle('bodyWeight')}
|
||||||
aria-label={t('bodyWeightTooltip')}
|
onTouchStart={handleTooltipToggle('bodyWeight')}
|
||||||
>
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
|
aria-label={t('bodyWeightTooltip')}
|
||||||
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'bodyWeightTooltip', defaultsForT))}</p>
|
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'bodyWeightTooltip', defaultsForT))}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -775,17 +853,19 @@ const Settings = ({
|
|||||||
{t('foodEffectEnabled')}
|
{t('foodEffectEnabled')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'foodEffect'} onOpenChange={(open) => setOpenTooltipId(open ? 'foodEffect' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('foodEffect')}
|
||||||
|
onTouchStart={handleTooltipToggle('foodEffect')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('foodEffectTooltip')}
|
aria-label={t('foodEffectTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'foodEffectTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'foodEffectTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -796,17 +876,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="text-sm font-medium">{t('tmaxDelay')}</Label>
|
<Label className="text-sm font-medium">{t('tmaxDelay')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'tmaxDelay'} onOpenChange={(open) => setOpenTooltipId(open ? 'tmaxDelay' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
onClick={handleTooltipToggle('tmaxDelay')}
|
||||||
aria-label={t('tmaxDelayTooltip')}
|
onTouchStart={handleTooltipToggle('tmaxDelay')}
|
||||||
>
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
|
aria-label={t('tmaxDelayTooltip')}
|
||||||
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'tmaxDelayTooltip', defaultsForT))}</p>
|
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'tmaxDelayTooltip', defaultsForT))}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -839,17 +921,19 @@ const Settings = ({
|
|||||||
{t('urinePHTendency')}
|
{t('urinePHTendency')}
|
||||||
</Label>
|
</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'urinePH'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePH' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('urinePH')}
|
||||||
|
onTouchStart={handleTooltipToggle('urinePH')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('urinePHTooltip')}
|
aria-label={t('urinePHTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -860,17 +944,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="text-sm font-medium">{t('urinePHValue')}</Label>
|
<Label className="text-sm font-medium">{t('urinePHValue')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'urinePHValue'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePHValue' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
onClick={handleTooltipToggle('urinePHValue')}
|
||||||
aria-label={t('urinePHValueTooltip')}
|
onTouchStart={handleTooltipToggle('urinePHValue')}
|
||||||
>
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
|
aria-label={t('urinePHValueTooltip')}
|
||||||
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHValueTooltip', defaultsForT)}</p>
|
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHValueTooltip', defaultsForT)}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -896,17 +982,19 @@ const Settings = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label className="font-medium">{t('oralBioavailability')}</Label>
|
<Label className="font-medium">{t('oralBioavailability')}</Label>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip open={openTooltipId === 'oralBioavailability'} onOpenChange={(open) => setOpenTooltipId(open ? 'oralBioavailability' : null)}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
onClick={handleTooltipToggle('oralBioavailability')}
|
||||||
|
onTouchStart={handleTooltipToggle('oralBioavailability')}
|
||||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
aria-label={t('oralBioavailabilityTooltip')}
|
aria-label={t('oralBioavailabilityTooltip')}
|
||||||
>
|
>
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side={tooltipSide}>
|
||||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'oralBioavailabilityTooltip', defaultsForT))}</p>
|
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'oralBioavailabilityTooltip', defaultsForT))}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
95
src/components/ui/info-tooltip.tsx
Normal file
95
src/components/ui/info-tooltip.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* useInfoTooltip Hook & InfoTooltipButton Component
|
||||||
|
*
|
||||||
|
* Provides mobile-friendly tooltip handling for info icons.
|
||||||
|
* On touch devices, the tooltip persists until user clicks outside.
|
||||||
|
* On desktop, it shows on hover as normal.
|
||||||
|
*
|
||||||
|
* Usage in settings:
|
||||||
|
* ```tsx
|
||||||
|
* const [isOpen, handlers] = useInfoTooltip();
|
||||||
|
* <Tooltip open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
* <TooltipTrigger asChild>
|
||||||
|
* <button {...handlers}>
|
||||||
|
* <Info className="h-4 w-4" />
|
||||||
|
* </button>
|
||||||
|
* </TooltipTrigger>
|
||||||
|
* <TooltipContent>...</TooltipContent>
|
||||||
|
* </Tooltip>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @author Andreas Weyer
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface TooltipHandlers {
|
||||||
|
onTouchStart: (e: React.TouchEvent<HTMLButtonElement>) => void;
|
||||||
|
onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to manage tooltip state with touch persistence.
|
||||||
|
* Returns [isOpen, handlers, setIsOpen] for use with Radix Tooltip.
|
||||||
|
*/
|
||||||
|
export const useInfoTooltip = (): [boolean, TooltipHandlers, (open: boolean) => void] => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const [isTouchDevice, setIsTouchDevice] = React.useState(false);
|
||||||
|
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
// Detect if device supports touch
|
||||||
|
React.useEffect(() => {
|
||||||
|
const isTouchScreen = () => {
|
||||||
|
return (
|
||||||
|
(typeof window !== 'undefined' &&
|
||||||
|
window.matchMedia('(hover: none) and (pointer: coarse)').matches) ||
|
||||||
|
('ontouchstart' in window) ||
|
||||||
|
(navigator.maxTouchPoints > 0)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
setIsTouchDevice(isTouchScreen());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle click outside to close tooltip (for touch devices)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isOpen || !isTouchDevice) return;
|
||||||
|
|
||||||
|
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (triggerRef.current && !triggerRef.current.contains(e.target as Node)) {
|
||||||
|
const tooltip = document.querySelector('[role="tooltip"]');
|
||||||
|
if (tooltip && !tooltip.contains(e.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use a small delay to avoid immediate closing on the same touch
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
document.addEventListener('touchstart', handleClickOutside);
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
document.removeEventListener('touchstart', handleClickOutside);
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [isOpen, isTouchDevice]);
|
||||||
|
|
||||||
|
const handlers: TooltipHandlers = {
|
||||||
|
onTouchStart: (e: React.TouchEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsOpen(true);
|
||||||
|
},
|
||||||
|
// For desktop hover, let Radix UI handle it (will work via open prop)
|
||||||
|
// But we can optionally close on mouse leave for consistency
|
||||||
|
onMouseLeave: isTouchDevice ? undefined : (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
// Let Radix UI handle this naturally
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return [isOpen, handlers, setIsOpen];
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { cn } from "../../lib/utils"
|
|||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
// Tooltip with slightly longer delay to support touch interactions better
|
||||||
const Tooltip = TooltipPrimitive.Root
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|||||||
Reference in New Issue
Block a user