From 8bda98b0756b23d8d9fc866eee9558957bc69dd4 Mon Sep 17 00:00:00 2001
From: kooriookami <38392315+kooriookami@users.noreply.github.com>
Date: Thu, 14 Jan 2021 03:02:47 -0600
Subject: [PATCH] feat(affix): new component affix (#1260)
* feat(affix): new component affix
re #1247
* feat: add test
* perf: update state
n
* docs: update docs
* perf: pref the component
---
packages/affix/__tests__/affix.spec.ts | 158 +++++++++++++++++++++++++
packages/affix/index.ts | 11 ++
packages/affix/package.json | 12 ++
packages/affix/src/index.vue | 141 ++++++++++++++++++++++
packages/element-plus/index.ts | 7 +-
packages/theme-chalk/src/affix.scss | 7 ++
packages/theme-chalk/src/index.scss | 1 +
website/demo-styles/affix.scss | 8 ++
website/demo-styles/index.scss | 1 +
website/docs/en-US/affix.md | 55 +++++++++
website/docs/es/affix.md | 55 +++++++++
website/docs/fr-FR/affix.md | 55 +++++++++
website/docs/jp/affix.md | 55 +++++++++
website/docs/zh-CN/affix.md | 55 +++++++++
website/nav.config.json | 20 ++++
15 files changed, 639 insertions(+), 2 deletions(-)
create mode 100644 packages/affix/__tests__/affix.spec.ts
create mode 100644 packages/affix/index.ts
create mode 100644 packages/affix/package.json
create mode 100644 packages/affix/src/index.vue
create mode 100644 packages/theme-chalk/src/affix.scss
create mode 100644 website/demo-styles/affix.scss
create mode 100644 website/docs/en-US/affix.md
create mode 100644 website/docs/es/affix.md
create mode 100644 website/docs/fr-FR/affix.md
create mode 100644 website/docs/jp/affix.md
create mode 100644 website/docs/zh-CN/affix.md
diff --git a/packages/affix/__tests__/affix.spec.ts b/packages/affix/__tests__/affix.spec.ts
new file mode 100644
index 0000000000..acc92ac0a8
--- /dev/null
+++ b/packages/affix/__tests__/affix.spec.ts
@@ -0,0 +1,158 @@
+import { mount } from '@vue/test-utils'
+import Affix from '../src/index.vue'
+import { defineGetter, makeScroll } from '@element-plus/test-utils'
+
+let clientHeightRestore = null
+
+const _mount = (template: string) => mount({
+ components: {
+ 'el-affix': Affix,
+ },
+ template,
+}, { attachTo: document.body })
+
+const AXIOM = 'Rem is the best girl'
+
+beforeAll(() => {
+ clientHeightRestore = defineGetter(window.HTMLElement.prototype, 'clientHeight', 1000, 0)
+})
+
+afterAll(() => {
+ clientHeightRestore()
+})
+
+describe('Affix.vue', () => {
+ test('render test', async () => {
+ const wrapper = _mount(`
+ ${AXIOM}
+ `)
+ expect(wrapper.text()).toEqual(AXIOM)
+ const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: -100,
+ bottom: -80,
+ } as DOMRect)
+ const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
+ height: 200,
+ width: 1000,
+ top: 0,
+ bottom: 200,
+ } as DOMRect)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(false)
+ await makeScroll(document.documentElement, 'scrollTop', 200)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
+ mockAffixRect.mockRestore()
+ mockDocumentRect.mockRestore()
+ })
+
+ test('should render offset props', async () => {
+ const wrapper = _mount(`
+ ${AXIOM}
+ `)
+ const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: -100,
+ bottom: -80,
+ } as DOMRect)
+ const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
+ height: 200,
+ width: 1000,
+ top: 0,
+ bottom: 200,
+ } as DOMRect)
+ await makeScroll(document.documentElement, 'scrollTop', 200)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
+ expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain('top: 30px;')
+ mockAffixRect.mockRestore()
+ mockDocumentRect.mockRestore()
+ })
+
+ test('should render position props', async () => {
+ const wrapper = _mount(`
+ ${AXIOM}
+ `)
+
+ const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: 2000,
+ bottom: 2040,
+ } as DOMRect)
+ const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
+ height: 200,
+ width: 1000,
+ top: 0,
+ bottom: 200,
+ } as DOMRect)
+ await makeScroll(document.documentElement, 'scrollTop', 0)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
+ expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain('bottom: 20px;')
+ mockAffixRect.mockRestore()
+ mockDocumentRect.mockRestore()
+ })
+
+ test('should render target props', async () => {
+ const wrapper = _mount(`
+
+ ${AXIOM}
+
+
+ `)
+
+ const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: -100,
+ bottom: -60,
+ } as DOMRect)
+ const mockTargetRect = jest.spyOn(wrapper.find('.target').element, 'getBoundingClientRect').mockReturnValue({
+ height: 200,
+ width: 1000,
+ top: -100,
+ bottom: 100,
+ } as DOMRect)
+ await makeScroll(document.documentElement, 'scrollTop', 100)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
+ mockAffixRect.mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: -300,
+ bottom: -260,
+ } as DOMRect)
+ mockTargetRect.mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: -300,
+ bottom: -260,
+ } as DOMRect)
+ await makeScroll(document.documentElement, 'scrollTop', 300)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(false)
+ mockAffixRect.mockRestore()
+ mockTargetRect.mockRestore()
+ })
+
+ test('should render z-index props', async () => {
+ const wrapper = _mount(`
+ ${AXIOM}
+ `)
+ const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
+ height: 40,
+ width: 1000,
+ top: -100,
+ bottom: -80,
+ } as DOMRect)
+ const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
+ height: 200,
+ width: 1000,
+ top: 0,
+ bottom: 200,
+ } as DOMRect)
+ await makeScroll(document.documentElement, 'scrollTop', 200)
+ expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
+ expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain('z-index: 1000;')
+ mockAffixRect.mockRestore()
+ mockDocumentRect.mockRestore()
+ })
+})
diff --git a/packages/affix/index.ts b/packages/affix/index.ts
new file mode 100644
index 0000000000..b3a8f07506
--- /dev/null
+++ b/packages/affix/index.ts
@@ -0,0 +1,11 @@
+import { App } from 'vue'
+import type { SFCWithInstall } from '@element-plus/utils/types'
+import Affix from './src/index.vue'
+
+Affix.install = (app: App): void => {
+ app.component(Affix.name, Affix)
+}
+
+const _Affix: SFCWithInstall = Affix
+
+export default _Affix
diff --git a/packages/affix/package.json b/packages/affix/package.json
new file mode 100644
index 0000000000..3d0279146b
--- /dev/null
+++ b/packages/affix/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@element-plus/affix",
+ "version": "0.0.0",
+ "main": "dist/index.js",
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": "^3.0.5"
+ },
+ "devDependencies": {
+ "@vue/test-utils": "^2.0.0-beta.3"
+ }
+}
diff --git a/packages/affix/src/index.vue b/packages/affix/src/index.vue
new file mode 100644
index 0000000000..221f6ace91
--- /dev/null
+++ b/packages/affix/src/index.vue
@@ -0,0 +1,141 @@
+
+
+
+
diff --git a/packages/element-plus/index.ts b/packages/element-plus/index.ts
index 833a4788c0..7aa84ab7e8 100644
--- a/packages/element-plus/index.ts
+++ b/packages/element-plus/index.ts
@@ -1,4 +1,5 @@
import type { App } from 'vue'
+import ElAffix from '@element-plus/affix'
import ElAlert from '@element-plus/alert'
import ElAside from '@element-plus/aside'
import ElAutocomplete from '@element-plus/autocomplete'
@@ -92,19 +93,20 @@ import { use } from '@element-plus/locale'
// if you encountered problems alike "Can't resolve './version'"
// please run `yarn bootstrap` first
import { version as version_ } from './version'
-import { setConfig } from '@element-plus/utils/config'
import type { InstallOptions } from '@element-plus/utils/config'
+import { setConfig } from '@element-plus/utils/config'
const version = version_ // version_ to fix tsc issue
const locale = use
-const defaultInstallOpt: InstallOptions = {
+const defaultInstallOpt: InstallOptions = {
size: '' as ComponentSize,
zIndex: 2000,
}
const components = [
+ ElAffix,
ElAlert,
ElAside,
ElAutocomplete,
@@ -215,6 +217,7 @@ const install = (app: App, opt: InstallOptions): void => {
}
export {
+ ElAffix,
ElAlert,
ElAside,
ElAutocomplete,
diff --git a/packages/theme-chalk/src/affix.scss b/packages/theme-chalk/src/affix.scss
new file mode 100644
index 0000000000..4d0268eb5e
--- /dev/null
+++ b/packages/theme-chalk/src/affix.scss
@@ -0,0 +1,7 @@
+@import "mixins/mixins";
+
+@include b(affix) {
+ @include m(fixed) {
+ position: fixed;
+ }
+}
diff --git a/packages/theme-chalk/src/index.scss b/packages/theme-chalk/src/index.scss
index a54dc7bfdf..64c04fb839 100644
--- a/packages/theme-chalk/src/index.scss
+++ b/packages/theme-chalk/src/index.scss
@@ -85,3 +85,4 @@
@import "./skeleton.scss";
@import "./skeleton-item.scss";
@import "./empty.scss";
+@import "./affix.scss";
diff --git a/website/demo-styles/affix.scss b/website/demo-styles/affix.scss
new file mode 100644
index 0000000000..5037877fad
--- /dev/null
+++ b/website/demo-styles/affix.scss
@@ -0,0 +1,8 @@
+.demo-block.demo-affix {
+ .affix-container {
+ text-align: center;
+ height: 400px;
+ border-radius: 4px;
+ background: #ECF5FF;
+ }
+}
diff --git a/website/demo-styles/index.scss b/website/demo-styles/index.scss
index 4df50dbb69..28b0915e4b 100644
--- a/website/demo-styles/index.scss
+++ b/website/demo-styles/index.scss
@@ -1,3 +1,4 @@
+@import "./affix.scss";
@import "./alert.scss";
@import "./badge.scss";
@import "./border.scss";
diff --git a/website/docs/en-US/affix.md b/website/docs/en-US/affix.md
new file mode 100644
index 0000000000..2820e1b4ae
--- /dev/null
+++ b/website/docs/en-US/affix.md
@@ -0,0 +1,55 @@
+## Affix
+
+Fix the element to a specific visible area.
+
+### Basic usage
+
+Affix is fixed at the top of the page by default.
+
+:::demo You can set `offset` attribute to change the offset top,the default value is 0。
+```html
+
+ Offset top 120px
+
+```
+:::
+
+### Target container
+
+You can set `target` attribute to keep the affix in the container at all times. It will be hidden if out of range.
+
+:::demo Please notice that the container avoid having scrollbar.
+```html
+
+
+ Target container
+
+
+```
+:::
+
+### Fixed position
+
+The affix component provides two fixed positions: `top` and `bottom`.
+
+:::demo You can set `position` attribute to change the fixed position, the default value is `top`.
+```html
+
+ Offset bottom 20px
+
+```
+:::
+
+### Attributes
+| Attribute | Description | Type | Accepted Values | Default |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| offset | offset distance | number | — | 0 |
+| position | position of affix | string | top / bottom | top |
+| target | target container (CSS selector) | string | — | — |
+| z-index | z-index of affix | number | — | 100 |
+
+### Events
+| Event Name | Description | Parameters |
+|---------- |-------- |---------- |
+| change | triggers when fixed state changed | (value: boolean) |
+| scroll | triggers when scrolling | scroll top and fixed state |
diff --git a/website/docs/es/affix.md b/website/docs/es/affix.md
new file mode 100644
index 0000000000..2820e1b4ae
--- /dev/null
+++ b/website/docs/es/affix.md
@@ -0,0 +1,55 @@
+## Affix
+
+Fix the element to a specific visible area.
+
+### Basic usage
+
+Affix is fixed at the top of the page by default.
+
+:::demo You can set `offset` attribute to change the offset top,the default value is 0。
+```html
+
+ Offset top 120px
+
+```
+:::
+
+### Target container
+
+You can set `target` attribute to keep the affix in the container at all times. It will be hidden if out of range.
+
+:::demo Please notice that the container avoid having scrollbar.
+```html
+
+
+ Target container
+
+
+```
+:::
+
+### Fixed position
+
+The affix component provides two fixed positions: `top` and `bottom`.
+
+:::demo You can set `position` attribute to change the fixed position, the default value is `top`.
+```html
+
+ Offset bottom 20px
+
+```
+:::
+
+### Attributes
+| Attribute | Description | Type | Accepted Values | Default |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| offset | offset distance | number | — | 0 |
+| position | position of affix | string | top / bottom | top |
+| target | target container (CSS selector) | string | — | — |
+| z-index | z-index of affix | number | — | 100 |
+
+### Events
+| Event Name | Description | Parameters |
+|---------- |-------- |---------- |
+| change | triggers when fixed state changed | (value: boolean) |
+| scroll | triggers when scrolling | scroll top and fixed state |
diff --git a/website/docs/fr-FR/affix.md b/website/docs/fr-FR/affix.md
new file mode 100644
index 0000000000..2820e1b4ae
--- /dev/null
+++ b/website/docs/fr-FR/affix.md
@@ -0,0 +1,55 @@
+## Affix
+
+Fix the element to a specific visible area.
+
+### Basic usage
+
+Affix is fixed at the top of the page by default.
+
+:::demo You can set `offset` attribute to change the offset top,the default value is 0。
+```html
+
+ Offset top 120px
+
+```
+:::
+
+### Target container
+
+You can set `target` attribute to keep the affix in the container at all times. It will be hidden if out of range.
+
+:::demo Please notice that the container avoid having scrollbar.
+```html
+
+
+ Target container
+
+
+```
+:::
+
+### Fixed position
+
+The affix component provides two fixed positions: `top` and `bottom`.
+
+:::demo You can set `position` attribute to change the fixed position, the default value is `top`.
+```html
+
+ Offset bottom 20px
+
+```
+:::
+
+### Attributes
+| Attribute | Description | Type | Accepted Values | Default |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| offset | offset distance | number | — | 0 |
+| position | position of affix | string | top / bottom | top |
+| target | target container (CSS selector) | string | — | — |
+| z-index | z-index of affix | number | — | 100 |
+
+### Events
+| Event Name | Description | Parameters |
+|---------- |-------- |---------- |
+| change | triggers when fixed state changed | (value: boolean) |
+| scroll | triggers when scrolling | scroll top and fixed state |
diff --git a/website/docs/jp/affix.md b/website/docs/jp/affix.md
new file mode 100644
index 0000000000..2820e1b4ae
--- /dev/null
+++ b/website/docs/jp/affix.md
@@ -0,0 +1,55 @@
+## Affix
+
+Fix the element to a specific visible area.
+
+### Basic usage
+
+Affix is fixed at the top of the page by default.
+
+:::demo You can set `offset` attribute to change the offset top,the default value is 0。
+```html
+
+ Offset top 120px
+
+```
+:::
+
+### Target container
+
+You can set `target` attribute to keep the affix in the container at all times. It will be hidden if out of range.
+
+:::demo Please notice that the container avoid having scrollbar.
+```html
+
+
+ Target container
+
+
+```
+:::
+
+### Fixed position
+
+The affix component provides two fixed positions: `top` and `bottom`.
+
+:::demo You can set `position` attribute to change the fixed position, the default value is `top`.
+```html
+
+ Offset bottom 20px
+
+```
+:::
+
+### Attributes
+| Attribute | Description | Type | Accepted Values | Default |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| offset | offset distance | number | — | 0 |
+| position | position of affix | string | top / bottom | top |
+| target | target container (CSS selector) | string | — | — |
+| z-index | z-index of affix | number | — | 100 |
+
+### Events
+| Event Name | Description | Parameters |
+|---------- |-------- |---------- |
+| change | triggers when fixed state changed | (value: boolean) |
+| scroll | triggers when scrolling | scroll top and fixed state |
diff --git a/website/docs/zh-CN/affix.md b/website/docs/zh-CN/affix.md
new file mode 100644
index 0000000000..1c9c3043e9
--- /dev/null
+++ b/website/docs/zh-CN/affix.md
@@ -0,0 +1,55 @@
+## Affix 固钉
+
+将页面元素固定在特定可视区域。
+
+### 基本用法
+
+固钉默认固定在页面顶部。
+
+:::demo 通过设置 `offset` 属性来改变吸顶距离,默认值为 0。
+```html
+
+ 距离顶部 120px
+
+```
+:::
+
+### 指定容器
+
+通过设置 `target` 属性,让固钉始终保持在容器内,超过范围则隐藏。
+
+:::demo 请注意容器避免出现滚动条。
+```html
+
+
+ 指定容器
+
+
+```
+:::
+
+### 固定位置
+
+Affix 组件提供了两个固定位置:`top` 和 `bottom`。
+
+:::demo 通过设置 `position` 属性来改变固定位置,默认值为 `top` 。
+```html
+
+ 距离底部 20px
+
+```
+:::
+
+### Attributes
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| offset | 偏移距离 | number | — | 0 |
+| position | 固钉位置 | string | top / bottom | top |
+| target | 指定容器(CSS 选择器) | string | — | — |
+| z-index | 固钉层级 | number | — | 100 |
+
+### Events
+| 事件名称 | 说明 | 回调参数 |
+|---------- |-------- |---------- |
+| change | 固钉状态改变时触发的事件 | (value: boolean) |
+| scroll | 滚动时触发的事件 | 滚动距离和固钉状态 |
diff --git a/website/nav.config.json b/website/nav.config.json
index d615858e83..48e6cd5f82 100644
--- a/website/nav.config.json
+++ b/website/nav.config.json
@@ -227,6 +227,10 @@
{
"groupName": "Navigation",
"list": [
+ {
+ "path": "/affix",
+ "title": "Affix 固钉"
+ },
{
"path": "/menu",
"title": "NavMenu 导航菜单"
@@ -541,6 +545,10 @@
{
"groupName": "Navigation",
"list": [
+ {
+ "path": "/affix",
+ "title": "Affix"
+ },
{
"path": "/menu",
"title": "NavMenu"
@@ -859,6 +867,10 @@
{
"groupName": "Navigation",
"list": [
+ {
+ "path": "/affix",
+ "title": "Affix"
+ },
{
"path": "/menu",
"title": "NavMenu"
@@ -1177,6 +1189,10 @@
{
"groupName": "Navigation",
"list": [
+ {
+ "path": "/affix",
+ "title": "Affix"
+ },
{
"path": "/menu",
"title": "NavMenu"
@@ -1495,6 +1511,10 @@
{
"groupName": "Navigation",
"list": [
+ {
+ "path": "/affix",
+ "title": "Affix"
+ },
{
"path": "/menu",
"title": "NavMenu"