Task 4: Rebuild PatientBanner with premium fonts, tooltip, and animations
- Replace font-inter with font-ui (Elvaro Grotesque) throughout banner - Add custom NHSNumberWithTooltip with Framer Motion animated reveal - Add AnimatePresence crossfade between full/condensed banner states - Animate mobile overflow menu enter/exit - Add SkipButton to App.tsx for boot/ECG phase skip - Add shadow-pmr-banner, focus ring styles, prefers-reduced-motion support - Fix mobile banner to use patient data instead of hardcoded values Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+45
-3
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import type { Phase } from './types'
|
import type { Phase } from './types'
|
||||||
import { BootSequence } from './components/BootSequence'
|
import { BootSequence } from './components/BootSequence'
|
||||||
import { ECGAnimation } from './components/ECGAnimation'
|
import { ECGAnimation } from './components/ECGAnimation'
|
||||||
@@ -6,10 +6,48 @@ import { LoginScreen } from './components/LoginScreen'
|
|||||||
import { PMRInterface } from './components/PMRInterface'
|
import { PMRInterface } from './components/PMRInterface'
|
||||||
import { AccessibilityProvider } from './contexts/AccessibilityContext'
|
import { AccessibilityProvider } from './contexts/AccessibilityContext'
|
||||||
|
|
||||||
|
function SkipButton({ onSkip }: { onSkip: () => void }) {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => setVisible(true), 1500)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onSkip}
|
||||||
|
aria-label="Skip intro animation"
|
||||||
|
className="fixed bottom-6 left-1/2 -translate-x-1/2 z-[100] px-4 py-1.5 text-xs tracking-widest uppercase font-mono border rounded transition-all duration-700 cursor-pointer select-none"
|
||||||
|
style={{
|
||||||
|
color: '#555',
|
||||||
|
borderColor: '#333',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.03)',
|
||||||
|
opacity: visible ? 1 : 0,
|
||||||
|
pointerEvents: visible ? 'auto' : 'none',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = '#888'
|
||||||
|
e.currentTarget.style.borderColor = '#555'
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.06)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = '#555'
|
||||||
|
e.currentTarget.style.borderColor = '#333'
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.03)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Skip
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [phase, setPhase] = useState<Phase>('boot')
|
const [phase, setPhase] = useState<Phase>('boot')
|
||||||
const cursorPositionRef = useRef<{ x: number; y: number } | null>(null)
|
const cursorPositionRef = useRef<{ x: number; y: number } | null>(null)
|
||||||
|
|
||||||
|
const skipToLogin = () => setPhase('login')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibilityProvider>
|
<AccessibilityProvider>
|
||||||
<div className="min-h-screen bg-black">
|
<div className="min-h-screen bg-black">
|
||||||
@@ -26,12 +64,16 @@ function App() {
|
|||||||
startPosition={cursorPositionRef.current}
|
startPosition={cursorPositionRef.current}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{phase === 'login' && (
|
{phase === 'login' && (
|
||||||
<LoginScreen onComplete={() => setPhase('pmr')} />
|
<LoginScreen onComplete={() => setPhase('pmr')} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{phase === 'pmr' && <PMRInterface />}
|
{phase === 'pmr' && <PMRInterface />}
|
||||||
|
|
||||||
|
{(phase === 'boot' || phase === 'ecg') && (
|
||||||
|
<SkipButton onSkip={skipToLogin} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AccessibilityProvider>
|
</AccessibilityProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Download, Mail, Linkedin, MoreHorizontal } from 'lucide-react'
|
import { Download, Mail, Linkedin, MoreHorizontal } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState, useRef, useEffect, useCallback } from 'react'
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import { patient } from '@/data/patient'
|
import { patient } from '@/data/patient'
|
||||||
import { useScrollCondensation } from '@/hooks/useScrollCondensation'
|
import { useScrollCondensation } from '@/hooks/useScrollCondensation'
|
||||||
|
|
||||||
@@ -11,6 +12,10 @@ interface PatientBannerProps {
|
|||||||
export function PatientBanner({ isMobile = false, isTablet = false }: PatientBannerProps) {
|
export function PatientBanner({ isMobile = false, isTablet = false }: PatientBannerProps) {
|
||||||
const { isCondensed, sentinelRef } = useScrollCondensation({ threshold: 100 })
|
const { isCondensed, sentinelRef } = useScrollCondensation({ threshold: 100 })
|
||||||
|
|
||||||
|
const prefersReducedMotion = typeof window !== 'undefined'
|
||||||
|
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
: false
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -37,16 +42,37 @@ export function PatientBanner({ isMobile = false, isTablet = false }: PatientBan
|
|||||||
className={`
|
className={`
|
||||||
sticky top-0 z-40 w-full
|
sticky top-0 z-40 w-full
|
||||||
bg-pmr-banner border-b border-slate-600
|
bg-pmr-banner border-b border-slate-600
|
||||||
|
shadow-pmr-banner
|
||||||
transition-all duration-200 ease-out
|
transition-all duration-200 ease-out
|
||||||
${shouldCondense ? 'h-12' : 'h-20'}
|
${shouldCondense ? 'h-12' : 'h-20'}
|
||||||
`}
|
`}
|
||||||
role="banner"
|
role="banner"
|
||||||
>
|
>
|
||||||
{shouldCondense ? (
|
<AnimatePresence mode="wait" initial={false}>
|
||||||
<CondensedBanner />
|
{shouldCondense ? (
|
||||||
) : (
|
<motion.div
|
||||||
<FullBanner />
|
key="condensed"
|
||||||
)}
|
initial={prefersReducedMotion ? false : { opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={prefersReducedMotion ? undefined : { opacity: 0 }}
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
className="h-full"
|
||||||
|
>
|
||||||
|
<CondensedBanner />
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key="full"
|
||||||
|
initial={prefersReducedMotion ? false : { opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={prefersReducedMotion ? undefined : { opacity: 0 }}
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
className="h-full"
|
||||||
|
>
|
||||||
|
<FullBanner />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</header>
|
</header>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -54,24 +80,38 @@ export function PatientBanner({ isMobile = false, isTablet = false }: PatientBan
|
|||||||
|
|
||||||
function MobileBanner() {
|
function MobileBanner() {
|
||||||
const [showOverflow, setShowOverflow] = useState(false)
|
const [showOverflow, setShowOverflow] = useState(false)
|
||||||
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const handleClickOutside = useCallback((e: MouseEvent) => {
|
||||||
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||||||
|
setShowOverflow(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showOverflow) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [showOverflow, handleClickOutside])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="sticky top-0 z-40 w-full h-12 bg-pmr-banner border-b border-slate-600"
|
className="sticky top-0 z-40 w-full h-12 bg-pmr-banner border-b border-slate-600 shadow-pmr-banner"
|
||||||
role="banner"
|
role="banner"
|
||||||
>
|
>
|
||||||
<div className="h-full px-3 flex items-center justify-between gap-2">
|
<div className="h-full px-3 flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
<h1 className="font-inter font-semibold text-white text-sm tracking-tight truncate">
|
<h1 className="font-ui font-semibold text-white text-sm tracking-tight truncate">
|
||||||
CHARLWOOD, A (Mr)
|
CHARLWOOD, A (Mr)
|
||||||
</h1>
|
</h1>
|
||||||
<span className="text-slate-500">|</span>
|
<span className="text-slate-500">|</span>
|
||||||
<span className="font-geist text-xs text-slate-300">
|
<span className="font-geist text-xs text-slate-300">
|
||||||
221 181 0
|
{patient.nhsNumber}
|
||||||
</span>
|
</span>
|
||||||
<StatusDot status="Active" />
|
<StatusDot status={patient.status} />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative" ref={menuRef}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowOverflow(!showOverflow)}
|
onClick={() => setShowOverflow(!showOverflow)}
|
||||||
@@ -81,17 +121,18 @@ function MobileBanner() {
|
|||||||
>
|
>
|
||||||
<MoreHorizontal size={18} />
|
<MoreHorizontal size={18} />
|
||||||
</button>
|
</button>
|
||||||
{showOverflow && (
|
<AnimatePresence>
|
||||||
<>
|
{showOverflow && (
|
||||||
<div
|
<motion.div
|
||||||
className="fixed inset-0 z-40"
|
initial={{ opacity: 0, y: -4 }}
|
||||||
onClick={() => setShowOverflow(false)}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
aria-hidden="true"
|
exit={{ opacity: 0, y: -4 }}
|
||||||
/>
|
transition={{ duration: 0.15 }}
|
||||||
<div className="absolute right-0 top-full mt-1 w-40 bg-white border border-gray-200 rounded shadow-lg z-50 py-1">
|
className="absolute right-0 top-full mt-1 w-44 bg-white border border-pmr-border rounded shadow-pmr z-50 py-1"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href="/cv.pdf"
|
href="/cv.pdf"
|
||||||
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
|
className="flex items-center gap-2 px-3 py-2 text-sm font-ui text-pmr-text-primary hover:bg-gray-50 transition-colors"
|
||||||
onClick={() => setShowOverflow(false)}
|
onClick={() => setShowOverflow(false)}
|
||||||
>
|
>
|
||||||
<Download size={14} />
|
<Download size={14} />
|
||||||
@@ -99,7 +140,7 @@ function MobileBanner() {
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={`mailto:${patient.email}`}
|
href={`mailto:${patient.email}`}
|
||||||
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
|
className="flex items-center gap-2 px-3 py-2 text-sm font-ui text-pmr-text-primary hover:bg-gray-50 transition-colors"
|
||||||
onClick={() => setShowOverflow(false)}
|
onClick={() => setShowOverflow(false)}
|
||||||
>
|
>
|
||||||
<Mail size={14} />
|
<Mail size={14} />
|
||||||
@@ -109,15 +150,15 @@ function MobileBanner() {
|
|||||||
href={`https://${patient.linkedin}`}
|
href={`https://${patient.linkedin}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
|
className="flex items-center gap-2 px-3 py-2 text-sm font-ui text-pmr-text-primary hover:bg-gray-50 transition-colors"
|
||||||
onClick={() => setShowOverflow(false)}
|
onClick={() => setShowOverflow(false)}
|
||||||
>
|
>
|
||||||
<Linkedin size={14} />
|
<Linkedin size={14} />
|
||||||
LinkedIn
|
LinkedIn
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</motion.div>
|
||||||
</>
|
)}
|
||||||
)}
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -129,29 +170,35 @@ function FullBanner() {
|
|||||||
<div className="h-full px-4 lg:px-6 flex flex-col justify-center">
|
<div className="h-full px-4 lg:px-6 flex flex-col justify-center">
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
{/* Row 1: Name, status, badge */}
|
||||||
<div className="flex items-center gap-3 flex-wrap">
|
<div className="flex items-center gap-3 flex-wrap">
|
||||||
<h1 className="font-inter font-semibold text-white text-lg tracking-tight">
|
<h1 className="font-ui font-semibold text-white text-lg tracking-tight">
|
||||||
{patient.name}
|
{patient.name}
|
||||||
</h1>
|
</h1>
|
||||||
<StatusDot status={patient.status} />
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-slate-400 text-sm">{patient.status}</span>
|
<StatusDot status={patient.status} />
|
||||||
<StatusBadge badge={patient.badge} />
|
<span className="text-slate-400 text-sm font-ui">{patient.status}</span>
|
||||||
|
</div>
|
||||||
|
{patient.badge && <StatusBadge badge={patient.badge} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 mt-1 flex-wrap text-sm text-slate-300">
|
|
||||||
|
{/* Row 2: Demographics with pipe separators */}
|
||||||
|
<div className="flex items-center gap-4 mt-1 flex-wrap text-sm text-slate-300 font-ui">
|
||||||
<span>
|
<span>
|
||||||
<span className="text-slate-500">DOB:</span> {patient.dob}
|
<span className="text-slate-500">DOB:</span>{' '}
|
||||||
|
<span className="font-geist">{patient.dob}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500">|</span>
|
<span className="text-slate-500">|</span>
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<span className="text-slate-500">NHS No:</span>{' '}
|
<span className="text-slate-500">NHS No:</span>{' '}
|
||||||
<span className="font-geist" title={patient.nhsNumberTooltip}>
|
<NHSNumberWithTooltip />
|
||||||
{patient.nhsNumber}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500">|</span>
|
<span className="text-slate-500">|</span>
|
||||||
<span>{patient.address}</span>
|
<span>{patient.address}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 mt-1 flex-wrap text-sm text-slate-300">
|
|
||||||
|
{/* Row 3: Contact details */}
|
||||||
|
<div className="flex items-center gap-4 mt-1 flex-wrap text-sm text-slate-300 font-ui">
|
||||||
<a
|
<a
|
||||||
href={`tel:${patient.phone}`}
|
href={`tel:${patient.phone}`}
|
||||||
className="hover:text-white transition-colors"
|
className="hover:text-white transition-colors"
|
||||||
@@ -167,6 +214,8 @@ function FullBanner() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Action buttons */}
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<Download size={14} />}
|
icon={<Download size={14} />}
|
||||||
@@ -194,18 +243,19 @@ function CondensedBanner() {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full px-4 lg:px-6 flex items-center justify-between gap-4">
|
<div className="h-full px-4 lg:px-6 flex items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-4 min-w-0">
|
<div className="flex items-center gap-4 min-w-0">
|
||||||
<h1 className="font-inter font-semibold text-white text-sm tracking-tight truncate">
|
<h1 className="font-ui font-semibold text-white text-sm tracking-tight truncate">
|
||||||
{patient.name}
|
{patient.name}
|
||||||
</h1>
|
</h1>
|
||||||
<span className="text-slate-500">|</span>
|
<span className="text-slate-500">|</span>
|
||||||
<span className="flex items-center gap-1 text-sm text-slate-300">
|
<span className="flex items-center gap-1 text-sm text-slate-300">
|
||||||
<span className="text-slate-500">NHS No:</span>{' '}
|
<span className="text-slate-500 font-ui">NHS No:</span>{' '}
|
||||||
<span className="font-geist" title={patient.nhsNumberTooltip}>
|
<NHSNumberWithTooltip condensed />
|
||||||
{patient.nhsNumber}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500">|</span>
|
<span className="text-slate-500">|</span>
|
||||||
<StatusDot status={patient.status} />
|
<div className="flex items-center gap-1.5">
|
||||||
|
<StatusDot status={patient.status} />
|
||||||
|
<span className="text-slate-400 text-xs font-ui">{patient.status}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@@ -225,6 +275,70 @@ function CondensedBanner() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Sub-components --- */
|
||||||
|
|
||||||
|
interface NHSNumberWithTooltipProps {
|
||||||
|
condensed?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function NHSNumberWithTooltip({ condensed = false }: NHSNumberWithTooltipProps) {
|
||||||
|
const [showTooltip, setShowTooltip] = useState(false)
|
||||||
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
timeoutRef.current = setTimeout(() => setShowTooltip(true), 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = null
|
||||||
|
}
|
||||||
|
setShowTooltip(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="relative inline-flex items-center"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onFocus={() => setShowTooltip(true)}
|
||||||
|
onBlur={() => setShowTooltip(false)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`font-geist cursor-help border-b border-dotted border-slate-500 ${condensed ? 'text-sm' : ''}`}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
aria-describedby="nhs-tooltip"
|
||||||
|
>
|
||||||
|
{patient.nhsNumber}
|
||||||
|
</span>
|
||||||
|
<AnimatePresence>
|
||||||
|
{showTooltip && (
|
||||||
|
<motion.span
|
||||||
|
id="nhs-tooltip"
|
||||||
|
role="tooltip"
|
||||||
|
initial={{ opacity: 0, y: 4 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: 4 }}
|
||||||
|
transition={{ duration: 0.12 }}
|
||||||
|
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 px-2.5 py-1 bg-slate-800 text-white text-xs font-ui rounded whitespace-nowrap z-50 shadow-lg pointer-events-none"
|
||||||
|
>
|
||||||
|
{patient.nhsNumberTooltip}
|
||||||
|
<span className="absolute left-1/2 -translate-x-1/2 -top-1 w-2 h-2 bg-slate-800 rotate-45" />
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface StatusDotProps {
|
interface StatusDotProps {
|
||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
@@ -245,7 +359,7 @@ interface StatusBadgeProps {
|
|||||||
|
|
||||||
function StatusBadge({ badge }: StatusBadgeProps) {
|
function StatusBadge({ badge }: StatusBadgeProps) {
|
||||||
return (
|
return (
|
||||||
<span className="px-2 py-0.5 bg-pmr-nhsblue text-white text-xs font-medium rounded-sm">
|
<span className="px-2.5 py-0.5 bg-pmr-nhsblue text-white text-xs font-ui font-medium rounded-full">
|
||||||
{badge}
|
{badge}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -269,10 +383,11 @@ function ActionButton({ icon, label, href, external, compact }: ActionButtonProp
|
|||||||
inline-flex items-center gap-1.5
|
inline-flex items-center gap-1.5
|
||||||
border border-pmr-nhsblue text-pmr-nhsblue
|
border border-pmr-nhsblue text-pmr-nhsblue
|
||||||
hover:bg-pmr-nhsblue hover:text-white
|
hover:bg-pmr-nhsblue hover:text-white
|
||||||
transition-colors duration-100
|
transition-colors duration-150
|
||||||
rounded
|
rounded
|
||||||
|
font-ui font-medium
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-pmr-nhsblue/40 focus:ring-offset-1 focus:ring-offset-pmr-banner
|
||||||
${compact ? 'px-2 py-1 text-xs' : 'px-3 py-1.5 text-sm'}
|
${compact ? 'px-2 py-1 text-xs' : 'px-3 py-1.5 text-sm'}
|
||||||
font-inter font-medium
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
|
|||||||
Reference in New Issue
Block a user