diff --git a/packages/components/default.less b/packages/components/default.less
index 582b8405e..684aa35f1 100644
--- a/packages/components/default.less
+++ b/packages/components/default.less
@@ -39,6 +39,7 @@
@import './input-number/style/themes/default.less';
@import './layout/style/themes/default.less';
@import './list/style/themes/default.less';
+@import './loading-bar/style/themes/default.less';
@import './menu/style/themes/default.less';
@import './message/style/themes/default.less';
@import './modal/style/themes/default.less';
@@ -61,6 +62,7 @@
@import './table/style/themes/default.less';
@import './tabs/style/themes/default.less';
@import './tag/style/themes/default.less';
+@import './text/style/themes/default.less';
@import './textarea/style/themes/default.less';
@import './time-picker/style/themes/default.less';
@import './timeline/style/themes/default.less';
@@ -71,4 +73,3 @@
@import './typography/style/themes/default.less';
@import './upload/style/themes/default.less';
@import './watermark/style/themes/default.less';
-@import './loading-bar/style/themes/default.less';
diff --git a/packages/components/form/docs/Theme.zh.md b/packages/components/form/docs/Theme.zh.md
index e00ad020a..f7c7d4094 100644
--- a/packages/components/form/docs/Theme.zh.md
+++ b/packages/components/form/docs/Theme.zh.md
@@ -32,8 +32,8 @@
| `@form-focus-color` | `@color-primary-d10` | - | - |
| `@form-focus-box-shadow` | `0 0 0 2px fade(@form-focus-color, 20%)` | - | - |
| `@form-disabled-color` | `@text-color-disabled` | - | - |
-| `@form-disabled-border-color` | `@color-graphite-l20` | - | - |
-| `@form-disabled-background-color` | `@background-color-disabled` | - | - |
+| `@form-disabled-border-color` | `var(--ix-background-color)` | - | - |
+| `@form-disabled-background-color` | `var(--ix-background-color-deep)` | - | - |
| `@form-icon-color` | `@color-graphite-d20` | - | - |
| `@form-icon-hover-color` | `@color-graphite-d20` | `@color-primary` | - |
| `@form-item-valid-color` | `@color-success` | `@color-success-d10` | - |
diff --git a/packages/components/index.ts b/packages/components/index.ts
index d2e9b1dcb..8e6880954 100644
--- a/packages/components/index.ts
+++ b/packages/components/index.ts
@@ -66,6 +66,7 @@ import { IxSwitch } from '@idux/components/switch'
import { IxTable, IxTableColumn } from '@idux/components/table'
import { IxTab, IxTabs } from '@idux/components/tabs'
import { IxTag, IxTagGroup } from '@idux/components/tag'
+import { IxText } from '@idux/components/text'
import { IxTextarea } from '@idux/components/textarea'
import { IxTimePicker, IxTimeRangePicker } from '@idux/components/time-picker'
import { IxTimeline, IxTimelineItem } from '@idux/components/timeline'
@@ -169,6 +170,7 @@ const components = [
IxTabs,
IxTag,
IxTagGroup,
+ IxText,
IxTextarea,
IxTimePicker,
IxTimeRangePicker,
@@ -257,6 +259,7 @@ export * from '@idux/components/switch'
export * from '@idux/components/table'
export * from '@idux/components/tabs'
export * from '@idux/components/tag'
+export * from '@idux/components/text'
export * from '@idux/components/textarea'
export * from '@idux/components/time-picker'
export * from '@idux/components/timeline'
diff --git a/packages/components/seer.less b/packages/components/seer.less
index a6e333033..ad950b6c2 100644
--- a/packages/components/seer.less
+++ b/packages/components/seer.less
@@ -39,6 +39,7 @@
@import './input-number/style/themes/seer.less';
@import './layout/style/themes/seer.less';
@import './list/style/themes/seer.less';
+@import './loading-bar/style/themes/seer.less';
@import './menu/style/themes/seer.less';
@import './message/style/themes/seer.less';
@import './modal/style/themes/seer.less';
@@ -61,6 +62,7 @@
@import './table/style/themes/seer.less';
@import './tabs/style/themes/seer.less';
@import './tag/style/themes/seer.less';
+@import './text/style/themes/seer.less';
@import './textarea/style/themes/seer.less';
@import './time-picker/style/themes/seer.less';
@import './timeline/style/themes/seer.less';
@@ -71,4 +73,3 @@
@import './typography/style/themes/seer.less';
@import './upload/style/themes/seer.less';
@import './watermark/style/themes/seer.less';
-@import './loading-bar/style/themes/seer.less';
diff --git a/packages/components/style/variable/prefix.less b/packages/components/style/variable/prefix.less
index 176d0586c..bf17aefa0 100644
--- a/packages/components/style/variable/prefix.less
+++ b/packages/components/style/variable/prefix.less
@@ -6,6 +6,7 @@
@icon-prefix: ~'@{idux-prefix}-icon';
@tag-prefix: ~'@{idux-prefix}-tag';
@tag-group-prefix: ~'@{idux-prefix}-tag-group';
+@text-prefix: ~'@{idux-prefix}-text';
@typography-prefix: ~'@{idux-prefix}-typography';
// Layout
diff --git a/packages/components/table/docs/Theme.zh.md b/packages/components/table/docs/Theme.zh.md
index c7dd0a405..f171799da 100644
--- a/packages/components/table/docs/Theme.zh.md
+++ b/packages/components/table/docs/Theme.zh.md
@@ -24,5 +24,5 @@
| `@table-body-row-background-color-hover` | `var(--ix-background-color-light)` | - | - |
| `@table-body-row-background-color-selected` | `var(--ix-color-primary-l50)` | `var(--ix-background-color)` | - |
| `@table-expandable-icon-color` | `var(--ix-text-color-secondary)` | `@color-graphite-l10` | - |
-| `@table-expandable-background-color` | `var(--ix-background-color-light)` | - | - |
-| `@table-expandable-background-color-hover` | `var(--ix-background-color-medium)` | - | - |
+| `@table-expandable-background-color` | `@table-body-row-background-color-hover` | - | - |
+| `@table-expandable-background-color-hover` | `@table-body-row-background-color-hover` | - | - |
diff --git a/packages/components/tabs/docs/Theme.zh.md b/packages/components/tabs/docs/Theme.zh.md
index 21d90377a..c9f4d7228 100644
--- a/packages/components/tabs/docs/Theme.zh.md
+++ b/packages/components/tabs/docs/Theme.zh.md
@@ -21,7 +21,6 @@
| `@tabs-nav-tab-text-color` | `@color-graphite-d40` | - | - |
| `@tabs-nav-bar-color` | `@color-primary` | - | - |
| `@tabs-nav-bar-height` | `2px` | - | - |
-| `@tabs-nav-pre-next-width` | `20px` | - | - |
| `@tabs-border-radius` | `2px` | - | - |
| `@tabs-pane-min-width` | `72px` | - | - |
| `@tabs-pane-padding` | `16px` | - | - |
diff --git a/packages/components/text/__tests__/__snapshots__/text.spec.ts.snap b/packages/components/text/__tests__/__snapshots__/text.spec.ts.snap
new file mode 100644
index 000000000..9f4eba225
--- /dev/null
+++ b/packages/components/text/__tests__/__snapshots__/text.spec.ts.snap
@@ -0,0 +1,61 @@
+// Vitest Snapshot v1
+
+exports[`Text > copyable work 1`] = `
+"
@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。
"
+`;
+
+exports[`Text > copyable work 2`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > expandable work 1`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > expandable work 2`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > lineClamp work 1`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > lineClamp work 2`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > render work 1`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > tag work 1`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。
"
+`;
+
+exports[`Text > tag work 2`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > tooltip work 1`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > tooltip work 2`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
+
+exports[`Text > tooltip work 3`] = `
+"@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。"
+`;
diff --git a/packages/components/text/__tests__/text.spec.ts b/packages/components/text/__tests__/text.spec.ts
new file mode 100644
index 000000000..1960fd739
--- /dev/null
+++ b/packages/components/text/__tests__/text.spec.ts
@@ -0,0 +1,91 @@
+import { MountingOptions, mount } from '@vue/test-utils'
+
+import { renderWork } from '@tests'
+
+import Text from '../src/Text'
+import { TextProps } from '../src/types'
+
+const defaultSlot =
+ () => `@idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。`
+
+describe('Text', () => {
+ const TextMount = (options?: MountingOptions>) => {
+ const { slots } = options || {}
+ return mount(Text, { ...(options as MountingOptions), slots: { default: defaultSlot, ...slots } })
+ }
+
+ renderWork(Text, {
+ slots: { default: defaultSlot },
+ })
+
+ test('copyable work', async () => {
+ const wrapper = TextMount({ props: { copyable: true } })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('ix-text-wrapper')
+
+ document.execCommand = vi.fn().mockReturnValue(true)
+ await wrapper.find('.ix-text-copy-icon').trigger('click')
+
+ expect(document.execCommand).toHaveBeenCalledWith('copy')
+
+ await wrapper.setProps({ copyable: false })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.classes()).not.toContain('ix-text-wrapper')
+ expect(wrapper.classes()).toContain('ix-text')
+ })
+
+ // 需要 E2E 测试
+ test('expandable work', async () => {
+ const wrapper = TextMount({ props: { lineClamp: 2, expandable: true } })
+
+ expect(wrapper.html()).toMatchSnapshot()
+
+ await wrapper.trigger('click')
+
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+
+ test('lineClamp work', async () => {
+ const wrapper = TextMount({ props: { lineClamp: 2 } })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('ix-text-line-clamp')
+
+ await wrapper.setProps({ lineClamp: undefined })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.classes()).not.toContain('ix-text-line-clamp')
+ })
+
+ test('tag work', async () => {
+ const wrapper = TextMount({ props: { tag: 'div' } })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.element.tagName).toBe('DIV')
+
+ await wrapper.setProps({ tag: undefined })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ expect(wrapper.element.tagName).toBe('SPAN')
+ })
+
+ test('tooltip work', async () => {
+ const wrapper = TextMount({ props: { tooltip: 'native' } })
+
+ // 无效
+ await wrapper.trigger('mouseenter')
+
+ expect(wrapper.html()).toMatchSnapshot()
+
+ await wrapper.setProps({ tooltip: true })
+
+ expect(wrapper.html()).toMatchSnapshot()
+
+ await wrapper.setProps({ tooltip: false })
+
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+})
diff --git a/packages/components/text/demo/Basic.md b/packages/components/text/demo/Basic.md
new file mode 100644
index 000000000..0ddf241aa
--- /dev/null
+++ b/packages/components/text/demo/Basic.md
@@ -0,0 +1,14 @@
+---
+order: 0
+title:
+ zh: 基本使用
+ en: Basic usage
+---
+
+## zh
+
+最简单的用法。
+
+## en
+
+The simplest usage.
diff --git a/packages/components/text/demo/Basic.vue b/packages/components/text/demo/Basic.vue
new file mode 100644
index 000000000..0b918b86f
--- /dev/null
+++ b/packages/components/text/demo/Basic.vue
@@ -0,0 +1,5 @@
+
+
+ @idux是一套企业级中后台UI组件库, 致力于提供高效愉悦的开发体验。
+
+
diff --git a/packages/components/text/demo/Copyable.md b/packages/components/text/demo/Copyable.md
new file mode 100644
index 000000000..544869412
--- /dev/null
+++ b/packages/components/text/demo/Copyable.md
@@ -0,0 +1,14 @@
+---
+order: 2
+title:
+ zh: 可复制
+ en: Copyable
+---
+
+## zh
+
+提供了简单的文本复制能力,需要注意的是,内容只能是纯文本。
+
+## en
+
+The simplest usage.
diff --git a/packages/components/text/demo/Copyable.vue b/packages/components/text/demo/Copyable.vue
new file mode 100644
index 000000000..e5246e5d1
--- /dev/null
+++ b/packages/components/text/demo/Copyable.vue
@@ -0,0 +1,5 @@
+
+
+ @idux是一套企业级中后台UI组件库, 致力于提供高效愉悦的开发体验。
+
+
diff --git a/packages/components/text/demo/Expandable.md b/packages/components/text/demo/Expandable.md
new file mode 100644
index 000000000..0179ab1d3
--- /dev/null
+++ b/packages/components/text/demo/Expandable.md
@@ -0,0 +1,14 @@
+---
+order: 8
+title:
+ zh: 可展开的
+ en: Expandable
+---
+
+## zh
+
+搭配 `lineClamp` 使用时,可以通过点击展开全部文本。
+
+## en
+
+The simplest usage.
diff --git a/packages/components/text/demo/Expandable.vue b/packages/components/text/demo/Expandable.vue
new file mode 100644
index 000000000..05587011c
--- /dev/null
+++ b/packages/components/text/demo/Expandable.vue
@@ -0,0 +1,8 @@
+
+
+
+ @idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。
+
+
+
diff --git a/packages/components/text/demo/LineClamp.md b/packages/components/text/demo/LineClamp.md
new file mode 100644
index 000000000..b6fc7bc94
--- /dev/null
+++ b/packages/components/text/demo/LineClamp.md
@@ -0,0 +1,14 @@
+---
+order: 6
+title:
+ zh: 多行省略
+ en: Basic usage
+---
+
+## zh
+
+提供基于 `-webkit-line-clamp` 的多行省略,兼容性参见 [caniuse](https://caniuse.com/?search=line-clamp)
+
+## en
+
+The simplest usage.
diff --git a/packages/components/text/demo/LineClamp.vue b/packages/components/text/demo/LineClamp.vue
new file mode 100644
index 000000000..74dc8b97f
--- /dev/null
+++ b/packages/components/text/demo/LineClamp.vue
@@ -0,0 +1,8 @@
+
+
+
+ @idux 是一套企业级中后台 UI 组件库, 致力于提供高效愉悦的开发体验。 基于 Vue 3.x + TypeScript 开发,
+ 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。
+
+
+
diff --git a/packages/components/text/demo/Tooltip.md b/packages/components/text/demo/Tooltip.md
new file mode 100644
index 000000000..3eb299e55
--- /dev/null
+++ b/packages/components/text/demo/Tooltip.md
@@ -0,0 +1,15 @@
+---
+order: 10
+title:
+ zh: 浮层配置
+ en: Tooltip
+---
+
+## zh
+
+可以通过 `tooltip` 来配置悬浮提示,还可以通过 `title` 插槽来自定义提示内容。
+注意:当 `tooltip='native'` 时,`title` 插槽仅支持纯文本内容。
+
+## en
+
+The simplest usage.
diff --git a/packages/components/text/demo/Tooltip.vue b/packages/components/text/demo/Tooltip.vue
new file mode 100644
index 000000000..c632e3dd6
--- /dev/null
+++ b/packages/components/text/demo/Tooltip.vue
@@ -0,0 +1,24 @@
+
+
+
+
+ @idux是一套企业级中后台UI组件库, 致力于提供高效愉悦的开发体验。
+
+ @idux是一套企业级中后台UI组件库, 致力于提供高效愉悦的开发体验。
+
+ 基于 Vue 3.x + TypeScript 开发, 全部代码开源并遵循 MIT 协议,任何企业、组织及个人均可免费使用。
+
+
+
+
+
+
diff --git a/packages/components/text/docs/Api.en.md b/packages/components/text/docs/Api.en.md
new file mode 100644
index 000000000..6ab9c55e5
--- /dev/null
+++ b/packages/components/text/docs/Api.en.md
@@ -0,0 +1,19 @@
+### IxText
+
+#### TextProps
+
+| Name | Description | Type | Default | Global Config | Remark |
+| --- | --- | --- | --- | --- | --- |
+| - | - | - | - | ✅ | - |
+
+#### TextSlots
+
+| Name | Description | Parameter Type | Remark |
+| --- | --- | --- | --- |
+| - | - | - | - |
+
+#### TextMethods
+
+| Name | Description | Parameter Type | Remark |
+| --- | --- | --- | --- |
+| - | - | - | - |
diff --git a/packages/components/text/docs/Api.zh.md b/packages/components/text/docs/Api.zh.md
new file mode 100644
index 000000000..ff420cc7d
--- /dev/null
+++ b/packages/components/text/docs/Api.zh.md
@@ -0,0 +1,18 @@
+### IxText
+
+#### TextProps
+
+| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
+| --- | --- | --- | --- | --- | --- |
+| `copyable` | 点击图标可复制文本 | `boolean` | `false` | - | 仅支持普通文本 |
+| `expandable` | 点击可展开文本 | `boolean` | `false` | - | 建议搭配 `lineClamp` 一起使用 |
+| `lineClamp` | 多行省略 | `string \| number` | - | - | 基于 `-webkit-line-clamp` 的多行省略,兼容性参见 [caniuse](https://caniuse.com/?search=line-clamp) |
+| `tag` | 渲染的标签名 | `string \| Component` | `span` | - | - |
+| `tooltip` | 浮层配置 | `boolean \| 'native' \| TooltipProps` | `true` | - | 为 `native` 时展示原生提示,此时的提示内容仅支持普通文本 |
+
+#### TextSlots
+
+| 名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+| `title` | 自定义提示的内容 | - | - |
+| `copyIcon` | 自定义复制图表 | `{ copied: boolean }` | - |
diff --git a/packages/components/text/docs/Index.en.md b/packages/components/text/docs/Index.en.md
new file mode 100644
index 000000000..4720bc6c8
--- /dev/null
+++ b/packages/components/text/docs/Index.en.md
@@ -0,0 +1,8 @@
+---
+category: components
+type: General
+order: 0
+title: Text
+subtitle:
+---
+
diff --git a/packages/components/text/docs/Index.zh.md b/packages/components/text/docs/Index.zh.md
new file mode 100644
index 000000000..43f9f9bbf
--- /dev/null
+++ b/packages/components/text/docs/Index.zh.md
@@ -0,0 +1,9 @@
+---
+category: components
+type: 通用
+order: 0
+title: Text
+subtitle: 文本
+theme: true
+---
+
diff --git a/packages/components/text/docs/Theme.en.md b/packages/components/text/docs/Theme.en.md
new file mode 100644
index 000000000..06e4c3fce
--- /dev/null
+++ b/packages/components/text/docs/Theme.en.md
@@ -0,0 +1,3 @@
+| name | default | seer | mark |
+| --- | --- | --- | --- |
+| - | - | - | - |
diff --git a/packages/components/text/docs/Theme.zh.md b/packages/components/text/docs/Theme.zh.md
new file mode 100644
index 000000000..957cc1a90
--- /dev/null
+++ b/packages/components/text/docs/Theme.zh.md
@@ -0,0 +1,4 @@
+| 名称 | default | seer | 备注 |
+| --- | --- | --- | --- |
+| `@text-copy-icon-font-size` | `var(--ix-font-size-lg)` | - | - |
+| `@text-copy-icon-color` | `var(--ix-color-primary)` | - | - |
diff --git a/packages/components/text/index.ts b/packages/components/text/index.ts
new file mode 100644
index 000000000..22845806d
--- /dev/null
+++ b/packages/components/text/index.ts
@@ -0,0 +1,16 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+import type { TextComponent } from './src/types'
+
+import Text from './src/Text'
+
+const IxText = Text as unknown as TextComponent
+
+export { IxText }
+
+export type { TextInstance, TextComponent, TextPublicProps as TextProps } from './src/types'
diff --git a/packages/components/text/src/Text.tsx b/packages/components/text/src/Text.tsx
new file mode 100644
index 000000000..2f232301f
--- /dev/null
+++ b/packages/components/text/src/Text.tsx
@@ -0,0 +1,157 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+import { type Slot, computed, defineComponent, normalizeClass, shallowRef } from 'vue'
+
+import { isObject, isString, throttle } from 'lodash-es'
+
+import { useClipboard } from '@idux/cdk/clipboard'
+import { getFirstValidNode, useState } from '@idux/cdk/utils'
+import { useGlobalConfig } from '@idux/components/config'
+import { IxIcon } from '@idux/components/icon'
+import { IxTooltip } from '@idux/components/tooltip'
+
+import { textProps } from './types'
+
+export default defineComponent({
+ name: 'IxText',
+ inheritAttrs: false,
+ props: textProps,
+ setup(props, { attrs, slots }) {
+ const common = useGlobalConfig('common')
+ const mergedPrefixCls = computed(() => `${common.prefixCls}-text`)
+
+ const elementRef = shallowRef()
+ const [mergedDisabled, setDisabled] = useState(true)
+ const [mergedEntered, setEntered] = useState(false)
+ const [mergedExpanded, setExpanded] = useState(false)
+ const [mergedCopied, setCopied] = useState(false)
+
+ const { copy } = useClipboard()
+
+ const checkDisabled = () => {
+ const element = elementRef.value
+ if (mergedExpanded.value || !element) {
+ return true
+ }
+
+ const { lineClamp } = props
+ if (lineClamp !== undefined) {
+ return element.scrollHeight <= element.offsetHeight
+ } else {
+ const currOverflow = element.style.overflow
+ if (!currOverflow || currOverflow === 'visible') {
+ element.style.overflow = 'hidden'
+ }
+ const isOverflowing = element.clientWidth < element.scrollWidth || element.clientHeight < element.scrollHeight
+ element.style.overflow = currOverflow
+ return !isOverflowing
+ }
+ }
+
+ const onClick = () => {
+ const nextExpanded = !mergedExpanded.value
+ setExpanded(nextExpanded)
+ setDisabled(nextExpanded)
+ }
+
+ const onMouseEnter = () => {
+ setEntered(true)
+ setDisabled(checkDisabled())
+ }
+
+ const onCopy = throttle((text: string) => {
+ if (!text || mergedCopied.value) {
+ return
+ }
+ copy(text).then(success => {
+ if (success) {
+ setCopied(true)
+ setTimeout(() => setCopied(false), 3000)
+ }
+ })
+ }, 300)
+
+ const classes = computed(() => {
+ const prefixCls = mergedPrefixCls.value
+ const { lineClamp } = props
+ return normalizeClass({
+ [prefixCls]: true,
+ [`${prefixCls}-line-clamp`]: lineClamp !== undefined,
+ })
+ })
+
+ const style = computed(() => {
+ const { expandable, lineClamp } = props
+ const expanded = mergedExpanded.value
+ return {
+ cursor: expandable ? 'pointer' : '',
+ 'text-overflow': !expanded && lineClamp === undefined ? 'ellipsis' : '',
+ '-webkit-line-clamp': !expanded && lineClamp !== undefined ? lineClamp : '',
+ }
+ })
+
+ return () => {
+ const { tag: Tag, tooltip, expandable, copyable } = props
+ const prefixCls = mergedPrefixCls.value
+ const disabled = mergedDisabled.value
+ const entered = mergedEntered.value
+ const isNative = tooltip === 'native'
+ const titleSlot = slots.title || slots.default
+
+ let node = (
+
+ {slots.default?.()}
+
+ )
+
+ if (!disabled && entered && tooltip && !isNative) {
+ const tooltipProps = isObject(tooltip) ? { ...tooltip, disabled } : { disabled }
+ node = (
+
+ {node}
+
+ )
+ }
+
+ if (copyable) {
+ const copied = mergedCopied.value
+ const copyIcon = slots.copyIcon ? slots.copyIcon({ copied }) :
+ node = (
+
+ {node}
+ onCopy(getStringBySlot(slots.default))}>
+ {copyIcon}
+
+
+ )
+ }
+
+ return node
+ }
+ },
+})
+
+function getStringBySlot(slot: Slot | undefined) {
+ const validNode = getFirstValidNode(slot?.())
+ if (!validNode) {
+ return ''
+ }
+ const { children } = validNode
+ if (isString(children)) {
+ return children
+ }
+ return ''
+}
diff --git a/packages/components/text/src/types.ts b/packages/components/text/src/types.ts
new file mode 100644
index 000000000..1a008aca5
--- /dev/null
+++ b/packages/components/text/src/types.ts
@@ -0,0 +1,23 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+import type { ExtractInnerPropTypes, ExtractPublicPropTypes } from '@idux/cdk/utils'
+import type { TooltipProps } from '@idux/components/tooltip'
+import type { Component, DefineComponent, HTMLAttributes, PropType } from 'vue'
+
+export const textProps = {
+ copyable: { type: Boolean, default: false },
+ expandable: { type: Boolean, default: false },
+ lineClamp: { type: [String, Number] as PropType, default: undefined },
+ tag: { type: [String, Object] as PropType, default: 'span' },
+ tooltip: { type: [Boolean, String, Object] as PropType, default: true },
+} as const
+
+export type TextProps = ExtractInnerPropTypes
+export type TextPublicProps = ExtractPublicPropTypes
+export type TextComponent = DefineComponent & TextPublicProps>
+export type TextInstance = InstanceType>
diff --git a/packages/components/text/style/index.less b/packages/components/text/style/index.less
new file mode 100644
index 000000000..516f9c478
--- /dev/null
+++ b/packages/components/text/style/index.less
@@ -0,0 +1,37 @@
+@import '../../style/mixins/reset.less';
+
+.@{text-prefix} {
+ .reset-component-new();
+ .reset-font-size();
+
+ overflow: hidden;
+
+ &-line-clamp {
+ display: inline-box;
+ -webkit-box-orient: vertical;
+ }
+
+ &:not(&-line-clamp) {
+ display: inline-block;
+ vertical-align: bottom;
+ white-space: nowrap;
+ max-width: 100%;
+ }
+
+ &-copy-icon {
+ margin-left: 2px;
+ padding: 2px;
+ line-height: 1;
+ font-size: @text-copy-icon-font-size;
+ color: @text-copy-icon-color;
+ cursor: pointer;
+ }
+
+ &-wrapper {
+ .reset-component-new();
+
+ display: inline-flex;
+ align-items: center;
+ max-width: 100%;
+ }
+}
diff --git a/packages/components/text/style/themes/default.less b/packages/components/text/style/themes/default.less
new file mode 100644
index 000000000..a66443560
--- /dev/null
+++ b/packages/components/text/style/themes/default.less
@@ -0,0 +1,4 @@
+@import '../../../style/themes/default.less';
+@import './default.variable.less';
+
+@import '../index.less';
diff --git a/packages/components/text/style/themes/default.ts b/packages/components/text/style/themes/default.ts
new file mode 100644
index 000000000..215f89b3f
--- /dev/null
+++ b/packages/components/text/style/themes/default.ts
@@ -0,0 +1,5 @@
+// style dependencies
+import '@idux/components/icon/style/themes/default'
+import '@idux/components/tooltip/style/themes/default'
+
+import './default.less'
diff --git a/packages/components/text/style/themes/default.variable.less b/packages/components/text/style/themes/default.variable.less
new file mode 100644
index 000000000..af748b69e
--- /dev/null
+++ b/packages/components/text/style/themes/default.variable.less
@@ -0,0 +1,2 @@
+@text-copy-icon-font-size: var(--ix-font-size-lg);
+@text-copy-icon-color: var(--ix-color-primary);
diff --git a/packages/components/text/style/themes/seer.less b/packages/components/text/style/themes/seer.less
new file mode 100644
index 000000000..32bf707c1
--- /dev/null
+++ b/packages/components/text/style/themes/seer.less
@@ -0,0 +1,4 @@
+@import '../../../style/themes/seer.less';
+@import './seer.variable.less';
+
+@import '../index.less';
diff --git a/packages/components/text/style/themes/seer.ts b/packages/components/text/style/themes/seer.ts
new file mode 100644
index 000000000..659f933e7
--- /dev/null
+++ b/packages/components/text/style/themes/seer.ts
@@ -0,0 +1,5 @@
+// style dependencies
+import '@idux/components/icon/style/themes/seer'
+import '@idux/components/tooltip/style/themes/seer'
+
+import './seer.less'
diff --git a/packages/components/text/style/themes/seer.variable.less b/packages/components/text/style/themes/seer.variable.less
new file mode 100644
index 000000000..498793af1
--- /dev/null
+++ b/packages/components/text/style/themes/seer.variable.less
@@ -0,0 +1 @@
+@import './default.variable.less';
diff --git a/packages/components/types.d.ts b/packages/components/types.d.ts
index f5cfaf0f2..0e770cf7f 100644
--- a/packages/components/types.d.ts
+++ b/packages/components/types.d.ts
@@ -78,6 +78,7 @@ import type { SwitchComponent } from '@idux/components/switch'
import type { TableColumnComponent, TableComponent } from '@idux/components/table'
import type { TabComponent, TabsComponent } from '@idux/components/tabs'
import type { TagComponent, TagGroupComponent } from '@idux/components/tag'
+import type { TextComponent } from '@idux/components/text'
import type { TextareaComponent } from '@idux/components/textarea'
import type { TimePickerComponent, TimeRangePickerComponent } from '@idux/components/time-picker'
import type { TimelineComponent, TimelineItemComponent } from '@idux/components/timeline'
@@ -177,6 +178,7 @@ declare module 'vue' {
IxTabs: TabsComponent
IxTag: TagComponent
IxTagGroup: TagGroupComponent
+ IxText: TextComponent
IxTextarea: TextareaComponent
IxTimeline: TimelineComponent
IxTimelineItem: TimelineItemComponent