Skip to content

Commit

Permalink
fix. prevent tooltip from being hidden when scroll option is true
Browse files Browse the repository at this point in the history
  • Loading branch information
seiwonpark committed Feb 2, 2025
1 parent cc5ed9f commit 18ae98a
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 51 deletions.
6 changes: 3 additions & 3 deletions src/components/Cell/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@
border-width: 6px;
border-style: solid;
border-color: transparent;
border-top-color: #323232;
border-top-color: #25292E;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
}

.top:after {
content: attr(data-tooltip);
padding: 10px 16px;
background-color: #323232;
padding: 4px 8px;
background-color: #25292E;
color: #ecf0f1;
border-radius: 5px;
bottom: calc(100% + 10px);
Expand Down
104 changes: 84 additions & 20 deletions src/components/Cell/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
import { useState, useRef, useEffect, CSSProperties, HTMLAttributes } from 'react'
import React, { useState, useRef, useEffect, CSSProperties, HTMLAttributes } from 'react'
import { createPortal } from 'react-dom'
import styles from './index.module.css'

interface TooltipProps {
content: string
offsetX: number
targetRect: DOMRect | null
fontSize?: number
}

const Tooltip = ({ content, offsetX, targetRect, fontSize = 12 }: TooltipProps) => {
if (!targetRect) return null

const tooltipStyle: CSSProperties = {
position: 'fixed',
zIndex: 999,
backgroundColor: '#25292E',
color: '#ecf0f1',
padding: '4px 8px',
borderRadius: '5px',
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji'`,
fontWeight: 400,
whiteSpace: 'nowrap',
pointerEvents: 'none',
fontSize: `${fontSize}px`,
left: `${targetRect.left + offsetX}px`,
top: `${targetRect.top - 40}px`,
}

const arrowStyle: CSSProperties = {
position: 'fixed',
zIndex: 999,
width: 0,
height: 0,
borderLeft: '6px solid transparent',
borderRight: '6px solid transparent',
borderTop: '6px solid #25292E',
pointerEvents: 'none',
left: `${targetRect.left + targetRect.width / 2 - 6}px`,
top: `${targetRect.top - 18}px`,
}

return createPortal(
<>
<div style={tooltipStyle}>{content}</div>
<div style={arrowStyle} />
</>,
document.body
)
}

interface CellProps extends HTMLAttributes<HTMLTableCellElement> {
cx: number
theme: string | ThemeProps
Expand All @@ -23,6 +73,8 @@ export default function Cell({
}: CellProps) {
const cellRef = useRef<HTMLTableCellElement>(null)
const [tooltipOffset, setTooltipOffset] = useState<number>(-10)
const [showTooltip, setShowTooltip] = useState(false)
const [cellRect, setCellRect] = useState<DOMRect | null>(null)

const getVisibleChildren = (parent: Node | null) => {
if (!parent) return 0
Expand Down Expand Up @@ -73,43 +125,55 @@ export default function Cell({
}
setTooltipOffset(offset)
}

setCellRect(cellRef.current.getBoundingClientRect())
setShowTooltip(true)
}
}

const handleMouseOut = () => {
setShowTooltip(false)
}

if (cellRef.current) {
cellRef.current.addEventListener('mouseover', handleMouseOver)
cellRef.current.addEventListener('mouseout', handleMouseOut)
}

return () => {
if (cellRef.current) {
cellRef.current.removeEventListener('mouseover', handleMouseOver)
cellRef.current.removeEventListener('mouseout', handleMouseOut)
}
}
}, [])
}, [cx])

const isEmojiTheme = (theme: string | ThemeProps): boolean => {
const isCustomTextTheme = typeof theme === 'string' ? false : theme.isTextTheme || false
return theme === 'emoji_positive' || theme === 'emoji_negative' || isCustomTextTheme
}

