Skip to content

Commit f0a5534

Browse files
authored
feat(CAlert): add transition, onClosed and ariaCloseLabel (#491)
* feat(CAlert): add transition prop and onClosed callback Adds a `transition` prop (default true, matching CModal) that controls the fade animation, decoupling `fade` from `dismissible` — a non-dismissible alert now fades by default, and `transition={false}` renders it without animation. Adds an `onClosed` callback fired after the close transition completes (React `onExited`), mirroring Bootstrap's `closed.coreui.alert` event. Part of the cross-framework alert test-parity work; ports the same API to Vue. * feat(CAlert): add ariaCloseLabel prop Makes the dismissible close button's aria-label configurable (default "Close") via a new ariaCloseLabel prop, forwarded to CCloseButton.
1 parent 2e8fd74 commit f0a5534

3 files changed

Lines changed: 94 additions & 4 deletions

File tree

packages/coreui-react/src/components/alert/CAlert.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import { colorPropType } from '../../props'
1010
import type { Colors } from '../../types'
1111

1212
export interface CAlertProps extends HTMLAttributes<HTMLDivElement> {
13+
/**
14+
* Sets the `aria-label` of the dismissible close button.
15+
*
16+
* @since 5.13.0
17+
*/
18+
ariaCloseLabel?: string
1319
/**
1420
* A string of all className you want applied to the component.
1521
*/
@@ -28,6 +34,18 @@ export interface CAlertProps extends HTMLAttributes<HTMLDivElement> {
2834
* Callback fired when the component requests to be closed.
2935
*/
3036
onClose?: () => void
37+
/**
38+
* Callback fired when the component has been closed and the CSS transition has completed.
39+
*
40+
* @since 5.13.0
41+
*/
42+
onClosed?: () => void
43+
/**
44+
* Set whether the alert fades in and out when it is shown and hidden. Set to `false` to make it appear and disappear without a fade animation.
45+
*
46+
* @since 5.13.0
47+
*/
48+
transition?: boolean
3149
/**
3250
* Set the alert variant to a solid.
3351
*/
@@ -42,12 +60,15 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
4260
(
4361
{
4462
children,
63+
ariaCloseLabel = 'Close',
4564
className,
4665
color = 'primary',
4766
dismissible,
67+
transition = true,
4868
variant,
4969
visible = true,
5070
onClose,
71+
onClosed,
5172
...rest
5273
},
5374
ref
@@ -66,7 +87,8 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
6687
mountOnEnter
6788
nodeRef={alertRef}
6889
onExit={onClose}
69-
timeout={150}
90+
onExited={onClosed}
91+
timeout={transition ? 150 : 0}
7092
unmountOnExit
7193
>
7294
{(state) => (
@@ -75,7 +97,8 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
7597
'alert',
7698
variant === 'solid' ? `bg-${color} text-white` : `alert-${color}`,
7799
{
78-
'alert-dismissible fade': dismissible,
100+
'alert-dismissible': dismissible,
101+
fade: transition,
79102
show: state === 'entered',
80103
},
81104
className
@@ -85,7 +108,9 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
85108
ref={forkedRef}
86109
>
87110
{children}
88-
{dismissible && <CCloseButton onClick={() => setVisible(false)} />}
111+
{dismissible && (
112+
<CCloseButton aria-label={ariaCloseLabel} onClick={() => setVisible(false)} />
113+
)}
89114
</div>
90115
)}
91116
</Transition>
@@ -94,11 +119,14 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
94119
)
95120

96121
CAlert.propTypes = {
122+
ariaCloseLabel: PropTypes.string,
97123
children: PropTypes.node,
98124
className: PropTypes.string,
99125
color: colorPropType.isRequired,
100126
dismissible: PropTypes.bool,
101127
onClose: PropTypes.func,
128+
onClosed: PropTypes.func,
129+
transition: PropTypes.bool,
102130
variant: PropTypes.string,
103131
visible: PropTypes.bool,
104132
}

packages/coreui-react/src/components/alert/__tests__/CAlert.spec.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react'
2-
import { act, render, fireEvent } from '@testing-library/react'
2+
import { act, render, fireEvent, waitFor } from '@testing-library/react'
33
import '@testing-library/jest-dom'
44
import { CAlert } from '../index'
55

@@ -45,6 +45,15 @@ describe('CAlert', () => {
4545
expect(container.querySelector('.btn-close')).toBeInTheDocument()
4646
})
4747

48+
it('should set a custom close button label', () => {
49+
const { container } = render(
50+
<CAlert color="primary" dismissible ariaCloseLabel="Zamknij">
51+
Test
52+
</CAlert>
53+
)
54+
expect(container.querySelector('.btn-close')).toHaveAttribute('aria-label', 'Zamknij')
55+
})
56+
4857
it('should close an alert', () => {
4958
vi.useFakeTimers()
5059
const onClose = vi.fn()
@@ -62,6 +71,35 @@ describe('CAlert', () => {
6271
expect(onClose).toHaveBeenCalledTimes(1)
6372
vi.useRealTimers()
6473
})
74+
75+
it('should emit closed after the transition', async () => {
76+
const onClosed = vi.fn()
77+
const { container } = render(
78+
<CAlert color="primary" dismissible onClosed={onClosed}>
79+
Test
80+
</CAlert>
81+
)
82+
83+
fireEvent.click(container.querySelector('.btn-close')!)
84+
85+
await waitFor(() => expect(onClosed).toHaveBeenCalledTimes(1))
86+
})
87+
})
88+
89+
describe('transition', () => {
90+
it('should apply the fade class', () => {
91+
const { container } = render(<CAlert color="primary">Test</CAlert>)
92+
expect(container.firstChild).toHaveClass('fade')
93+
})
94+
95+
it('should not apply the fade class when transition is disabled', () => {
96+
const { container } = render(
97+
<CAlert color="primary" transition={false}>
98+
Test
99+
</CAlert>
100+
)
101+
expect(container.firstChild).not.toHaveClass('fade')
102+
})
65103
})
66104

67105
describe('visibility', () => {

packages/docs/src/api/CAlert.api.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
{
22
"name": "CAlert",
33
"props": [
4+
{
5+
"name": "ariaCloseLabel",
6+
"type": "string | undefined",
7+
"default": "Close",
8+
"description": "Sets the `aria-label` of the dismissible close button.",
9+
"since": "5.13.0",
10+
"deprecated": null
11+
},
412
{
513
"name": "className",
614
"type": "string | undefined",
@@ -33,6 +41,22 @@
3341
"since": null,
3442
"deprecated": null
3543
},
44+
{
45+
"name": "onClosed",
46+
"type": "(() => void) | undefined",
47+
"default": null,
48+
"description": "Callback fired when the component has been closed and the CSS transition has completed.",
49+
"since": "5.13.0",
50+
"deprecated": null
51+
},
52+
{
53+
"name": "transition",
54+
"type": "boolean | undefined",
55+
"default": true,
56+
"description": "Set whether the alert fades in and out when it is shown and hidden. Set to `false` to make it appear and disappear without a fade animation.",
57+
"since": "5.13.0",
58+
"deprecated": null
59+
},
3660
{
3761
"name": "variant",
3862
"type": "string | undefined",

0 commit comments

Comments
 (0)