Convert components to CSS modules and fix tooltip positioning

- Convert Tooltip and Modal components from inline styles to CSS modules
- Add centralized z-index management with CSS variables (--z-modal: 10000, --z-tooltip: 50000)
- Implement fixed positioning for tooltips with dynamic coordinate calculation
- Fix header row hover effects to not inherit data row brightness filters
- Add proper table container positioning context for tooltips
- Create RequestsTable.module.css for table-specific styling
- Ensure tooltips are always visible above all content with proper arrow positioning

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-08-12 08:43:58 -05:00
parent 35537b8a5b
commit 9f41ff72f0
8 changed files with 385 additions and 95 deletions

View File

@ -49,7 +49,8 @@
--radius-xxl: 12px;
/* Z-index */
--z-modal: 1000;
--z-modal: 10000;
--z-tooltip: 50000;
}
body {
background-color: var(--color-bg);

View File

@ -25,10 +25,11 @@ tr {
border: 1px solid #ffffff;
}
tr:hover, tr:hover td {
/* Only apply hover effects to data rows, not header rows */
tbody tr:hover, tbody tr:hover td {
border: 1px dashed var(--color-bg-hover);
}
tr:hover {
tbody tr:hover {
filter: brightness(290%);
}
a {

View File

@ -0,0 +1,73 @@
/* Table styles using CSS variables from App.module.css */
.tableContainer {
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
/* Allow tooltips to show above table */
position: relative;
}
.table {
width: 100%;
border-collapse: collapse;
}
.tableHeader {
background: var(--color-bg-light);
}
/* Ensure header row doesn't inherit hover effects */
.tableHeader tr:hover {
filter: none !important;
background: var(--color-bg-light) !important;
border: none !important;
}
.tableHeader tr:hover td,
.tableHeader tr:hover th {
border: 1px solid var(--color-border) !important;
filter: none !important;
}
/* Ensure tooltips in header work properly */
.tableHeader .tooltipContainer {
position: relative;
z-index: 10;
}
.tableHeaderCell {
padding: var(--spacing-sm);
font-size: var(--font-size-md);
font-weight: bold;
border-bottom: 1px solid var(--color-border);
color: var(--color-text);
/* Ensure tooltip positioning context */
position: relative;
overflow: visible;
}
.tableHeaderCell.center {
text-align: center;
}
.tableHeaderCell.left {
text-align: left;
}
.tableHeaderCell.right {
text-align: right;
}
.tableHeaderCell.expandColumn {
width: 30px;
}
/* No results message */
.noResults {
text-align: center;
color: var(--color-text-muted);
padding: 40px;
font-size: var(--font-size-xl);
}

View File

@ -3,7 +3,7 @@ import RequestRowSummary from './RequestRowSummary'
import ScreenshotRow from './ScreenshotRow'
import { Tooltip } from '../shared/Tooltip'
import { TooltipType } from '../shared/tooltipDefinitions'
import styles from './HTTPRequestViewer.module.css'
import styles from './RequestsTable.module.css'
import type { HTTPRequest, ScreenshotEvent } from './types/httpRequest'
interface RequestsTableProps {

View File

@ -0,0 +1,71 @@
/* Modal component styles using CSS variables from App.module.css */
.modalOverlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: var(--z-modal);
padding: var(--spacing-lg);
}
.modalContainer {
background-color: var(--color-bg-secondary);
border-radius: var(--radius-lg);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
max-width: 600px;
max-height: 80vh;
width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
border: 1px solid var(--color-border);
}
.modalHeader {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--color-bg-light);
}
.modalTitle {
margin: 0;
color: var(--color-text);
font-size: var(--font-size-xxl);
font-weight: bold;
}
.modalCloseButton {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--color-text-muted);
padding: 0;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
}
.modalCloseButton:hover {
background-color: var(--color-bg-hover);
color: var(--color-text);
}
.modalBody {
padding: var(--spacing-lg);
overflow: auto;
flex: 1;
}

View File

@ -1,4 +1,5 @@
import { useEffect } from 'react'
import styles from './Modal.module.css'
interface ModalProps {
isOpen: boolean
@ -32,85 +33,28 @@ export function Modal({ isOpen, onClose, title, children }: ModalProps) {
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 10000,
padding: '20px'
}}
className={styles.modalOverlay}
onClick={onClose}
>
<div
style={{
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)',
maxWidth: '600px',
maxHeight: '80vh',
width: '100%',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}}
className={styles.modalContainer}
onClick={(e) => e.stopPropagation()}
>
{/* Modal Header */}
<div
style={{
padding: '20px',
borderBottom: '1px solid #e9ecef',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#f8f9fa'
}}
>
<h2 style={{ margin: 0, color: '#495057', fontSize: '18px', fontWeight: 'bold' }}>
<div className={styles.modalHeader}>
<h2 className={styles.modalTitle}>
{title}
</h2>
<button
onClick={onClose}
style={{
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
color: '#6c757d',
padding: '0',
width: '30px',
height: '30px',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'background-color 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#e9ecef'
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent'
}}
className={styles.modalCloseButton}
>
×
</button>
</div>
{/* Modal Content */}
<div
style={{
padding: '20px',
overflow: 'auto',
flex: 1
}}
>
<div className={styles.modalBody}>
{children}
</div>
</div>

View File

@ -0,0 +1,174 @@
/* Tooltip component styles using CSS variables from App.module.css */
.tooltipContainer {
position: relative;
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
/* Ensure tooltip positioning context */
z-index: 1;
}
.tooltipIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
background-color: var(--color-primary);
color: white;
border-radius: 50%;
font-size: var(--font-size-xs);
font-weight: bold;
cursor: help;
transition: all 0.2s ease;
user-select: none;
}
.tooltipIcon:hover {
background-color: var(--color-text);
transform: scale(1.1);
}
/* Hover tooltip */
.hoverTooltip {
position: absolute;
top: auto;
bottom: calc(100% + var(--spacing-xs));
left: 50%;
transform: translateX(-50%);
background-color: var(--color-bg-secondary);
color: var(--color-text);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
font-size: var(--font-size-sm);
z-index: var(--z-tooltip);
max-width: 300px;
word-wrap: break-word;
white-space: normal;
/* Ensure tooltip doesn't get clipped by containers */
pointer-events: none;
/* Prevent text selection */
user-select: none;
}
.hoverTooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid var(--color-bg-secondary);
/* Ensure arrow is also on top */
z-index: calc(var(--z-tooltip) + 1);
pointer-events: none;
}
/* Fixed positioned tooltip for better visibility */
.hoverTooltipFixed {
position: fixed;
transform: translateX(-50%) translateY(-100%);
background-color: var(--color-bg-secondary);
color: var(--color-text);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
font-size: var(--font-size-sm);
z-index: var(--z-tooltip);
max-width: 300px;
word-wrap: break-word;
white-space: normal;
pointer-events: none;
user-select: none;
border: 1px solid var(--color-border);
margin-top: -8px;
}
.hoverTooltipFixed::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid var(--color-bg-secondary);
z-index: calc(var(--z-tooltip) + 1);
pointer-events: none;
}
.tooltipTitle {
font-weight: bold;
margin-bottom: var(--spacing-xs);
color: var(--color-text-highlight);
}
.tooltipDescription {
margin-bottom: var(--spacing-xs);
line-height: 1.4;
}
.tooltipHint {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
font-style: italic;
}
/* Modal content styling */
.modalContent {
line-height: 1.6;
}
.modalDescription {
margin-bottom: var(--spacing-lg);
color: var(--color-text);
}
.modalSection {
margin-bottom: var(--spacing-lg);
padding: var(--spacing-md);
background-color: var(--color-bg-light);
border-radius: var(--radius-md);
border-left: 4px solid var(--color-primary);
}
.modalSectionTitle {
font-weight: bold;
margin-bottom: var(--spacing-sm);
color: var(--color-text);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.modalSectionContent {
color: var(--color-text);
line-height: 1.5;
}
.modalLinks {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.modalLink {
color: var(--color-primary);
text-decoration: none;
display: flex;
align-items: center;
gap: var(--spacing-xs);
transition: color 0.2s ease;
}
.modalLink:hover {
color: var(--color-text-highlight);
text-decoration: underline;
}

View File

@ -2,6 +2,7 @@ import { useState } from 'react'
import { Modal } from './Modal'
import { TOOLTIP_DEFINITIONS } from './tooltipDefinitions'
import type { TooltipTypeValues } from './tooltipDefinitions'
import styles from './Tooltip.module.css'
// Tooltip component for field explanations
interface TooltipProps {
@ -13,6 +14,7 @@ export function Tooltip({ children, type }: TooltipProps) {
const { title, description, lighthouseRelation, calculation, links } = TOOLTIP_DEFINITIONS[type]
const [isHovered, setIsHovered] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 })
const handleIconClick = (e: React.MouseEvent) => {
e.preventDefault()
@ -21,29 +23,51 @@ export function Tooltip({ children, type }: TooltipProps) {
setIsModalOpen(true)
}
const handleMouseEnter = (e: React.MouseEvent) => {
const rect = (e.target as HTMLElement).getBoundingClientRect()
setTooltipPosition({
top: rect.top - 10, // Position above the icon
left: rect.left + rect.width / 2 // Center horizontally
})
setIsHovered(true)
}
return (
<>
<div>
<div className={styles.tooltipContainer}>
{children}
<span
onClick={handleIconClick}>
className={styles.tooltipIcon}
onClick={handleIconClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={() => setIsHovered(false)}
>
?
</span>
</div>
{/* Hover tooltip - only show when not modal open */}
{/* Hover tooltip - rendered as fixed positioned element */}
{isHovered && !isModalOpen && (
<div>
<div>
<div
className={styles.hoverTooltipFixed}
style={{
top: `${tooltipPosition.top}px`,
left: `${tooltipPosition.left}px`
}}
>
<div className={styles.tooltipTitle}>
{title}
</div>
<div>
<div className={styles.tooltipDescription}>
{description}
</div>
<div>
<div className={styles.tooltipHint}>
Click for detailed information
</div>
</div>
)}
<div style={{ display: 'none' }}>
</div>
{/* Modal with detailed content */}
@ -52,45 +76,47 @@ export function Tooltip({ children, type }: TooltipProps) {
onClose={() => setIsModalOpen(false)}
title={title}
>
<div>
<div>
<div className={styles.modalContent}>
<div className={styles.modalDescription}>
{description}
</div>
{lighthouseRelation && (
<div>
<div>
<div className={styles.modalSection}>
<div className={styles.modalSectionTitle}>
🎯 Lighthouse Relationship
</div>
<div>
<div className={styles.modalSectionContent}>
{lighthouseRelation}
</div>
</div>
)}
{calculation && (
<div>
<div>
<div className={styles.modalSection}>
<div className={styles.modalSectionTitle}>
🧮 Calculation
</div>
<div>
<div className={styles.modalSectionContent}>
{calculation}
</div>
</div>
)}
{links && links.length > 0 && (
<div>
<div>
<div className={styles.modalSection}>
<div className={styles.modalSectionTitle}>
📚 Learn More
</div>
<div>
<div className={styles.modalLinks}>
{links.map((link, index) => (
<a
key={index}
href={link.url}
target="_blank"
rel="noopener noreferrer">
rel="noopener noreferrer"
className={styles.modalLink}
>
🔗 {link.text}
</a>
))}