Skip to content

Commit

Permalink
feat(TextHighlight): 新增 TextHighlight 文本高亮组件 (#740)
Browse files Browse the repository at this point in the history
* feat: 优化mark样式

* feat: 优化mark样式,解决本地冲突

---------

Co-authored-by: blankzhang <blankzhang@blankzhangdeMac-mini.local>
  • Loading branch information
zym19960704 and blankzhang committed Apr 26, 2024
1 parent 2642e2d commit f3634c5
Show file tree
Hide file tree
Showing 13 changed files with 287 additions and 7 deletions.
1 change: 1 addition & 0 deletions components/_style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ import './progress/style';
import './transfer/style';
import './input-file/style';
import './breadcrumb/style';
import './text-highlight/style';
1 change: 1 addition & 0 deletions components/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ export * from './progress';
export * from './transfer';
export * from './input-file';
export * from './breadcrumb';
export * from './text-highlight';
13 changes: 13 additions & 0 deletions components/text-highlight/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { withInstall } from '../_util/withInstall';
import type { SFCWithInstall } from '../_util/interface';
import TextHighlight from './text-highlight';

export { textHighlightProps } from './props';
export type { TextHighlightProps } from './props';

type TextHighlightType = SFCWithInstall<typeof TextHighlight>;
export const FTextHighlight = withInstall<TextHighlightType>(
TextHighlight as TextHighlightType,
);

export default FTextHighlight;
20 changes: 20 additions & 0 deletions components/text-highlight/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ComponentObjectPropsOptions, PropType } from 'vue';
import type { ExtractPublicPropTypes } from '../_util/interface';

export const textHighlightProps = {
// 高亮内容,数组
searchValues: {
type: Array as PropType<string[]>,
default: (): string[] => [],
},
// 严格模式,是否严格大小写
strict: {
type: Boolean,
default: false,
},
} as const satisfies ComponentObjectPropsOptions;

// 组件暴露给外部的 props 类型
export type TextHighlightProps = ExtractPublicPropTypes<
typeof textHighlightProps
>;
10 changes: 10 additions & 0 deletions components/text-highlight/style/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';

@text-highlight: ~'@{cls-prefix}-text-highlight';

.@{text-highlight} {
.highlight {
font-size: inherit;
}
}
2 changes: 2 additions & 0 deletions components/text-highlight/style/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import '../../style';
import './index.less';
126 changes: 126 additions & 0 deletions components/text-highlight/text-highlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
Text,
type VNode,
type VNodeTypes,
defineComponent,
isVNode,
} from 'vue';
import { isArray, isObject, isString } from 'lodash-es';
import getPrefixCls from '../_util/getPrefixCls';
import { useTheme } from '../_theme/useTheme';
import FText from '../text';
import { textHighlightProps } from './props';

const prefixCls = getPrefixCls('text-highlight');

export default defineComponent({
name: 'FTextHighlight',
props: textHighlightProps,
setup(props, { slots }) {
useTheme();

const createRegExp = (value: string): RegExp => {
const flags = props.strict ? 'g' : 'gi';
return new RegExp(`(${value})`, flags);
};

// 判断是否相等
const judgeEqual = (word: string, text: string): boolean => {
return (
(props.strict && text === word)
|| (!props.strict && text.toLowerCase() === word.toLowerCase())
);
};

// 渲染高亮的部分
const renderHighLight = (part: string): VNode => {
return (
<FText tag="mark" class="highlight">
{part}
</FText>
);
};

// 渲染文本
const renderText = (text: string): (string | VNode)[] => {
let parts: (string | VNode)[] = [text];
props.searchValues.forEach((value) => {
const regExp = createRegExp(value);
parts = parts.reduce((result: (string | VNode)[], part) => {
if (isString(part)) {
const split = part.split(regExp);
return result.concat(
split.map((txt: string) => {
return judgeEqual(value, txt)
? renderHighLight(txt)
: txt;
}),
);
} else {
return [...result, part];
}
}, [] as (string | VNode)[]);
});
return parts;
};

// 渲染节点
const renderNode = (node: VNode): VNode => {
// 节点的标签
const NodeType = node.type as VNodeTypes;
let children;

// 处理children
if (isArray(node.children)) {
// 如果是数组,对每个子节点进行处理
children = node.children.map((child) => {
// 如果子节点是字符串,使用 renderText 函数处理
if (isString(child)) {
return renderText(child);
} else if (isVNode(child)) {
// 如果子节点是 VNode,递归使用 renderNode 函数处理
return renderNode(child);
} else { // 如果子节点既不是字符串也不是 VNode,直接返回它
return child;
}
});
} else if (isString(node.children)) {
// node.children 是字符串,使用 renderText 函数处理
children = renderText(node.children);
} else {
// 如果 node.children 既不是数组也不是字符串,直接返回它
children = node.children;
}

if (NodeType === Text) {
// 如果是纯文本,渲染一个空标签
return <span {...node.props}>{children}</span>;
} else if (isString(NodeType)) {
// 字符串(对应 HTML 标签名)
return <NodeType {...node.props}>{children}</NodeType>;
} else if (isObject(NodeType)) {
// 用户自定义的组件对象
const ChildComponent = NodeType as any;
const child = node.children as any;
const childSlots
= child && child.default ? child.default() : [];
return (
<ChildComponent {...node.props}>
{childSlots.map(renderNode)}
</ChildComponent>
);
}
};

// 渲染内容
const renderContent = () => {
if (!slots.default) {
return '';
}
// 遍历内容的所有节点
return slots.default().map(renderNode);
};

return () => <div class={prefixCls}>{renderContent()}</div>;
},
});
5 changes: 5 additions & 0 deletions components/text/style/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@text-color-info: var(--f-primary-color);
@text-color-warning: var(--f-warning-color);
@text-color-danger: var(--f-danger-color);
@text-color-tag-mark: var(--f-primary-color);

