Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Truncate: Convert component to TypeScript #41697

Merged
merged 17 commits into from
Jun 23, 2022
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- `InputControl`: Add tests and update to use `@testing-library/user-event` ([#41421](https://github.com/WordPress/gutenberg/pull/41421)).
- `FormToggle`: Convert to TypeScript ([#41729](https://github.com/WordPress/gutenberg/pull/41729)).
- `ColorIndicator`: Convert to TypeScript ([#41587](https://github.com/WordPress/gutenberg/pull/41587)).
- `Truncate`: Convert to TypeScript ([#41697](https://github.com/WordPress/gutenberg/pull/41697)).
- `VStack`: Convert to TypeScript ([#41850](https://github.com/WordPress/gutenberg/pull/41587)).
- `AlignmentMatrixControl`: Refactor away from `_.flattenDeep()` in utils ([#41814](https://github.com/WordPress/gutenberg/pull/41814/)).
- `AutoComplete`: Revert recent `exhaustive-deps` refactor ([#41820](https://github.com/WordPress/gutenberg/pull/41820)).
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/text/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
import type { Props as TruncateProps } from '../truncate/types';
import type { TruncateProps } from '../truncate/types';

/**
* External dependencies
Expand Down
28 changes: 16 additions & 12 deletions packages/components/src/truncate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ function Example() {

## Props

##### ellipsis
##### `ellipsis`: `string`

**Type**: `string`
The ellipsis string when truncating the text by the `limit` prop's value.

The ellipsis string when `truncate` is set.
- Required: No
- Default: `…`

##### ellipsizeMode

**Type**: `"auto"`,`"head"`,`"tail"`,`"middle"`
##### `ellipsizeMode`: `'auto' | 'head' | 'tail' | 'middle' | 'none'`

Determines where to truncate. For example, we can truncate text right in the middle. To do this, we need to set `ellipsizeMode` to `middle` and a text `limit`.

Expand All @@ -41,17 +40,22 @@ Determines where to truncate. For example, we can truncate text right in the mid
- `middle`: Trims content in the middle. Requires a `limit`.
- `tail`: Trims content at the end. Requires a `limit`.

##### limit
- Required: No
- Default: `auto`

##### `limit`: `number`

**Type**: `number`
Determines the max number of characters to be displayed before the rest of the text gets truncated. Requires `ellipsizeMode` to assume values different from `auto` and `none`.

Determines the max characters when `truncate` is set.
- Required: No
- Default: `0`

##### numberOfLines
##### `numberOfLines`: `number`

**Type**: `number`
Clamps the text content to the specified `numberOfLines`, adding an ellipsis at the end. Note: this feature ignores the value of the `ellipsis` prop and always displays the default `…` ellipsis.

Clamps the text content to the specifiec `numberOfLines`, adding the `ellipsis` at the end.
- Required: No
- Default: `0`

```jsx
import { __experimentalTruncate as Truncate } from '@wordpress/components';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
/**
* External dependencies
*/
import type { ForwardedRef } from 'react';

/**
* Internal dependencies
*/
import { contextConnect } from '../ui/context';
import { contextConnect, WordPressComponentProps } from '../ui/context';
import { View } from '../view';
import useTruncate from './hook';
import type { TruncateProps } from './types';

/**
* @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'span'>} props
* @param {import('react').ForwardedRef<any>} forwardedRef
*/
function Truncate( props, forwardedRef ) {
function UnconnectedTruncate(
props: WordPressComponentProps< TruncateProps, 'span' >,
forwardedRef: ForwardedRef< any >
) {
const truncateProps = useTruncate( props );

return <View as="span" { ...truncateProps } ref={ forwardedRef } />;
Expand All @@ -21,7 +26,6 @@ function Truncate( props, forwardedRef ) {
* `Subheading` is used to render text content. However,`Truncate` is
* available for custom implementations.
*
* @example
* ```jsx
* import { __experimentalTruncate as Truncate } from `@wordpress/components`;
*
Expand All @@ -36,6 +40,6 @@ function Truncate( props, forwardedRef ) {
* }
* ```
*/
const ConnectedTruncate = contextConnect( Truncate, 'Truncate' );
export const Truncate = contextConnect( UnconnectedTruncate, 'Truncate' );

export default ConnectedTruncate;
export default Truncate;
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import { useMemo } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useContextSystem } from '../ui/context';
import { useContextSystem, WordPressComponentProps } from '../ui/context';
import * as styles from './styles';
import { TRUNCATE_ELLIPSIS, TRUNCATE_TYPE, truncateContent } from './utils';
import { useCx } from '../utils/hooks/use-cx';
import type { TruncateProps } from './types';

/**
* @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'span'>} props
*/
export default function useTruncate( props ) {
export default function useTruncate(
props: WordPressComponentProps< TruncateProps, 'span' >
) {
const {
className,
children,
Expand All @@ -33,7 +33,7 @@ export default function useTruncate( props ) {
const cx = useCx();

const truncatedContent = truncateContent(
typeof children === 'string' ? /** @type {string} */ ( children ) : '',
typeof children === 'string' ? children : '',
{
ellipsis,
ellipsizeMode,
Expand All @@ -45,9 +45,7 @@ export default function useTruncate( props ) {
const shouldTruncate = ellipsizeMode === TRUNCATE_TYPE.auto;

const classes = useMemo( () => {
const sx = {};

sx.numberOfLines = css`
const truncateLines = css`
-webkit-box-orient: vertical;
-webkit-line-clamp: ${ numberOfLines };
display: -webkit-box;
Expand All @@ -56,7 +54,7 @@ export default function useTruncate( props ) {

return cx(
shouldTruncate && ! numberOfLines && styles.Truncate,
shouldTruncate && !! numberOfLines && sx.numberOfLines,
shouldTruncate && !! numberOfLines && truncateLines,
Comment on lines 56 to +57
Copy link
Member

@mirka mirka Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: Sorry I almost forgot this was a TS migration PR. No runtime changes! 😂

Nit: I find this 👇 simpler to read, might just be me though.

			shouldTruncate &&
				( numberOfLines ? truncateLines : styles.Truncate ),

className
);
}, [ className, cx, numberOfLines, shouldTruncate ] );
Expand Down
38 changes: 0 additions & 38 deletions packages/components/src/truncate/stories/index.js

This file was deleted.

49 changes: 49 additions & 0 deletions packages/components/src/truncate/stories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* Internal dependencies
*/
import { Truncate } from '..';

const meta: ComponentMeta< typeof Truncate > = {
component: Truncate,
title: 'Components (Experimental)/Truncate',
argTypes: {
children: { control: { type: 'text' } },
as: { control: { type: 'text' } },
},
parameters: {
controls: {
expanded: true,
},
docs: { source: { state: 'open' } },
},
};
export default meta;

const defaultText =
'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.';

const Template: ComponentStory< typeof Truncate > = ( args ) => {
return <Truncate { ...args } />;
};

export const Default: ComponentStory< typeof Truncate > = Template.bind( {} );
walbo marked this conversation as resolved.
Show resolved Hide resolved
Default.args = {
numberOfLines: 2,
children: defaultText,
};

export const CharacterCount: ComponentStory< typeof Truncate > = Template.bind(
walbo marked this conversation as resolved.
Show resolved Hide resolved
{}
);
CharacterCount.args = {
limit: 23,
children: defaultText,
ellipsizeMode: 'tail',
ellipsis: '[---]',
};
CharacterCount.storyName = 'Truncate by character count';
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Truncate } from '..';
describe( 'props', () => {
test( 'should render correctly', () => {
const { container } = render( <Truncate>Lorem ipsum.</Truncate> );
expect( container.firstChild.textContent ).toEqual( 'Lorem ipsum.' );
expect( container.firstChild?.textContent ).toEqual( 'Lorem ipsum.' );
} );

test( 'should render limit', () => {
Expand All @@ -20,7 +20,7 @@ describe( 'props', () => {
Lorem ipsum.
</Truncate>
);
expect( container.firstChild.textContent ).toEqual( 'L…' );
expect( container.firstChild?.textContent ).toEqual( 'L…' );
} );

test( 'should render custom ellipsis', () => {
Expand All @@ -29,7 +29,7 @@ describe( 'props', () => {
Lorem ipsum.
</Truncate>
);
expect( container.firstChild.textContent ).toEqual( 'Lorem!!!' );
expect( container.firstChild?.textContent ).toEqual( 'Lorem!!!' );
} );

test( 'should render custom ellipsizeMode', () => {
Expand All @@ -38,6 +38,6 @@ describe( 'props', () => {
Lorem ipsum.
</Truncate>
);
expect( container.firstChild.textContent ).toEqual( 'Lo!!!m.' );
expect( container.firstChild?.textContent ).toEqual( 'Lo!!!m.' );
} );
} );
38 changes: 28 additions & 10 deletions packages/components/src/truncate/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
export type TruncateEllisizeMode = 'auto' | 'head' | 'tail' | 'middle' | 'none';
/**
* External dependencies
*/
import type { ReactNode } from 'react';

export interface Props {
export type TruncateEllipsizeMode =
| 'auto'
| 'head'
| 'tail'
| 'middle'
| 'none';

export type TruncateProps = {
/**
* The ellipsis string when `truncate` is set.
* The ellipsis string when truncating the text by the `limit` prop's value.
*
* @default '...'
* @default ''
walbo marked this conversation as resolved.
Show resolved Hide resolved
*/
ellipsis?: string;
/**
* Determines where to truncate. For example, we can truncate text right in the middle. To do this, we need to set `ellipsizeMode` to `middle` and a text `limit`.
* Determines where to truncate. For example, we can truncate text right in
* the middle. To do this, we need to set `ellipsizeMode` to `middle` and a
* text `limit`.
*
* * `auto`: Trims content at the end automatically without a `limit`.
* * `head`: Trims content at the beginning. Requires a `limit`.
Expand All @@ -17,19 +29,25 @@ export interface Props {
*
* @default 'auto'
*/
ellipsizeMode?: TruncateEllisizeMode;
ellipsizeMode?: TruncateEllipsizeMode;
/**
* Determines the max characters when `truncate` is set.
* Determines the max number of characters to be displayed before the rest
* of the text gets truncated. Requires `ellipsizeMode` to assume values
* different from `auto` and `none`.
*
* @default 0
*/
limit?: number;
walbo marked this conversation as resolved.
Show resolved Hide resolved
/**
* Clamps the text content to the specified `numberOfLines`, adding the `ellipsis` at the end.
* Clamps the text content to the specified `numberOfLines`, adding an
* ellipsis at the end. Note: this feature ignores the value of the
* `ellipsis` prop and always displays the default `…` ellipsis.
*
* @default 0
*/
numberOfLines?: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also realised that this type is not a great fit for numberOfLines: according to the spec, only positive integer numbers make sense (apart from other string keywords).

Even a default of 0 is non-sensical

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL. Good to know the spec there.

/**
* The children elements.
*/
children: React.ReactNode;
}
children: ReactNode;
};
Loading