@orangesk/orange-design-system 3.7.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -6
- package/build/components/Alert/style.css +1 -1
- package/build/components/Alert/style.css.map +1 -1
- package/build/components/Bar/style.css +1 -1
- package/build/components/Bar/style.css.map +1 -1
- package/build/components/Drawer/style.css +2 -0
- package/build/components/Drawer/style.css.map +1 -0
- package/build/components/Footer/style.css +1 -1
- package/build/components/Footer/style.css.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/Modal/style.css +1 -1
- package/build/components/Modal/style.css.map +1 -1
- package/build/components/StatusIndicator/style.css +2 -0
- package/build/components/StatusIndicator/style.css.map +1 -0
- package/build/components/Table/style.css +1 -1
- package/build/components/Table/style.css.map +1 -1
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/components/types/index.d.ts +55 -3
- package/build/lib/base.css +1 -1
- package/build/lib/base.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/footer.css +1 -1
- package/build/lib/footer.css.map +1 -1
- package/build/lib/footer.js +1 -1
- package/build/lib/footer.js.map +1 -1
- package/build/lib/megamenu.css +1 -1
- package/build/lib/megamenu.css.map +1 -1
- package/build/lib/megamenu.js +1 -1
- package/build/lib/megamenu.js.map +1 -1
- package/build/lib/reset.css +2 -0
- package/build/lib/reset.css.map +1 -0
- package/build/lib/reset.js +3 -0
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/build/lib/utilities.css +1 -1
- package/build/lib/utilities.css.map +1 -1
- package/package.json +27 -33
- package/src/components/Alert/Alert.tsx +2 -2
- package/src/components/Alert/data.ts +8 -5
- package/src/components/Alert/styles/config.scss +4 -4
- package/src/components/Alert/styles/mixins.scss +8 -1
- package/src/components/Alert/tests/Alert.test.jsx +11 -1
- package/src/components/Bar/Bar.tsx +1 -1
- package/src/components/Bar/styles/config.scss +1 -0
- package/src/components/Bar/tests/Bar.unit.test.jsx +1 -1
- package/src/components/Carousel/Carousel.static.ts +103 -12
- package/src/components/Carousel/tests/Carousel.static.test.jsx +134 -0
- package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +12 -4
- package/src/components/Drawer/Drawer.static.ts +281 -0
- package/src/components/Drawer/Drawer.tsx +82 -0
- package/src/components/Drawer/DrawerBody.tsx +21 -0
- package/src/components/Drawer/DrawerFooter.tsx +21 -0
- package/src/components/Drawer/DrawerHeader.tsx +21 -0
- package/src/components/Drawer/index.ts +5 -0
- package/src/components/Drawer/styles/config.scss +17 -0
- package/src/components/Drawer/styles/mixins.scss +119 -0
- package/src/components/Drawer/styles/style.scss +71 -0
- package/src/components/Drawer/tests/Drawer.unit.test.jsx +149 -0
- package/src/components/Footer/Footer.tsx +1 -4
- package/src/components/Footer/styles/mixins.scss +3 -1
- package/src/components/Footer/styles/style.scss +31 -0
- package/src/components/Footer/tests/Footer.styles.unit.test.jsx +62 -0
- package/src/components/Forms/SegmentedControl/SegmentedControl.tsx +122 -0
- package/src/components/Forms/SegmentedControl/index.ts +5 -0
- package/src/components/Forms/SegmentedControl/styles/mixins.scss +52 -0
- package/src/components/Forms/SegmentedControl/styles/style.scss +35 -0
- package/src/components/Forms/SegmentedControl/tests/SegmentedControl.conformance.test.jsx +36 -0
- package/src/components/Forms/SegmentedControl/tests/SegmentedControl.unit.test.jsx +125 -0
- package/src/components/Forms/index.ts +1 -0
- package/src/components/Icon/index.ts +1 -0
- package/src/components/Megamenu/styles/mixins.scss +24 -18
- package/src/components/Modal/styles/config.scss +3 -2
- package/src/components/StatusIndicator/StatusIndicator.tsx +29 -0
- package/src/components/StatusIndicator/index.ts +2 -0
- package/src/components/StatusIndicator/styles/config.scss +12 -0
- package/src/components/StatusIndicator/styles/mixins.scss +36 -0
- package/src/components/StatusIndicator/styles/style.scss +16 -0
- package/src/components/StatusIndicator/tests/StatusIndicator.conformance.test.jsx +22 -0
- package/src/components/StatusIndicator/tests/StatusIndicator.unit.test.jsx +70 -0
- package/src/components/Table/Table.static.ts +23 -10
- package/src/components/Table/Table.tsx +7 -2
- package/src/components/Table/styles/mixins.scss +59 -0
- package/src/components/Table/styles/style.scss +25 -0
- package/src/components/Table/tests/Table.static.unit.test.jsx +72 -0
- package/src/components/Table/tests/Table.unit.test.jsx +33 -0
- package/src/components/index.ts +11 -0
- package/src/styles/tokens/overlay.scss +3 -0
- package/src/styles/typography/style.scss +0 -13
- package/src/styles/utilities/border.scss +4 -0
- package/src/styles/utilities/border.styles.unit.test.jsx +9 -0
- package/src/styles/utilities/text.scss +12 -0
- package/src/components/Alert/tests/Alert.visual.test.jsx +0 -137
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-custom-title-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-danger-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-full-width-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-success-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-buttons-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-description-buttons-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-description-chromium-linux.png +0 -0
- package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-warning-chromium-linux.png +0 -0
- package/src/components/Carousel/CHANGELOG.md +0 -11
- package/src/components/CarouselHero/tests/CarouselHero.visual.test.jsx +0 -90
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import React, { createContext, type ReactNode, useContext } from "react";
|
|
5
|
+
|
|
6
|
+
import { Label } from "../Label";
|
|
7
|
+
import { Radio } from "../Radio";
|
|
8
|
+
import type { RadioProps } from "../Radio/Radio";
|
|
9
|
+
|
|
10
|
+
const CLASS_ROOT = "segmented-control";
|
|
11
|
+
interface SegmentedControlContextValue {
|
|
12
|
+
name: string;
|
|
13
|
+
isInvalid: boolean;
|
|
14
|
+
describedBy?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SegmentedControlContext =
|
|
18
|
+
createContext<SegmentedControlContextValue | null>(null);
|
|
19
|
+
|
|
20
|
+
export interface SegmentedControlProps
|
|
21
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
22
|
+
name: string;
|
|
23
|
+
isInvalid?: boolean;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type SegmentedControlInputProps = Omit<
|
|
28
|
+
RadioProps,
|
|
29
|
+
| "id"
|
|
30
|
+
| "name"
|
|
31
|
+
| "value"
|
|
32
|
+
| "type"
|
|
33
|
+
| "checked"
|
|
34
|
+
| "defaultChecked"
|
|
35
|
+
| "disabled"
|
|
36
|
+
| "isChecked"
|
|
37
|
+
| "isDisabled"
|
|
38
|
+
| "isInvalid"
|
|
39
|
+
| "children"
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
export interface SegmentedControlItemProps
|
|
43
|
+
extends Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "htmlFor"> {
|
|
44
|
+
id: string;
|
|
45
|
+
value: string;
|
|
46
|
+
isChecked?: boolean;
|
|
47
|
+
isDisabled?: boolean;
|
|
48
|
+
inputProps?: SegmentedControlInputProps;
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SegmentedControl: React.FC<SegmentedControlProps> = ({
|
|
53
|
+
name,
|
|
54
|
+
isInvalid = false,
|
|
55
|
+
"aria-describedby": describedBy,
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
...other
|
|
59
|
+
}) => (
|
|
60
|
+
<div
|
|
61
|
+
className={cx(
|
|
62
|
+
CLASS_ROOT,
|
|
63
|
+
{ [`${CLASS_ROOT}--invalid`]: isInvalid },
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...other}
|
|
67
|
+
>
|
|
68
|
+
<SegmentedControlContext.Provider value={{ name, isInvalid, describedBy }}>
|
|
69
|
+
{children}
|
|
70
|
+
</SegmentedControlContext.Provider>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const SegmentedControlItem: React.FC<SegmentedControlItemProps> = ({
|
|
75
|
+
id,
|
|
76
|
+
value,
|
|
77
|
+
isChecked,
|
|
78
|
+
isDisabled,
|
|
79
|
+
inputProps,
|
|
80
|
+
className,
|
|
81
|
+
children,
|
|
82
|
+
...other
|
|
83
|
+
}) => {
|
|
84
|
+
const context = useContext(SegmentedControlContext);
|
|
85
|
+
|
|
86
|
+
if (context === null) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"SegmentedControlItem must be rendered inside SegmentedControl.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { name, isInvalid, describedBy: groupDescribedBy } = context;
|
|
93
|
+
const describedBy = [groupDescribedBy, inputProps?.["aria-describedby"]]
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.join(" ");
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Label
|
|
99
|
+
htmlFor={id}
|
|
100
|
+
className={cx(`${CLASS_ROOT}__item`, className)}
|
|
101
|
+
{...other}
|
|
102
|
+
>
|
|
103
|
+
<Radio
|
|
104
|
+
{...inputProps}
|
|
105
|
+
id={id}
|
|
106
|
+
name={name}
|
|
107
|
+
value={value}
|
|
108
|
+
isChecked={isChecked}
|
|
109
|
+
isDisabled={isDisabled}
|
|
110
|
+
isInvalid={isInvalid}
|
|
111
|
+
aria-invalid={isInvalid || undefined}
|
|
112
|
+
aria-describedby={describedBy || undefined}
|
|
113
|
+
/>
|
|
114
|
+
<span className={`${CLASS_ROOT}__label`}>{children}</span>
|
|
115
|
+
</Label>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
120
|
+
SegmentedControlItem.displayName = "SegmentedControlItem";
|
|
121
|
+
|
|
122
|
+
export { SegmentedControl, SegmentedControlItem };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
@use "../../../../styles/tokens/space";
|
|
2
|
+
@use "../../../../styles/tools/convert";
|
|
3
|
+
|
|
4
|
+
@mixin base {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
width: fit-content;
|
|
7
|
+
max-width: 100%;
|
|
8
|
+
background: var(--color-fill-primary);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@mixin item {
|
|
12
|
+
position: relative;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex: 1 1 auto;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: space.get("small");
|
|
17
|
+
min-height: convert.to-rem(50px);
|
|
18
|
+
min-width: 0;
|
|
19
|
+
padding: convert.to-rem(13px) convert.to-rem(15px);
|
|
20
|
+
font-weight: 700;
|
|
21
|
+
border: 2px solid var(--color-border-strong);
|
|
22
|
+
border-radius: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@mixin first-item {
|
|
26
|
+
border-start-start-radius: space.get("small");
|
|
27
|
+
border-end-start-radius: space.get("small");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@mixin adjacent-item {
|
|
31
|
+
margin-inline-start: -2px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@mixin last-item {
|
|
35
|
+
border-start-end-radius: space.get("small");
|
|
36
|
+
border-end-end-radius: space.get("small");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@mixin selected {
|
|
40
|
+
z-index: 1;
|
|
41
|
+
border-color: var(--color-border-accent);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@mixin disabled {
|
|
45
|
+
cursor: not-allowed;
|
|
46
|
+
color: var(--color-text-secondary);
|
|
47
|
+
background: var(--color-surface-subtle);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@mixin invalid {
|
|
51
|
+
border-color: var(--color-border-negative);
|
|
52
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@use "./mixins";
|
|
2
|
+
|
|
3
|
+
@layer components {
|
|
4
|
+
.segmented-control {
|
|
5
|
+
@include mixins.base;
|
|
6
|
+
|
|
7
|
+
&__item {
|
|
8
|
+
@include mixins.item;
|
|
9
|
+
|
|
10
|
+
&:first-child {
|
|
11
|
+
@include mixins.first-item;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
& + & {
|
|
15
|
+
@include mixins.adjacent-item;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&:last-child {
|
|
19
|
+
@include mixins.last-item;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&:has(:checked) {
|
|
23
|
+
@include mixins.selected;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:has(:disabled) {
|
|
27
|
+
@include mixins.disabled;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.segmented-control--invalid &:has(input) {
|
|
31
|
+
@include mixins.invalid;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import { axe } from "vitest-axe";
|
|
3
|
+
|
|
4
|
+
import { Fieldset } from "../../Fieldset";
|
|
5
|
+
import { Message } from "../../Message";
|
|
6
|
+
import { SegmentedControl, SegmentedControlItem } from "..";
|
|
7
|
+
|
|
8
|
+
it("is accessible as a labelled radio group with feedback", async () => {
|
|
9
|
+
const { container } = render(
|
|
10
|
+
<Fieldset legend="Payment method">
|
|
11
|
+
<SegmentedControl
|
|
12
|
+
name="payment-method"
|
|
13
|
+
isInvalid
|
|
14
|
+
aria-describedby="payment-method-error"
|
|
15
|
+
>
|
|
16
|
+
<SegmentedControlItem id="payment-card" value="card">
|
|
17
|
+
Card
|
|
18
|
+
</SegmentedControlItem>
|
|
19
|
+
<SegmentedControlItem id="payment-cash" value="cash">
|
|
20
|
+
Cash
|
|
21
|
+
</SegmentedControlItem>
|
|
22
|
+
</SegmentedControl>
|
|
23
|
+
<Message id="payment-method-error" type="error">
|
|
24
|
+
Choose a payment method.
|
|
25
|
+
</Message>
|
|
26
|
+
</Fieldset>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(container).toHTMLValidate({
|
|
30
|
+
rules: {
|
|
31
|
+
"element-permitted-content": "off",
|
|
32
|
+
"no-redundant-for": "off",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
36
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Fieldset } from "../../Fieldset";
|
|
4
|
+
import { SegmentedControl, SegmentedControlItem } from "..";
|
|
5
|
+
|
|
6
|
+
describe("SegmentedControl", () => {
|
|
7
|
+
it("renders any number of choices as one native radio group", () => {
|
|
8
|
+
render(
|
|
9
|
+
<Fieldset legend="Number type">
|
|
10
|
+
<SegmentedControl name="numberType">
|
|
11
|
+
<SegmentedControlItem id="number-new" value="new">
|
|
12
|
+
New number
|
|
13
|
+
</SegmentedControlItem>
|
|
14
|
+
<SegmentedControlItem id="number-existing" value="existing">
|
|
15
|
+
Existing number
|
|
16
|
+
</SegmentedControlItem>
|
|
17
|
+
<SegmentedControlItem id="number-transfer" value="transfer">
|
|
18
|
+
Transfer number
|
|
19
|
+
</SegmentedControlItem>
|
|
20
|
+
</SegmentedControl>
|
|
21
|
+
</Fieldset>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const choices = screen.getAllByRole("radio");
|
|
25
|
+
|
|
26
|
+
expect(screen.getByRole("group", { name: "Number type" })).toContainElement(
|
|
27
|
+
choices[0],
|
|
28
|
+
);
|
|
29
|
+
expect(choices).toHaveLength(3);
|
|
30
|
+
expect(choices.map((choice) => choice.name)).toEqual([
|
|
31
|
+
"numberType",
|
|
32
|
+
"numberType",
|
|
33
|
+
"numberType",
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("selects an option through its label and preserves radio exclusivity", () => {
|
|
38
|
+
render(
|
|
39
|
+
<SegmentedControl name="delivery">
|
|
40
|
+
<SegmentedControlItem id="delivery-email" value="email">
|
|
41
|
+
Email
|
|
42
|
+
</SegmentedControlItem>
|
|
43
|
+
<SegmentedControlItem id="delivery-sms" value="sms">
|
|
44
|
+
SMS
|
|
45
|
+
</SegmentedControlItem>
|
|
46
|
+
</SegmentedControl>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const email = screen.getByRole("radio", { name: "Email" });
|
|
50
|
+
const sms = screen.getByRole("radio", { name: "SMS" });
|
|
51
|
+
|
|
52
|
+
fireEvent.click(screen.getByText("Email"));
|
|
53
|
+
expect(email).toBeChecked();
|
|
54
|
+
|
|
55
|
+
fireEvent.click(screen.getByText("SMS"));
|
|
56
|
+
expect(email).not.toBeChecked();
|
|
57
|
+
expect(sms).toBeChecked();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("forwards native radio states and attributes", () => {
|
|
61
|
+
render(
|
|
62
|
+
<SegmentedControl name="plan" className="custom-control">
|
|
63
|
+
<SegmentedControlItem
|
|
64
|
+
id="plan-standard"
|
|
65
|
+
value="standard"
|
|
66
|
+
isChecked
|
|
67
|
+
isDisabled
|
|
68
|
+
inputProps={{ "aria-describedby": "plan-hint" }}
|
|
69
|
+
className="custom-item"
|
|
70
|
+
>
|
|
71
|
+
Standard
|
|
72
|
+
</SegmentedControlItem>
|
|
73
|
+
</SegmentedControl>,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const choice = screen.getByRole("radio", { name: "Standard" });
|
|
77
|
+
|
|
78
|
+
expect(choice).toBeChecked();
|
|
79
|
+
expect(choice).toBeDisabled();
|
|
80
|
+
expect(choice).toHaveAttribute("value", "standard");
|
|
81
|
+
expect(choice).toHaveAttribute("aria-describedby", "plan-hint");
|
|
82
|
+
expect(choice.closest(".segmented-control")).toHaveClass("custom-control");
|
|
83
|
+
expect(choice.closest(".segmented-control__item")).toHaveClass(
|
|
84
|
+
"custom-item",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("marks the whole radio group as invalid", () => {
|
|
89
|
+
render(
|
|
90
|
+
<SegmentedControl name="payment" isInvalid data-testid="payment-method">
|
|
91
|
+
<SegmentedControlItem id="payment-card" value="card" isChecked>
|
|
92
|
+
Card
|
|
93
|
+
</SegmentedControlItem>
|
|
94
|
+
<SegmentedControlItem id="payment-cash" value="cash">
|
|
95
|
+
Cash
|
|
96
|
+
</SegmentedControlItem>
|
|
97
|
+
</SegmentedControl>,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(screen.getByTestId("payment-method")).toHaveClass(
|
|
101
|
+
"segmented-control--invalid",
|
|
102
|
+
);
|
|
103
|
+
for (const choice of screen.getAllByRole("radio")) {
|
|
104
|
+
expect(choice).toHaveAttribute("aria-invalid", "true");
|
|
105
|
+
expect(choice).toHaveClass("is-invalid");
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("associates group feedback with every radio", () => {
|
|
110
|
+
render(
|
|
111
|
+
<SegmentedControl name="contact" aria-describedby="contact-error">
|
|
112
|
+
<SegmentedControlItem id="contact-email" value="email">
|
|
113
|
+
Email
|
|
114
|
+
</SegmentedControlItem>
|
|
115
|
+
<SegmentedControlItem id="contact-phone" value="phone">
|
|
116
|
+
Phone
|
|
117
|
+
</SegmentedControlItem>
|
|
118
|
+
</SegmentedControl>,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
for (const choice of screen.getAllByRole("radio")) {
|
|
122
|
+
expect(choice).toHaveAttribute("aria-describedby", "contact-error");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -11,6 +11,7 @@ export { Label } from "./Label";
|
|
|
11
11
|
export { Message } from "./Message";
|
|
12
12
|
export { Radio } from "./Radio";
|
|
13
13
|
export { RangeSlider } from "./RangeSlider";
|
|
14
|
+
export { SegmentedControl, SegmentedControlItem } from "./SegmentedControl";
|
|
14
15
|
export { Select } from "./Select";
|
|
15
16
|
export { SelectionTile } from "./SelectionTile";
|
|
16
17
|
export { TextArea } from "./TextArea";
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
@use "../../Button/styles/mixins" as button;
|
|
18
18
|
@use "../../Card/styles/mixins" as card;
|
|
19
19
|
@use "../../../styles/typography/mixins" as typography;
|
|
20
|
+
@use "../../../styles/tokens/overlay";
|
|
20
21
|
|
|
21
22
|
@use "./config";
|
|
22
23
|
|
|
@@ -101,11 +102,16 @@
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
* {
|
|
104
|
-
box-sizing: border-box;
|
|
105
105
|
letter-spacing: convert.to-rem(-0.1px);
|
|
106
106
|
line-height: convert.to-rem(18px);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
*,
|
|
110
|
+
*::before,
|
|
111
|
+
*::after {
|
|
112
|
+
box-sizing: border-box;
|
|
113
|
+
}
|
|
114
|
+
|
|
109
115
|
::selection {
|
|
110
116
|
background-color: base.$selection-background;
|
|
111
117
|
color: base.$text-color;
|
|
@@ -262,7 +268,7 @@
|
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
@mixin nav-item-lead {
|
|
265
|
-
margin-right:
|
|
271
|
+
margin-right: 0;
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
@mixin nav-item-push {
|
|
@@ -417,7 +423,7 @@
|
|
|
417
423
|
}
|
|
418
424
|
|
|
419
425
|
&.is-active {
|
|
420
|
-
color: var(--color-border-accent);
|
|
426
|
+
color: var(--color-border-accent) !important;
|
|
421
427
|
}
|
|
422
428
|
}
|
|
423
429
|
|
|
@@ -636,7 +642,7 @@
|
|
|
636
642
|
min-width: 0;
|
|
637
643
|
}
|
|
638
644
|
|
|
639
|
-
> #{config.$class-root}__nav:last-child {
|
|
645
|
+
> #{config.$class-root}__nav:last-child:not(:first-child) {
|
|
640
646
|
flex: 0 0 auto;
|
|
641
647
|
width: auto;
|
|
642
648
|
}
|
|
@@ -652,10 +658,16 @@
|
|
|
652
658
|
|
|
653
659
|
@mixin subnav-item {
|
|
654
660
|
display: flex;
|
|
655
|
-
gap: convert.to-rem(10px);
|
|
661
|
+
column-gap: convert.to-rem(10px);
|
|
656
662
|
margin-bottom: convert.to-rem(10px) !important;
|
|
657
663
|
|
|
664
|
+
> #{config.$class-root}__tag {
|
|
665
|
+
align-self: flex-start;
|
|
666
|
+
}
|
|
667
|
+
|
|
658
668
|
a {
|
|
669
|
+
min-width: 0;
|
|
670
|
+
|
|
659
671
|
&:hover,
|
|
660
672
|
&:focus,
|
|
661
673
|
&:focus-visible,
|
|
@@ -715,13 +727,9 @@
|
|
|
715
727
|
left: 0;
|
|
716
728
|
width: 100%;
|
|
717
729
|
height: 100%;
|
|
718
|
-
background-color:
|
|
719
|
-
in srgb,
|
|
720
|
-
var(--color-surface-contrast) 75%,
|
|
721
|
-
transparent
|
|
722
|
-
);
|
|
730
|
+
background-color: overlay.$background;
|
|
723
731
|
opacity: 0;
|
|
724
|
-
transition: opacity
|
|
732
|
+
transition: opacity overlay.$transition;
|
|
725
733
|
}
|
|
726
734
|
|
|
727
735
|
&.is-active {
|
|
@@ -1135,11 +1143,7 @@
|
|
|
1135
1143
|
left: 0;
|
|
1136
1144
|
width: 100%;
|
|
1137
1145
|
height: 0;
|
|
1138
|
-
background-color:
|
|
1139
|
-
in srgb,
|
|
1140
|
-
var(--color-surface-contrast) 75%,
|
|
1141
|
-
transparent
|
|
1142
|
-
);
|
|
1146
|
+
background-color: overlay.$background;
|
|
1143
1147
|
border: none;
|
|
1144
1148
|
appearance: none;
|
|
1145
1149
|
-webkit-appearance: none;
|
|
@@ -1148,8 +1152,8 @@
|
|
|
1148
1152
|
opacity: 0;
|
|
1149
1153
|
visibility: hidden;
|
|
1150
1154
|
transition:
|
|
1151
|
-
opacity
|
|
1152
|
-
visibility
|
|
1155
|
+
opacity overlay.$transition,
|
|
1156
|
+
visibility overlay.$transition;
|
|
1153
1157
|
outline: none;
|
|
1154
1158
|
|
|
1155
1159
|
&.is-active {
|
|
@@ -1327,6 +1331,8 @@
|
|
|
1327
1331
|
|
|
1328
1332
|
@mixin tag {
|
|
1329
1333
|
@include tag.base;
|
|
1334
|
+
flex: 0 0 auto;
|
|
1335
|
+
white-space: nowrap;
|
|
1330
1336
|
text-decoration: none !important;
|
|
1331
1337
|
|
|
1332
1338
|
@each $name, $props in tagConfig.$sizes {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
@use "../../../styles/tokens/space";
|
|
2
2
|
@use "../../../styles/tokens/color";
|
|
3
|
+
@use "../../../styles/tokens/overlay";
|
|
3
4
|
@use "../../../styles/tools/convert";
|
|
4
5
|
|
|
5
|
-
$overlay-bg-color:
|
|
6
|
+
$overlay-bg-color: overlay.$background !default;
|
|
6
7
|
$overlay-z-index: 500 !default;
|
|
7
|
-
$overlay-e:
|
|
8
|
+
$overlay-e: overlay.$transition !default;
|
|
8
9
|
|
|
9
10
|
$overlay: (
|
|
10
11
|
background-color: $overlay-bg-color,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import cx from "classnames";
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
export interface StatusIndicatorProps extends HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
type: "success" | "warning" | "danger";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CLASS_ROOT = "status-indicator";
|
|
11
|
+
|
|
12
|
+
const StatusIndicator: React.FC<StatusIndicatorProps> = ({
|
|
13
|
+
children,
|
|
14
|
+
className,
|
|
15
|
+
type,
|
|
16
|
+
...other
|
|
17
|
+
}) => (
|
|
18
|
+
<span
|
|
19
|
+
className={cx(CLASS_ROOT, `${CLASS_ROOT}--${type}`, className)}
|
|
20
|
+
{...other}
|
|
21
|
+
>
|
|
22
|
+
<span className={`${CLASS_ROOT}__marker`} aria-hidden="true" />
|
|
23
|
+
<span className={`${CLASS_ROOT}__content`}>{children}</span>
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
StatusIndicator.displayName = "StatusIndicator";
|
|
28
|
+
|
|
29
|
+
export { StatusIndicator };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@use "../../../styles/tools/generate";
|
|
2
|
+
@use "../../../styles/typography/config" as typography;
|
|
3
|
+
@use "./config";
|
|
4
|
+
|
|
5
|
+
@mixin base($config: config.$base) {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: flex-start;
|
|
8
|
+
|
|
9
|
+
@include generate.css-map($config);
|
|
10
|
+
@include generate.css-map(typography.$caption, "default");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@mixin marker($config: config.$marker) {
|
|
14
|
+
flex: 0 0 auto;
|
|
15
|
+
border-radius: 50%;
|
|
16
|
+
|
|
17
|
+
@include generate.css-map($config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@mixin content() {
|
|
21
|
+
min-width: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@mixin marker-types() {
|
|
25
|
+
.status-indicator--success & {
|
|
26
|
+
background-color: var(--color-icon-positive);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.status-indicator--warning & {
|
|
30
|
+
background-color: var(--color-icon-warning);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.status-indicator--danger & {
|
|
34
|
+
background-color: var(--color-icon-negative);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import { axe } from "vitest-axe";
|
|
3
|
+
|
|
4
|
+
import { StatusIndicator } from "../";
|
|
5
|
+
|
|
6
|
+
const example = (
|
|
7
|
+
<>
|
|
8
|
+
<StatusIndicator type="success">Available</StatusIndicator>
|
|
9
|
+
<StatusIndicator type="warning">Limited availability</StatusIndicator>
|
|
10
|
+
<StatusIndicator type="danger">Unavailable</StatusIndicator>
|
|
11
|
+
</>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
it("is valid html", () => {
|
|
15
|
+
const { container } = render(example);
|
|
16
|
+
expect(container).toHTMLValidate();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("is accessible", async () => {
|
|
20
|
+
const { container } = render(example);
|
|
21
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
22
|
+
});
|