return (
<td
ref={cellRef}
className={`${styles.calendarCell} ${styles.top}`}
style={
{
...style,
outline: isEmojiTheme(theme) ? 'transparent' : '1px solid rgba(27, 31, 35, 0.06)',
backgroundColor: isEmojiTheme(theme) ? 'transparent' : themeProps[`level${dataLevel}`],
'--tooltip-offset': `${tooltipOffset}px`,
fontSize: tooltipSize,
} as React.CSSProperties
}
data-tooltip={dataTooltip}
data-level={dataLevel}
{...otherProps}
>
{isEmojiTheme(theme) ? themeProps[`level${dataLevel}`] : undefined}
</td>
<>
<td
ref={cellRef}
className={styles.calendarCell}
style={
{
...style,
'--tooltip-offset': `${tooltipOffset}px`,
outline: isEmojiTheme(theme) ? 'transparent' : '1px solid rgba(27, 31, 35, 0.06)',
backgroundColor: isEmojiTheme(theme) ? 'transparent' : themeProps[`level${dataLevel}`],
} as React.CSSProperties
}
data-level={dataLevel}
{...otherProps}
>
{isEmojiTheme(theme) ? themeProps[`level${dataLevel}`] : undefined}
</td>
{showTooltip && (
<Tooltip content={dataTooltip} offsetX={tooltipOffset} targetRect={cellRect} fontSize={tooltipSize} />
)}
</>
)
}
1 change: 1 addition & 0 deletions src/components/Description/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
display: flex;
flex-direction: row;
align-items: center;
padding-top: 4px;
}

