Skip to content

Commit

Permalink
feat(Highlight): add support for Highlight component (#3810)
Browse files Browse the repository at this point in the history
* feat(Highlight): add support for Highlight component

* feat(AutoComplete): use Highlight to highlight the matching part of the option text

* fix: fix redundant less dependency

* docs(Highlight): update docs

* fix: update --rs-text-highlight-bg
  • Loading branch information
simonguo committed May 17, 2024
1 parent 1f284ea commit 876377b
Show file tree
Hide file tree
Showing 39 changed files with 644 additions and 112 deletions.
8 changes: 5 additions & 3 deletions docs/pages/components/check-picker/fragments/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ const data = ['Eugenia', 'Bryan', 'Linda', 'Nancy', 'Lloyd', 'Alice', 'Julia', '
const App = () => {
const [items, setItems] = React.useState([]);
const updateData = () => {
if (items.length === 0) {
setItems(data);
}
setTimeout(() => {
if (items.length === 0) {
setItems(data);
}
}, 2000);
};
const renderMenu = menu => {
if (items.length === 0) {
Expand Down
36 changes: 36 additions & 0 deletions docs/pages/components/highlight/en-US/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Highlight

Used to mark or highlight matched text content. For example, used to highlight search results.

## Import

<!--{include:<import-guide>}-->

## Examples

### Default

<!--{include:`basic.md`}-->

### Highlight with multiple words

<!--{include:`multiple-words.md`}-->

### Custom Highlight

<!--{include:`custom.md`}-->

### Combine with search

<!--{include:`search.md`}-->

## Props

### `<Highlight>`

| Property | Type`(Default)` | Description |
| ----------- | ------------------------------------------- | -------------------------------------- |
| children | ReactNode | The content to highlight. |
| classPrefix | string `('highlight')` | The prefix of the component CSS class. |
| query | string \| string[] | The keyword to highlight. |
| renderMark | (match: string, index: number) => ReactNode | Custom render the highlight mark. |
16 changes: 16 additions & 0 deletions docs/pages/components/highlight/fragments/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--start-code-->

```js
import { Highlight } from 'rsuite';

const App = () => {
return (
<Highlight query="react">
React Suite is a set of react components that have high quality and high performance.
</Highlight>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
```

<!--end-code-->
23 changes: 23 additions & 0 deletions docs/pages/components/highlight/fragments/custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--start-code-->

```js
import { Highlight } from 'rsuite';

const App = () => {
return (
<Highlight
query={['high quality', 'high performance']}
renderMark={(match, index) => (
<mark key={index} style={{ backgroundColor: '#f4f4f4', color: '#f00' }}>
{match}
</mark>
)}
>
React Suite is a set of react components that have high quality and high performance.
</Highlight>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
```

<!--end-code-->
16 changes: 16 additions & 0 deletions docs/pages/components/highlight/fragments/multiple-words.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--start-code-->

```js
import { Highlight } from 'rsuite';

const App = () => {
return (
<Highlight query={['high quality', 'high performance']}>
React Suite is a set of react components that have high quality and high performance.
</Highlight>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
```

<!--end-code-->
75 changes: 75 additions & 0 deletions docs/pages/components/highlight/fragments/search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!--start-code-->

```js
import { Highlight, List, Input } from 'rsuite';

const App = () => {
const [query, setQuery] = React.useState('react');

const filteredData = data.filter(item => {
return (
item.name.toLowerCase().includes(query.toLowerCase()) ||
item.description.toLowerCase().includes(query.toLowerCase())
);
});

return (
<div>
<Input placeholder="Search" value={query} onChange={setQuery} />
<hr />
<List>
{filteredData.map(item => (
<List.Item key={item.name}>
<Highlight query={query}>{item.name}</Highlight>
<p>
<Highlight query={query}>{item.description}</Highlight>
</p>
</List.Item>
))}
</List>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));

const data = [
{
name: 'React',
description:
'React is a declarative, efficient, and flexible JavaScript library for building user interfaces.'
},
{
name: 'React Router',
description:
'React Router is a collection of navigational components that compose declaratively with your application.'
},
{
name: 'React Suite',
description:
'React Suite is a set of react components that have high quality and high performance.'
},
{
name: 'React Virtualized',
description: 'React components for efficiently rendering large lists and tabular data.'
},
{
name: 'React DnD',
description: 'Drag and Drop for React.'
},
{
name: 'React Bootstrap',
description:
'React-Bootstrap replaces the Bootstrap JavaScript. Each component has been built from scratch as a true React component, without unneeded dependencies like jQuery.'
},
{
name: 'Ant Design',
description: 'An enterprise-class UI design language and React UI library.'
},
{
name: 'Material-UI',
description: "React components that implement Google's Material Design."
}
];
```

<!--end-code-->
16 changes: 16 additions & 0 deletions docs/pages/components/highlight/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { Highlight, Input, List } from 'rsuite';
import DefaultPage from '@/components/Page';
import ImportGuide from '@/components/ImportGuide';

const inDocsComponents = {
'import-guide': () => <ImportGuide components={['Highlight']} />
};

export default function Page() {
return (
<DefaultPage inDocsComponents={inDocsComponents} dependencies={{ Highlight, Input, List }} />
);
}
36 changes: 36 additions & 0 deletions docs/pages/components/highlight/zh-CN/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Highlight 高亮

用于标记或突出显示匹配的文本内容。例如,用于高亮搜索结果。

## 获取组件

<!--{include:<import-guide>}-->

## 示例

### 默认

<!--{include:`basic.md`}-->

### 高亮多个词

<!--{include:`multiple-words.md`}-->

### 自定义高亮

<!--{include:`custom.md`}-->

### 与搜索结合

<!--{include:`search.md`}-->

## Props

### `<Highlight>`

| 属性 | 类型`(默认值)` | 描述 |
| ----------- | ------------------------------------------- | -------------------------- |
| children | ReactNode | 需要高亮的内容。 |
| classPrefix | string `('highlight')` | 组件 CSS 类名的前缀。 |
| query | string \| string[] | 需要高亮的关键词。 |
| renderMark | (match: string, index: number) => ReactNode | 自定义高亮标记的渲染函数。 |
7 changes: 7 additions & 0 deletions docs/utils/component.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
"components": ["Heading"],
"minVersion": "5.58.0"
},
{
"id": "highlight",
"name": "Highlight",
"title": "高亮",
"components": ["Highlight"],
"minVersion": "5.61.0"
},
{
"id": "text",
"name": "Text",
Expand Down
1 change: 1 addition & 0 deletions src/AutoComplete/AutoComplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ const AutoComplete: PickerComponent<AutoCompleteProps> = React.forwardRef(
renderMenuItem={renderMenuItem}
data={items}
className={menuClassName}
query={value}
/>
);

Expand Down
8 changes: 6 additions & 2 deletions src/CascadeTree/SearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { ItemDataType, WithAsProps } from '../@types/common';
import { useClassNames, useCustom } from '../utils';
import { getPathTowardsItem } from '../internals/Tree/utils';
import { highlightLabel } from '../internals/utils';
import Highlight from '../Highlight';
import SearchBox from '../internals/SearchBox';

interface SearchViewProps<T> extends WithAsProps {
Expand Down Expand Up @@ -46,7 +46,11 @@ function SearchView<T>(props: SearchViewProps<T>) {
const items = getPathTowardsItem(item, item => parentMap.get(item));

const formattedNodes = items.map(itemData => {
const label = highlightLabel(itemData[labelKey], { searchKeyword });
const label = (
<Highlight as="span" query={searchKeyword}>
{itemData[labelKey]}
</Highlight>
);

return { ...itemData, [labelKey]: label };
});
Expand Down
7 changes: 7 additions & 0 deletions src/CascadeTree/styles/index.less
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
@import '../../styles/common.less';
@import '../../styles/mixins/listbox.less';
@import '../../styles/mixins/combobox.less';
@import '../../Highlight/styles/index.less';
@import './search.less';

// CascadeTree
// ----------------------

.rs-cascade-tree {
.rs-highlight-mark {
padding: 0;
}
}

// Menu Items
.rs-cascade-tree-items {
padding: @border-radius 0;
Expand Down
10 changes: 0 additions & 10 deletions src/CascadeTree/styles/search.less
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@
&:disabled,
&&-disabled {
.listbox-options-disabled();

.rs-label-match {
opacity: 0.7;
font-weight: normal;
}
}
}

Expand All @@ -38,8 +33,3 @@
display: none;
}
}

.rs-label-match {
color: var(--rs-red-500);
font-weight: bold;
}
4 changes: 1 addition & 3 deletions src/CheckPicker/CheckPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import clone from 'lodash/clone';
import isUndefined from 'lodash/isUndefined';
import isFunction from 'lodash/isFunction';
import remove from 'lodash/remove';
import omit from 'lodash/omit';
Expand Down Expand Up @@ -326,12 +325,11 @@ const CheckPicker = React.forwardRef(
activeItemValues={value}
focusItemValue={focusItemValue}
data={[...filteredStickyItems, ...items]}
// `group` is redundant so long as `groupBy` exists
group={!isUndefined(groupBy)}
groupBy={groupBy}
onSelect={handleItemSelect}
onGroupTitleClick={onGroupTitleClick}
virtualized={virtualized}
query={searchKeyword}
/>
) : (
<div className={prefix`none`}>{locale?.noResultsText}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import React, { useCallback } from 'react';
import { isAllSiblingNodeUncheckable, getDisabledState, isNodeUncheckable } from '../utils';
import { highlightLabel } from '../../internals/utils';
import { useItemDataKeys } from '../../Tree/TreeProvider';
import Highlight from '../../Highlight';
import type { TreeNode } from '../../Tree/types';

interface Props {
Expand All @@ -28,16 +28,21 @@ function useTreeNodeProps(props: Props) {
(nodeData: TreeNode) => {
const { visible, checkState } = nodeData;
const value = nodeData[valueKey];
const nodeLabel = nodeData[labelKey];
const allUncheckable = isAllSiblingNodeUncheckable(
nodeData,
flattenedNodes,
uncheckableItemValues,
valueKey
);

const label = keyword
? highlightLabel(nodeData[labelKey], { searchKeyword: keyword })
: nodeData[labelKey];
const label = keyword ? (
<Highlight as="span" query={keyword}>
{nodeLabel}
</Highlight>
) : (
nodeLabel
);

const disabled = getDisabledState(flattenedNodes, nodeData, { disabledItemValues, valueKey });
const uncheckable = isNodeUncheckable(nodeData, { uncheckableItemValues, valueKey });
Expand Down
4 changes: 4 additions & 0 deletions src/CheckTree/styles/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
&.rs-tree-virtualized &-view {
overflow: hidden;
}

.rs-highlight-mark {
padding: 0;
}
}

// Only has the first level
Expand Down
Loading

0 comments on commit 876377b

Please sign in to comment.