Repository: ant-design/ant-design-mobile (ts)

Issue:
imageUpload 内部调用URL.createObjectURL(file)是否会造成内存泄露？
### Version of antd-mobile

5.22.0

### Operating system and its version

Others

### Browser and its version

_No response_

### Sandbox to reproduce

xx

### What happened?

通过webview嵌入到微信小程序，当上传多张图的情况下，提醒小程序意外退出，请稍后重试。

源代码中片段
```
{tasks.map(task => {
          if (!props.showFailed && task.status === 'fail') {
            return null
          }
          return (
            <PreviewItem
              key={task.id}
              file={task.file}
              deletable={task.status !== 'pending'}
              status={task.status}
              imageFit={props.imageFit}
              onDelete={() => {
                setTasks(tasks.filter(x => x.id !== task.id))
              }}
            />
          )
        })}
```
PreviewItem组件会根据   file={task.file} 都调用 URL.createObjectURL(file)生成blob预览


### Relevant log output

```shell
无
```


Pull request:
enhance(ImageUploader): optimize image preview memory
fix #5625

Patch:
diff --git a/config/components.ts b/config/components.ts
index 460b2c9c4..4e95e34e5 100644
--- a/config/components.ts
+++ b/config/components.ts
@@ -31,6 +31,7 @@ export const components = {
     '/components/swiper',
     '/components/tag',
     '/components/water-mark',
+    '/components/footer',
   ],
   dataEntry: [
     '/components/cascader',
diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx
index 3352121e5..f08f641f7 100644
--- a/src/components/avatar/avatar.tsx
+++ b/src/components/avatar/avatar.tsx
@@ -1,6 +1,6 @@
 import React, { FC, ReactNode } from 'react'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
-import { Image } from '../../index'
+import Image from '../image'
 import { mergeProps } from '../../utils/with-default-props'
 import { Fallback } from './fallback'
 import { ImageProps } from '../image'
diff --git a/src/components/footer/demos/demo1.tsx b/src/components/footer/demos/demo1.tsx
new file mode 100644
index 000000000..152e93aca
--- /dev/null
+++ b/src/components/footer/demos/demo1.tsx
@@ -0,0 +1,99 @@
+import React from 'react'
+import { Footer } from 'antd-mobile'
+import { DemoBlock } from 'demos'
+import { HandPayCircleOutline } from 'antd-mobile-icons'
+import { ChipItem, LinkItem } from '../footer'
+
+export default () => {
+  const links: LinkItem[] = [
+    {
+      text: '阿里云',
+      href: 'https://www.aliyun.com/',
+    },
+    {
+      text: '支付宝',
+      href: 'https://www.antgroup.com/',
+    },
+  ]
+  const chips: ChipItem[] = [
+    {
+      text: '蚂蚁借呗',
+    },
+    {
+      text: '备用金',
+    },
+    {
+      text: '蚂蚁花呗',
+    },
+  ]
+  const chipsLinkData: ChipItem[] = [
+    {
+      text: '蚂蚁借呗',
+      type: 'link',
+    },
+    {
+      text: '备用金',
+      type: 'link',
+    },
+    {
+      text: '蚂蚁花呗',
+      type: 'link',
+    },
+  ]
+
+  const onChipClick = (item: ChipItem, index: number) => {
+    console.log(item, index)
+    alert(`${item?.text}被点击了`)
+  }
+  const onLinkClick = (item: LinkItem, index: number) => {
+    console.log(item, index)
+    alert(`跳转到${item?.href}`)
+  }
+
+  return (
+    <>
+      <DemoBlock title='基础页脚'>
+        <Footer label='没有更多了'></Footer>
+      </DemoBlock>
+      <DemoBlock title='自定义 label'>
+        <Footer
+          label={
+            <div>
+              <HandPayCircleOutline /> 蚂蚁财富
+            </div>
+          }
+        ></Footer>
+      </DemoBlock>
+      <DemoBlock title='带内容的页脚'>
+        <Footer content='@ 2004-2020 Alipay.com All rights reserved'></Footer>
+      </DemoBlock>
+      <DemoBlock title='带链接的页脚'>
+        <Footer
+          links={[
+            {
+              text: '阿里云',
+              href: 'https://www.aliyun.com/',
+            },
+          ]}
+        ></Footer>
+      </DemoBlock>
+      <DemoBlock title='通过点击事件跳转'>
+        <Footer links={links} onLinkClick={onLinkClick}></Footer>
+      </DemoBlock>
+      <DemoBlock title='带标签的页脚'>
+        <Footer chips={chips}></Footer>
+      </DemoBlock>
+      <DemoBlock title='标签可点击'>
+        <Footer chips={chipsLinkData} onChipClick={onChipClick}></Footer>
+      </DemoBlock>
+      <DemoBlock title='组合使用'>
+        <Footer
+          label='没有更多了'
+          content='@ 2004-2020 Alipay.com All rights reserved'
+          links={links}
+          chips={chips}
+        ></Footer>
+      </DemoBlock>
+    </>
+  )
+}
diff --git a/src/components/footer/footer.less b/src/components/footer/footer.less
new file mode 100644
index 000000000..123163285
--- /dev/null
+++ b/src/components/footer/footer.less
@@ -0,0 +1,55 @@
+@class-prefix-footer: ~'adm-footer';
+
+.@{class-prefix-footer} {
+  --background-color: var(--adm-color-background);
+
+  color: var(--adm-color-text);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background-color: var(--background-color);
+
+  &-label {
+    width: 100%;
+    .adm-divider.adm-divider {
+      color: var(--adm-color-light);
+    }
+  }
+  &-links {
+    margin: 8px 0;
+    color: var(--adm-color-primary);
+    white-space: nowrap;
+    a {
+      text-decoration: none;
+    }
+  }
+  &-content {
+    display: flex;
+    align-items: center;
+    margin: 8px 0;
+    color: var(--adm-color-light);
+  }
+  &-chips {
+    display: flex;
+    align-items: center;
+    white-space: nowrap;
+    margin: 8px 0;
+  }
+  &-chip {
+    white-space: nowrap;
+    margin-right: 20px;
+    font-size: 12px;
+    background-color: var(--adm-color-box);
+    color: var(--adm-color-weak);
+    padding: 4px 12px;
+    border-radius: 100px;
+    &-link {
+      cursor: pointer;
+      background-color: #e7f1ff;
+      color: var(--adm-color-primary);
+    }
+    &:last-child {
+      margin-right: 0;
+    }
+  }
+}
diff --git a/src/components/footer/footer.tsx b/src/components/footer/footer.tsx
new file mode 100644
index 000000000..1aa6f8765
--- /dev/null
+++ b/src/components/footer/footer.tsx
@@ -0,0 +1,101 @@
+import React, { FC, ReactNode } from 'react'
+import classNames from 'classnames'
+import { NativeProps, withNativeProps } from '../../utils/native-props'
+import { mergeProps } from '../../utils/with-default-props'
+import { Divider } from '../divider/divider'
+
+const classPrefix = `adm-footer`
+
+export type LinkItem = {
+  text: string
+  href: string
+}
+
+export type ChipItem = {
+  text: ReactNode
+  type?: 'plain' | 'link'
+}
+
+export type FooterProps = {
+  label?: ReactNode
+  links?: LinkItem[]
+  content?: ReactNode
+  chips?: ChipItem[]
+  onChipClick?: (item: ChipItem, index: number) => void
+  onLinkClick?: (item: LinkItem, index: number) => void
+} & NativeProps<'--background-color'>
+
+const defaultProps: FooterProps = {
+  label: '',
+  links: [],
+  content: '',
+  chips: [],
+}
+
+export const Footer: FC<FooterProps> = p => {
+  const props = mergeProps(defaultProps, p)
+  const { label, links, content, chips, onChipClick, onLinkClick } = props
+
+  const clickChipItem = (item: ChipItem, index: number) => {
+    if (chips?.length && item.type === 'link') {
+      onChipClick?.(item, index)
+    }
+  }
+
+  const clickLinkItem = (
+    item: LinkItem,
+    index: number,
+    e: React.MouseEvent<HTMLAnchorElement, MouseEvent>
+  ) => {
+    if (onLinkClick) {
+      e.preventDefault()
+      onLinkClick(item, index)
+    }
+  }
+  return withNativeProps(
+    props,
+    <div className={classNames(classPrefix)}>
+      {label && (
+        <div className={`${classPrefix}-label`}>
+          <Divider>{label}</Divider>
+        </div>
+      )}
+      {links && links.length > 0 && (
+        <div className={`${classPrefix}-links`}>
+          {links.map((link, index) => {
+            return (
+              <React.Fragment key={index}>
+                <a
+                  href={link.href}
+                  rel='noopener noreferrer'
+                  onClick={event => clickLinkItem(link, index, event)}
+                >
+                  {link.text}
+                </a>
+                {index !== links.length - 1 && <Divider direction='vertical' />}
+              </React.Fragment>
+            )
+          })}
+        </div>
+      )}
+      {content && <div className={`${classPrefix}-content`}>{content}</div>}
+      {chips && chips.length > 0 && (
+        <div className={`${classPrefix}-chips`}>
+          {chips.map((chip, index) => {
+            return (
+              <div
+                key={index}
+                onClick={() => clickChipItem(chip, index)}
+                className={classNames(`${classPrefix}-chip`, {
+                  [`${classPrefix}-chip-link`]: chip.type === 'link',
+                })}
+              >
+                {chip.text}
+              </div>
+            )
+          })}
+        </div>
+      )}
+    </div>
+  )
+}
diff --git a/src/components/footer/index.en.md b/src/components/footer/index.en.md
new file mode 100644
index 000000000..374606574
--- /dev/null
+++ b/src/components/footer/index.en.md
@@ -0,0 +1,42 @@
+# Footer
+
+## When to Use
+
+Applicable to information description at the bottom of the page。
+
+## Demos
+
+<code src="./demos/demo1.tsx"></code>
+
+## Footer
+
+### Props
+
+| Name        | Description                                                                                                                 | Type                                      | Default |
+| ----------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------- |
+| label       | Top content with a dividing line                                                                                            | `string \| ReactNode`                     | -       |
+| links       | Links to content                                                                                                            | `LinkItem[]`                              | -       |
+| label       | The common content section                                                                                                  | `string \| ReactNode`                     | -       |
+| chips       | Chips, only one line of content is allowed. If the content exceeds the width, the content will be automatically intercepted | `ChipItem[]`                              | -       |
+| onTagClick  | Click the bottom label to trigger, and it will only take effect when the label 'type' is' link '                            | `(item: ChipItem, index: number) => void` | -       |
+| onLinkClick | Clicking the link triggers an event that intercepts the href jump to the A tag                                              | `(item: LinkItem, index: number) => void` | -       |
+
+### ChipItem
+
+| Name | Description                                           | Type                  | Default   |
+| ---- | ----------------------------------------------------- | --------------------- | --------- |
+| text | Tag Text                                              | `string \| ReactNode` | -         |
+| type | Tag Type, `plain`is pure display, `link` is clickable | `'plain' \| 'link'`   | `'plain'` |
+
+### LinkItem
+
+| Name | Description   | Type     | Default |
+| ---- | ------------- | -------- | ------- |
+| text | Text of links | `string` | -       |
+| href | Href of links | `string` | -       |
+
+### CSS Variables
+
+| Name               | Description | Default                       |
+| ------------------ | ----------- | ----------------------------- |
+| --background-color | background  | `var(--adm-color-background)` |
diff --git a/src/components/footer/index.ts b/src/components/footer/index.ts
new file mode 100644
index 000000000..9d9657ce7
--- /dev/null
+++ b/src/components/footer/index.ts
@@ -0,0 +1,6 @@
+import './footer.less'
+import { Footer } from './footer'
+
+export type { FooterProps, ChipItem, LinkItem } from './footer'
+
+export default Footer
diff --git a/src/components/footer/index.zh.md b/src/components/footer/index.zh.md
new file mode 100644
index 000000000..7b392d9a7
--- /dev/null
+++ b/src/components/footer/index.zh.md
@@ -0,0 +1,42 @@
+# Footer 页脚
+
+## 何时使用
+
+适用于页面底部信息描述。
+
+## 示例
+
+<code src="./demos/demo1.tsx"></code>
+
+## Footer
+
+### 属性
+
+| 属性        | 说明                                             | 类型                                      | 默认值 |
+| ----------- | ------------------------------------------------ | ----------------------------------------- | ------ |
+| label       | 带分割线的顶部内容                               | `string \| ReactNode`                     | -      |
+| links       | 链接内容                                         | `LinkItem[]`                              | -      |
+| content     | 普通的内容部分                                   | `string \| ReactNode`                     | -      |
+| chips       | 底部标签                                         | `ChipItem[]`                              | -      |
+| onChipClick | 点击底部标签触发，仅在标签 `type` 为 `link` 生效 | `(item: ChipItem, index: number) => void` | -      |
+| onLinkClick | 点击链接触发事件，拦截 a 标签的 href 跳转        | `(item: LinkItem, index: number) => void` | -      |
+
+### ChipItem
+
+| 属性 | 说明                                        | 类型                  | 默认值    |
+| ---- | ------------------------------------------- | --------------------- | --------- |
+| text | 标签内容                                    | `string \| ReactNode` | -         |
+| type | 标签类型，`plain` 为纯展示，`link` 为可点击 | `'plain' \| 'link'`   | `'plain'` |
+
+### LinkItem
+
+| 属性 | 说明        | 类型     | 默认值 |
+| ---- | ----------- | -------- | ------ |
+| text | 链接的文字  | `string` | -      |
+| href | 链接的 href | `string` | -      |
+
+### CSS 变量
+
+| 属性               | 说明   | 默认值                        |
+| ------------------ | ------ | ----------------------------- |
+| --background-color | 背景色 | `var(--adm-color-background)` |
diff --git a/src/components/image-uploader/preview-item.tsx b/src/components/image-uploader/preview-item.tsx
index e286d5cb9..193afb6da 100644
--- a/src/components/image-uploader/preview-item.tsx
+++ b/src/components/image-uploader/preview-item.tsx
@@ -1,4 +1,4 @@
-import React, { FC, useMemo } from 'react'
+import React, { FC, useLayoutEffect, useMemo } from 'react'
 import { CloseOutline } from 'antd-mobile-icons'
 import classNames from 'classnames'
 import { TaskStatus } from './image-uploader'
@@ -32,6 +32,12 @@ const PreviewItem: FC<Props> = props => {
     return ''
   }, [url, file])
 
