diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 3c8176d2c29621..779c1b42382904 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -34,6 +34,7 @@
- `SlotFill`: Migrate to TypeScript and Convert to Functional Component ``. ([#51350](https://github.com/WordPress/gutenberg/pull/51350)).
- `Components`: move `ui/utils` to `utils` and remove `ui/` folder ([#54922](https://github.com/WordPress/gutenberg/pull/54922)).
- Ensure `@types/` dependencies used by final type files are included in the main dependency field ([#50231](https://github.com/WordPress/gutenberg/pull/50231)).
+- `Text`: Migrate to TypeScript. ([#54953](https://github.com/WordPress/gutenberg/pull/54953)).
## 25.8.0 (2023-09-20)
diff --git a/packages/components/src/heading/stories/index.story.tsx b/packages/components/src/heading/stories/index.story.tsx
index e774fd53312732..d82a59f08c825b 100644
--- a/packages/components/src/heading/stories/index.story.tsx
+++ b/packages/components/src/heading/stories/index.story.tsx
@@ -12,7 +12,6 @@ const meta: Meta< typeof Heading > = {
component: Heading,
title: 'Components (Experimental)/Heading',
argTypes: {
- adjustLineHeightForInnerControls: { control: { type: 'text' } },
as: { control: { type: 'text' } },
color: { control: { type: 'color' } },
display: { control: { type: 'text' } },
@@ -20,9 +19,8 @@ const meta: Meta< typeof Heading > = {
lineHeight: { control: { type: 'text' } },
optimizeReadabilityFor: { control: { type: 'color' } },
variant: {
- control: { type: 'radio' },
- options: [ 'undefined', 'muted' ],
- mapping: { undefined, muted: 'muted' },
+ control: { type: 'select' },
+ options: [ undefined, 'muted' ],
},
weight: { control: { type: 'text' } },
},
diff --git a/packages/components/src/text/README.md b/packages/components/src/text/README.md
index 7747ec9cbc7277..6b1fc156158407 100644
--- a/packages/components/src/text/README.md
+++ b/packages/components/src/text/README.md
@@ -22,7 +22,7 @@ function Example() {
### adjustLineHeightForInnerControls
-**Type**: `boolean`,`"large"`,`"medium"`,`"small"`,`"xSmall"`
+**Type**: `"large"`,`"medium"`,`"small"`,`"xSmall"`
Automatically calculate the appropriate line-height value for contents that render text and Control elements (e.g. `TextInput`).
@@ -31,7 +31,7 @@ import { __experimentalText as Text, TextInput } from '@wordpress/components';
function Example() {
return (
-
+
Lorem ipsum dolor sit amet, consectetur
diff --git a/packages/components/src/text/component.js b/packages/components/src/text/component.tsx
similarity index 64%
rename from packages/components/src/text/component.js
rename to packages/components/src/text/component.tsx
index f1ce842dae9153..f3fe69d936584c 100644
--- a/packages/components/src/text/component.js
+++ b/packages/components/src/text/component.tsx
@@ -1,15 +1,20 @@
/**
* Internal dependencies
*/
+import type { WordPressComponentProps } from '../context';
import { contextConnect } from '../context';
import { View } from '../view';
import useText from './hook';
+import type { Props } from './types';
/**
- * @param {import('../context').WordPressComponentProps} props
- * @param {import('react').ForwardedRef} forwardedRef
+ * @param props
+ * @param forwardedRef
*/
-function Text( props, forwardedRef ) {
+function UnconnectedText(
+ props: WordPressComponentProps< Props, 'span' >,
+ forwardedRef: React.ForwardedRef< any >
+) {
const textProps = useText( props );
return ;
@@ -31,6 +36,5 @@ function Text( props, forwardedRef ) {
* }
* ```
*/
-const ConnectedText = contextConnect( Text, 'Text' );
-
-export default ConnectedText;
+export const Text = contextConnect( UnconnectedText, 'Text' );
+export default Text;
diff --git a/packages/components/src/text/hook.js b/packages/components/src/text/hook.ts
similarity index 87%
rename from packages/components/src/text/hook.js
rename to packages/components/src/text/hook.ts
index 5198845c1dae78..a447b2ce5133be 100644
--- a/packages/components/src/text/hook.js
+++ b/packages/components/src/text/hook.ts
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
+import type { SerializedStyles } from '@emotion/react';
import { css } from '@emotion/react';
/**
@@ -11,6 +12,7 @@ import { useMemo, Children, cloneElement } from '@wordpress/element';
/**
* Internal dependencies
*/
+import type { WordPressComponentProps } from '../context';
import { hasConnectNamespace, useContextSystem } from '../context';
import { useTruncate } from '../truncate';
import { getOptimalTextShade } from '../utils/colors';
@@ -20,11 +22,15 @@ import { getFontSize } from '../utils/font-size';
import { CONFIG, COLORS } from '../utils';
import { getLineHeight } from './get-line-height';
import { useCx } from '../utils/hooks/use-cx';
+import type { Props } from './types';
+import type React from 'react';
/**
* @param {import('../context').WordPressComponentProps} props
*/
-export default function useText( props ) {
+export default function useText(
+ props: WordPressComponentProps< Props, 'span' >
+) {
const {
adjustLineHeightForInnerControls,
align,
@@ -50,8 +56,7 @@ export default function useText( props ) {
...otherProps
} = useContextSystem( props, 'Text' );
- /** @type {import('react').ReactNode} */
- let content = children;
+ let content: React.ReactNode = children;
const isHighlighter = Array.isArray( highlightWords );
const isCaption = size === 'caption';
@@ -64,9 +69,7 @@ export default function useText( props ) {
content = createHighlighterText( {
autoEscape: highlightEscape,
- // Disable reason: We need to disable this otherwise it erases the cast
- // eslint-disable-next-line object-shorthand
- children: /** @type {string} */ ( children ),
+ children,
caseSensitive: highlightCaseSensitive,
searchWords: highlightWords,
sanitize: highlightSanitize,
@@ -76,7 +79,7 @@ export default function useText( props ) {
const cx = useCx();
const classes = useMemo( () => {
- const sx = {};
+ const sx: Record< string, SerializedStyles | null > = {};
const lineHeight = getLineHeight(
adjustLineHeightForInnerControls,
@@ -87,12 +90,7 @@ export default function useText( props ) {
color,
display,
fontSize: getFontSize( size ),
- /* eslint-disable jsdoc/valid-types */
- fontWeight:
- /** @type {import('react').CSSProperties['fontWeight']} */ (
- weight
- ),
- /* eslint-enable jsdoc/valid-types */
+ fontWeight: weight,
lineHeight,
letterSpacing,
textAlign: align,
@@ -143,8 +141,7 @@ export default function useText( props ) {
weight,
] );
- /** @type {undefined | 'auto' | 'none'} */
- let finalEllipsizeMode;
+ let finalEllipsizeMode: undefined | 'auto' | 'none';
if ( truncate === true ) {
finalEllipsizeMode = 'auto';
}
diff --git a/packages/components/src/text/index.js b/packages/components/src/text/index.ts
similarity index 100%
rename from packages/components/src/text/index.js
rename to packages/components/src/text/index.ts
diff --git a/packages/components/src/text/stories/index.story.js b/packages/components/src/text/stories/index.story.js
deleted file mode 100644
index b1b4e3f455536b..00000000000000
--- a/packages/components/src/text/stories/index.story.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Internal dependencies
- */
-import { Text } from '..';
-
-export default {
- component: Text,
- title: 'Components (Experimental)/Text',
-};
-
-export const _default = () => {
- return Hello;
-};
-
-export const Truncate = () => {
- return (
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut
- facilisis dictum tortor, eu tincidunt justo scelerisque tincidunt.
- Duis semper dui id augue malesuada, ut feugiat nisi aliquam.
- Vestibulum venenatis diam sem, finibus dictum massa semper in. Nulla
- facilisi. Nunc vulputate faucibus diam, in lobortis arcu ornare vel.
- In dignissim nunc sed facilisis finibus. Etiam imperdiet mattis
- arcu, sed rutrum sapien blandit gravida. Aenean sollicitudin neque
- eget enim blandit, sit amet rutrum leo vehicula. Nunc malesuada
- ultricies eros ut faucibus. Aliquam erat volutpat. Nulla nec feugiat
- risus. Vivamus iaculis dui aliquet ante ultricies feugiat.
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
- posuere cubilia curae; Vivamus nec pretium velit, sit amet
- consectetur ante. Praesent porttitor ex eget fermentum mattis.
-
- );
-};
-
-export const Highlight = () => {
- return (
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut
- facilisis dictum tortor, eu tincidunt justo scelerisque tincidunt.
- Duis semper dui id augue malesuada, ut feugiat nisi aliquam.
- Vestibulum venenatis diam sem, finibus dictum massa semper in. Nulla
- facilisi. Nunc vulputate faucibus diam, in lobortis arcu ornare vel.
- In dignissim nunc sed facilisis finibus. Etiam imperdiet mattis
- arcu, sed rutrum sapien blandit gravida. Aenean sollicitudin neque
- eget enim blandit, sit amet rutrum leo vehicula. Nunc malesuada
- ultricies eros ut faucibus. Aliquam erat volutpat. Nulla nec feugiat
- risus. Vivamus iaculis dui aliquet ante ultricies feugiat.
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
- posuere cubilia curae; Vivamus nec pretium velit, sit amet
- consectetur ante. Praesent porttitor ex eget fermentum mattis.
-
- );
-};
diff --git a/packages/components/src/text/stories/index.story.tsx b/packages/components/src/text/stories/index.story.tsx
new file mode 100644
index 00000000000000..f762ca3b4e3ff7
--- /dev/null
+++ b/packages/components/src/text/stories/index.story.tsx
@@ -0,0 +1,80 @@
+/**
+ * External dependencies
+ */
+import type { Meta, StoryFn } from '@storybook/react';
+
+/**
+ * Internal dependencies
+ */
+import { Text } from '../component';
+
+const meta: Meta< typeof Text > = {
+ component: Text,
+ title: 'Components (Experimental)/Text',
+ argTypes: {
+ as: { control: { type: 'text' } },
+ color: { control: { type: 'color' } },
+ display: { control: { type: 'text' } },
+ lineHeight: { control: { type: 'text' } },
+ letterSpacing: { control: { type: 'text' } },
+ optimizeReadabilityFor: { control: { type: 'color' } },
+ size: { control: { type: 'text' } },
+ variant: {
+ options: [ undefined, 'muted' ],
+ control: { type: 'select' },
+ },
+ weight: { control: { type: 'text' } },
+ },
+ parameters: {
+ actions: { argTypesRegex: '^on.*' },
+ controls: { expanded: true },
+ docs: { canvas: { sourceState: 'shown' } },
+ },
+};
+export default meta;
+
+const Template: StoryFn< typeof Text > = ( props ) => {
+ return ;
+};
+
+export const Default = Template.bind( {} );
+Default.args = {
+ children: 'Code is poetry',
+};
+
+export const Truncate = Template.bind( {} );
+Truncate.args = {
+ children: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut
+facilisis dictum tortor, eu tincidunt justo scelerisque tincidunt.
+Duis semper dui id augue malesuada, ut feugiat nisi aliquam.
+Vestibulum venenatis diam sem, finibus dictum massa semper in. Nulla
+facilisi. Nunc vulputate faucibus diam, in lobortis arcu ornare vel.
+In dignissim nunc sed facilisis finibus. Etiam imperdiet mattis
+arcu, sed rutrum sapien blandit gravida. Aenean sollicitudin neque
+eget enim blandit, sit amet rutrum leo vehicula. Nunc malesuada
+ultricies eros ut faucibus. Aliquam erat volutpat. Nulla nec feugiat
+risus. Vivamus iaculis dui aliquet ante ultricies feugiat.
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
+posuere cubilia curae; Vivamus nec pretium velit, sit amet
+consectetur ante. Praesent porttitor ex eget fermentum mattis.`,
+ numberOfLines: 2,
+ truncate: true,
+};
+
+export const Highlight = Template.bind( {} );
+Highlight.args = {
+ children: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut
+facilisis dictum tortor, eu tincidunt justo scelerisque tincidunt.
+Duis semper dui id augue malesuada, ut feugiat nisi aliquam.
+Vestibulum venenatis diam sem, finibus dictum massa semper in. Nulla
+facilisi. Nunc vulputate faucibus diam, in lobortis arcu ornare vel.
+In dignissim nunc sed facilisis finibus. Etiam imperdiet mattis
+arcu, sed rutrum sapien blandit gravida. Aenean sollicitudin neque
+eget enim blandit, sit amet rutrum leo vehicula. Nunc malesuada
+ultricies eros ut faucibus. Aliquam erat volutpat. Nulla nec feugiat
+risus. Vivamus iaculis dui aliquet ante ultricies feugiat.
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
+posuere cubilia curae; Vivamus nec pretium velit, sit amet
+consectetur ante. Praesent porttitor ex eget fermentum mattis.`,
+ highlightWords: [ 'con' ],
+};
diff --git a/packages/components/src/text/styles.js b/packages/components/src/text/styles.ts
similarity index 100%
rename from packages/components/src/text/styles.js
rename to packages/components/src/text/styles.ts
diff --git a/packages/components/src/text/types.ts b/packages/components/src/text/types.ts
index df60f4ee0e2d7b..e4702c1f3257dd 100644
--- a/packages/components/src/text/types.ts
+++ b/packages/components/src/text/types.ts
@@ -29,12 +29,7 @@ export interface Props extends TruncateProps {
/**
* Automatically calculate the appropriate line-height value for contents that render text and Control elements (e.g. `TextInput`).
*/
- adjustLineHeightForInnerControls?:
- | boolean
- | 'large'
- | 'medium'
- | 'small'
- | 'xSmall';
+ adjustLineHeightForInnerControls?: 'large' | 'medium' | 'small' | 'xSmall';
/**
* Adjusts the text color.
*/
diff --git a/packages/components/src/text/utils.js b/packages/components/src/text/utils.ts
similarity index 76%
rename from packages/components/src/text/utils.js
rename to packages/components/src/text/utils.ts
index 2496c86cca25ae..85e41a56c6e349 100644
--- a/packages/components/src/text/utils.js
+++ b/packages/components/src/text/utils.ts
@@ -2,6 +2,7 @@
* External dependencies
*/
import memoize from 'memize';
+import type { FindAllArgs } from 'highlight-words-core';
import { findAll } from 'highlight-words-core';
/**
@@ -14,7 +15,6 @@ import { createElement } from '@wordpress/element';
* https://github.com/bvaughn/react-highlight-words/blob/HEAD/src/Highlighter.js
*/
-/* eslint-disable jsdoc/valid-types */
/**
* @typedef Options
* @property {string} [activeClassName=''] Classname for active highlighted areas.
@@ -33,28 +33,55 @@ import { createElement } from '@wordpress/element';
* @property {import('react').AllHTMLAttributes['style']} [unhighlightStyle] Style to apply to unhighlighted text.
*/
+interface Options {
+ activeClassName?: string;
+ activeIndex?: number;
+ activeStyle?: React.AllHTMLAttributes< HTMLDivElement >[ 'style' ];
+ autoEscape?: boolean;
+ caseSensitive?: boolean;
+ children: string;
+ findChunks?: FindAllArgs[ 'findChunks' ];
+ highlightClassName?: string | Record< string, unknown >;
+ highlightStyle?: React.AllHTMLAttributes< HTMLDivElement >[ 'style' ];
+ highlightTag?: keyof JSX.IntrinsicElements;
+ sanitize?: FindAllArgs[ 'sanitize' ];
+ searchWords?: string[];
+ unhighlightClassName?: string;
+ unhighlightStyle?: React.AllHTMLAttributes< HTMLDivElement >[ 'style' ];
+}
+
/**
* Maps props to lowercase names.
*
- * @template {Record} T
- * @param {T} object Props to map.
- * @return {{[K in keyof T as Lowercase]: T[K]}} The mapped props.
+ * @param object Props to map.
+ * @return The mapped props.
*/
-/* eslint-enable jsdoc/valid-types */
-const lowercaseProps = ( object ) => {
- /** @type {any} */
- const mapped = {};
+const lowercaseProps = < T extends Record< string, unknown > >( object: T ) => {
+ const mapped: Record< string, unknown > = {};
for ( const key in object ) {
mapped[ key.toLowerCase() ] = object[ key ];
}
- return mapped;
+ return mapped as { [ K in keyof T as Lowercase< string & K > ]: T[ K ] };
};
const memoizedLowercaseProps = memoize( lowercaseProps );
/**
- *
- * @param {Options} options
+ * @param options
+ * @param options.activeClassName
+ * @param options.activeIndex
+ * @param options.activeStyle
+ * @param options.autoEscape
+ * @param options.caseSensitive
+ * @param options.children
+ * @param options.findChunks
+ * @param options.highlightClassName
+ * @param options.highlightStyle
+ * @param options.highlightTag
+ * @param options.sanitize
+ * @param options.searchWords
+ * @param options.unhighlightClassName
+ * @param options.unhighlightStyle
*/
export function createHighlighterText( {
activeClassName = '',
@@ -71,7 +98,7 @@ export function createHighlighterText( {
searchWords = [],
unhighlightClassName = '',
unhighlightStyle,
-} ) {
+}: Options ) {
if ( ! children ) return null;
if ( typeof children !== 'string' ) return children;
@@ -122,8 +149,7 @@ export function createHighlighterText( {
? Object.assign( {}, highlightStyle, activeStyle )
: highlightStyle;
- /** @type {Record} */
- const props = {
+ const props: Record< string, unknown > = {
children: text,
className: highlightClassNames,
key: index,