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:
parent
35537b8a5b
commit
9f41ff72f0
@ -49,7 +49,8 @@
|
||||
--radius-xxl: 12px;
|
||||
|
||||
/* Z-index */
|
||||
--z-modal: 1000;
|
||||
--z-modal: 10000;
|
||||
--z-tooltip: 50000;
|
||||
}
|
||||
body {
|
||||
background-color: var(--color-bg);
|
||||
|
@ -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 {
|
||||
|
73
src/components/httprequestviewer/RequestsTable.module.css
Normal file
73
src/components/httprequestviewer/RequestsTable.module.css
Normal 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);
|
||||
}
|
@ -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 {
|
||||
|
71
src/components/shared/Modal.module.css
Normal file
71
src/components/shared/Modal.module.css
Normal 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;
|
||||
}
|
@ -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>
|
||||
|
174
src/components/shared/Tooltip.module.css
Normal file
174
src/components/shared/Tooltip.module.css
Normal 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;
|
||||
}
|
@ -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 */}
|
||||
{isHovered && !isModalOpen && (
|
||||
<div>
|
||||
<div>
|
||||
{title}
|
||||
</div>
|
||||
<div>
|
||||
{description}
|
||||
</div>
|
||||
<div>
|
||||
Click for detailed information
|
||||
</div>
|
||||
{/* Hover tooltip - rendered as fixed positioned element */}
|
||||
{isHovered && !isModalOpen && (
|
||||
<div
|
||||
className={styles.hoverTooltipFixed}
|
||||
style={{
|
||||
top: `${tooltipPosition.top}px`,
|
||||
left: `${tooltipPosition.left}px`
|
||||
}}
|
||||
>
|
||||
<div className={styles.tooltipTitle}>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.tooltipDescription}>
|
||||
{description}
|
||||
</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>
|
||||
))}
|
||||
|
Loading…
Reference in New Issue
Block a user