Skip to content
4 changes: 2 additions & 2 deletions src/components/BankTransactions/BankTransactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ const BankTransactionsTableView = ({
const removeTransaction = (bankTransaction: BankTransaction) =>
removeAfterCategorize([bankTransaction.id])

const containerRef = useElementSize<HTMLDivElement>((_el, _en, size) => {
if (size?.height && size?.height >= 90) {
const containerRef = useElementSize<HTMLDivElement>((size) => {
if (size.height >= 90) {
const newShift = -Math.floor(size.height / 2) + 6
if (newShift !== shiftStickyHeader) {
void debounceShiftStickyHeader(newShift)
Expand Down
6 changes: 3 additions & 3 deletions src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export const Tabs = ({ name, options, selected, onChange }: TabsProps) => {
initialized && 'Layer__tabs--initialized',
)

const elementRef = useElementSize<HTMLDivElement>((_a, _b, c) => {
if (c.width && c?.width !== currentWidth) {
setCurrentWidth(c.width)
const elementRef = useElementSize<HTMLDivElement>((size) => {
if (size.width !== currentWidth) {
setCurrentWidth(size.width)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed truthy check allows zero width update

Low Severity

The condition was changed from c.width && c?.width !== currentWidth to size.width !== currentWidth, removing the truthy check on width. Previously, when the element's width became 0 (e.g., when hidden), currentWidth would retain its last non-zero value. Now it will update to 0, which triggers updateSelectPosition via the useEffect dependency on currentWidth. This could cause incorrect thumb positioning calculations when the tabs component is temporarily hidden and then shown again.

Fix in Cursor Fix in Web

}
})

Expand Down
36 changes: 36 additions & 0 deletions src/components/ui/ResponsiveComponent/ResponsiveComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type ReactNode, useCallback, useState } from 'react'

import { type ElementSize, useElementSize } from '@hooks/useElementSize/useElementSize'

import './responsiveComponent.scss'

export type DefaultVariant = 'Desktop' | 'SmallDesktop' | 'Mobile'

export type VariantResolver<T extends string> = ({ width }: { width: number }) => T

export interface ResponsiveComponentProps<T extends string = DefaultVariant> {
slots: Record<T, ReactNode>
resolveVariant: VariantResolver<T>
}

export const ResponsiveComponent = <T extends string = DefaultVariant>({
slots,
resolveVariant,
}: ResponsiveComponentProps<T>) => {
const [currentVariant, setCurrentVariant] = useState<T | null>(null)

const handleResize = useCallback((size: ElementSize) => {
setCurrentVariant(resolveVariant({ width: size.width }))
}, [resolveVariant])

const containerRef = useElementSize<HTMLDivElement>(handleResize)

return (
<div
ref={containerRef}
className='Layer__ResponsiveComponent'
>
{currentVariant !== null && slots[currentVariant]}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.Layer__ResponsiveComponent {
height: 100%;
width: 100%;
}
51 changes: 30 additions & 21 deletions src/hooks/useElementSize/useElementSize.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,58 @@
import { useLayoutEffect, useRef } from 'react'

export interface ElementSize {
width: number
height: number
}

export const useElementSize = <T extends HTMLElement>(
callback: (
target: T,
entry: ResizeObserverEntry,
size: {
width: number
height: number
clientWidth: number
clientHeight: number
},
) => void,
callback: (size: ElementSize) => void,
) => {
const ref = useRef<T>(null)
const callbackRef = useRef(callback)
const isFirstRender = useRef(true)
const resizeTimeout = useRef<number | null>(null)

callbackRef.current = callback

useLayoutEffect(() => {
const element = ref?.current

if (!element) {
return
if (!element) return

const invokeCallback = ({ width, height }: ElementSize) => {
callbackRef.current({ width, height })
}

if (isFirstRender.current) {
const rect = element.getBoundingClientRect()
invokeCallback({
width: rect.width,
height: rect.height,
})
isFirstRender.current = false
}

const observer = new ResizeObserver((entries) => {
const width = entries[0].borderBoxSize[0].inlineSize
const height = entries[0].borderBoxSize[0].blockSize

if (resizeTimeout.current) {
clearTimeout(resizeTimeout.current)
}
resizeTimeout.current = window.setTimeout(() => {
const entry = entries[0]
callback(element, entry, {
width: element.offsetWidth,
height: element.offsetHeight,
clientWidth: element.clientWidth,
clientHeight: element.clientHeight,
})
invokeCallback({ width, height })
}, 100)
})

observer.observe(element)

return () => {
observer.disconnect()
if (resizeTimeout.current) {
clearTimeout(resizeTimeout.current)
}
}
}, [callback, ref])

}, [])
return ref
}