-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #273 from mediamonks/feature/create-use-content-re…
…ct-hook Create useContentRect hook
- Loading branch information
Showing
7 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { Meta } from '@storybook/blocks'; | ||
|
||
<Meta title="Hooks / useContentRect" /> | ||
|
||
# useContentRect | ||
|
||
The `useContentRect` hook is used to get the size of an HTML element, the `DOMRect` is stored in a | ||
`RefObject`. It uses the `ResizeObserver` API to observe the element `DOMRect`, the target is | ||
disconnected when the component unmounts. | ||
|
||
## Reference | ||
|
||
```ts | ||
function useContentRect(ref: Unreffable<Element | null>): RefObject<DOMRectReadOnly>; | ||
``` | ||
|
||
## Using `useContentRect` with a RefObject | ||
|
||
In this example, the `useRef` hook is used to create a `RefObject` for the `div` element which is | ||
initially set to null. The `useContentRect` hook is then called with the ref variable. | ||
|
||
```tsx | ||
function MyComponent() { | ||
const ref = useRef<HTMLDivElement>(null); | ||
|
||
const contentRectRef = useContentRect(ref); | ||
|
||
return <div ref={ref}></div>; | ||
} | ||
``` | ||
|
||
## Using `useContentRect` with an element | ||
|
||
In this example, the `useState` hook is used to create a state variable element which is initially | ||
set to null. The `useContentRect` hook is then called with the element variable. | ||
|
||
```tsx | ||
function MyComponent() { | ||
const [element, setElement] = useState<HTMLDivElement | null>(null); | ||
|
||
const contentRectRef = useContentRect(element); | ||
|
||
return <div ref={setElement}></div>; | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* eslint-disable react/jsx-no-literals, react-hooks/rules-of-hooks */ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { useEffect, useRef } from 'react'; | ||
import { useForceRerender } from '../../index.js'; | ||
import { useContentRect } from './useContentRect.js'; | ||
|
||
const meta = { | ||
title: 'Hooks / useContentRect', | ||
} satisfies Meta; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const UseContentRect: Story = { | ||
render() { | ||
const onClick = useForceRerender(); | ||
const ref = useRef<HTMLDivElement>(null); | ||
const contentRectRef = useContentRect(ref); | ||
|
||
useEffect(() => { | ||
const animation = ref.current?.animate( | ||
[ | ||
// keyframes | ||
{ inlineSize: '300px', blockSize: '300px' }, | ||
{ inlineSize: '500px', blockSize: '500px' }, | ||
{ inlineSize: '300px', blockSize: '300px' }, | ||
], | ||
{ | ||
// timing options | ||
duration: 2000, | ||
iterations: Number.POSITIVE_INFINITY, | ||
}, | ||
); | ||
|
||
return () => animation?.cancel(); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
<h4> | ||
Element size:{' '} | ||
<button type="button" onClick={onClick}> | ||
Rerender | ||
</button> | ||
</h4> | ||
<div | ||
ref={ref} | ||
style={{ | ||
outline: '1px solid red', | ||
padding: 18, | ||
marginBlock: 20, | ||
}} | ||
> | ||
<code | ||
style={{ | ||
display: 'block', | ||
whiteSpace: 'pre', | ||
}} | ||
> | ||
{JSON.stringify(contentRectRef.current, null, 2)} | ||
</code> | ||
</div> | ||
</> | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { useRef, type RefObject } from 'react'; | ||
import { useMount } from '../../lifecycle/hooks/useMount/useMount.js'; | ||
import { unref, type Unreffable } from '../../utils/unref/unref.js'; | ||
import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js'; | ||
|
||
/** | ||
* A hook that returns a ref object containing the content rectangle of the target | ||
* element. The content rectangle is updated whenever the target element is resized. | ||
*/ | ||
export function useContentRect( | ||
target: Unreffable<Element | null>, | ||
): RefObject<DOMRectReadOnly | null> { | ||
const contentRectRef = useRef<DOMRectReadOnly | null>(null); | ||
|
||
useResizeObserver(target, (entries): void => { | ||
contentRectRef.current = entries.at(0)?.contentRect ?? null; | ||
}); | ||
|
||
useMount(() => { | ||
contentRectRef.current = unref(target)?.getBoundingClientRect() ?? null; | ||
}); | ||
|
||
return contentRectRef; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Meta } from '@storybook/blocks'; | ||
|
||
<Meta title="Hooks / useContentRectState" /> | ||
|
||
# useContentRectState | ||
|
||
The `useContentRectState` hook is used to get the size of an HTML element, the `DOMRect` is stored | ||
in a state variable. It uses the `ResizeObserver` API to observe the element `DOMRect`, the target | ||
is disconnected when the component unmounts. | ||
|
||
## Reference | ||
|
||
```ts | ||
function useContentRectState(ref: Unreffable<Element | null>): RefObject<DOMRectReadOnly>; | ||
``` | ||
|
||
## Using `useContentRectState` with a RefObject | ||
|
||
In this example, the `useRef` hook is used to create a `RefObject` for the `div` element which is | ||
initially set to null. The `useContentRectState` hook is then called with the ref variable. | ||
|
||
```tsx | ||
function MyComponent() { | ||
const ref = useRef<HTMLDivElement>(null); | ||
const contentRect = useContentRectState(ref); | ||
|
||
return <div ref={ref}></div>; | ||
} | ||
``` | ||
|
||
## Using `useContentRectState` with an element | ||
|
||
In this example, the `useState` hook is used to create a state variable element which is initially | ||
set to null. The `useContentRectState` hook is then called with the element variable. | ||
|
||
```tsx | ||
function MyComponent() { | ||
const [element, setElement] = useState<HTMLDivElement | null>(null); | ||
const contentRect = useContentRectState(element); | ||
|
||
return <div ref={setElement}></div>; | ||
} | ||
``` |
60 changes: 60 additions & 0 deletions
60
src/hooks/useContentRectState/useContentRectState.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* eslint-disable react/jsx-no-literals, react-hooks/rules-of-hooks */ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { useEffect, useRef } from 'react'; | ||
import { useContentRectState } from './useContentRectState.js'; | ||
|
||
const meta = { | ||
title: 'Hooks / useContentRectState', | ||
} satisfies Meta; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const UseContentRectState: Story = { | ||
render() { | ||
const ref = useRef<HTMLDivElement>(null); | ||
const contentRect = useContentRectState(ref); | ||
|
||
useEffect(() => { | ||
const animation = ref.current?.animate( | ||
[ | ||
// keyframes | ||
{ inlineSize: '300px', blockSize: '300px' }, | ||
{ inlineSize: '500px', blockSize: '500px' }, | ||
{ inlineSize: '300px', blockSize: '300px' }, | ||
], | ||
{ | ||
// timing options | ||
duration: 2000, | ||
iterations: Number.POSITIVE_INFINITY, | ||
}, | ||
); | ||
|
||
return () => animation?.cancel(); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
<h4>Element size:</h4> | ||
<div | ||
ref={ref} | ||
style={{ | ||
outline: '1px solid red', | ||
padding: 18, | ||
marginBlock: 20, | ||
}} | ||
> | ||
<code | ||
style={{ | ||
display: 'block', | ||
whiteSpace: 'pre', | ||
}} | ||
> | ||
{JSON.stringify(contentRect, null, 2)} | ||
</code> | ||
</div> | ||
</> | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { useRef, useState } from 'react'; | ||
import { useMount } from '../../index.js'; | ||
import { unref, type Unreffable } from '../../utils/unref/unref.js'; | ||
import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js'; | ||
|
||
/** | ||
* A hook that returns the content rectangle of the target element. | ||
* The content rectangle is updated whenever the target element is resized. | ||
*/ | ||
export function useContentRectState(target: Unreffable<Element | null>): DOMRectReadOnly | null { | ||
const [contentRect, setContentRect] = useState<DOMRectReadOnly | null>(null); | ||
const rafRef = useRef(0); | ||
|
||
useResizeObserver(target, (entries) => { | ||
cancelAnimationFrame(rafRef.current); | ||
rafRef.current = requestAnimationFrame(() => { | ||
setContentRect(entries.at(0)?.contentRect ?? null); | ||
}); | ||
}); | ||
|
||
useMount(() => { | ||
setContentRect(unref(target)?.getBoundingClientRect() ?? null); | ||
}); | ||
|
||
return contentRect; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters