Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 46 additions & 12 deletions frontend/src/components/BasicDashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import SourceLink from './SourceLink';
import InfoTooltip from './InfoTooltip';

function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinue, loading }) {
const groups = metadata?.groups ?? [];
Expand Down Expand Up @@ -30,19 +31,36 @@ function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinu
<span className="zus-brand-primary">Symulator</span>
<span className="zus-brand-secondary">Emerytalny</span>
</div>
<p>Poznaj swoje prognozy w Symulatorze Emerytalnym</p>
<InfoTooltip
label="O symulatorze"
ariaLabel="Poznaj swoje prognozy w Symulatorze Emerytalnym"
>
Poznaj swoje prognozy w Symulatorze Emerytalnym
</InfoTooltip>
</header>

<div className="zus-dashboard__content">
<div className="zus-panel zus-panel--calculator">
<h2>Policz wysokość świadczenia</h2>
<p className="zus-panel__lead">
<InfoTooltip
label="Dlaczego to ważne?"
ariaLabel="Co oznacza pole oczekiwanej emerytury?"
>
Podaj oczekiwaną kwotę emerytury, aby porównać swoje prognozy z wynikami innych grup
świadczeniobiorców.
</p>
</InfoTooltip>

<div className="zus-field">
<label htmlFor="expected-pension">Oczekiwana emerytura (PLN)</label>
<label htmlFor="expected-pension" className="zus-field__label">
Oczekiwana emerytura (PLN)
<InfoTooltip
label="Jaką kwotę wpisać?"
ariaLabel="Jaką kwotę wpisać w pole oczekiwanej emerytury?"
>
Wpisz miesięczną kwotę brutto, aby w kolejnym kroku porównać ją z indywidualną prognozą,
wykresami akumulacji składek oraz średnimi dla innych grup emerytów.
</InfoTooltip>
</label>
<input
id="expected-pension"
type="number"
Expand All @@ -52,7 +70,7 @@ function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinu
disabled={loading}
aria-describedby={expectedPensionHintId}
/>
<small id={expectedPensionHintId} className="zus-field__hint">
<small id={expectedPensionHintId} className="visually-hidden">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Tooltip Nesting Violates Semantics

The InfoTooltip button is incorrectly nested within the "Oczekiwana emerytura" input's <label>, violating HTML semantics and potentially causing unpredictable behavior. This tooltip also duplicates the input's aria-describedby hint, causing screen readers to announce the same information twice and creating a redundant user experience.

Fix in Cursor Fix in Web

Wpisz miesięczną kwotę brutto, aby w kolejnym kroku porównać ją z indywidualną prognozą,
wykresami akumulacji składek oraz średnimi dla innych grup emerytów.
</small>
Expand All @@ -62,12 +80,15 @@ function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinu
Przejdź do symulacji
</button>

<p className="zus-panel__hint">
<InfoTooltip
label="Co dalej?"
ariaLabel="Co będzie potrzebne w kolejnym kroku?"
>
W kolejnym kroku poprosimy Cię o podanie kilku informacji o przebiegu kariery, aby na ich
podstawie wyliczyć indywidualną prognozę. Po zakończeniu symulacji zobaczysz szczegółowe
porównanie oczekiwanej i prognozowanej emerytury, wykres gromadzenia środków oraz wpływ
różnych scenariuszy przejścia na świadczenie.
</p>
</InfoTooltip>
</div>

<aside className="zus-panel zus-panel--support" aria-label="Porównanie z grupami emerytów">
Expand All @@ -76,10 +97,17 @@ function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinu
{!loading && (
<ul className="zus-support-list">
{groups.map((group) => (
<li key={group.label} className="zus-support-item" title={group.description}>
<li key={group.label} className="zus-support-item">
<div className="zus-support-item__content">
<span className="zus-support-item__title">{group.label}</span>
<p className="zus-support-item__desc">{group.description}</p>
<div className="zus-support-item__actions">
<InfoTooltip
label="Opis"
ariaLabel={`Opis grupy ${group.label}`}
>
{group.description}
</InfoTooltip>
</div>
</div>
<div className="zus-support-item__meta">
<div className="zus-figure">
Expand Down Expand Up @@ -107,7 +135,9 @@ function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinu
<div className="zus-forecast" aria-label="Prognoza salda Funduszu Emerytalnego">
<div className="zus-forecast__header">
<strong>Prognoza salda Funduszu Emerytalnego</strong>
<span>Na podstawie oficjalnej prognozy ZUS (kwoty w mln zł zdyskontowane na 2021 r.).</span>
<InfoTooltip label="Założenia" ariaLabel="Założenia prognozy">
Na podstawie oficjalnej prognozy ZUS (kwoty w mln zł zdyskontowane na 2021 r.).
</InfoTooltip>
</div>
<div className="zus-forecast__table-wrapper">
<table className="zus-forecast__table">
Expand Down Expand Up @@ -153,8 +183,12 @@ function BasicDashboard({ metadata, expectedPension, onExpectedChange, onContinu
)}
{metadata?.fact && (
<div className="zus-footer-fact">
<strong>Czy wiesz, że...</strong>
<p>{metadata.fact}</p>
<div className="zus-footer-fact__trigger">
<strong>Czy wiesz, że...</strong>
<InfoTooltip label="Ciekawostka" ariaLabel="Ciekawostka ZUS">
{metadata.fact}
</InfoTooltip>
</div>
</div>
)}
</footer>
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/components/InfoTooltip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useId } from 'react';
import PropTypes from 'prop-types';

function InfoTooltip({ label, ariaLabel, children }) {
const tooltipId = useId();
const buttonProps = {
type: 'button',
className: 'zus-tooltip__trigger',
'aria-describedby': tooltipId
};

if (ariaLabel) {
buttonProps['aria-label'] = ariaLabel;
}

return (
<span className="zus-tooltip">
<button {...buttonProps}>{label}</button>
<span id={tooltipId} role="tooltip" className="zus-tooltip__bubble">
{children}
</span>
</span>
);
}

InfoTooltip.propTypes = {
label: PropTypes.string.isRequired,
ariaLabel: PropTypes.string,
children: PropTypes.node.isRequired
};

export default InfoTooltip;
119 changes: 119 additions & 0 deletions frontend/src/styles/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -1736,3 +1736,122 @@ button:disabled {
grid-column: 1 / -1;
}
}

.zus-inline-help {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.95rem;
color: var(--zus-gray-600);
}

.zus-inline-help__label {
font-weight: 600;
}

.zus-tooltip {
position: relative;
display: inline-flex;
align-items: baseline;
}

.zus-tooltip__trigger {
padding: 0;
border: none;
background: none;
color: var(--zus-blue);
font: inherit;
line-height: 1.2;
cursor: help;
text-decoration: underline dotted;
text-decoration-thickness: 0.08em;
text-underline-offset: 0.25em;
border-radius: 0.35rem;
transition: color 0.2s ease, text-decoration-color 0.2s ease;
}

.zus-tooltip__trigger:hover,
.zus-tooltip__trigger:focus-visible {
color: var(--zus-navy);
text-decoration-color: currentColor;
}

.zus-tooltip__trigger:focus-visible {
outline: 2px solid var(--zus-blue);
outline-offset: 2px;
}

.zus-tooltip__bubble {
position: absolute;
bottom: calc(100% + 0.6rem);
left: 50%;
transform: translate(-50%, 0.5rem);
background: var(--zus-black);
color: #fff;
padding: 0.85rem 1rem;
border-radius: 0.85rem;
width: clamp(220px, 60vw, 320px);
font-size: 0.85rem;
line-height: 1.45;
box-shadow: 0 18px 36px rgba(0, 59, 101, 0.4);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 24;
}

.zus-tooltip__bubble::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border-width: 8px;
border-style: solid;
border-color: var(--zus-black) transparent transparent transparent;
}

.zus-tooltip:hover .zus-tooltip__bubble,
.zus-tooltip:focus-within .zus-tooltip__bubble {
opacity: 1;
transform: translate(-50%, 0);
pointer-events: auto;
}

.zus-field__label {
display: flex;
align-items: center;
gap: 0.5rem;
}

.zus-support-item__actions {
display: flex;
align-items: center;
gap: 0.35rem;
}

.zus-forecast__header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 1rem;
}

.zus-footer-fact__trigger {
display: flex;
align-items: center;
gap: 0.5rem;
}

@media (max-width: 640px) {
.zus-tooltip__bubble {
left: auto;
right: 0;
transform: translate(0, 0.5rem);
}

.zus-tooltip:hover .zus-tooltip__bubble,
.zus-tooltip:focus-within .zus-tooltip__bubble {
transform: translate(0, 0);
}
}