@text-font-size-sm: @font-size-caption;
@text-font-size-lg: @font-size-head;
Expand Down Expand Up @@ -49,4 +50,8 @@
&-text--italic {
font-style: italic
}
&-tag--mark {
--f-text-text-color: @text-color-tag-mark;
background:var(--f-hover-color-light) ;
}
}
1 change: 1 addition & 0 deletions components/text/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default defineComponent({
[`${prefixCls}-size--${props.size}`]: props.size,
[`${prefixCls}-text--strong`]: props.strong,
[`${prefixCls}-text--italic`]: props.italic,
[`${prefixCls}-tag--mark`]: props.tag === 'mark', // 定义mark样式
}));

return {
Expand Down
56 changes: 56 additions & 0 deletions docs/.vitepress/components/text-highlight/base.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<FSpace>
<FForm :labelWidth="80">
<FFormItem label="严格模式:">
<FSwitch v-model="strict">
<template #active> 开 </template>
<template #inactive> 关 </template>
</FSwitch>
</FFormItem>
<FFormItem label="高亮词:">
<FInput
v-model="str"
placeholder="空格分隔"
@change="strChange"
></FInput>
</FFormItem>
</FForm>
</FSpace>
<FSpace>
<FTextHighlight :searchValues="searchValues" :strict="strict">
<FText>
This is text mixed icon
<BellOutlined />
and component
<FButton>Button</FButton>
and
<FText>a subtext</FText>
.
</FText>
<div class="text">This is the string for the div wrap.</div>
This is a plain text string.
</FTextHighlight>
</FSpace>
</template>

<script setup>
import { ref } from 'vue';
import { BellOutlined } from '@fesjs/fes-design/icon';
const searchValues = ref([]);
const str = ref('');
const strChange = () => {
searchValues.value = str.value.split(' ');
};
const strict = ref(false);
</script>

<style>
.text {
font-size: 30px;
margin: 10px 0px;
}
</style>
32 changes: 32 additions & 0 deletions docs/.vitepress/components/text-highlight/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# TextHighlight 文本高亮

用于高亮文本中的指定文字

## 组件注册

```js
import { FTextHighlight } from '@fesjs/fes-design';

app.use(FTextHighlight);
```

## 代码演示

### 基础用法

:::demo
base.vue
:::

## TextHighlight Props

| 属性 | 说明 | 类型 | 默认值 |
| ------------ | ---------------------------- | --------------- | ------- |
| searchValues | 搜索内容 | `Array<string>` | `[]` |
| strict | 严格模式,是否区分大小写匹配 | `boolean` | `false` |

## TextHighlight slots

| slot 名称 | 说明 |
| --------- | -------------- |
| default | 用户的文本内容 |
23 changes: 16 additions & 7 deletions docs/.vitepress/components/text/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,33 @@ app.use(FText);

### 基础用法

--BASIC
:::demo
basic.vue
:::

### 尺寸

--SIZE
:::demo
size.vue
:::

### 字体效果

--EFFECT
:::demo
effect.vue
:::

### 自定义元素标签

--TAG
:::demo
tag.vue
:::

### 混合使用

--MIXIN

--CODE
:::demo
mixin.vue
:::

## Text Props

Expand All @@ -44,6 +52,7 @@ app.use(FText);
| italic | 是否字体倾斜 | boolean | `false` |
| tag | 自定义元素标签,可选值为`span` `div` `p` `h1` `h2` `h3`| string | `span` |


## Text Slots

| slot 名称 | 说明 |
Expand Down
4 changes: 4 additions & 0 deletions docs/.vitepress/configs/sidebar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ const sidebarConfig: Record<string, DefaultTheme.Config['sidebar']> = {
text: 'Text 文本',
link: '/zh/components/text',
},
{
text: 'TextHighlight 文本高亮',
link: '/zh/components/text-highlight',
},
{
text: 'Tree 树形控件',
link: '/zh/components/tree',
Expand Down

0 comments on commit f3634c5

Please sign in to comment.