Skip to content

Commit 0f7b39d

Browse files
authored
feat(Carousel): add indicatorLabels prop and fix indicator styling (react-bootstrap#5773)
1 parent aba14d9 commit 0f7b39d

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

src/Carousel.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface CarouselProps
4040
fade?: boolean;
4141
controls?: boolean;
4242
indicators?: boolean;
43+
indicatorLabels?: string[];
4344
activeIndex?: number;
4445
onSelect?: (eventKey: number, event: Record<string, unknown> | null) => void;
4546
defaultActiveIndex?: number;
@@ -85,6 +86,11 @@ const propTypes = {
8586
*/
8687
indicators: PropTypes.bool,
8788

89+
/**
90+
* An array of labels for the indicators. Defaults to "Slide #" if not provided.
91+
*/
92+
indicatorLabels: PropTypes.array,
93+
8894
/**
8995
* Controls the current visible slide
9096
*
@@ -174,7 +180,7 @@ const defaultProps = {
174180
fade: false,
175181
controls: true,
176182
indicators: true,
177-
183+
indicatorLabels: [],
178184
defaultActiveIndex: 0,
179185
interval: 5000,
180186
keyboard: true,
@@ -220,6 +226,7 @@ const Carousel: BsPrefixRefForwardingComponent<
220226
fade,
221227
controls,
222228
indicators,
229+
indicatorLabels,
223230
activeIndex,
224231
onSelect,
225232
onSlide,
@@ -524,15 +531,23 @@ const Carousel: BsPrefixRefForwardingComponent<
524531
)}
525532
>
526533
{indicators && (
527-
<ol className={`${prefix}-indicators`}>
528-
{map(children, (_child, index) => (
529-
<li
534+
<div className={`${prefix}-indicators`}>
535+
{map(children, (_, index) => (
536+
<button
530537
key={index}
538+
type="button"
539+
data-bs-target="" // Bootstrap requires this in their css.
540+
aria-label={
541+
indicatorLabels?.length
542+
? indicatorLabels[index]
543+
: `Slide ${index + 1}`
544+
}
531545
className={index === renderedActiveIndex ? 'active' : undefined}
532546
onClick={indicatorOnClicks ? indicatorOnClicks[index] : undefined}
547+
aria-current={index === renderedActiveIndex}
533548
/>
534549
))}
535-
</ol>
550+
</div>
536551
)}
537552

538553
<div className={`${prefix}-inner`}>

test/CarouselSpec.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('<Carousel>', () => {
3131

3232
expect(carouselItems.at(0).is('.active')).to.be.true;
3333
expect(carouselItems.at(1).is('.active')).to.be.false;
34-
expect(wrapper.find('.carousel-indicators > li')).to.have.lengthOf(
34+
expect(wrapper.find('.carousel-indicators > button')).to.have.lengthOf(
3535
items.length,
3636
);
3737
});
@@ -60,7 +60,7 @@ describe('<Carousel>', () => {
6060

6161
expect(carouselItems.at(0).is('.active')).to.be.true;
6262
expect(carouselItems.at(0).text()).to.equal('Item 1 content');
63-
expect(wrapper.find('.carousel-indicators > li')).to.have.lengthOf(2);
63+
expect(wrapper.find('.carousel-indicators > button')).to.have.lengthOf(2);
6464
});
6565

6666
it('should call onSelect when indicator selected', (done) => {
@@ -76,7 +76,23 @@ describe('<Carousel>', () => {
7676
</Carousel>,
7777
);
7878

79-
wrapper.find('.carousel-indicators li').first().simulate('click');
79+
wrapper.find('.carousel-indicators button').first().simulate('click');
80+
});
81+
82+
it('should render custom indicator labels', () => {
83+
const labels = ['custom1', 'custom2', 'custom3'];
84+
85+
const wrapper = mount(
86+
<Carousel activeIndex={1} interval={null} indicatorLabels={labels}>
87+
{items}
88+
</Carousel>,
89+
);
90+
91+
const indicators = wrapper.find('.carousel-indicators button');
92+
for (let i = 0; i < labels.length; i++) {
93+
const node = indicators.at(i).getDOMNode();
94+
expect(node.getAttribute('aria-label')).to.equal(labels[i]);
95+
}
8096
});
8197

8298
it('should render variant', () => {
@@ -137,7 +153,7 @@ describe('<Carousel>', () => {
137153
</Carousel>,
138154
);
139155

140-
wrapper.find('.carousel-indicators li').first().simulate('click');
156+
wrapper.find('.carousel-indicators button').first().simulate('click');
141157
});
142158

143159
it(`should call ${eventName} with next index and direction`, (done) => {
@@ -159,7 +175,7 @@ describe('<Carousel>', () => {
159175
</Carousel>,
160176
);
161177

162-
wrapper.find('.carousel-indicators li').last().simulate('click');
178+
wrapper.find('.carousel-indicators button').last().simulate('click');
163179
});
164180
});
165181

@@ -289,15 +305,15 @@ describe('<Carousel>', () => {
289305
<Carousel defaultActiveIndex={items.length - 1}>{items}</Carousel>,
290306
);
291307

292-
expect(wrapper.find('.carousel-indicators > li')).to.have.lengthOf(
308+
expect(wrapper.find('.carousel-indicators > button')).to.have.lengthOf(
293309
items.length,
294310
);
295311

296312
let fewerItems = items.slice(2);
297313

298314
wrapper.setProps({ children: fewerItems });
299315

300-
expect(wrapper.find('.carousel-indicators > li')).to.have.lengthOf(
316+
expect(wrapper.find('.carousel-indicators > button')).to.have.lengthOf(
301317
fewerItems.length,
302318
);
303319
expect(wrapper.find('div.carousel-item')).to.have.lengthOf(

0 commit comments

Comments
 (0)