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'