-
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.
- Loading branch information
1 parent
be8568c
commit 4d93490
Showing
6 changed files
with
335 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,50 @@ | ||
import { Meta, Canvas, Story } from '@storybook/addon-docs/blocks'; | ||
import { AutoAdjustFontSize } from './AutoAdjustFontSize'; | ||
import * as stories from './AutoAdjustFontSize.stories'; | ||
|
||
<Meta title="Components / AutoAdjustFontSize" component={AutoAdjustFontSize} /> | ||
|
||
# AutoAdjustFontSize | ||
|
||
A component that automatically adjusts the font size of its children to fit within its parent | ||
container. | ||
|
||
## Props | ||
|
||
- `children`: The content to be displayed within the component. | ||
- `minFontSize`: The minimum font size in pixels. Default is 13 (minimal accessible font size). | ||
- `maxFontSize`: The maximum font size in pixels. | ||
- `axis`: The axis along which the font size should be adjusted. Can be 'x' or 'y'. Default is | ||
undefined (both axes). | ||
|
||
## Usage | ||
|
||
```tsx | ||
<AutoAdjustFontSize> | ||
Hello World! | ||
<AutoAdjustFontSize> | ||
``` | ||
|
||
Limit the font adjust to an axis: | ||
|
||
```tsx | ||
<AutoAdjustFontSize axis="x"> | ||
Hello World! | ||
<AutoAdjustFontSize> | ||
``` | ||
|
||
```tsx | ||
<AutoAdjustFontSize axis="y"> | ||
Hello World! | ||
<AutoAdjustFontSize> | ||
``` | ||
|
||
## Demo | ||
|
||
<Canvas withToolbar> | ||
<Story of={stories.Horizontal} /> | ||
</Canvas> | ||
|
||
<Canvas withToolbar> | ||
<Story of={stories.Vertical} /> | ||
</Canvas> |
137 changes: 137 additions & 0 deletions
137
src/components/AutoAdjustFontSize/AutoAdjustFontSize.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,137 @@ | ||
import { expect } from '@storybook/jest'; | ||
import { type Meta, type StoryObj } from '@storybook/react'; | ||
import { within } from '@storybook/testing-library'; | ||
import { AutoAdjustFontSize } from './AutoAdjustFontSize.js'; | ||
|
||
const meta = { | ||
title: 'Components / AutoAdjustFontSize', | ||
component: AutoAdjustFontSize, | ||
argTypes: { | ||
minFontSize: { | ||
control: { | ||
type: 'number', | ||
}, | ||
}, | ||
maxFontSize: { | ||
control: { | ||
type: 'number', | ||
}, | ||
}, | ||
axis: { | ||
options: ['x', 'y'], | ||
control: { type: 'select' }, | ||
}, | ||
}, | ||
} satisfies Meta; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Horizontal: Story = { | ||
args: { | ||
children: "Hello World, how's life?", | ||
axis: 'x', | ||
}, | ||
render(props) { | ||
return ( | ||
<div | ||
style={{ | ||
outline: '1px solid red', | ||
inlineSize: '50vw', | ||
}} | ||
> | ||
<AutoAdjustFontSize {...props} /> | ||
</div> | ||
); | ||
}, | ||
}; | ||
|
||
export const HorizontalStatic: Story = { | ||
args: { | ||
children: "Hello World, how's life?", | ||
axis: 'x', | ||
}, | ||
render(props) { | ||
return ( | ||
<div | ||
style={{ | ||
outline: '1px solid red', | ||
inlineSize: 250, | ||
}} | ||
> | ||
<AutoAdjustFontSize {...props} /> | ||
</div> | ||
); | ||
}, | ||
async play({ canvasElement }) { | ||
const canvas = within(canvasElement); | ||
const element = canvas.getByText("Hello World, how's life?"); | ||
|
||
expect(element).toHaveStyle({ | ||
fontSize: '24px', | ||
whiteSpace: 'nowrap', | ||
}); | ||
}, | ||
}; | ||
|
||
export const Vertical: Story = { | ||
args: { | ||
children: "Hello World, how's life?", | ||
axis: 'y', | ||
}, | ||
render(props) { | ||
return ( | ||
<div | ||
style={{ | ||
outline: '1px solid red', | ||
inlineSize: 'min-content', | ||
blockSize: '50vh', | ||
}} | ||
> | ||
<AutoAdjustFontSize | ||
{...props} | ||
style={{ | ||
writingMode: 'vertical-rl', | ||
textOrientation: 'mixed', | ||
}} | ||
/> | ||
</div> | ||
); | ||
}, | ||
}; | ||
|
||
export const VerticalStatic: Story = { | ||
args: { | ||
children: "Hello World, how's life?", | ||
axis: 'y', | ||
}, | ||
render(props) { | ||
return ( | ||
<div | ||
style={{ | ||
outline: '1px solid red', | ||
inlineSize: 'min-content', | ||
blockSize: 250, | ||
}} | ||
> | ||
<AutoAdjustFontSize | ||
{...props} | ||
style={{ | ||
writingMode: 'vertical-rl', | ||
textOrientation: 'mixed', | ||
}} | ||
/> | ||
</div> | ||
); | ||
}, | ||
async play({ canvasElement }) { | ||
const canvas = within(canvasElement); | ||
const element = canvas.getByText("Hello World, how's life?"); | ||
|
||
expect(element).toHaveStyle({ | ||
fontSize: '24px', | ||
whiteSpace: 'nowrap', | ||
}); | ||
}, | ||
}; |
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,52 @@ | ||
import { useCallback, useEffect, type ComponentProps, type ReactNode, useState } from 'react'; | ||
import { ensuredForwardRef, useResizeObserver } from '../../index.js'; | ||
import { adjustFontSize } from '../../utils/adjustFontSize/adjustFontSize.js'; | ||
|
||
type AutoAdjustFontSizeProps = ComponentProps<'div'> & { | ||
children: ReactNode; | ||
minFontSize?: number; | ||
maxFontSize?: number; | ||
axis?: 'x' | 'y'; | ||
}; | ||
|
||
export const AutoAdjustFontSize = ensuredForwardRef<HTMLDivElement, AutoAdjustFontSizeProps>( | ||
({ children, minFontSize, maxFontSize, axis, style }, ref) => { | ||
const [parentElement, setParentElement] = useState<HTMLElement | null>(null); | ||
|
||
const updateFontSize = useCallback(() => { | ||
if (!ref.current) { | ||
return; | ||
} | ||
|
||
adjustFontSize(ref.current, minFontSize, maxFontSize, axis); | ||
}, [axis, maxFontSize, minFontSize, ref]); | ||
|
||
useEffect(() => { | ||
if (!ref.current) { | ||
return; | ||
} | ||
|
||
setParentElement(ref.current.parentElement); | ||
|
||
updateFontSize(); | ||
ref.current.style.visibility = 'visible'; | ||
}, [ref, updateFontSize]); | ||
|
||
useResizeObserver(parentElement, updateFontSize); | ||
|
||
return ( | ||
<div | ||
ref={ref} | ||
style={{ | ||
whiteSpace: 'nowrap', | ||
inlineSize: 'max-content', | ||
blockSize: 'max-content', | ||
visibility: 'hidden', | ||
...style, | ||
}} | ||
> | ||
{children} | ||
</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
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,37 @@ | ||
import { Meta, Canvas, Story } from '@storybook/blocks'; | ||
import { adjustFontSize } from './adjustFontSize'; | ||
|
||
<Meta title="utils/adjustFontSize" /> | ||
|
||
# adjustFontSize | ||
|
||
Adjusts the font size of an HTML element to fit within its parent container. | ||
|
||
## Reference | ||
|
||
```ts | ||
function adjustFontSize( | ||
element: HTMLElement, | ||
minFontSize = 13, | ||
maxFontSize = 113, | ||
axis?: 'x' | 'y', | ||
) => void; | ||
``` | ||
|
||
## Parameters | ||
|
||
- `element`: The HTML element whose font size needs to be adjusted. | ||
- `minFontSize`: The minimum font size in pixels. Default is 13 (minimal accessible font size). | ||
- `maxFontSize`: The maximum font size in pixels. | ||
- `axis`: The axis along which the font size should be adjusted. Can be 'x' or 'y'. Default is | ||
undefined (both axes). | ||
|
||
## Usage | ||
|
||
```ts | ||
const element = document.createElement('div'); | ||
element.textContent = 'Hello, world!'; | ||
document.body.appendChild(element); | ||
|
||
adjustFontSize(element); | ||
``` |
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,57 @@ | ||
/** | ||
* Adjusts the font size of an HTML element to fit within its parent container. | ||
* | ||
* @param element - The HTML element whose font size needs to be adjusted. | ||
* @param minFontSize - The minimum font size in pixels. Default is 13 (minimal accessible font size). | ||
* @param maxFontSize - The maximum font size in pixels. | ||
* @param axis - The axis along which the font size should be adjusted. Can be 'x' or 'y'. Default is undefined (both axes). | ||
* | ||
* @throws {TypeError} If the parent element is null or if minFontSize is greater than maxFontSize. | ||
*/ | ||
export function adjustFontSize( | ||
element: HTMLElement, | ||
// eslint-disable-next-line default-param-last | ||
minFontSize = 13, | ||
// eslint-disable-next-line default-param-last | ||
maxFontSize?: number, | ||
axis?: 'x' | 'y', | ||
): void { | ||
if (maxFontSize && minFontSize > maxFontSize) { | ||
throw new TypeError('minFontSize is greater than maxFontSize'); | ||
} | ||
|
||
if (element.parentElement === null) { | ||
throw new TypeError('Parent element is null'); | ||
} | ||
|
||
// minimum font size in pixels | ||
let min = minFontSize; | ||
// maximum font size in pixels | ||
let max = | ||
maxFontSize ?? Math.max(element.parentElement.clientWidth, element.parentElement.clientHeight); | ||
let lastGoodFontSize; | ||
|
||
while (min <= max) { | ||
const mid = Math.floor((min + max) / 2); | ||
element.style.fontSize = `${mid}px`; | ||
|
||
const { width: elementWidthAfterLayout, height: elementHeightAfterLayout } = | ||
element.getBoundingClientRect(); | ||
const { width: parentElementWidthAfterLayout, height: parentElementHeightAfterLayout } = | ||
element.parentElement.getBoundingClientRect(); | ||
|
||
const exceedsWidth = axis !== 'y' && elementWidthAfterLayout > parentElementWidthAfterLayout; | ||
const exceedsHeight = axis !== 'x' && elementHeightAfterLayout > parentElementHeightAfterLayout; | ||
|
||
if (exceedsWidth || exceedsHeight) { | ||
// If the text is too wide/tall, decrease the font size | ||
max = mid - 1; | ||
} else { | ||
// If the text fits, increase the font size | ||
lastGoodFontSize = mid; | ||
min = mid + 1; | ||
} | ||
} | ||
|
||
element.style.fontSize = `${lastGoodFontSize}px`; | ||
} |