.themes {
Expand Down
4 changes: 1 addition & 3 deletions src/components/Description/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ interface DescriptionProps {

export default function Description({ textColor, cx, cy, cr, theme }: DescriptionProps) {
const themeProps = createTheme(theme)
const padding = `4px ${cx + 72}px 0 0`

const isEmojiTheme = (theme: string | ThemeProps): boolean => {
const isCustomTextTheme = typeof theme === 'string' ? false : theme.isTextTheme || false
return theme === 'emoji_positive' || theme === 'emoji_negative' || isCustomTextTheme
Expand All @@ -32,7 +30,7 @@ export default function Description({ textColor, cx, cy, cr, theme }: Descriptio
}

return (
<div className={styles.description} style={{ padding: padding }}>
<div className={styles.description}>
<div className={styles.themes}>
<span style={{ color: textColor, fontSize: cx }}>Less</span>
{Array.from({ length: 5 }, (_, i) => i).map((i) => (
Expand Down
5 changes: 4 additions & 1 deletion src/components/Table/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
}

.container {
width: fit-content;
width: 100%;
max-width: 100%;
}

.calendar {
margin: 0;
display: block;
white-space: nowrap;
width: 100%;
max-width: 100%;
}

.table {
Expand Down
56 changes: 37 additions & 19 deletions src/components/Table/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { CSSProperties } from 'react'
import { CSSProperties, useMemo, useRef, useEffect, useState } from 'react'
import TableHead from '../TableHead'
import TableBody from '../TableBody'
import { getCurrentYear, getDateString } from '../../utils'
import Description from '../Description'
import styles from './index.module.css'
import { getCurrentYear, getDateString } from '../../utils'
import { isValidDateFormat, isValidDateRange, isValidDaysOfTheWeek } from '../../validators'
import { useMemo } from 'react'
import { useEffect } from 'react'

interface TableDateOptions {
start?: string
Expand Down Expand Up @@ -50,17 +48,15 @@ export default function Table({
}: TableProps) {
const start = useMemo(() => dateOptions?.start || getDateString(getCurrentYear(), 0, 1), [dateOptions?.start])
const end = useMemo(() => dateOptions?.end || getDateString(getCurrentYear(), 11, 31), [dateOptions?.end])
const padding = useMemo(
() => `0 ${(styleOptions?.cx || 0) + 70}px 0 ${(styleOptions?.cx || 0) + 10}px`,
[styleOptions?.cx]
)
const startsOnSunday = useMemo(() => dateOptions?.startsOnSunday || true, [dateOptions?.startsOnSunday])
const daysOfTheWeek = useMemo(
() => dateOptions?.daysOfTheWeek || ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
[dateOptions?.daysOfTheWeek]
)
const textColor = useMemo(() => styleOptions?.textColor || '#1f2328', [styleOptions?.textColor])
const includeBoundary = useMemo(() => dateOptions?.includeBoundary || true, [dateOptions?.includeBoundary])
const tableRef = useRef<HTMLTableElement>(null)
const [tableWidth, setTableWidth] = useState(0)

useEffect(() => {
isValidDateFormat(start)
Expand All @@ -69,10 +65,30 @@ export default function Table({
isValidDaysOfTheWeek(daysOfTheWeek)
}, [daysOfTheWeek, start, end])

useEffect(() => {
if (tableRef.current) {
const updateWidth = () => {
setTableWidth(tableRef.current?.offsetWidth || 0)
}

updateWidth() // 초기 너비 설정

// ResizeObserver로 테이블 크기 변화 감지
const resizeObserver = new ResizeObserver(updateWidth)
resizeObserver.observe(tableRef.current)

return () => {
if (tableRef.current) {
resizeObserver.unobserve(tableRef.current)
}
}
}
}, [])

return (
<div className={`${styles.base} ${styles.container}`} style={styleOptions?.style}>
<div className={styles.calendar} style={{ padding, overflowX: scroll ? 'scroll' : 'clip' }}>
<table className={styles.table}>
<div className={styles.calendar} style={{ overflowX: scroll ? 'scroll' : 'clip', display: 'block' }}>
<table ref={tableRef} className={styles.table}>
{!visibilityOptions?.hideMonthLabels && (
<TableHead
start={start}
Expand All @@ -98,16 +114,18 @@ export default function Table({
hideDayLabels={visibilityOptions?.hideDayLabels || false}
/>
</table>
{!visibilityOptions?.hideDescription && (
<div style={{ width: tableWidth }}>
<Description
textColor={textColor}
cx={styleOptions?.cx || 10}
cy={styleOptions?.cy || 10}
cr={styleOptions?.cr || 2}
theme={styleOptions?.theme || 'grass'}
/>
</div>
)}
</div>
{!visibilityOptions?.hideDescription && (
<Description
textColor={textColor}
cx={styleOptions?.cx || 10}
cy={styleOptions?.cy || 10}
cr={styleOptions?.cr || 2}
theme={styleOptions?.theme || 'grass'}
/>
)}
</div>
)
}
3 changes: 2 additions & 1 deletion src/components/TableBody/index.module.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
.tbody {
width: fit-content;
width: 100%;
overflow: visible;
}

.tr {
display: table-row;
vertical-align: inherit;
border-color: inherit;
width: 100%;
}

.td {
Expand Down
3 changes: 2 additions & 1 deletion src/components/TableHead/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
vertical-align: middle;
border-color: inherit;
top: 0;
width: fit-content;
width: 100%;
}

.tr {
display: table-row;
vertical-align: inherit;
border-color: inherit;
width: 100%;
}

.dayOfTheWeek {
Expand Down
5 changes: 2 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ContributionCalendar
dateOptions={{
daysOfTheWeek: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
daysOfTheWeek: ['', 'Mon', '', 'Wed', '', 'Fri', ''],
startsOnSunday: true,
includeBoundary: true,
}}
Expand All @@ -16,15 +16,14 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
cx: 12,
cy: 12,
cr: 2,
style: {},
}}
visibilityOptions={{
hideDescription: false,
hideMonthLabels: false,
hideDayLabels: false,
}}
onCellClick={(_, data) => console.log(data)}
scroll={false}
scroll={true}
/>
</React.StrictMode>
)

0 comments on commit 18ae98a

Please sign in to comment.