Skip to content

Commit 07667fc

Browse files
authored
Feature: FitText (#1342)
Add `FitText` component.
1 parent c31b26a commit 07667fc

File tree

8 files changed

+136
-4
lines changed

8 files changed

+136
-4
lines changed

.changeset/fuzzy-tips-build.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'spectacle': minor
3+
---
4+
5+
Add `FitText` typography component.

docs/api-reference.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ These tags are for displaying textual content.
5656
| Tag Name | Theme Props | Additional Props | Default Props |
5757
|---------------------|-------------------------------------------------------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5858
| **`Text`** | [**Space**](./props#space)<br />[**Color**](./props#color)<br /> [**Typography**](./props#typography) || **color**: primary<br /> **fontFamily**: text<br />**fontSize**: text<br />**textAlign**: left<br />**margin**: textMargin |
59+
| **`FitText`** | [**Space**](./props#space)<br />[**Color**](./props#color)<br /> [**Typography**](./props#typography) || **color**: primary<br /> **fontFamily**: text<br />**fontSize**: text<br />**textAlign**: center<br />**margin**: textMargin |
5960
| **`Heading`** | [**Space**](./props#space)<br />[**Color**](./props#color)<br /> [**Typography**](./props#typography) || **color**: secondary<br /> **fontFamily**: header<br />**fontSize**: h1<br />**fontWeight**: bold<br />**textAlign**: center<br />**margin**: headerMargin |
6061
| **`Link`** | [**Space**](./props#space)<br />[**Color**](./props#color)<br /> [**Typography**](./props#typography)<br /> | **href**: PropTypes.string | **color**: quaternary<br /> **fontFamily**: text<br />**fontSize**: text<br />**textDecoration**: underline<br />**textAlign**: left<br />**margin**: textMargin |
6162
| **`Quote`** | [**Space**](./props#space)<br />[**Color**](./props#color)<br /> [**Typography**](./props#typography)<br /> || **color**: primary<br /> **fontFamily**: text<br />**fontSize**: text<br />**textAlign**: left<br />**borderLeft**: 1px solid secondary |

examples/js/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Slide,
1313
Deck,
1414
Text,
15+
FitText,
1516
Grid,
1617
Box,
1718
Image,
@@ -138,6 +139,19 @@ const Presentation = () => (
138139
</Appear>
139140
</OrderedList>
140141
</Slide>
142+
<Slide>
143+
<Heading>This is a Heading</Heading>
144+
<FitText>
145+
This is a <CodeSpan>FitText</CodeSpan> component
146+
</FitText>
147+
<FitText
148+
color="secondary"
149+
style={{ textTransform: 'uppercase', fontFamily: 'Comic Sans MS' }}
150+
>
151+
Shorter fit text
152+
</FitText>
153+
<Text>This is a Text. (Resize this window!)</Text>
154+
</Slide>
141155
<Slide>
142156
<FlexBox>
143157
<Text>These</Text>

examples/one-page/index.html

+10
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
Slide,
6161
Deck,
6262
Text,
63+
FitText,
6364
Grid,
6465
Box,
6566
Image,
@@ -170,6 +171,15 @@
170171
</${Appear}>
171172
</${OrderedList}>
172173
</${Slide}>
174+
<${Slide}>
175+
<${Heading}>This is a Heading</${Heading}>
176+
<${FitText}>This is a <${CodeSpan}>FitText</${CodeSpan}> component</${FitText}>
177+
<${FitText} color="secondary" style=${{
178+
textTransform: 'uppercase',
179+
fontFamily: 'Comic Sans MS'
180+
}}>Shorter fit text</${FitText}>
181+
<${Text}>This is a Text. (Resize this window!)</${Text}>
182+
</${Slide}>
173183
<${Slide}>
174184
<${FlexBox}>
175185
<${Text}>These</${Text}>

examples/typescript/index.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Slide,
1313
Deck,
1414
Text,
15+
FitText,
1516
Grid,
1617
Box,
1718
Image,
@@ -137,6 +138,19 @@ const Presentation = () => (
137138
</Appear>
138139
</OrderedList>
139140
</Slide>
141+
<Slide>
142+
<Heading>This is a Heading</Heading>
143+
<FitText>
144+
This is a <CodeSpan>FitText</CodeSpan> component
145+
</FitText>
146+
<FitText
147+
color="secondary"
148+
style={{ textTransform: 'uppercase', fontFamily: 'Comic Sans MS' }}
149+
>
150+
Shorter fit text
151+
</FitText>
152+
<Text>This is a Text. (Resize this window!)</Text>
153+
</Slide>
140154
<Slide>
141155
<FlexBox>
142156
<Text>These</Text>

packages/spectacle/src/components/typography.test.tsx

+31-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
UnorderedList,
1111
ListItem,
1212
Link,
13-
CodeSpan
13+
CodeSpan,
14+
FitText
1415
} from './typography';
1516
import { render } from '@testing-library/react';
1617

@@ -91,3 +92,32 @@ describe('<CodeSpan />', () => {
9192
expect(container.querySelector('code')?.innerHTML).toBe('Code!');
9293
});
9394
});
95+
96+
describe('<FitText />', () => {
97+
beforeEach(() => {
98+
// Default mock implementation
99+
jest.mock('use-resize-observer', () => {
100+
return { width: 500, height: 100 };
101+
});
102+
});
103+
104+
afterEach(() => {
105+
jest.clearAllMocks();
106+
});
107+
108+
it('should render text content correctly', () => {
109+
const { getByText } = mountWithTheme(<FitText>Spectacle!</FitText>);
110+
expect(getByText('Spectacle!')).toBeInTheDocument();
111+
});
112+
113+
it('should apply color and typography props correctly', () => {
114+
const { getByText } = mountWithTheme(
115+
<FitText color="secondary" fontSize="h1">
116+
Spectacle!
117+
</FitText>
118+
);
119+
const textElement = getByText('Spectacle!');
120+
expect(textElement).toHaveStyle({ color: defaultTheme.colors.secondary });
121+
expect(textElement).toHaveStyle({ fontSize: 'h1' });
122+
});
123+
});

packages/spectacle/src/components/typography.tsx

+59-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import {
1010
SpaceProps,
1111
BorderProps
1212
} from 'styled-system';
13-
import { FC, PropsWithChildren } from 'react';
13+
import {
14+
FC,
15+
PropsWithChildren,
16+
RefAttributes,
17+
useRef,
18+
useState,
19+
HTMLAttributes
20+
} from 'react';
21+
import useResizeObserver from 'use-resize-observer';
1422

1523
const decoration = system({ textDecoration: true });
1624
type DecorationProps = Pick<CSSObject, 'textDecoration'>;
@@ -113,6 +121,54 @@ ListItem.defaultProps = {
113121
margin: 0
114122
};
115123

124+
const FitContainer = styled.div`
125+
width: 100%;
126+
display: flex;
127+
align-items: center;
128+
justify-content: center;
129+
`;
130+
131+
const ScalableText = styled(
132+
Text as FC<CommonTypographyProps & RefAttributes<HTMLDivElement>>
133+
)<{ scale: number }>`
134+
transform-origin: center;
135+
transform: scale(${(props) => props.scale});
136+
white-space: nowrap;
137+
`;
138+
ScalableText.defaultProps = {
139+
...Text.defaultProps,
140+
textAlign: 'center',
141+
scale: 1
142+
};
143+
144+
const FitText: FC<
145+
PropsWithChildren<CommonTypographyProps & HTMLAttributes<HTMLDivElement>>
146+
> = (props) => {
147+
const containerRef = useRef<HTMLDivElement>(null);
148+
const textRef = useRef<HTMLDivElement>(null);
149+
const [scale, setScale] = useState(1);
150+
151+
useResizeObserver({
152+
ref: containerRef,
153+
onResize: () => {
154+
if (!containerRef.current || !textRef.current) return;
155+
156+
const containerWidth = containerRef.current.offsetWidth;
157+
const textWidth = textRef.current.offsetWidth;
158+
if (textWidth === 0) return;
159+
160+
const newScale = Math.min(containerWidth / textWidth);
161+
setScale(newScale);
162+
}
163+
});
164+
165+
return (
166+
<FitContainer ref={containerRef}>
167+
<ScalableText {...props} ref={textRef} scale={scale} />
168+
</FitContainer>
169+
);
170+
};
171+
116172
export {
117173
Text,
118174
Heading,
@@ -121,5 +177,6 @@ export {
121177
UnorderedList,
122178
ListItem,
123179
Link,
124-
CodeSpan
180+
CodeSpan,
181+
FitText
125182
};

packages/spectacle/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export {
1010
UnorderedList,
1111
Text,
1212
Link,
13-
CodeSpan
13+
CodeSpan,
14+
FitText
1415
} from './components/typography';
1516
export {
1617
Table,

0 commit comments

Comments
 (0)