Skip to content

Commit 147d5e4

Browse files
committed
feat(distributeur): improved design & accessibility of Tabs
- added keyboard navigation - deprecated classModifier in favor of className - improved design of Tabs up to specs - Tags are now next to text instead of above fixes #1000
1 parent a45591a commit 147d5e4

File tree

6 files changed

+179
-177
lines changed

6 files changed

+179
-177
lines changed

apps/slash-stories/src/Tabs.stories.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Badge, Tabs } from "@axa-fr/canopee-react/distributeur";
1+
import { Tabs, Tag } from "@axa-fr/canopee-react/distributeur";
22
import { Meta, type StoryObj } from "@storybook/react";
33
import { fn } from "@storybook/test";
44

@@ -18,7 +18,8 @@ export default {
1818

1919
const TabTitleIconLeft = (
2020
<span>
21-
<i className="glyphicon glyphicon-ok" /> Title with left icon
21+
{/* <i className="glyphicon glyphicon-ok" /> */}
22+
Title with left icon
2223
</span>
2324
);
2425
const TabTitleIconRight = (
@@ -28,14 +29,14 @@ const TabTitleIconRight = (
2829
);
2930
const TabTitleBadge = (
3031
<span>
31-
Title with badge <Badge classModifier="danger"> 42 </Badge>
32+
Title with badge
33+
<Tag variant="success">42</Tag>
3234
</span>
3335
);
3436
const TabTitleIconBadge = (
3537
<span>
3638
Title with badge and left icon
37-
<Badge classModifier="error"> Lorem ipsum </Badge>
38-
<i className="glyphicon glyphicon-facetime-video" />
39+
<Tag variant="error"> Lorem ipsum </Tag>
3940
</span>
4041
);
4142

@@ -60,7 +61,7 @@ export const ComplexTabs: StoryObj<typeof Tabs> = {
6061
</Tabs.Tab>
6162
<Tabs.Tab title={TabTitleBadge}>Content of my third tab </Tabs.Tab>
6263
<Tabs.Tab title={TabTitleIconBadge} classModifier="has-icon-left">
63-
Content of my fifth tab
64+
Content of my fourth tab
6465
</Tabs.Tab>
6566
</Tabs>
6667
),

packages/canopee-css/src/distributeur/Tabs/Tabs.css

Lines changed: 55 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,122 +2,101 @@
22
display: flex;
33
margin-bottom: 0;
44
padding-left: 0;
5+
align-items: flex-end;
56
list-style: none;
67
}
78

9+
/* Button to change tab */
810
.af-tabs__link {
11+
--background-color: var(--white);
12+
--color: var(--brand-primary);
13+
--margin-top: 0.5rem;
14+
--padding: 0.5rem 1rem;
15+
16+
position: relative;
917
display: block;
10-
height: 100%;
11-
padding: 0 2.875rem;
18+
height: auto;
19+
margin-top: var(--margin-top);
20+
padding: var(--padding);
1221
border: none;
22+
border: 1px solid var(--brand-primary);
23+
border-bottom: 0;
24+
border-radius: 0.25rem 0.25rem 0 0;
1325
overflow: hidden;
14-
font-weight: lighter;
15-
line-height: 2.688rem;
16-
color: var(--gray60);
26+
font-size: 0.875rem;
27+
font-weight: 600;
28+
29+
/* To equal the line-height of the tags */
30+
line-height: 1.75rem;
31+
color: var(--color);
1732
background: none;
33+
background-color: var(--background-color);
34+
transition:
35+
padding 0.5s linear,
36+
margin-top 0.5s linear;
37+
38+
&:focus {
39+
outline: 2px solid var(--brand-primary);
40+
outline-offset: 2px;
41+
}
1842
}
1943

20-
.af-tabs__link:focus {
21-
outline: 0;
44+
@media (prefers-reduced-motion: reduce) {
45+
.af-tabs__link {
46+
transition: none;
47+
}
2248
}
2349

50+
/* List item wrapping the button */
2451
.af-tabs__item {
2552
position: relative;
2653
box-sizing: border-box;
27-
height: 2.5rem;
28-
margin-top: 0.4375rem;
29-
margin-right: 0.375rem;
54+
margin-right: 0.25rem;
3055
margin-left: 0;
31-
border: 1px solid transparent;
32-
border-bottom: 0;
33-
border-radius: 4px 4px 0 0;
34-
vertical-align: bottom;
35-
background-color: var(--gray40);
36-
cursor: pointer;
3756
}
3857

3958
.af-tabs__item--active {
40-
height: 46px;
41-
margin-top: 1px;
42-
border-color: var(--azur);
43-
background-color: var(--azur);
44-
}
45-
46-
.af-tabs__item--active .af-tabs__link {
47-
color: var(--white);
59+
.af-tabs__link {
60+
--color: var(--white);
61+
--background-color: var(--brand-primary);
62+
--margin-top: 0;
63+
--padding: 0.75rem 1rem;
64+
}
4865
}
4966

50-
.af-tabs__item:hover {
51-
border-color: var(--azur);
52-
background-color: var(--azur);
67+
.af-tabs__link:hover {
68+
--background-color: var(--brand-primary);
5369
}
5470

5571
.af-tabs__item:hover .af-tabs__link {
56-
border: none;
5772
color: var(--white);
5873
}
5974

6075
.af-tabs__item:last-child {
6176
margin-right: 0;
6277
}
6378

64-
.af-tabs__item--disabled {
65-
background-color: var(--gray40);
66-
cursor: not-allowed;
67-
}
68-
69-
.af-tabs__item--disabled button {
70-
color: var(--gray80);
71-
cursor: not-allowed;
72-
pointer-events: none;
73-
}
74-
75-
.af-tabs__item--disabled:hover {
76-
border-color: transparent;
77-
background-color: var(--gray40);
78-
}
79-
80-
.af-tabs__item--disabled:hover button {
81-
color: var(--gray80);
82-
}
83-
84-
.af-tabs__item--has-icon-left {
85-
padding-left: 1rem;
86-
}
87-
88-
.af-tabs__item--has-icon-left .glyphicon {
89-
left: 1.5rem;
90-
}
91-
92-
.af-tabs__item--has-icon-right {
93-
padding-right: 1rem;
94-
}
95-
96-
.af-tabs__item--has-icon-right .glyphicon {
97-
right: 1.5rem;
98-
}
99-
10079
.af-tabs__item .glyphicon {
101-
position: absolute;
102-
top: 50%;
103-
width: 17px;
104-
transform: translate(0, -50%);
80+
margin-left: 0.5rem;
81+
vertical-align: sub;
82+
font-size: 1.125rem;
10583
}
10684

10785
.af-tabs__item .af-badge {
108-
position: absolute;
109-
z-index: 1;
110-
top: -14px;
111-
right: -14px;
86+
margin-left: 0.5rem;
11287
}
11388

11489
.af-tabs__content {
115-
border: solid 1px var(--gray30);
116-
border-top: 2px solid var(--azur);
90+
border-top: 2px solid var(--brand-primary);
11791
background-color: var(--white);
118-
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 11%);
92+
box-shadow: 0 0 9px 0 rgba(0, 0, 0, 18%);
11993
}
12094

12195
.af-tabs__pane {
122-
padding: 1.25rem;
96+
display: none;
97+
padding: 1.5rem 1.25rem;
98+
99+
&.af-tabs__pane--active {
100+
display: block;
101+
}
123102
}

packages/canopee-react/src/distributeur/Tabs/components/Tab.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ export type TabProps = {
55
title: ReactNode;
66
children?: ReactNode;
77
className?: string;
8+
/**
9+
* @deprecated Use className instead
10+
*/
811
classModifier?: string;
912
};
1013

11-
const Tab = () => <span />;
14+
const Tab = () => null;
1215

1316
export { Tab };

packages/canopee-react/src/distributeur/Tabs/components/TabsCore.tsx

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
1-
import React, { useState } from "react";
2-
import { TabsStateless, TabsStatelessProps } from "./TabsStateless";
1+
import React, { useEffect, useState } from "react";
2+
import { TabsStateless, type TabsStatelessProps } from "./TabsStateless";
33

44
const DEFAULT_ACTIVE_INDEX = "0";
55

66
type Tabs = {
7-
onChange?: (event: React.MouseEvent<HTMLButtonElement>) => void;
7+
onChange?: (event: React.FormEvent<HTMLButtonElement>) => void;
88
activeIndex?: string;
99
};
1010

1111
export type TabsCoreProps = Tabs & Omit<TabsStatelessProps, "activeIndex">;
1212

13-
export const onChangeEvent =
14-
(onChange?: (event: React.MouseEvent<HTMLButtonElement>) => void) =>
15-
(setState: (newValue: string) => void) =>
16-
(event: React.MouseEvent<HTMLButtonElement>, index: string) => {
17-
if (onChange) {
18-
onChange(event);
19-
}
20-
if (index) {
21-
setState(index);
22-
}
23-
};
24-
2513
const TabsCore: React.FunctionComponent<TabsCoreProps> = ({
26-
activeIndex = DEFAULT_ACTIVE_INDEX,
14+
activeIndex: activeIndexProp = DEFAULT_ACTIVE_INDEX,
2715
onChange,
2816
...otherProps
2917
}) => {
30-
const [stateActiveIndex, setActiveIndex] = useState<string>(activeIndex);
18+
const [activeIndex, setActiveIndex] = useState<number>(
19+
Number(activeIndexProp),
20+
);
21+
22+
// Allow to update activeIndex from props
23+
useEffect(() => {
24+
setActiveIndex(Number(activeIndexProp));
25+
}, [activeIndexProp]);
3126

3227
return (
3328
<TabsStateless
34-
activeIndex={stateActiveIndex}
29+
activeIndex={activeIndex}
3530
{...otherProps}
36-
onChange={onChangeEvent(onChange)(setActiveIndex)}
31+
onChange={(e, index) => {
32+
if (onChange) {
33+
onChange(e);
34+
}
35+
if (index >= 0) {
36+
setActiveIndex(index);
37+
}
38+
}}
3739
/>
3840
);
3941
};

0 commit comments

Comments
 (0)