This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/website/components/compliance/GlossaryTooltip.tsx
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

166 lines
4.6 KiB
TypeScript

'use client'
/**
* GlossaryTooltip Component
*
* Displays a term with a hover tooltip that explains the compliance concept.
* Supports bilingual content (DE/EN) from the i18n system.
*/
import { useState, useRef, useEffect } from 'react'
import { getTerm, getDescription, Language } from '@/lib/compliance-i18n'
interface GlossaryTooltipProps {
termKey: string
lang?: Language
children?: React.ReactNode
className?: string
showIcon?: boolean
}
export default function GlossaryTooltip({
termKey,
lang = 'de',
children,
className = '',
showIcon = true,
}: GlossaryTooltipProps) {
const [isVisible, setIsVisible] = useState(false)
const [position, setPosition] = useState<'top' | 'bottom'>('top')
const triggerRef = useRef<HTMLSpanElement>(null)
const tooltipRef = useRef<HTMLDivElement>(null)
const term = getTerm(lang, termKey)
const description = getDescription(lang, termKey)
useEffect(() => {
if (isVisible && triggerRef.current && tooltipRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect()
const tooltipHeight = tooltipRef.current.offsetHeight
const spaceAbove = triggerRect.top
const spaceBelow = window.innerHeight - triggerRect.bottom
// Position tooltip where there's more space
if (spaceAbove < tooltipHeight + 10 && spaceBelow > spaceAbove) {
setPosition('bottom')
} else {
setPosition('top')
}
}
}, [isVisible])
if (!description) {
// No description available, just render the term
return <span className={className}>{children || term}</span>
}
return (
<span
ref={triggerRef}
className={`relative inline-flex items-center gap-1 cursor-help ${className}`}
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
onFocus={() => setIsVisible(true)}
onBlur={() => setIsVisible(false)}
tabIndex={0}
>
{children || term}
{showIcon && (
<svg
className="w-3.5 h-3.5 text-slate-400 hover:text-slate-600 transition-colors"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
)}
{/* Tooltip */}
{isVisible && (
<div
ref={tooltipRef}
className={`
absolute z-50 w-64 p-3 text-sm
bg-slate-900 text-white rounded-lg shadow-xl
transition-opacity duration-150
${position === 'top' ? 'bottom-full mb-2' : 'top-full mt-2'}
left-1/2 -translate-x-1/2
`}
role="tooltip"
>
{/* Arrow */}
<div
className={`
absolute left-1/2 -translate-x-1/2 w-0 h-0
border-l-8 border-r-8 border-transparent
${position === 'top'
? 'top-full border-t-8 border-t-slate-900'
: 'bottom-full border-b-8 border-b-slate-900'
}
`}
/>
{/* Content */}
<div className="font-semibold text-white mb-1">{term}</div>
<div className="text-slate-300 text-xs leading-relaxed">{description}</div>
</div>
)}
</span>
)
}
/**
* InfoIcon Component
*
* A standalone info icon that shows a tooltip on hover.
*/
interface InfoIconProps {
text: string
className?: string
}
export function InfoIcon({ text, className = '' }: InfoIconProps) {
const [isVisible, setIsVisible] = useState(false)
return (
<span
className={`relative inline-flex items-center cursor-help ${className}`}
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
>
<svg
className="w-4 h-4 text-slate-400 hover:text-slate-600 transition-colors"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{isVisible && (
<div
className="
absolute z-50 w-56 p-2 text-xs
bg-slate-800 text-slate-200 rounded-lg shadow-lg
bottom-full mb-2 left-1/2 -translate-x-1/2
"
>
{text}
<div className="absolute left-1/2 -translate-x-1/2 top-full w-0 h-0 border-l-6 border-r-6 border-transparent border-t-6 border-t-slate-800" />
</div>
)}
</span>
)
}