mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
Add discreet LearnMoreLink pointer component
A small, calm anchor that links a concept out to its public /learn guide. Fail-closed: the href is registry-backed (a closed-set value, never user input) and an unmapped concept renders nothing. Plain anchor, no markdown. Adds a common.learnMore label across all locales.
This commit is contained in:
1 parent
fe6d4c10df
commit
6ed5b55f3d
7 files changed
+69
-6
No files matched your search
+2
-1
@@ -34,7 +34,8 @@
|
||||
"retry": "Erneut versuchen",
|
||||
"reportIssue": "Fehler melden",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"hidePassword": "Passwort verbergen"
|
||||
"hidePassword": "Passwort verbergen",
|
||||
"learnMore": "Mehr erfahren"
|
||||
},
|
||||
"doctorReport": {
|
||||
"title": "Gesundheitsbericht",
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
"retry": "Retry",
|
||||
"reportIssue": "Report issue",
|
||||
"showPassword": "Show password",
|
||||
"hidePassword": "Hide password"
|
||||
"hidePassword": "Hide password",
|
||||
"learnMore": "Learn more"
|
||||
},
|
||||
"doctorReport": {
|
||||
"title": "Health Report",
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
"retry": "Reintentar",
|
||||
"reportIssue": "Reportar problema",
|
||||
"showPassword": "Mostrar contraseña",
|
||||
"hidePassword": "Ocultar contraseña"
|
||||
"hidePassword": "Ocultar contraseña",
|
||||
"learnMore": "Más información"
|
||||
},
|
||||
"doctorReport": {
|
||||
"title": "Informe de salud",
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
"retry": "Réessayer",
|
||||
"reportIssue": "Signaler un problème",
|
||||
"showPassword": "Afficher le mot de passe",
|
||||
"hidePassword": "Masquer le mot de passe"
|
||||
"hidePassword": "Masquer le mot de passe",
|
||||
"learnMore": "En savoir plus"
|
||||
},
|
||||
"doctorReport": {
|
||||
"title": "Rapport de santé",
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
"retry": "Riprova",
|
||||
"reportIssue": "Segnala un problema",
|
||||
"showPassword": "Mostra password",
|
||||
"hidePassword": "Nascondi password"
|
||||
"hidePassword": "Nascondi password",
|
||||
"learnMore": "Scopri di più"
|
||||
},
|
||||
"doctorReport": {
|
||||
"title": "Rapporto sanitario",
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
"retry": "Spróbuj ponownie",
|
||||
"reportIssue": "Zgłoś problem",
|
||||
"showPassword": "Pokaż hasło",
|
||||
"hidePassword": "Ukryj hasło"
|
||||
"hidePassword": "Ukryj hasło",
|
||||
"learnMore": "Dowiedz się więcej"
|
||||
},
|
||||
"doctorReport": {
|
||||
"title": "Raport zdrowotny",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { ExternalLink } from "lucide-react";
|
||||
|
||||
import { learnLinkForMetric } from "@/lib/learn-links";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* v1.21.0 — discreet "Learn more" pointer to a public /learn guide.
|
||||
*
|
||||
* One small, calm anchor (no new colour, no card chrome) that links a concept
|
||||
* out to its plain-language guide. Fail-closed by construction:
|
||||
*
|
||||
* - the href is resolved through `learnLinkForMetric` — a closed-set,
|
||||
* registry-backed URL, never user/model content — so there is no XSS
|
||||
* surface and no invented link;
|
||||
* - an unmapped concept resolves to `null` and the component renders
|
||||
* nothing, so a surface that wires a concept with no guide simply shows
|
||||
* no pointer rather than a broken one.
|
||||
*
|
||||
* It is a literal `<a>` (no markdown library): `target="_blank"` with
|
||||
* `rel="noopener noreferrer"`, the guide title as the accessible label.
|
||||
*/
|
||||
export interface LearnMoreLinkProps {
|
||||
/**
|
||||
* A `LearnConcept` key (or any metric id) to resolve. Anything unmapped
|
||||
* renders nothing.
|
||||
*/
|
||||
concept: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function LearnMoreLink({ concept, className }: LearnMoreLinkProps) {
|
||||
const { t } = useTranslations();
|
||||
const guide = learnLinkForMetric(concept);
|
||||
if (guide == null) return null;
|
||||
|
||||
const label = t("common.learnMore");
|
||||
|
||||
return (
|
||||
<a
|
||||
href={guide.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-slot="learn-more-link"
|
||||
aria-label={`${label}: ${guide.title}`}
|
||||
className={cn(
|
||||
"text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs underline-offset-2 transition-colors hover:underline",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
<ExternalLink className="h-3 w-3 shrink-0" aria-hidden="true" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user