+  useLayoutEffect(() => {
+    return () => {
+      URL.revokeObjectURL(src)
+    }
+  }, [src])
+
   function renderLoading() {
     return (
       props.status === 'pending' && (
diff --git a/src/components/image-viewer/slides.tsx b/src/components/image-viewer/slides.tsx
index f0b9f61f9..606204a6c 100644
--- a/src/components/image-viewer/slides.tsx
+++ b/src/components/image-viewer/slides.tsx
@@ -91,9 +91,9 @@ export const Slides = forwardRef<SlidesRef, SlidesType>((props, ref) => {
         className={`${classPrefix}-slides-inner`}
         style={{ x: x.to(x => -x) }}
       >
-        {props.images.map(image => (
+        {props.images.map((image, index) => (
           <Slide
-            key={image}
+            key={index}
             image={image}
             onTap={props.onTap}
             maxZoom={props.maxZoom}
diff --git a/src/components/slider/index.en.md b/src/components/slider/index.en.md
index da3050f34..9b2481cbd 100644
--- a/src/components/slider/index.en.md
+++ b/src/components/slider/index.en.md
@@ -16,21 +16,22 @@ When the user needs to select within the numerical interval/custom interval, it
 
 ### Props
 
-| Name          | Description                                                                                                                                              | Type                                          | Default              |
-| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------- |
-| defaultValue  | Default value                                                                                                                                            | `number \| [number, number]`                  | `range ? [0, 0] : 0` |
-| disabled      | Whether disabled                                                                                                                                         | `boolean`                                     | `false`              |
-| icon          | The icon of slider                                                                                                                                       | `ReactNode`                                   | -                    |
-| marks         | Tick marks                                                                                                                                               | `{ [key: number]: React.ReactNode }`          | -                    |
-| max           | Max value                                                                                                                                                | `number`                                      | `100`                |
-| min           | Min value                                                                                                                                                | `number`                                      | `0`                  |
-| onAfterChange | Consistent with the trigger timing of `touchend`, pass the current value as a parameter                                                                  | `(value: number \| [number, number]) => void` | -                    |
-| onChange      | Triggered when the slider is dragged, and the current dragged value is passed in as a parameter                                                          | `(value: number \| [number, number]) => void` | -                    |
-| popover       | Whether to display the popover when dragging. Support passing in function to customize the rendering content.                                            | `boolean \| ((value: number) => ReactNode)`   | `false`              |
-| range         | Whether it is a double sliders                                                                                                                           | `boolean`                                     | `false`              |
-| step          | Step distance, the value must be greater than `0`, and can be divisible by `(max-min)`. When `marks` is not null, the configuration of `step` is invalid | `number`                                      | `1`                  |
-| ticks         | Whether to display the scale                                                                                                                             | `boolean`                                     | `false`              |
-| value         | Current value                                                                                                                                            | `number \| [number, number]`                  | -                    |
+| Name            | Description                                                                                                                                              | Type                                          | Default              |
+| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------- |
+| defaultValue    | Default value                                                                                                                                            | `number \| [number, number]`                  | `range ? [0, 0] : 0` |
+| disabled        | Whether disabled                                                                                                                                         | `boolean`                                     | `false`              |
+| icon            | The icon of slider                                                                                                                                       | `ReactNode`                                   | -                    |
+| marks           | Tick marks                                                                                                                                               | `{ [key: number]: React.ReactNode }`          | -                    |
+| max             | Max value                                                                                                                                                | `number`                                      | `100`                |
+| min             | Min value                                                                                                                                                | `number`                                      | `0`                  |
+| onAfterChange   | Consistent with the trigger timing of `touchend`, pass the current value as a parameter                                                                  | `(value: number \| [number, number]) => void` | -                    |
+| onChange        | Triggered when the slider is dragged, and the current dragged value is passed in as a parameter                                                          | `(value: number \| [number, number]) => void` | -                    |
+| popover         | Whether to display the popover when dragging. Support passing in function to customize the rendering content.                                            | `boolean \| ((value: number) => ReactNode)`   | `false`              |
+| residentPopover | Whether the `popover` is permanently displayed , this attribute takes effect when `popover` exists                                                       | `boolean `                                    | `false`              |
+| range           | Whether it is a double sliders                                                                                                                           | `boolean`                                     | `false`              |
+| step            | Step distance, the value must be greater than `0`, and can be divisible by `(max-min)`. When `marks` is not null, the configuration of `step` is invalid | `number`                                      | `1`                  |
+| ticks           | Whether to display the scale                                                                                                                             | `boolean`                                     | `false`              |
+| value           | Current value                                                                                                                                            | `number \| [number, number]`                  | -                    |
 
 ### CSS Variables
 
diff --git a/src/components/slider/index.zh.md b/src/components/slider/index.zh.md
index 2067b0586..f689d1f4a 100644
--- a/src/components/slider/index.zh.md
+++ b/src/components/slider/index.zh.md
@@ -16,21 +16,22 @@
 
 ### 属性
 
-| 属性          | 说明                                                                                            | 类型                                          | 默认值               |
-| ------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------- |
-| defaultValue  | 默认值                                                                                          | `number \| [number, number]`                  | `range ? [0, 0] : 0` |
-| disabled      | 是否禁用                                                                                        | `boolean`                                     | `false`              |
-| icon          | 滑块的图标                                                                                      | `ReactNode`                                   | -                    |
-| marks         | 刻度标记                                                                                        | `{ [key: number]: React.ReactNode }`          | -                    |
-| max           | 最大值                                                                                          | `number`                                      | `100`                |
-| min           | 最小值                                                                                          | `number`                                      | `0`                  |
-| onAfterChange | 与 `touchend` 触发时机一致，把当前值作为参数传入                                                | `(value: number \| [number, number]) => void` | -                    |
-| onChange      | 拖拽滑块时触发，并把当前拖拽的值作为参数传入                                                    | `(value: number \| [number, number]) => void` | -                    |
-| popover       | 是否在拖动时显示悬浮提示，支持传入函数自定义渲染内容                                            | `boolean \| ((value: number) => ReactNode)`   | `false`              |
-| range         | 是否为双滑块                                                                                    | `boolean`                                     | `false`              |
-| step          | 步距，取值必须大于 `0`，并且可被 `(max - min)` 整除。当 `marks` 不为空对象时，`step` 的配置失效 | `number`                                      | `1`                  |
-| ticks         | 是否显示刻度                                                                                    | `boolean`                                     | `false`              |
-| value         | 当前值                                                                                          | `number \| [number, number]`                  | -                    |
+| 属性            | 说明                                                                                            | 类型                                          | 默认值               |
+| --------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------- |
+| defaultValue    | 默认值                                                                                          | `number \| [number, number]`                  | `range ? [0, 0] : 0` |
+| disabled        | 是否禁用                                                                                        | `boolean`                                     | `false`              |
+| icon            | 滑块的图标                                                                                      | `ReactNode`                                   | -                    |
+| marks           | 刻度标记                                                                                        | `{ [key: number]: React.ReactNode }`          | -                    |
+| max             | 最大值                                                                                          | `number`                                      | `100`                |
+| min             | 最小值                                                                                          | `number`                                      | `0`                  |
+| onAfterChange   | 与 `touchend` 触发时机一致，把当前值作为参数传入                                                | `(value: number \| [number, number]) => void` | -                    |
+| onChange        | 拖拽滑块时触发，并把当前拖拽的值作为参数传入                                                    | `(value: number \| [number, number]) => void` | -                    |
+| popover         | 是否在拖动时显示悬浮提示，支持传入函数自定义渲染内容                                            | `boolean \| ((value: number) => ReactNode)`   | `false`              |
+| residentPopover | `popover` 是否常驻显示，`popover` 存在时生效                                                    | `boolean `                                    | `false`              |
+| range           | 是否为双滑块                                                                                    | `boolean`                                     | `false`              |
+| step            | 步距，取值必须大于 `0`，并且可被 `(max - min)` 整除。当 `marks` 不为空对象时，`step` 的配置失效 | `number`                                      | `1`                  |
+| ticks           | 是否显示刻度                                                                                    | `boolean`                                     | `false`              |
+| value           | 当前值                                                                                          | `number \| [number, number]`                  | -                    |
 
 ### CSS 变量
 
diff --git a/src/components/slider/slider.tsx b/src/components/slider/slider.tsx
index f46dc1bc0..9271b82ee 100644
--- a/src/components/slider/slider.tsx
+++ b/src/components/slider/slider.tsx
@@ -25,6 +25,7 @@ export type SliderProps = {
   range?: boolean
   icon?: ReactNode
   popover?: boolean | ((value: number) => ReactNode)
+  residentPopover?: boolean
   onChange?: (value: SliderValue) => void
   onAfterChange?: (value: SliderValue) => void
 } & NativeProps<'--fill-color'>
@@ -37,6 +38,7 @@ const defaultProps = {
   range: false,
   disabled: false,
   popover: false,
+  residentPopover: false,
 }
 
 export const Slider: FC<SliderProps> = p => {
@@ -161,6 +163,7 @@ export const Slider: FC<SliderProps> = p => {
         trackRef={trackRef}
         icon={icon}
         popover={props.popover}
+        residentPopover={props.residentPopover}
         onDrag={(position, first, last) => {
           if (first) {
             dragLockRef.current += 1
diff --git a/src/components/slider/thumb.tsx b/src/components/slider/thumb.tsx
index 9c48b625d..28064bb95 100644
--- a/src/components/slider/thumb.tsx
+++ b/src/components/slider/thumb.tsx
@@ -16,10 +16,11 @@ type ThumbProps = {
   trackRef: RefObject<HTMLDivElement>
   icon?: React.ReactNode
   popover: boolean | ((value: number) => ReactNode)
+  residentPopover: boolean
 } & NativeProps
 
 const Thumb: FC<ThumbProps> = props => {
-  const { value, min, max, disabled, onDrag, icon } = props
+  const { value, min, max, disabled, icon, residentPopover, onDrag } = props
   const prevValue = useRef(value)
   const { locale } = useConfig()
 
@@ -80,7 +81,7 @@ const Thumb: FC<ThumbProps> = props => {
         <Popover
           content={renderPopoverContent(value)}
           placement='top'
-          visible={dragging}
+          visible={residentPopover || dragging}
           getContainer={null}
           mode='dark'
         >
diff --git a/src/index.ts b/src/index.ts
index 5858a072d..c51eb939c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -81,7 +81,7 @@ export { default as Toast } from './components/toast'
 export { default as TreeSelect } from './components/tree-select'
 export { default as VirtualInput } from './components/virtual-input'
 export { default as WaterMark } from './components/water-mark'
-
+export { default as Footer } from './components/footer'
 export { createErrorBlock } from './components/error-block'
 
 export { reduceMotion, restoreMotion } from './utils/reduce-and-restore-motion'


Test patch:
diff --git a/src/components/config-provider/tests/config-provider.test.tsx b/src/components/config-provider/tests/config-provider.test.tsx
index a8bc72470..4070556a7 100644
--- a/src/components/config-provider/tests/config-provider.test.tsx
+++ b/src/components/config-provider/tests/config-provider.test.tsx
@@ -23,6 +23,8 @@ import kkKZ from '../../../locales/kk-KZ'
 const locales = [zhCN, zhTW, zhHK, enUS, faIR, esES, koKR, frFR, kkKZ, idID]
 
 describe('ConfigProvider', () => {
+  URL.revokeObjectURL = jest.fn(() => '')
+
   beforeAll(() => {
     MockDate.set(new Date('2022-03-22'))
   })
diff --git a/src/components/image-uploader/tests/image-uploader.test.tsx b/src/components/image-uploader/tests/image-uploader.test.tsx
index 32c217190..1f5a28ddd 100644
--- a/src/components/image-uploader/tests/image-uploader.test.tsx
+++ b/src/components/image-uploader/tests/image-uploader.test.tsx
@@ -45,6 +45,7 @@ async function mockInputFile(file: File | File[] = mockImg) {
 describe('ImageUploader', () => {
   // jsdom does not support createObjectURL
   URL.createObjectURL = jest.fn(() => '')
+  URL.revokeObjectURL = jest.fn(() => '')
 
   const App = (props: any) => {
     const [fileList, setFileList] = useState<ImageUploadItem[]>([