diff --git a/.changeset/fuzzy-tips-build.md b/.changeset/fuzzy-tips-build.md new file mode 100644 index 00000000..7c1bb9e5 --- /dev/null +++ b/.changeset/fuzzy-tips-build.md @@ -0,0 +1,5 @@ +--- +'spectacle': minor +--- + +Add `FitText` typography component. diff --git a/docs/api-reference.md b/docs/api-reference.md index fd085794..6fe4b8d6 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -56,6 +56,7 @@ These tags are for displaying textual content. | Tag Name | Theme Props | Additional Props | Default Props | |---------------------|-------------------------------------------------------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **`Text`** | [**Space**](./props#space)
[**Color**](./props#color)
[**Typography**](./props#typography) | — | **color**: primary
**fontFamily**: text
**fontSize**: text
**textAlign**: left
**margin**: textMargin | +| **`FitText`** | [**Space**](./props#space)
[**Color**](./props#color)
[**Typography**](./props#typography) | — | **color**: primary
**fontFamily**: text
**fontSize**: text
**textAlign**: center
**margin**: textMargin | | **`Heading`** | [**Space**](./props#space)
[**Color**](./props#color)
[**Typography**](./props#typography) | — | **color**: secondary
**fontFamily**: header
**fontSize**: h1
**fontWeight**: bold
**textAlign**: center
**margin**: headerMargin | | **`Link`** | [**Space**](./props#space)
[**Color**](./props#color)
[**Typography**](./props#typography)
| **href**: PropTypes.string | **color**: quaternary
**fontFamily**: text
**fontSize**: text
**textDecoration**: underline
**textAlign**: left
**margin**: textMargin | | **`Quote`** | [**Space**](./props#space)
[**Color**](./props#color)
[**Typography**](./props#typography)
| — | **color**: primary
**fontFamily**: text
**fontSize**: text
**textAlign**: left
**borderLeft**: 1px solid secondary | diff --git a/examples/js/index.js b/examples/js/index.js index 2ae8f6de..b4e063b4 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -12,6 +12,7 @@ import { Slide, Deck, Text, + FitText, Grid, Box, Image, @@ -138,6 +139,19 @@ const Presentation = () => ( + + This is a Heading + + This is a FitText component + + + Shorter fit text + + This is a Text. (Resize this window!) + These diff --git a/examples/one-page/index.html b/examples/one-page/index.html index 5cf3f921..b7adc7c6 100644 --- a/examples/one-page/index.html +++ b/examples/one-page/index.html @@ -60,6 +60,7 @@ Slide, Deck, Text, + FitText, Grid, Box, Image, @@ -170,6 +171,15 @@ + <${Slide}> + <${Heading}>This is a Heading + <${FitText}>This is a <${CodeSpan}>FitText component + <${FitText} color="secondary" style=${{ + textTransform: 'uppercase', + fontFamily: 'Comic Sans MS' + }}>Shorter fit text + <${Text}>This is a Text. (Resize this window!) + <${Slide}> <${FlexBox}> <${Text}>These diff --git a/examples/typescript/index.tsx b/examples/typescript/index.tsx index 88aa0155..15337ced 100644 --- a/examples/typescript/index.tsx +++ b/examples/typescript/index.tsx @@ -12,6 +12,7 @@ import { Slide, Deck, Text, + FitText, Grid, Box, Image, @@ -137,6 +138,19 @@ const Presentation = () => ( + + This is a Heading + + This is a FitText component + + + Shorter fit text + + This is a Text. (Resize this window!) + These diff --git a/packages/spectacle/src/components/typography.test.tsx b/packages/spectacle/src/components/typography.test.tsx index 9ea28997..b8eb7338 100644 --- a/packages/spectacle/src/components/typography.test.tsx +++ b/packages/spectacle/src/components/typography.test.tsx @@ -10,7 +10,8 @@ import { UnorderedList, ListItem, Link, - CodeSpan + CodeSpan, + FitText } from './typography'; import { render } from '@testing-library/react'; @@ -91,3 +92,32 @@ describe('', () => { expect(container.querySelector('code')?.innerHTML).toBe('Code!'); }); }); + +describe('', () => { + beforeEach(() => { + // Default mock implementation + jest.mock('use-resize-observer', () => { + return { width: 500, height: 100 }; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render text content correctly', () => { + const { getByText } = mountWithTheme(Spectacle!); + expect(getByText('Spectacle!')).toBeInTheDocument(); + }); + + it('should apply color and typography props correctly', () => { + const { getByText } = mountWithTheme( + + Spectacle! + + ); + const textElement = getByText('Spectacle!'); + expect(textElement).toHaveStyle({ color: defaultTheme.colors.secondary }); + expect(textElement).toHaveStyle({ fontSize: 'h1' }); + }); +}); diff --git a/packages/spectacle/src/components/typography.tsx b/packages/spectacle/src/components/typography.tsx index 3e67c920..895acaa8 100644 --- a/packages/spectacle/src/components/typography.tsx +++ b/packages/spectacle/src/components/typography.tsx @@ -10,7 +10,15 @@ import { SpaceProps, BorderProps } from 'styled-system'; -import { FC, PropsWithChildren } from 'react'; +import { + FC, + PropsWithChildren, + RefAttributes, + useRef, + useState, + HTMLAttributes +} from 'react'; +import useResizeObserver from 'use-resize-observer'; const decoration = system({ textDecoration: true }); type DecorationProps = Pick; @@ -113,6 +121,54 @@ ListItem.defaultProps = { margin: 0 }; +const FitContainer = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: center; +`; + +const ScalableText = styled( + Text as FC> +)<{ scale: number }>` + transform-origin: center; + transform: scale(${(props) => props.scale}); + white-space: nowrap; +`; +ScalableText.defaultProps = { + ...Text.defaultProps, + textAlign: 'center', + scale: 1 +}; + +const FitText: FC< + PropsWithChildren> +> = (props) => { + const containerRef = useRef(null); + const textRef = useRef(null); + const [scale, setScale] = useState(1); + + useResizeObserver({ + ref: containerRef, + onResize: () => { + if (!containerRef.current || !textRef.current) return; + + const containerWidth = containerRef.current.offsetWidth; + const textWidth = textRef.current.offsetWidth; + if (textWidth === 0) return; + + const newScale = Math.min(containerWidth / textWidth); + setScale(newScale); + } + }); + + return ( + + + + ); +}; + export { Text, Heading, @@ -121,5 +177,6 @@ export { UnorderedList, ListItem, Link, - CodeSpan + CodeSpan, + FitText }; diff --git a/packages/spectacle/src/index.ts b/packages/spectacle/src/index.ts index e6e75ff8..ee448226 100644 --- a/packages/spectacle/src/index.ts +++ b/packages/spectacle/src/index.ts @@ -10,7 +10,8 @@ export { UnorderedList, Text, Link, - CodeSpan + CodeSpan, + FitText } from './components/typography'; export { Table,