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

Add hightlighter to the tree control #36480

Merged
merged 11 commits into from
Mar 1, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: dev

Add hightlighter to the tree control
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo here! highlighter instead of hightlighter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohh! Nice catch. Thanks!

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { useMemo } from 'react';

/**
* Internal dependencies
*/
import { CheckedStatus, TreeItemProps } from '../types';

export function useHighlighter( {
item,
multiple,
checkedStatus,
shouldItemBeHighlighted,
}: Pick< TreeItemProps, 'item' | 'multiple' | 'shouldItemBeHighlighted' > & {
checkedStatus: CheckedStatus;
} ) {
const isHighlighted = useMemo( () => {
if ( typeof shouldItemBeHighlighted === 'function' ) {
if ( multiple || item.children.length === 0 ) {
return shouldItemBeHighlighted( item );
}
}
if ( ! multiple ) {
return checkedStatus === 'checked';
}
}, [ item, multiple, checkedStatus, shouldItemBeHighlighted ] );

return { isHighlighted };
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from 'react';
*/
import { TreeItemProps } from '../types';
import { useExpander } from './use-expander';
import { useHighlighter } from './use-highlighter';
import { useSelection } from './use-selection';

export function useTreeItem( {
Expand All @@ -18,6 +19,7 @@ export function useTreeItem( {
index,
getLabel,
shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect,
onRemove,
...props
Expand All @@ -39,11 +41,19 @@ export function useTreeItem( {
onRemove,
} );

const highlighter = useHighlighter( {
item,
checkedStatus: selection.checkedStatus,
multiple,
shouldItemBeHighlighted,
} );

return {
item,
level: nextLevel,
expander,
selection,
highlighter,
getLabel,
treeItemProps: {
...props,
Expand All @@ -60,6 +70,7 @@ export function useTreeItem( {
selected: selection.selected,
getItemLabel: getLabel,
shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect: selection.onSelectChildren,
onRemove: selection.onRemoveChildren,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function useTree( {
selected,
getItemLabel,
shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect,
onRemove,
...props
Expand All @@ -31,6 +32,7 @@ export function useTree( {
selected,
getLabel: getItemLabel,
shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect,
onRemove,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import interpolate from '@automattic/interpolate-components';
import { BaseControl, TextControl } from '@wordpress/components';
import React, { createElement, useCallback, useState } from 'react';
import React, { createElement, useCallback, useRef, useState } from 'react';

/**
* Internal dependencies
Expand Down Expand Up @@ -121,6 +121,30 @@ function getItemLabel( item: LinkedTree, text: string ) {
);
}

export const CustomItemLabelOnSearch: React.FC = () => {
const [ text, setText ] = useState( '' );

return (
<>
<TextControl value={ text } onChange={ setText } />
<BaseControl
label="Custom item label on search"
id="custom-item-label-on-search"
>
<TreeControl
id="custom-item-label-on-search"
items={ listItems }
getItemLabel={ ( item ) => getItemLabel( item, text ) }
shouldItemBeExpanded={ useCallback(
( item ) => shouldItemBeExpanded( item, text ),
[ text ]
) }
/>
</BaseControl>
</>
);
};

export const SelectionSingle: React.FC = () => {
const [ selected, setSelected ] = useState( listItems[ 1 ] );

Expand Down Expand Up @@ -183,6 +207,53 @@ export const SelectionMultiple: React.FC = () => {
);
};

function getFirstMatchingItem(
item: LinkedTree,
text: string,
memo: Record< string, string >
) {
if ( ! text ) return false;
if ( memo[ text ] === item.data.value ) return true;

const matcher = new RegExp( text, 'ig' );
if ( matcher.test( item.data.label ) ) {
if ( ! memo[ text ] ) {
memo[ text ] = item.data.value;
return true;
}
}

return false;
}

export const HighlightFirstMatchingItem: React.FC = () => {
const [ text, setText ] = useState( '' );
const memo = useRef< Record< string, string > >( {} );

return (
<>
<TextControl value={ text } onChange={ setText } />
<BaseControl
label="Highlight first matching item"
id="highlight-first-matching-item"
>
<TreeControl
id="highlight-first-matching-item"
items={ listItems }
getItemLabel={ ( item ) => getItemLabel( item, text ) }
shouldItemBeExpanded={ useCallback(
( item ) => shouldItemBeExpanded( item, text ),
[ text ]
) }
shouldItemBeHighlighted={ ( item ) =>
getFirstMatchingItem( item, text, memo.current )
}
/>
</BaseControl>
</>
);
};

export default {
title: 'WooCommerce Admin/experimental/TreeControl',
component: TreeControl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
treeProps,
expander: { isExpanded, onToggleExpand },
selection,
highlighter: { isHighlighted },
getLabel,
} = useTreeItem( {
...props,
Expand All @@ -39,8 +40,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
'experimental-woocommerce-tree-item',
{
'experimental-woocommerce-tree-item--highlighted':
! selection.multiple &&
selection.checkedStatus === 'checked',
isHighlighted,
}
) }
>
Expand Down
15 changes: 15 additions & 0 deletions packages/js/components/src/experimental-tree-control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ type BaseTreeProps = {
* @param value The unselection
*/
onRemove?( value: Item | Item[] ): void;
/**
* It gives a way to determine whether the current rendering
Copy link
Contributor

Choose a reason for hiding this comment

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

I think provides instead of gives is more appropriate here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks

* item is highlighted or not from outside the tree.
*
* @example
* <Tree
* shouldItemBeHighlighted={ isFirstChild }
* />
*
* @param item The current linked tree item, useful to
* traverse the entire linked tree from this item.
*
* @see {@link LinkedTree}
*/
shouldItemBeHighlighted?( item: LinkedTree ): boolean;
};

export type TreeProps = BaseTreeProps &
Expand Down