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,