Update consolidated and improved tooltips
This commit is contained in:
39
src/App.tsx
39
src/App.tsx
@@ -19,6 +19,8 @@ import Settings from './components/settings';
|
||||
import LanguageSelector from './components/language-selector';
|
||||
import DisclaimerModal from './components/disclaimer-modal';
|
||||
import { Button } from './components/ui/button';
|
||||
import { TooltipProvider } from './components/ui/tooltip';
|
||||
import { IconButtonWithTooltip } from './components/ui/icon-button-with-tooltip';
|
||||
import { PROJECT_REPOSITORY_URL, APP_VERSION } from './constants/defaults';
|
||||
|
||||
// Custom Hooks
|
||||
@@ -100,19 +102,20 @@ const MedPlanAssistant = () => {
|
||||
} = useSimulation(appState);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-4 sm:p-6 lg:p-8">
|
||||
{/* Disclaimer Modal */}
|
||||
<DisclaimerModal
|
||||
isOpen={showDisclaimer}
|
||||
onAccept={handleAcceptDisclaimer}
|
||||
currentLanguage={currentLanguage}
|
||||
onLanguageChange={changeLanguage}
|
||||
t={t}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<div className="min-h-screen bg-background p-4 sm:p-6 lg:p-8">
|
||||
{/* Disclaimer Modal */}
|
||||
<DisclaimerModal
|
||||
isOpen={showDisclaimer}
|
||||
onAccept={handleAcceptDisclaimer}
|
||||
currentLanguage={currentLanguage}
|
||||
onLanguageChange={changeLanguage}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<header className="mb-8">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<header className="mb-8">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold tracking-tight">{t('appTitle')}</h1>
|
||||
</div>
|
||||
@@ -147,15 +150,14 @@ const MedPlanAssistant = () => {
|
||||
{t('both')}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
<IconButtonWithTooltip
|
||||
onClick={() => updateUiSetting('stickyChart', !uiSettings.stickyChart)}
|
||||
icon={uiSettings.stickyChart ? <Pin size={16} /> : <PinOff size={16} />}
|
||||
tooltip={uiSettings.stickyChart ? t('unpinChart') : t('pinChart')}
|
||||
variant={uiSettings.stickyChart ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
title={uiSettings.stickyChart ? t('unpinChart') : t('pinChart')}
|
||||
>
|
||||
{uiSettings.stickyChart ? <Pin size={16} /> : <PinOff size={16} />}
|
||||
</Button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SimulationChart
|
||||
@@ -237,8 +239,9 @@ const MedPlanAssistant = () => {
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ import { Card, CardContent } from './ui/card';
|
||||
import { Badge } from './ui/badge';
|
||||
import { FormTimeInput } from './ui/form-time-input';
|
||||
import { FormNumericInput } from './ui/form-numeric-input';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
import { IconButtonWithTooltip } from './ui/icon-button-with-tooltip';
|
||||
import CollapsibleCardHeader from './ui/collapsible-card-header';
|
||||
import { Plus, Copy, Trash2, ArrowDownAZ, TrendingUp, TrendingDown } from 'lucide-react';
|
||||
import type { DayGroup } from '../constants/defaults';
|
||||
@@ -116,25 +117,23 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
||||
rightSection={
|
||||
<>
|
||||
{canAddDay && (
|
||||
<Button
|
||||
<IconButtonWithTooltip
|
||||
onClick={() => onAddDay(day.id)}
|
||||
icon={<Copy className="h-4 w-4" />}
|
||||
tooltip={t('cloneDay')}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
title={t('cloneDay')}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
/>
|
||||
)}
|
||||
{!day.isTemplate && (
|
||||
<Button
|
||||
<IconButtonWithTooltip
|
||||
onClick={() => onRemoveDay(day.id)}
|
||||
icon={<Trash2 className="h-4 w-4" />}
|
||||
tooltip={t('removeDay')}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
||||
title={t('removeDay')}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@@ -143,58 +142,54 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
||||
{t('day')} {dayIndex + 1}
|
||||
</Badge>
|
||||
{!day.isTemplate && doseCountDiff !== 0 ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center cursor-help focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded-md"
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center cursor-help focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded-md"
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${doseCountDiff > 0 ? 'bg-blue-50' : 'bg-orange-50'}`}
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${doseCountDiff > 0 ? 'bg-blue-50' : 'bg-orange-50'}`}
|
||||
>
|
||||
{doseCountDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
||||
{day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
|
||||
</Badge>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
{doseCountDiff > 0 ? '+' : ''}{doseCountDiff} {Math.abs(doseCountDiff) === 1 ? t('dose') : t('doses')} {t('comparedToRegularPlan')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{doseCountDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
||||
{day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
|
||||
</Badge>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
{doseCountDiff > 0 ? '+' : ''}{doseCountDiff} {Math.abs(doseCountDiff) === 1 ? t('dose') : t('doses')} {t('comparedToRegularPlan')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
|
||||
</Badge>
|
||||
)}
|
||||
{!day.isTemplate && Math.abs(totalMgDiff) > 0.1 ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center cursor-help focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded-md"
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center cursor-help focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded-md"
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${totalMgDiff > 0 ? 'bg-blue-50' : 'bg-orange-50'}`}
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${totalMgDiff > 0 ? 'bg-blue-50' : 'bg-orange-50'}`}
|
||||
>
|
||||
{totalMgDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
||||
{day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
|
||||
</Badge>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
{totalMgDiff > 0 ? '+' : ''}{totalMgDiff.toFixed(1)} mg {t('comparedToRegularPlan')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{totalMgDiff > 0 ? <TrendingUp className="h-3 w-3 inline mr-1" /> : <TrendingDown className="h-3 w-3 inline mr-1" />}
|
||||
{day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
|
||||
</Badge>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
{totalMgDiff > 0 ? '+' : ''}{totalMgDiff.toFixed(1)} mg {t('comparedToRegularPlan')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
|
||||
@@ -207,31 +202,29 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
||||
<div className="grid grid-cols-[120px_1fr_auto] gap-3 text-sm font-medium text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{t('time')}</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className={
|
||||
isDaySorted(day)
|
||||
? "h-6 w-6 p-0 text-muted-foreground hover:text-muted-foreground cursor-default"
|
||||
: "h-6 w-6 p-0 text-primary hover:text-primary hover:bg-primary/10"
|
||||
}
|
||||
onClick={() => !isDaySorted(day) && onSortDoses(day.id)}
|
||||
disabled={isDaySorted(day)}
|
||||
>
|
||||
<ArrowDownAZ className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
{isDaySorted(day) ? t('sortByTimeSorted') : t('sortByTimeNeeded')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className={
|
||||
isDaySorted(day)
|
||||
? "h-6 w-6 p-0 text-muted-foreground hover:text-muted-foreground cursor-default"
|
||||
: "h-6 w-6 p-0 text-primary hover:text-primary hover:bg-primary/10"
|
||||
}
|
||||
onClick={() => !isDaySorted(day) && onSortDoses(day.id)}
|
||||
disabled={isDaySorted(day)}
|
||||
>
|
||||
<ArrowDownAZ className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
{isDaySorted(day) ? t('sortByTimeSorted') : t('sortByTimeNeeded')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div>{t('ldx')} (mg)</div>
|
||||
<div></div>
|
||||
@@ -267,16 +260,15 @@ const DaySchedule: React.FC<DayScheduleProps> = ({
|
||||
errorMessage={t('errorNumberRequired')}
|
||||
warningMessage={t('warningZeroDose')}
|
||||
/>
|
||||
<Button
|
||||
<IconButtonWithTooltip
|
||||
onClick={() => onRemoveDose(day.id, dose.id)}
|
||||
icon={<Trash2 className="h-4 w-4" />}
|
||||
tooltip={t('removeDose')}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
disabled={day.isTemplate && day.doses.length === 1}
|
||||
className="h-9 w-9 p-0"
|
||||
title={t('removeDose')}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Label } from './ui/label';
|
||||
import { Switch } from './ui/switch';
|
||||
import { Separator } from './ui/separator';
|
||||
import { Button } from './ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||
import { FormNumericInput } from './ui/form-numeric-input';
|
||||
import CollapsibleCardHeader from './ui/collapsible-card-header';
|
||||
@@ -269,7 +269,6 @@ const Settings = ({
|
||||
<Label htmlFor="showTemplateDay" className="font-medium">
|
||||
{t('showTemplateDayInChart')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'showTemplateDay'} onOpenChange={(open) => setOpenTooltipId(open ? 'showTemplateDay' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -286,7 +285,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showTemplateDayTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -300,7 +298,6 @@ const Settings = ({
|
||||
<Label htmlFor="showDayReferenceLines" className="font-medium">
|
||||
{t('showDayReferenceLines')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'showDayReferenceLines'} onOpenChange={(open) => setOpenTooltipId(open ? 'showDayReferenceLines' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -317,7 +314,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showDayReferenceLinesTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -331,7 +327,6 @@ const Settings = ({
|
||||
<Label htmlFor="showTherapeuticRange" className="font-medium">
|
||||
{t('showTherapeuticRangeLines')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'showTherapeuticRangeLines'} onOpenChange={(open) => setOpenTooltipId(open ? 'showTherapeuticRangeLines' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -348,7 +343,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'showTherapeuticRangeLinesTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{showTherapeuticRange && (
|
||||
<div className="ml-8 space-y-2">
|
||||
@@ -356,7 +350,6 @@ const Settings = ({
|
||||
<Label className="font-medium">
|
||||
{t('therapeuticRange')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'therapeuticRange'} onOpenChange={(open) => setOpenTooltipId(open ? 'therapeuticRange' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -373,7 +366,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'therapeuticRangeTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<FormNumericInput
|
||||
@@ -406,7 +398,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('displayedDays')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'displayedDays'} onOpenChange={(open) => setOpenTooltipId(open ? 'displayedDays' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -423,7 +414,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'displayedDaysTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={displayedDays}
|
||||
@@ -440,7 +430,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('yAxisRange')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'yAxisRange'} onOpenChange={(open) => setOpenTooltipId(open ? 'yAxisRange' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -457,7 +446,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'yAxisRangeTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<FormNumericInput
|
||||
@@ -487,7 +475,6 @@ const Settings = ({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium">{t('xAxisTimeFormat')}</Label>
|
||||
<TooltipProvider>
|
||||
<Select
|
||||
value={showDayTimeOnXAxis}
|
||||
onValueChange={value => onUpdateUiSetting('showDayTimeOnXAxis', value)}
|
||||
@@ -528,7 +515,6 @@ const Settings = ({
|
||||
</Tooltip>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</CardContent>
|
||||
)}
|
||||
@@ -546,7 +532,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('simulationDuration')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'simulationDuration'} onOpenChange={(open) => setOpenTooltipId(open ? 'simulationDuration' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -563,7 +548,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'simulationDurationTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={simulationDays}
|
||||
@@ -598,7 +582,6 @@ const Settings = ({
|
||||
<Label htmlFor="steadyStateDaysEnabled" className="font-medium">
|
||||
{t('steadyStateDays')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'steadyStateDays'} onOpenChange={(open) => setOpenTooltipId(open ? 'steadyStateDays' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -615,7 +598,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'steadyStateDaysTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{steadyStateDaysEnabled && (
|
||||
<div className="ml-8 space-y-2">
|
||||
@@ -648,7 +630,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('halfLife')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'halfLife'} onOpenChange={(open) => setOpenTooltipId(open ? 'halfLife' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -665,7 +646,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'halfLifeTooltip', defaultsForT))}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.damph.halfLife}
|
||||
@@ -688,7 +668,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('conversionHalfLife')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'conversionHalfLife'} onOpenChange={(open) => setOpenTooltipId(open ? 'conversionHalfLife' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -705,7 +684,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'conversionHalfLifeTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.ldx.halfLife}
|
||||
@@ -724,7 +702,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('absorptionHalfLife')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'absorptionHalfLife'} onOpenChange={(open) => setOpenTooltipId(open ? 'absorptionHalfLife' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -741,7 +718,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'absorptionHalfLifeTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.ldx.absorptionHalfLife}
|
||||
@@ -784,7 +760,6 @@ const Settings = ({
|
||||
<Label htmlFor="weightBasedVdEnabled" className="font-medium">
|
||||
{t('weightBasedVdScaling')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'weightBasedVd'} onOpenChange={(open) => setOpenTooltipId(open ? 'weightBasedVd' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -801,13 +776,11 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'weightBasedVdTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{pkParams.advanced.weightBasedVd.enabled && (
|
||||
<div className="ml-8 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-sm font-medium">{t('bodyWeight')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'bodyWeight'} onOpenChange={(open) => setOpenTooltipId(open ? 'bodyWeight' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -824,7 +797,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'bodyWeightTooltip', defaultsForT))}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.advanced.weightBasedVd.bodyWeight}
|
||||
@@ -852,7 +824,6 @@ const Settings = ({
|
||||
<Label htmlFor="foodEffectEnabled" className="font-medium">
|
||||
{t('foodEffectEnabled')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'foodEffect'} onOpenChange={(open) => setOpenTooltipId(open ? 'foodEffect' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -869,13 +840,11 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'foodEffectTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{pkParams.advanced.foodEffect.enabled && (
|
||||
<div className="ml-8 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-sm font-medium">{t('tmaxDelay')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'tmaxDelay'} onOpenChange={(open) => setOpenTooltipId(open ? 'tmaxDelay' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -892,7 +861,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'tmaxDelayTooltip', defaultsForT))}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.advanced.foodEffect.tmaxDelay}
|
||||
@@ -920,7 +888,6 @@ const Settings = ({
|
||||
<Label htmlFor="urinePHEnabled" className="font-medium">
|
||||
{t('urinePHTendency')}
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'urinePH'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePH' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -937,13 +904,11 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{pkParams.advanced.urinePh.enabled && (
|
||||
<div className="ml-8 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-sm font-medium">{t('urinePHValue')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'urinePHValue'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePHValue' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -960,7 +925,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHValueTooltip', defaultsForT)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.advanced.urinePh.phTendency}
|
||||
@@ -981,7 +945,6 @@ const Settings = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="font-medium">{t('oralBioavailability')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={openTooltipId === 'oralBioavailability'} onOpenChange={(open) => setOpenTooltipId(open ? 'oralBioavailability' : null)}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -998,7 +961,6 @@ const Settings = ({
|
||||
<p className="text-xs max-w-xs">{renderTooltipWithLinks(tWithDefaults(t, 'oralBioavailabilityTooltip', defaultsForT))}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<FormNumericInput
|
||||
value={pkParams.advanced.fOral}
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
Tooltip as UiTooltip,
|
||||
TooltipTrigger as UiTooltipTrigger,
|
||||
TooltipContent as UiTooltipContent,
|
||||
TooltipProvider as UiTooltipProvider,
|
||||
} from './ui/tooltip';
|
||||
|
||||
// Chart color scheme
|
||||
@@ -310,41 +309,39 @@ const chartDomain = React.useMemo(() => {
|
||||
? scrollableWidth
|
||||
: Math.ceil((scrollableWidth / dispDays) * simDays);
|
||||
|
||||
// Render legend with tooltips for full names (custom legend renderer)
|
||||
const renderLegend = React.useCallback((props: any) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
|
||||
return (
|
||||
<UiTooltipProvider>
|
||||
<ul className="flex flex-wrap gap-2 text-xs leading-tight">
|
||||
{payload.map((item: any) => {
|
||||
const labelInfo = seriesLabels[item.dataKey] || { display: item.value, full: item.value };
|
||||
const opacity = item.payload?.opacity ?? 1;
|
||||
<ul className="flex flex-wrap gap-2 text-xs leading-tight">
|
||||
{payload.map((item: any) => {
|
||||
const labelInfo = seriesLabels[item.dataKey] || { display: item.value, full: item.value };
|
||||
const opacity = item.payload?.opacity ?? 1;
|
||||
|
||||
return (
|
||||
<li key={item.dataKey} className="flex items-center gap-1 max-w-[140px]">
|
||||
<span
|
||||
className="inline-block w-3 h-3 rounded-sm"
|
||||
style={{ backgroundColor: item.color, opacity }}
|
||||
/>
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger asChild>
|
||||
<span
|
||||
className="px-1 py-0.5 rounded-sm bg-white text-black shadow-sm border border-muted truncate inline-block max-w-[100px]"
|
||||
title={labelInfo.full}
|
||||
>
|
||||
{labelInfo.display}
|
||||
</span>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent className="bg-white text-black shadow-md border max-w-xs">
|
||||
<span className="font-medium">{labelInfo.full}</span>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</UiTooltipProvider>
|
||||
return (
|
||||
<li key={item.dataKey} className="flex items-center gap-1 max-w-[140px]">
|
||||
<span
|
||||
className="inline-block w-3 h-3 rounded-sm"
|
||||
style={{ backgroundColor: item.color, opacity }}
|
||||
/>
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger asChild>
|
||||
<span
|
||||
className="px-1 py-0.5 rounded-sm bg-white text-black shadow-sm border border-muted truncate inline-block max-w-[100px]"
|
||||
>
|
||||
{labelInfo.display}
|
||||
</span>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent className="bg-white text-black shadow-md border max-w-xs">
|
||||
<span className="font-medium">{labelInfo.full}</span>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}, [seriesLabels]);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import * as React from "react"
|
||||
import { Minus, Plus, X } from "lucide-react"
|
||||
import { Button } from "./button"
|
||||
import { IconButtonWithTooltip } from "./icon-button-with-tooltip"
|
||||
import { Input } from "./input"
|
||||
import { cn } from "../../lib/utils"
|
||||
import { useTranslation } from "react-i18next"
|
||||
@@ -199,8 +200,10 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
{clearButton && allowEmpty && (
|
||||
<Button
|
||||
<IconButtonWithTooltip
|
||||
type="button"
|
||||
icon={<X className="h-4 w-4" />}
|
||||
tooltip={t('buttonClear')}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className={cn(
|
||||
@@ -210,10 +213,7 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
||||
)}
|
||||
onClick={() => onChange('')}
|
||||
tabIndex={-1}
|
||||
title={ t('buttonClear') }
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{unit && <span className="text-sm text-muted-foreground whitespace-nowrap">{unit}</span>}
|
||||
|
||||
49
src/components/ui/icon-button-with-tooltip.tsx
Normal file
49
src/components/ui/icon-button-with-tooltip.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* IconButtonWithTooltip
|
||||
*
|
||||
* A reusable button component that combines an icon button with a tooltip.
|
||||
* Provides consistent tooltip behavior across the app for icon-only action buttons.
|
||||
*
|
||||
* @author Andreas Weyer
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, ButtonProps } from './button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
|
||||
|
||||
interface IconButtonWithTooltipProps extends Omit<ButtonProps, 'children'> {
|
||||
/** The icon element to display in the button */
|
||||
icon: React.ReactNode;
|
||||
/** The tooltip text to show on hover */
|
||||
tooltip: string;
|
||||
/** Optional side for tooltip positioning */
|
||||
tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
|
||||
}
|
||||
|
||||
export const IconButtonWithTooltip: React.FC<IconButtonWithTooltipProps> = ({
|
||||
icon,
|
||||
tooltip,
|
||||
tooltipSide = 'top',
|
||||
disabled,
|
||||
...buttonProps
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
aria-label={tooltip}
|
||||
{...buttonProps}
|
||||
>
|
||||
{icon}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{!disabled && (
|
||||
<TooltipContent side={tooltipSide}>
|
||||
<p className="text-xs">{tooltip}</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user