Link contextual guidance to Docs

This commit is contained in:
2026-08-03 07:33:57 +02:00
parent 70fc6da811
commit b823a22b9b
13 changed files with 191 additions and 4 deletions
+6 -1
View File
@@ -1,6 +1,8 @@
import { AlertTriangle, Info } from "lucide-react";
import type { ReactNode } from "react";
import AdvancedOptionsPanel from "./AdvancedOptionsPanel";
import DocumentationHelpLink from "./help/DocumentationHelpLink";
import type { DocumentationHelpReference } from "./help/documentationHelp";
export type ActionBlockerReason = {
summary: ReactNode;
@@ -23,6 +25,7 @@ type ActionBlockerHintProps = {
tone?: "info" | "warning" | "danger";
className?: string;
labels?: ActionBlockerLabels;
documentation?: DocumentationHelpReference;
};
function joinClasses(...classes: Array<string | undefined | false>) {
@@ -33,7 +36,8 @@ export default function ActionBlockerHint({
reason,
tone = "warning",
className = "",
labels = {}
labels = {},
documentation
}: ActionBlockerHintProps) {
const Icon = tone === "info" ? Info : AlertTriangle;
const hasActionRows = Boolean(reason.requiredAction || reason.actor || reason.target);
@@ -71,6 +75,7 @@ export default function ActionBlockerHint({
<div>{reason.technicalDetails}</div>
</AdvancedOptionsPanel>
)}
{documentation && <DocumentationHelpLink reference={documentation} />}
</div>
</section>
);
+3 -2
View File
@@ -1,14 +1,15 @@
import type { ReactNode } from "react";
import FieldLabel from "./help/FieldLabel";
import type { DocumentationHelpReference } from "./help/documentationHelp";
import { helpForFieldLabel } from "../utils/fieldHelp";
import { usePlatformLanguage } from "../i18n/LanguageContext";
export default function FormField({ label, help, children }: { label: ReactNode; help?: ReactNode; children: ReactNode }) {
export default function FormField({ label, help, documentation, children }: { label: ReactNode; help?: ReactNode; documentation?: DocumentationHelpReference; children: ReactNode }) {
const { translateText } = usePlatformLanguage();
const renderedLabel = typeof label === "string" ? translateText(label) : label;
return (
<label className="form-field">
<FieldLabel className="form-label" help={help ?? helpForFieldLabel(label)}>{renderedLabel}</FieldLabel>
<FieldLabel className="form-label" help={help ?? helpForFieldLabel(label)} documentation={documentation}>{renderedLabel}</FieldLabel>
{children}
</label>
);
@@ -0,0 +1,63 @@
import { BookOpen } from "lucide-react";
import { createContext, useContext, type MouseEvent, type ReactNode } from "react";
import { usePlatformLanguage } from "../../i18n/LanguageContext";
import {
HOSTED_DOCUMENTATION_URL,
documentationHelpHref,
type DocumentationHelpReference
} from "./documentationHelp";
export { documentationHelpHref } from "./documentationHelp";
export type { DocumentationHelpReference } from "./documentationHelp";
const DocumentationHelpAvailabilityContext = createContext(false);
export function DocumentationHelpProvider({
localDocsAvailable,
children
}: {
localDocsAvailable: boolean;
children: ReactNode;
}) {
return (
<DocumentationHelpAvailabilityContext.Provider value={localDocsAvailable}>
{children}
</DocumentationHelpAvailabilityContext.Provider>
);
}
export default function DocumentationHelpLink({
reference,
label = "i18n:govoplan-core.open_user_documentation.084af515",
className = ""
}: {
reference: DocumentationHelpReference;
label?: string;
className?: string;
}) {
const { translateText } = usePlatformLanguage();
const localDocsAvailable = useContext(DocumentationHelpAvailabilityContext);
const href = documentationHelpHref(
reference,
localDocsAvailable ? "/docs" : HOSTED_DOCUMENTATION_URL
);
if (!href) return null;
const translatedLabel = translateText(label);
const stopLabelActivation = (event: MouseEvent<HTMLAnchorElement>) => event.stopPropagation();
return (
<a
className={["documentation-help-link", className].filter(Boolean).join(" ")}
href={href}
aria-label={translatedLabel}
title={translatedLabel}
target={localDocsAvailable ? undefined : "_blank"}
rel={localDocsAvailable ? undefined : "noreferrer"}
onClick={stopLabelActivation}
onMouseDown={stopLabelActivation}
>
<BookOpen size={13} aria-hidden="true" />
</a>
);
}
+5 -1
View File
@@ -1,17 +1,21 @@
import type { ReactNode } from "react";
import DocumentationHelpLink from "./DocumentationHelpLink";
import type { DocumentationHelpReference } from "./documentationHelp";
import InlineHelp from "./InlineHelp";
type FieldLabelProps = {
children: ReactNode;
help?: ReactNode;
documentation?: DocumentationHelpReference;
className?: string;
};
export default function FieldLabel({ children, help, className = "" }: FieldLabelProps) {
export default function FieldLabel({ children, help, documentation, className = "" }: FieldLabelProps) {
return (
<span className={`field-label ${className}`.trim()}>
<span className="field-label-text">{children}</span>
{help && <InlineHelp>{help}</InlineHelp>}
{documentation && <DocumentationHelpLink reference={documentation} />}
</span>
);
}
@@ -0,0 +1,25 @@
export const HOSTED_DOCUMENTATION_URL = "https://govoplan.add-ideas.de/";
export type DocumentationHelpReference = {
topicId?: string;
contextId?: string;
documentationType?: "user" | "admin";
anchorId?: string;
};
export function documentationHelpHref(
reference: DocumentationHelpReference,
baseUrl = "/docs"
): string | null {
const topicId = reference.topicId?.trim();
const contextId = reference.contextId?.trim();
if (!topicId && !contextId) return null;
const params = new URLSearchParams({
type: reference.documentationType === "admin" ? "admin" : "user"
});
if (topicId) params.set("topic", topicId);
else if (contextId) params.set("context", contextId);
const anchorId = reference.anchorId?.trim();
return `${baseUrl}?${params.toString()}${anchorId ? `#${encodeURIComponent(anchorId)}` : ""}`;
}