diff --git a/config/components.ts b/config/components.ts
index 534e13e71a..9b21077291 100644
--- a/config/components.ts
+++ b/config/components.ts
@@ -53,6 +53,7 @@ export const components = {
],
feedback: [
'/components/action-sheet',
+ '/components/alert',
'/components/dialog',
'/components/empty',
'/components/error-block',
diff --git a/package.json b/package.json
index bb1560d999..6d7e1790d3 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"pub:dev": "npm publish ./lib --tag dev",
"size-limit": "size-limit",
"size-limit:ci": "size-limit --json",
- "start": "dumi dev",
+ "start": "HOST=localhost dumi dev",
"test": "jest",
"test-with-coverage": "jest --coverage"
},
diff --git a/src/components/alert/alert.less b/src/components/alert/alert.less
new file mode 100644
index 0000000000..4319e5b0e3
--- /dev/null
+++ b/src/components/alert/alert.less
@@ -0,0 +1,78 @@
+.adm-alert {
+ --adm-alert-border-radius: 4px;
+ --adm-alert-close-color: var(--adm-color-light);
+ --adm-alert-close-size: 16px;
+ --adm-alert-font-size: 12px;
+ --adm-alert-gap: 8px;
+ --adm-alert-icon-size: 16px;
+ --adm-alert-line-height: 16px;
+
+ border-radius: var(--adm-alert-border-radius);
+ display: flex;
+ gap: var(--adm-alert-gap);
+ font-size: var(--adm-alert-font-size);
+ line-height: var(--adm-alert-line-height);
+ padding: 12px;
+
+ &-icon {
+ svg {
+ width: var(--adm-alert-icon-size);
+ height: var(--adm-alert-icon-size);
+ }
+ }
+
+ &-main {
+ flex: 1 1 auto;
+
+ &-ellipsis {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ &-extra {
+ white-space: nowrap;
+ }
+
+ &-close {
+ color: var(--adm-alert-close-color);
+
+ svg {
+ width: var(--adm-alert-close-size);
+ height: var(--adm-alert-close-size);
+ }
+ }
+
+ &-info {
+ background: var(--adm-color-primary-light);
+
+ .adm-alert-icon {
+ color: var(--adm-color-primary);
+ }
+ }
+
+ &-success {
+ background: var(--adm-color-success-light);
+
+ .adm-alert-icon {
+ color: var(--adm-color-success);
+ }
+ }
+
+ &-warning {
+ background: var(--adm-color-warning-light);
+
+ .adm-alert-icon {
+ color: var(--adm-color-warning);
+ }
+ }
+
+ &-error {
+ background: var(--adm-color-danger-light);
+
+ .adm-alert-icon {
+ color: var(--adm-color-danger);
+ }
+ }
+}
diff --git a/src/components/alert/alert.tsx b/src/components/alert/alert.tsx
new file mode 100644
index 0000000000..8302075a4e
--- /dev/null
+++ b/src/components/alert/alert.tsx
@@ -0,0 +1,53 @@
+import { CloseOutline } from 'antd-mobile-icons'
+import cn from 'classnames'
+import React, { CSSProperties, ReactNode } from 'react'
+import './alert.less'
+import { getFallbackIcon } from './getFallbackIcon'
+
+export interface AlertProps {
+ type?: 'info' | 'success' | 'warning' | 'error'
+ showIcon?: boolean
+ icon?: ReactNode
+ ellipsis?: boolean
+ extra?: ReactNode
+ closeable?: boolean
+ onClose?: () => void
+ children?: ReactNode
+ className?: string
+ style?: CSSProperties
+}
+
+export function Alert({
+ type = 'info',
+ showIcon,
+ icon,
+ ellipsis,
+ extra,
+ closeable,
+ onClose,
+ children,
+ className,
+ style,
+}: AlertProps) {
+ return (
+
+ {showIcon && (
+
{icon || getFallbackIcon(type)}
+ )}
+
+ {children}
+
+ {extra &&
{extra}
}
+ {closeable && (
+
+
+
+ )}
+
+ )
+}
diff --git a/src/components/alert/demos/demo1.tsx b/src/components/alert/demos/demo1.tsx
new file mode 100644
index 0000000000..4eb02d3d05
--- /dev/null
+++ b/src/components/alert/demos/demo1.tsx
@@ -0,0 +1,90 @@
+import { Alert, Button } from 'antd-mobile'
+import {
+ AppOutline,
+ AudioMutedOutline,
+ LockOutline,
+ PictureWrongOutline,
+} from 'antd-mobile-icons'
+import { DemoBlock } from 'demos'
+import React, { useState } from 'react'
+
+export default () => {
+ const [close, setClose] = useState(false)
+ return (
+ <>
+
+ Success message
+
+ Info message
+
+ Warning message
+
+ Error message
+
+
+
+
+ Success message
+
+
+
+ Info message
+
+
+
+ Warning message
+
+
+
+ Error message
+
+
+
+
+ }>
+ Success message
+
+
+ }>
+ Info message
+
+
+ }>
+ Warning message
+
+
+ }>
+ Error message
+
+
+
+
+ Learn more}>
+ Info message
+
+
+
+
+
+ This is a long long long long long long long long long long message
+
+
+
+
+
+
+ {!close && (
+ setClose(true)}>
+ Info message
+
+ )}
+
+ >
+ )
+}
diff --git a/src/components/alert/getFallbackIcon.tsx b/src/components/alert/getFallbackIcon.tsx
new file mode 100644
index 0000000000..221df95f03
--- /dev/null
+++ b/src/components/alert/getFallbackIcon.tsx
@@ -0,0 +1,20 @@
+import {
+ CheckCircleFill,
+ CloseCircleFill,
+ ExclamationCircleFill,
+ InformationCircleFill,
+} from 'antd-mobile-icons'
+import React from 'react'
+
+export function getFallbackIcon(type?: string) {
+ switch (type) {
+ case 'success':
+ return
+ case 'warning':
+ return
+ case 'error':
+ return
+ default:
+ return
+ }
+}
diff --git a/src/components/alert/index.en.md b/src/components/alert/index.en.md
new file mode 100644
index 0000000000..3b1f1b2db7
--- /dev/null
+++ b/src/components/alert/index.en.md
@@ -0,0 +1,38 @@
+# Alert
+
+Display warning messages that require attention.
+
+## When to Use
+
+- When you need to show alert messages to users.
+- When you need a persistent static container which is closeable by user actions.
+
+## Demos
+
+
+
+## API
+
+### Props
+
+| Name | Description | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| `type` | Decide color and default icon of alert. | `'info' \| 'success' \| 'warning' \| 'error'` | `'info'` | 5.38.0 |
+| `showIcon` | Whether to show icon. | `boolean` | `false` | 5.38.0 |
+| `icon` | Custom icon, effective when `showIcon` is `true`. | `ReactNode` | - | 5.38.0 |
+| `ellipsis` | Limit content to one line and show ellipsis when content is too long. | `boolean` | `false` | 5.38.0 |
+| `extra` | Show extra content at the end. | `ReactNode` | - | 5.38.0 |
+| `closeable` | Show close button. | `boolean` | `false` | 5.38.0 |
+| `onClose` | Callback when alert is closed | `() => void` | - | 5.38.0 |
+
+### CSS Variables
+
+| Name | Description | Default | Global | Version |
+| --- | --- | --- | --- | --- |
+| `--adm-alert-border-radius` | Border radius of the alert. | `4px` | `--adm-alert-border-radius` | 5.38.0 |
+| `--adm-alert-close-color` | Color of the alert close icon. | `var(--adm-color-light)` | `--adm-alert-close-color` | 5.38.0 |
+| `--adm-alert-close-size` | Size of the alert close icon. | `16px` | `--adm-alert-close-size` | 5.38.0 |
+| `--adm-alert-font-size` | Font size of the alert. | `12px` | `--adm-alert-font-size` | 5.38.0 |
+| `--adm-alert-gap` | Gap between icon, content, extra and close. | `8px` | `--adm-alert-gap` | 5.38.0 |
+| `--adm-alert-icon-size` | Size of the alert icon. | `16px` | `--adm-alert-icon-size` | 5.38.0 |
+| `--adm-alert-line-height` | Line height of the alert. | `16px` | `--adm-alert-line-height` | 5.38.0 |
diff --git a/src/components/alert/index.ts b/src/components/alert/index.ts
new file mode 100644
index 0000000000..4450afe76a
--- /dev/null
+++ b/src/components/alert/index.ts
@@ -0,0 +1,5 @@
+import { Alert, AlertProps } from './alert'
+
+export { AlertProps }
+
+export default Alert
diff --git a/src/components/alert/index.zh.md b/src/components/alert/index.zh.md
new file mode 100644
index 0000000000..51dfbae399
--- /dev/null
+++ b/src/components/alert/index.zh.md
@@ -0,0 +1,38 @@
+# Alert 警告提示
+
+警告提示,展现需要关注的信息。
+
+## 何时使用
+
+- 当某个页面需要向用户显示警告的信息时。
+- 非浮层的静态展现形式,始终展现,不会自动消失,用户可以点击关闭。
+
+## 示例
+
+
+
+## API
+
+### 属性
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| `type` | 决定 Alert 的颜色和默认图标 | `'info' \| 'success' \| 'warning' \| 'error'` | `'info'` | 5.38.0 |
+| `showIcon` | 是否展示图标 | `boolean` | `false` | 5.38.0 |
+| `icon` | 自定义图标,只在 `showIcon` 为 `true` 时生效 | `ReactNode` | - | 5.38.0 |
+| `ellipsis` | 限制内容为一行,内容过长时展示省略号 | `boolean` | `false` | 5.38.0 |
+| `extra` | 在末尾展示额外内容 | `ReactNode` | - | 5.38.0 |
+| `closeable` | 显示关闭按钮 | `boolean` | `false` | 5.38.0 |
+| `onClose` | 关闭时的回调函数 | `() => void` | - | 5.38.0 |
+
+### CSS 变量
+
+| 变量 | 说明 | 默认值 | 全局变量 | 版本 |
+| --- | --- | --- | --- | --- |
+| `--adm-alert-border-radius` | Alert 圆角大小 | `4px` | `--adm-alert-border-radius` | 5.38.0 |
+| `--adm-alert-close-color` | Alert 关闭按钮颜色 | `var(--adm-color-light)` | `--adm-alert-close-color` | 5.38.0 |
+| `--adm-alert-close-size` | Alert 关闭图标大小 | `16px` | `--adm-alert-close-size` | 5.38.0 |
+| `--adm-alert-font-size` | Alert 字体大小 | `12px` | `--adm-alert-font-size` | 5.38.0 |
+| `--adm-alert-gap` | 图标,主要内容,额外内容和关闭图标之间的间隙 | `8px` | `--adm-alert-gap` | 5.38.0 |
+| `--adm-alert-icon-size` | Alert 图标大小 | `16px` | `--adm-alert-icon-size` | 5.38.0 |
+| `--adm-alert-line-height` | Alert 文字行高 | `16px` | `--adm-alert-line-height` | 5.38.0 |
diff --git a/src/components/alert/tests/alert.test.tsx b/src/components/alert/tests/alert.test.tsx
new file mode 100644
index 0000000000..28c8ce75cd
--- /dev/null
+++ b/src/components/alert/tests/alert.test.tsx
@@ -0,0 +1,83 @@
+import React from 'react'
+import { fireEvent, render, screen, testA11y } from 'testing'
+import Alert from '..'
+
+describe('Avatar', () => {
+ test('a11y', async () => {
+ await testA11y()
+ })
+
+ test('show content', () => {
+ render(foobar)
+ expect(screen.getByText('foobar')).toBeVisible()
+ })
+
+ test('info alert icon', () => {
+ render(
+
+ foobar
+
+ )
+ expect(
+ document.querySelector('.adm-alert-info .antd-mobile-icon')
+ ).toBeVisible()
+ })
+
+ test('success alert icon', () => {
+ render(
+
+ foobar
+
+ )
+ expect(
+ document.querySelector('.adm-alert-success .antd-mobile-icon')
+ ).toBeVisible()
+ })
+
+ test('warning alert icon', () => {
+ render(
+
+ foobar
+
+ )
+ expect(
+ document.querySelector('.adm-alert-warning .antd-mobile-icon')
+ ).toBeVisible()
+ })
+
+ test('error alert icon', () => {
+ render(
+
+ foobar
+
+ )
+ expect(
+ document.querySelector('.adm-alert-error .antd-mobile-icon')
+ ).toBeVisible()
+ })
+
+ test('show custom icon', () => {
+ render(
+
+ foobar
+
+ )
+ expect(screen.getByText('my-icon')).toBeVisible()
+ })
+
+ test('show extra content', () => {
+ render(foobar)
+ expect(screen.getByText('my-extra')).toBeVisible()
+ })
+
+ test('show close button', () => {
+ const callback = jest.fn()
+ render(
+
+ foobar
+
+ )
+ fireEvent.click(document.querySelector('.adm-alert-close')!)
+ expect(callback).toBeCalledTimes(1)
+ })
+})
diff --git a/src/global/theme-dark.less b/src/global/theme-dark.less
index 59f27b3fb3..1742b4d004 100644
--- a/src/global/theme-dark.less
+++ b/src/global/theme-dark.less
@@ -1,9 +1,13 @@
html[data-prefers-color-scheme='dark'] {
// variables should be exposed to users:
--adm-color-primary: #3086ff;
+ --adm-color-primary-light: #3086ff33;
--adm-color-success: #34b368;
+ --adm-color-success-light: #34b36833;
--adm-color-warning: #ffa930;
+ --adm-color-warning-light: #ffa93033;
--adm-color-danger: #ff4a58;
+ --adm-color-danger-light: #ff4a5833;
--adm-color-yellow: #ffa930;
--adm-color-orange: #e65a2b;
diff --git a/src/global/theme-default.less b/src/global/theme-default.less
index 23e8c83b54..9d34b02c75 100644
--- a/src/global/theme-default.less
+++ b/src/global/theme-default.less
@@ -17,30 +17,34 @@
// variables should be exposed to users:
--adm-color-primary: #1677ff;
+ --adm-color-primary-light: #1677ff33;
--adm-color-success: #00b578;
+ --adm-color-success-light: #00b57833;
--adm-color-warning: #ff8f1f;
+ --adm-color-warning-light: #ff8f1f33;
--adm-color-danger: #ff3141;
+ --adm-color-danger-light: #ff314133;
--adm-color-yellow: #ff9f18;
--adm-color-orange: #ff6430;
--adm-color-wathet: #e7f1ff;
- --adm-color-text: #333333;
- --adm-color-text-secondary: #666666;
- --adm-color-weak: #999999;
- --adm-color-light: #cccccc;
- --adm-color-border: #eeeeee;
- --adm-color-background: #ffffff;
+ --adm-color-text: #333;
+ --adm-color-text-secondary: #666;
+ --adm-color-weak: #999;
+ --adm-color-light: #ccc;
+ --adm-color-border: #eee;
+ --adm-color-background: #fff;
--adm-color-highlight: var(--adm-color-danger);
// Deprecated. We use `--adm-color-text-light-solid` in code
// but keep here for compatible
- --adm-color-white: #ffffff;
+ --adm-color-white: #fff;
--adm-color-box: #f5f5f5;
--adm-color-text-light-solid: var(--adm-color-white);
- --adm-color-text-dark-solid: #000000;
+ --adm-color-text-dark-solid: #000;
--adm-color-fill-content: var(--adm-color-box);
--adm-font-size-main: var(--adm-font-size-5);
diff --git a/src/index.ts b/src/index.ts
index 50676a300b..bf131a7523 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,6 +4,8 @@ export { setDefaultConfig } from './components/config-provider'
export { default as ActionSheet } from './components/action-sheet'
export type { ActionSheetProps } from './components/action-sheet'
+export { default as Alert } from './components/alert'
+export type { AlertProps } from './components/alert'
export { default as AutoCenter } from './components/auto-center'
export type { AutoCenterProps } from './components/auto-center'
export { default as Avatar } from './components/avatar'
@@ -26,15 +28,15 @@ export type {
} from './components/calendar-picker-view'
export { default as CapsuleTabs } from './components/capsule-tabs'
export type {
- CapsuleTabsProps,
CapsuleTabProps,
+ CapsuleTabsProps,
} from './components/capsule-tabs'
export { default as Card } from './components/card'
export type { CardProps } from './components/card'
export { default as CascadePicker } from './components/cascade-picker'
export type {
- CascadePickerProps,
CascadePickerOption,
+ CascadePickerProps,
CascadePickerRef,
} from './components/cascade-picker'
export { default as CascadePickerView } from './components/cascade-picker-view'
@@ -43,25 +45,28 @@ export { default as Cascader } from './components/cascader'
export type { CascaderProps, CascaderRef } from './components/cascader'
export { default as CascaderView } from './components/cascader-view'
export type {
- CascaderViewProps,
CascaderOption,
+ CascaderViewProps,
} from './components/cascader-view'
export { default as CenterPopup } from './components/center-popup'
export type { CenterPopupProps } from './components/center-popup'
export { default as CheckList } from './components/check-list'
export type {
- CheckListProps,
CheckListItemProps,
+ CheckListProps,
} from './components/check-list'
export { default as Checkbox } from './components/checkbox'
export type {
- CheckboxProps,
CheckboxGroupProps,
+ CheckboxProps,
CheckboxRef,
} from './components/checkbox'
export { default as Collapse } from './components/collapse'
-export type { CollapseProps, CollapsePanelProps } from './components/collapse'
-export { default as ConfigProvider, useConfig } from './components/config-provider'
+export type { CollapsePanelProps, CollapseProps } from './components/collapse'
+export {
+ default as ConfigProvider,
+ useConfig,
+} from './components/config-provider'
export type { ConfigProviderProps } from './components/config-provider'
export { default as DatePicker } from './components/date-picker'
export type { DatePickerProps, DatePickerRef } from './components/date-picker'
@@ -69,10 +74,10 @@ export { default as DatePickerView } from './components/date-picker-view'
export type { DatePickerViewProps } from './components/date-picker-view'
export { default as Dialog } from './components/dialog'
export type {
- DialogProps,
- DialogShowProps,
DialogAlertProps,
DialogConfirmProps,
+ DialogProps,
+ DialogShowProps,
} from './components/dialog'
export { default as Divider } from './components/divider'
export type { DividerProps } from './components/divider'
@@ -80,15 +85,18 @@ export { default as DotLoading } from './components/dot-loading'
export type { DotLoadingProps } from './components/dot-loading'
export { default as Dropdown } from './components/dropdown'
export type {
- DropdownProps,
DropdownItemProps,
+ DropdownProps,
DropdownRef,
} from './components/dropdown'
export { default as Ellipsis } from './components/ellipsis'
export type { EllipsisProps } from './components/ellipsis'
export { default as Empty } from './components/empty'
export type { EmptyProps } from './components/empty'
-export { default as ErrorBlock } from './components/error-block'
+export {
+ default as ErrorBlock,
+ createErrorBlock,
+} from './components/error-block'
export type {
ErrorBlockProps,
ErrorBlockStatus,
@@ -100,16 +108,18 @@ export type {
FloatingPanelProps,
FloatingPanelRef,
} from './components/floating-panel'
+export { default as Footer } from './components/footer'
+export type { FooterProps } from './components/footer'
export { default as Form } from './components/form'
-export type { FormProps, FormItemProps } from './components/form'
+export type { FormItemProps, FormProps } from './components/form'
export { default as Grid } from './components/grid'
-export type { GridProps, GridItemProps } from './components/grid'
+export type { GridItemProps, GridProps } from './components/grid'
export { default as Image } from './components/image'
export type { ImageProps } from './components/image'
export { default as ImageUploader } from './components/image-uploader'
export type {
- ImageUploaderProps,
ImageUploadItem,
+ ImageUploaderProps,
ImageUploaderRef,
} from './components/image-uploader'
export { default as ImageViewer } from './components/image-viewer'
@@ -120,8 +130,8 @@ export type {
} from './components/image-viewer'
export { default as IndexBar } from './components/index-bar'
export type {
- IndexBarProps,
IndexBarPanelProps,
+ IndexBarProps,
IndexBarRef,
} from './components/index-bar'
export { default as InfiniteScroll } from './components/infinite-scroll'
@@ -129,19 +139,19 @@ export type { InfiniteScrollProps } from './components/infinite-scroll'
export { default as Input } from './components/input'
export type { InputProps, InputRef } from './components/input'
export { default as JumboTabs } from './components/jumbo-tabs'
-export type { JumboTabsProps, JumboTabProps } from './components/jumbo-tabs'
+export type { JumboTabProps, JumboTabsProps } from './components/jumbo-tabs'
export { default as List } from './components/list'
-export type { ListProps, ListItemProps, ListRef } from './components/list'
+export type { ListItemProps, ListProps, ListRef } from './components/list'
export { default as Loading } from './components/loading'
export type { LoadingProps } from './components/loading'
export { default as Mask } from './components/mask'
export type { MaskProps } from './components/mask'
export { default as Modal } from './components/modal'
export type {
- ModalProps,
- ModalShowProps,
ModalAlertProps,
ModalConfirmProps,
+ ModalProps,
+ ModalShowProps,
} from './components/modal'
export { default as NavBar } from './components/nav-bar'
export type { NavBarProps } from './components/nav-bar'
@@ -162,8 +172,8 @@ export { default as PickerView } from './components/picker-view'
export type { PickerViewProps } from './components/picker-view'
export { default as Popover } from './components/popover'
export type {
- PopoverProps,
PopoverMenuProps,
+ PopoverProps,
PopoverRef,
} from './components/popover'
export { default as Popup } from './components/popup'
@@ -175,7 +185,7 @@ export type { ProgressCircleProps } from './components/progress-circle'
export { default as PullToRefresh } from './components/pull-to-refresh'
export type { PullToRefreshProps } from './components/pull-to-refresh'
export { default as Radio } from './components/radio'
-export type { RadioProps, RadioGroupProps } from './components/radio'
+export type { RadioGroupProps, RadioProps } from './components/radio'
export { default as Rate } from './components/rate'
export type { RateProps } from './components/rate'
export { default as Result } from './components/result'
@@ -189,9 +199,9 @@ export type { ScrollMaskProps } from './components/scroll-mask'
export { default as SearchBar } from './components/search-bar'
export type { SearchBarProps, SearchBarRef } from './components/search-bar'
export { default as Selector } from './components/selector'
-export type { SelectorProps, SelectorOption } from './components/selector'
+export type { SelectorOption, SelectorProps } from './components/selector'
export { default as SideBar } from './components/side-bar'
-export type { SideBarProps, SideBarItemProps } from './components/side-bar'
+export type { SideBarItemProps, SideBarProps } from './components/side-bar'
export { default as Skeleton } from './components/skeleton'
export type { SkeletonProps, SkeletonTitleProps } from './components/skeleton'
export { default as Slider } from './components/slider'
@@ -203,7 +213,7 @@ export type { SpinLoadingProps } from './components/spin-loading'
export { default as Stepper } from './components/stepper'
export type { StepperProps, StepperRef } from './components/stepper'
export { default as Steps } from './components/steps'
-export type { StepsProps, StepProps } from './components/steps'
+export type { StepProps, StepsProps } from './components/steps'
export { default as SwipeAction } from './components/swipe-action'
export type {
SwipeActionProps,
@@ -214,9 +224,9 @@ export type { SwiperProps, SwiperRef } from './components/swiper'
export { default as Switch } from './components/switch'
export type { SwitchProps } from './components/switch'
export { default as TabBar } from './components/tab-bar'
-export type { TabBarProps, TabBarItemProps } from './components/tab-bar'
+export type { TabBarItemProps, TabBarProps } from './components/tab-bar'
export { default as Tabs } from './components/tabs'
-export type { TabsProps, TabProps } from './components/tabs'
+export type { TabProps, TabsProps } from './components/tabs'
export { default as Tag } from './components/tag'
export type { TagProps } from './components/tag'
export { default as TextArea } from './components/text-area'
@@ -225,8 +235,8 @@ export { default as Toast } from './components/toast'
export type { ToastShowProps } from './components/toast'
export { default as TreeSelect } from './components/tree-select'
export type {
- TreeSelectProps,
TreeSelectOption,
+ TreeSelectProps,
} from './components/tree-select'
export { default as VirtualInput } from './components/virtual-input'
export type {
@@ -235,8 +245,5 @@ export type {
} from './components/virtual-input'
export { default as WaterMark } from './components/water-mark'
export type { WaterMarkProps } from './components/water-mark'
-export { default as Footer } from './components/footer'
-export type { FooterProps } from './components/footer'
-export { createErrorBlock } from './components/error-block'
export { reduceMotion, restoreMotion } from './utils/reduce-and-restore-motion'