Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions docs/components/alert.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
<LivePlayground :code="`
() => {
return (
<Space direction='vertical' size={8}>
<div className='flex flex-col gap-2'>
<Alert type='info' message='提示信息' description='这是一条信息提示。' />
<Alert type='success' message='操作成功' closable />
<Alert type='warning' message='警告信息' showIcon />
<Alert type='error' message='错误信息' description='请检查后重试。' closable />
</Space>
<Alert
type='error'
message='错误信息'
description='请检查后重试。'
closable
closeIcon='关闭'
closeAriaLabel='关闭错误提示'
/>
</div>
)
}
`" />
Expand All @@ -21,6 +28,8 @@

Alert 使用 `role='alert'` 暴露重要提示内容。默认状态图标仅作视觉辅助,会从辅助技术树中隐藏;需要传达给读屏用户的信息应放在 `message` 或 `description` 中。传入自定义 `icon` 时,组件不会自动隐藏它。

可关闭 Alert 的默认关闭按钮使用 `aria-label='Close alert'`。当使用文字、图标或本地化关闭控件时,可以通过 `closeIcon` 和 `closeAriaLabel` 保持视觉内容与可访问名称一致。

## API

| 属性 | 说明 | 类型 | 默认值 |
Expand All @@ -33,4 +42,6 @@ Alert 使用 `role='alert'` 暴露重要提示内容。默认状态图标仅作
| icon | 自定义图标 | `ReactNode` | - |
| banner | 横幅模式 | `boolean` | `false` |
| action | 操作区域 | `ReactNode` | - |
| onClose | 关闭回调 | `() => void` | - |
| closeIcon | 自定义关闭控件内容 | `ReactNode` | `'✕'` |
| closeAriaLabel | 关闭按钮的可访问名称 | `string` | `'Close alert'` |
| onClose | 关闭回调 | `(event: MouseEvent<HTMLButtonElement>) => void` | - |
17 changes: 16 additions & 1 deletion packages/ui/src/components/feedback/Alert/Alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,27 @@ describe('Alert', () => {
await user.click(screen.getByRole('button', { name: 'Close alert' }))

expect(onClose).toHaveBeenCalledTimes(1)
expect(onClose.mock.calls[0]?.[0]).toHaveProperty('type', 'click')
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})

it('supports custom close icon and accessible name', () => {
render(
<Alert
closable
message="Dismiss me"
closeIcon={<span>Dismiss</span>}
closeAriaLabel="Dismiss alert"
/>,
)

const closeButton = screen.getByRole('button', { name: 'Dismiss alert' })
expect(closeButton).toHaveTextContent('Dismiss')
})

it('renders an optional action area', () => {
render(<Alert message="Update available" action={<button type="button">Review</button>} />)

expect(screen.getByRole('button', { name: 'Review' })).toBeInTheDocument()
})
})
})
20 changes: 12 additions & 8 deletions packages/ui/src/components/feedback/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef, useState, type HTMLAttributes, type ReactNode } from 'react'
import { forwardRef, useState, type HTMLAttributes, type MouseEvent, type ReactNode } from 'react'

import { cn } from '../../../utils/cn'
import { alertTypeSurfaceClass, type AlertType } from '../../../theme/componentTokens'
Expand All @@ -10,9 +10,11 @@ export interface AlertProps extends HTMLAttributes<HTMLDivElement> {
closable?: boolean
showIcon?: boolean
icon?: ReactNode
onClose?: () => void
onClose?: (event: MouseEvent<HTMLButtonElement>) => void
banner?: boolean
action?: ReactNode
closeIcon?: ReactNode
closeAriaLabel?: string
}

const typeIconMap: Record<AlertType, string> = {
Expand All @@ -34,6 +36,8 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
onClose,
banner = false,
action,
closeIcon = '✕',
closeAriaLabel = 'Close alert',
...props
},
ref,
Expand Down Expand Up @@ -67,16 +71,16 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
{closable ? (
<button
type="button"
onClick={() => {
onClick={(event) => {
setVisible(false)
onClose?.()
onClose?.(event)
}}
className="shrink-0 rounded px-1 opacity-60 hover:opacity-100"
aria-label="Close alert"
className="shrink-0 rounded px-1 opacity-60 hover:opacity-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
aria-label={closeAriaLabel}
>
{closeIcon}
</button>
) : null}
</div>
)
})
})
Loading