Skip to content

Commit

Permalink
fix: BoldNumber text resizing (#18820)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Nov 22, 2023
1 parent d65a775 commit e94bca5
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 101 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-number-edit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-number.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 10 additions & 22 deletions frontend/src/scenes/insights/views/BoldNumber/BoldNumber.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,30 @@

.BoldNumber {
width: 100%;
padding: 2rem 0 3rem;
padding: 2rem 3rem 3rem;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
flex: 1;

@include screen($md) {
padding: 3rem 0 5rem;

.InsightCard & {
padding: 2rem 0;
}
}
}

.BoldNumber__value {
width: 100%;
text-align: center;
padding: 0 2rem;
margin: 0 auto;
line-height: 1;
font-weight: 700;
letter-spacing: -0.025em;

.InsightCard & {
padding: 0 1rem;
padding: 1rem;
}

@include screen($md) {
padding: 0 5rem;
padding: 3rem 5rem 5rem;

.InsightCard & {
padding: 0 2rem;
padding: 2rem;
}
}

.BoldNumber__value {
font-weight: 700;
width: 100%;
letter-spacing: -0.025em;
}
}

.BoldNumber__comparison {
Expand Down
46 changes: 24 additions & 22 deletions frontend/src/scenes/insights/views/BoldNumber/BoldNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './BoldNumber.scss'
import './BoldNumber.scss'

import { LemonRow, Link } from '@posthog/lemon-ui'
import clsx from 'clsx'
Expand All @@ -8,6 +9,7 @@ import { IconFlare, IconTrendingDown, IconTrendingFlat, IconTrendingUp } from 'l
import { percentage } from 'lib/utils'
import { useLayoutEffect, useRef, useState } from 'react'
import { useEffect } from 'react'
import React from 'react'
import { formatAggregationAxisValue } from 'scenes/insights/aggregationAxisFormat'
import { InsightEmptyState } from 'scenes/insights/EmptyStates'
import { InsightTooltip } from 'scenes/insights/InsightTooltip/InsightTooltip'
Expand All @@ -19,7 +21,7 @@ import { ChartParams, TrendResult } from '~/types'

import { insightLogic } from '../../insightLogic'
import { ensureTooltip } from '../LineGraph/LineGraph'
import Textfit from './Textfit'
import { Textfit } from './Textfit'

/** The tooltip is offset by a few pixels from the cursor to give it some breathing room. */
const BOLD_NUMBER_TOOLTIP_OFFSET_PX = 8
Expand Down Expand Up @@ -94,29 +96,29 @@ export function BoldNumber({ showPersonsModal = true }: ChartParams): JSX.Elemen

return resultSeries ? (
<div className="BoldNumber">
<Textfit min={32} max={120}>
<div
className={clsx('BoldNumber__value', showPersonsModal ? 'cursor-pointer' : 'cursor-default')}
onClick={
// != is intentional to catch undefined too
showPersonsModal && resultSeries.aggregated_value != null
? () => {
if (resultSeries.persons?.url) {
openPersonsModal({
url: resultSeries.persons?.url,
title: <PropertyKeyInfo value={resultSeries.label} disablePopover />,
})
}
<div
className={clsx('BoldNumber__value', showPersonsModal ? 'cursor-pointer' : 'cursor-default')}
onClick={
// != is intentional to catch undefined too
showPersonsModal && resultSeries.aggregated_value != null
? () => {
if (resultSeries.persons?.url) {
openPersonsModal({
url: resultSeries.persons?.url,
title: <PropertyKeyInfo value={resultSeries.label} disablePopover />,
})
}
: undefined
}
onMouseLeave={() => setIsTooltipShown(false)}
ref={valueRef}
onMouseEnter={() => setIsTooltipShown(true)}
>
}
: undefined
}
onMouseLeave={() => setIsTooltipShown(false)}
ref={valueRef}
onMouseEnter={() => setIsTooltipShown(true)}
>
<Textfit min={32} max={120}>
{formatAggregationAxisValue(trendsFilter, resultSeries.aggregated_value)}
</div>
</Textfit>
</Textfit>
</div>
{showComparison && <BoldNumberComparison showPersonsModal={showPersonsModal} />}
</div>
) : (
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/scenes/insights/views/BoldNumber/Textfit.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Meta, StoryFn, StoryObj } from '@storybook/react'

import { Textfit } from './Textfit'

type Story = StoryObj<typeof Textfit>
const meta: Meta<typeof Textfit> = {
title: 'Lemon UI/TextFit',
component: Textfit,
tags: ['autodocs'],
args: {
min: 20,
max: 150,
children: '10000000',
},
}
export default meta

const Template: StoryFn<typeof Textfit> = (props) => {
return (
<div className="resize w-100 h-50 overflow-hidden border rounded">
<Textfit {...props} />
</div>
)
}

export const Basic: Story = Template.bind({})
108 changes: 51 additions & 57 deletions frontend/src/scenes/insights/views/BoldNumber/Textfit.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,77 @@
// Adapted from https://github.com/malte-wessel/react-textfit
// which is no longer maintained and does not support React 18
import { useRef } from 'react'
import useResizeObserver from 'use-resize-observer'

import { useEffect, useRef, useState } from 'react'

// Calculate width without padding.
const innerWidth = (el: HTMLDivElement): number => {
const style = window.getComputedStyle(el, null)
// Hidden iframe in Firefox returns null, https://github.com/malte-wessel/react-textfit/pull/34
if (!style) {
return el.clientWidth
}

return (
el.clientWidth -
parseInt(style.getPropertyValue('padding-left'), 10) -
parseInt(style.getPropertyValue('padding-right'), 10)
)
export type TextfitProps = {
min: number
max: number
children: string
}

const assertElementFitsWidth = (el: HTMLDivElement, width: number): boolean => el.scrollWidth - 1 <= width

const Textfit = ({ min, max, children }: { min: number; max: number; children: React.ReactNode }): JSX.Element => {
export const Textfit = ({ min, max, children }: TextfitProps): JSX.Element => {
const parentRef = useRef<HTMLDivElement>(null)
const childRef = useRef<HTMLDivElement>(null)

const [fontSize, setFontSize] = useState<number>()
const fontSizeRef = useRef<number>(min)

let resizeTimer: NodeJS.Timeout

const handleWindowResize = (): void => {
const updateFontSize = (size: number): void => {
fontSizeRef.current = size
childRef.current!.style.fontSize = `${size}px`
}

const handleResize = (): void => {
clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
const el = parentRef.current
const wrapper = childRef.current
const parent = parentRef.current
const child = childRef.current

if (!parent || !child) {
return
}

if (el && wrapper) {
const originalWidth = innerWidth(el)
let mid
let low = min
let high = max

let mid
let low = min
let high = max
while (low <= high) {
mid = Math.floor((low + high) / 2)
updateFontSize(mid)
const childRect = child.getBoundingClientRect()
const parentRect = parent.getBoundingClientRect()

while (low <= high) {
mid = Math.floor((low + high) / 2)
setFontSize(mid)
const childFitsParent = childRect.width <= parentRect.width && childRect.height <= parentRect.height

if (assertElementFitsWidth(wrapper, originalWidth)) {
low = mid + 1
} else {
high = mid - 1
}
if (childFitsParent) {
low = mid + 1
} else {
high = mid - 1
}
mid = Math.min(low, high)
}
mid = Math.min(low, high)

// Ensure we hit the user-supplied limits
mid = Math.max(mid, min)
mid = Math.min(mid, max)
// Ensure we hit the user-supplied limits
mid = Math.max(mid, min)
mid = Math.min(mid, max)

setFontSize(mid)
}
}, 10)
updateFontSize(mid)
}, 50)
}

useEffect(() => {
window.addEventListener('resize', handleWindowResize)
return () => window.removeEventListener('resize', handleWindowResize)
}, [])

useEffect(() => handleWindowResize(), [parentRef, childRef])
useResizeObserver<HTMLDivElement>({
ref: parentRef,
onResize: () => handleResize(),
})

return (
// eslint-disable-next-line react/forbid-dom-props
<div ref={parentRef} style={{ lineHeight: 1, fontSize: fontSize }}>
{/* eslint-disable-next-line react/forbid-dom-props */}
<div ref={childRef} style={{ whiteSpace: 'nowrap', display: 'inline-block' }}>
<div
ref={parentRef}
className="w-full h-full flex items-center justify-center"
// eslint-disable-next-line react/forbid-dom-props
style={{ lineHeight: 1, fontSize: fontSizeRef.current }}
>
<div ref={childRef} className="whitespace-nowrap">
{children}
</div>
</div>
)
}

export default Textfit
8 changes: 8 additions & 0 deletions frontend/src/styles/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,14 @@ $decorations: underline, overline, line-through, no-underline;
visibility: hidden;
}

.resize {
resize: both;
}

.resize-x {
resize: horizontal;
}

.resize-y {
resize: vertical;
}
Expand Down

0 comments on commit e94bca5

Please sign in to comment.