@codecademy/styleguide 78.5.5-alpha.86d3e6.0 → 78.5.5-alpha.ebe1b3.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/CHANGELOG.md +1 -1
- package/package.json +2 -2
- package/src/lib/Atoms/FormElements/FormGroup/FormGroup.mdx +6 -0
- package/src/lib/Atoms/FormElements/FormGroup/FormGroup.stories.tsx +11 -0
- package/src/lib/Molecules/Coachmark/Coachmark.mdx +0 -6
- package/src/lib/Molecules/Coachmark/Coachmark.stories.tsx +1 -37
- package/src/lib/Molecules/Modals/Modal/Modal.mdx +0 -10
- package/src/lib/Molecules/Modals/Overlay/Overlay.mdx +0 -6
- package/src/lib/Molecules/Modals/Overlay/Overlay.stories.tsx +2 -67
- package/src/lib/Molecules/Tips/InfoTip/InfoTip.mdx +11 -10
- package/src/lib/Molecules/Tips/InfoTip/InfoTip.stories.tsx +94 -105
- package/src/lib/Molecules/Tips/PreviewTip/PreviewTip.mdx +0 -6
- package/src/lib/Molecules/Tips/PreviewTip/PreviewTip.stories.tsx +1 -68
- package/src/lib/Molecules/Tips/ToolTip/ToolTip.mdx +0 -6
- package/src/lib/Molecules/Tips/ToolTip/ToolTip.stories.tsx +0 -59
- package/src/lib/Organisms/ConnectedForm/ConnectedFormGroup/ConnectedFormGroup.mdx +20 -0
- package/src/lib/Organisms/ConnectedForm/ConnectedFormGroup/ConnectedFormGroup.stories.tsx +84 -0
- package/src/lib/Organisms/GridForm/Fields.mdx +20 -0
- package/src/lib/Organisms/GridForm/Fields.stories.tsx +73 -1
- package/src/lib/Organisms/GridForm/Layout.mdx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
### [78.5.5-alpha.
|
|
6
|
+
### [78.5.5-alpha.ebe1b3.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@78.5.4...@codecademy/styleguide@78.5.5-alpha.ebe1b3.0) (2026-01-06)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @codecademy/styleguide
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecademy/styleguide",
|
|
3
3
|
"description": "Styleguide & Component library for codecademy.com",
|
|
4
|
-
"version": "78.5.5-alpha.
|
|
4
|
+
"version": "78.5.5-alpha.ebe1b3.0",
|
|
5
5
|
"author": "Codecademy Engineering",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"repository": "git@github.com:Codecademy/gamut.git",
|
|
11
|
-
"gitHead": "
|
|
11
|
+
"gitHead": "873587dd5513aace45c681f478a5ceb4de320604"
|
|
12
12
|
}
|
|
@@ -69,6 +69,12 @@ A field can include <LinkTo id="Molecules/Tips/InfoTip"> our existing `InfoTip`<
|
|
|
69
69
|
|
|
70
70
|
<Canvas of={FormGroupStories.HighEmphasisInfoTip} />
|
|
71
71
|
|
|
72
|
+
#### Accessibility
|
|
73
|
+
|
|
74
|
+
InfoTip buttons are automatically labelled by string field labels for accessibility.
|
|
75
|
+
|
|
76
|
+
<Canvas of={FormGroupStories.InfoTipAutoLabelling} />
|
|
77
|
+
|
|
72
78
|
## Playground
|
|
73
79
|
|
|
74
80
|
<Canvas sourceState="shown" of={FormGroupStories.Default} />
|
|
@@ -97,3 +97,14 @@ export const HighEmphasisInfoTip: Story = {
|
|
|
97
97
|
children: <Input />,
|
|
98
98
|
},
|
|
99
99
|
};
|
|
100
|
+
|
|
101
|
+
export const InfoTipAutoLabelling: Story = {
|
|
102
|
+
args: {
|
|
103
|
+
label: 'Email address',
|
|
104
|
+
htmlFor: 'auto-label-input',
|
|
105
|
+
infotip: {
|
|
106
|
+
info: 'We will never share your email with third parties.',
|
|
107
|
+
},
|
|
108
|
+
children: <Input htmlFor="auto-label-input" type="email" />,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -94,12 +94,6 @@ Using `popoverProps`, you can customize the look of the rendered popover. For ex
|
|
|
94
94
|
|
|
95
95
|
<Canvas of={CoachmarkStories.Customized} />
|
|
96
96
|
|
|
97
|
-
### Z-Index
|
|
98
|
-
|
|
99
|
-
You can customize the `zIndex` of the Coachmark's popover by passing `zIndex` through `popoverProps`. This is useful when the popover needs to appear above other positioned elements.
|
|
100
|
-
|
|
101
|
-
<Canvas of={CoachmarkStories.ZIndex} />
|
|
102
|
-
|
|
103
97
|
## Playground
|
|
104
98
|
|
|
105
99
|
<Canvas sourceState="shown" of={CoachmarkStories.Default} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Coachmark, FillButton, FlexBox, Text } from '@codecademy/gamut';
|
|
2
2
|
import * as patterns from '@codecademy/gamut-patterns';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
4
|
import { ComponentProps, useEffect, useState } from 'react';
|
|
@@ -197,39 +197,3 @@ export const Customized: Story = {
|
|
|
197
197
|
);
|
|
198
198
|
},
|
|
199
199
|
};
|
|
200
|
-
|
|
201
|
-
export const ZIndex: Story = {
|
|
202
|
-
args: {
|
|
203
|
-
popoverProps: {
|
|
204
|
-
position: 'below',
|
|
205
|
-
zIndex: 5,
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
render: function ZIndexExample(args) {
|
|
209
|
-
const [shouldShow, setShouldShow] = useState(true);
|
|
210
|
-
|
|
211
|
-
return (
|
|
212
|
-
<FlexBox flexDirection="column" gap={16}>
|
|
213
|
-
<Box bg="paleBlue" p={16} zIndex={3} position="relative">
|
|
214
|
-
Element with z-index: 3
|
|
215
|
-
</Box>
|
|
216
|
-
<Coachmark
|
|
217
|
-
{...args}
|
|
218
|
-
renderPopover={() => (
|
|
219
|
-
<FlexBox alignItems="flex-start" flexDirection="column" p={16}>
|
|
220
|
-
<Text mb={8}>This coachmark has z-index: 5 via popoverProps</Text>
|
|
221
|
-
<FillButton size="small" onClick={() => setShouldShow(false)}>
|
|
222
|
-
Got it
|
|
223
|
-
</FillButton>
|
|
224
|
-
</FlexBox>
|
|
225
|
-
)}
|
|
226
|
-
shouldShow={shouldShow}
|
|
227
|
-
>
|
|
228
|
-
<FillButton onClick={() => setShouldShow(true)}>
|
|
229
|
-
Show Coachmark
|
|
230
|
-
</FillButton>
|
|
231
|
-
</Coachmark>
|
|
232
|
-
</FlexBox>
|
|
233
|
-
);
|
|
234
|
-
},
|
|
235
|
-
};
|
|
@@ -89,16 +89,6 @@ A Modal can be made scrollable by including large content inside.
|
|
|
89
89
|
|
|
90
90
|
<Canvas of={ModalStories.Scrollable} />
|
|
91
91
|
|
|
92
|
-
## Z-Index
|
|
93
|
-
|
|
94
|
-
Modal accepts a `zIndex` prop (defaults to `3`) that controls the stacking order of its underlying Overlay. Use this when the modal needs to appear above other positioned elements like sticky headers or custom floating UI.
|
|
95
|
-
|
|
96
|
-
```tsx
|
|
97
|
-
<Modal zIndex={10} isOpen={isOpen} onRequestClose={handleClose}>
|
|
98
|
-
Content that needs to appear above other positioned elements
|
|
99
|
-
</Modal>
|
|
100
|
-
```
|
|
101
|
-
|
|
102
92
|
## Focus management
|
|
103
93
|
|
|
104
94
|
The `containerFocusRef` prop allows you to programmatically control focus on the Modal container. This is useful for advanced focus management scenarios where you need to override the default focus behavior (the Modal has `data-autofocus` by default).
|
|
@@ -32,12 +32,6 @@ Unlike the legacy `Modal` implementations in the monolith, this:
|
|
|
32
32
|
|
|
33
33
|
- If you need styles such as a background behind content, see `Modal` for general modals and `Dialog` for confirmation flows.
|
|
34
34
|
|
|
35
|
-
## Z-Index
|
|
36
|
-
|
|
37
|
-
The `zIndex` prop controls the stacking order of the overlay. It defaults to `3`, which places it above most common UI elements. Increase this value when the overlay needs to appear above other positioned elements like sticky headers or floating UI.
|
|
38
|
-
|
|
39
|
-
<Canvas of={OverlayStories.ZIndex} />
|
|
40
|
-
|
|
41
35
|
## Playground
|
|
42
36
|
|
|
43
37
|
<Canvas sourceState="shown" of={OverlayStories.Default} />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Meta
|
|
1
|
+
import { FillButton, FlexBox, Overlay, Text } from '@codecademy/gamut';
|
|
2
|
+
import type { Meta } from '@storybook/react';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof Overlay> = {
|
|
@@ -8,7 +8,6 @@ const meta: Meta<typeof Overlay> = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export default meta;
|
|
11
|
-
type Story = StoryObj<typeof Overlay>;
|
|
12
11
|
|
|
13
12
|
export const Default: React.FC<React.ComponentProps<typeof Overlay>> = (
|
|
14
13
|
args
|
|
@@ -35,67 +34,3 @@ export const Default: React.FC<React.ComponentProps<typeof Overlay>> = (
|
|
|
35
34
|
</>
|
|
36
35
|
);
|
|
37
36
|
};
|
|
38
|
-
|
|
39
|
-
export const ZIndex: Story = {
|
|
40
|
-
render: function ZIndexExample() {
|
|
41
|
-
const [defaultOpen, setDefaultOpen] = useState(false);
|
|
42
|
-
const [customOpen, setCustomOpen] = useState(false);
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<FlexBox flexDirection="column" gap={16}>
|
|
46
|
-
<FlexBox gap={16}>
|
|
47
|
-
<FillButton onClick={() => setDefaultOpen(true)}>
|
|
48
|
-
Open Overlay (default z-index: 3)
|
|
49
|
-
</FillButton>
|
|
50
|
-
<FillButton onClick={() => setCustomOpen(true)}>
|
|
51
|
-
Open Overlay (z-index: 10)
|
|
52
|
-
</FillButton>
|
|
53
|
-
</FlexBox>
|
|
54
|
-
|
|
55
|
-
<Box position="relative" height="100px">
|
|
56
|
-
<Box
|
|
57
|
-
position="absolute"
|
|
58
|
-
top={0}
|
|
59
|
-
left={0}
|
|
60
|
-
bg="paleYellow"
|
|
61
|
-
p={16}
|
|
62
|
-
zIndex={5}
|
|
63
|
-
>
|
|
64
|
-
<Text>Element with z-index: 5</Text>
|
|
65
|
-
</Box>
|
|
66
|
-
</Box>
|
|
67
|
-
|
|
68
|
-
<Overlay
|
|
69
|
-
isOpen={defaultOpen}
|
|
70
|
-
onRequestClose={() => setDefaultOpen(false)}
|
|
71
|
-
shroud
|
|
72
|
-
>
|
|
73
|
-
<FlexBox bg="white" p={24} borderRadius="md" flexDirection="column" gap={16}>
|
|
74
|
-
<Text>
|
|
75
|
-
This overlay uses the default z-index of 3.
|
|
76
|
-
<br />
|
|
77
|
-
It may appear behind elements with higher z-index values.
|
|
78
|
-
</Text>
|
|
79
|
-
<FillButton onClick={() => setDefaultOpen(false)}>Close</FillButton>
|
|
80
|
-
</FlexBox>
|
|
81
|
-
</Overlay>
|
|
82
|
-
|
|
83
|
-
<Overlay
|
|
84
|
-
isOpen={customOpen}
|
|
85
|
-
onRequestClose={() => setCustomOpen(false)}
|
|
86
|
-
shroud
|
|
87
|
-
zIndex={10}
|
|
88
|
-
>
|
|
89
|
-
<FlexBox bg="white" p={24} borderRadius="md" flexDirection="column" gap={16}>
|
|
90
|
-
<Text>
|
|
91
|
-
This overlay uses z-index: 10.
|
|
92
|
-
<br />
|
|
93
|
-
It will appear above the yellow element with z-index: 5.
|
|
94
|
-
</Text>
|
|
95
|
-
<FillButton onClick={() => setCustomOpen(false)}>Close</FillButton>
|
|
96
|
-
</FlexBox>
|
|
97
|
-
</Overlay>
|
|
98
|
-
</FlexBox>
|
|
99
|
-
);
|
|
100
|
-
},
|
|
101
|
-
};
|
|
@@ -28,8 +28,7 @@ export const parameters = {
|
|
|
28
28
|
A tip is triggered by clicking on an information icon button and can be closed by clicking outside, pressing <kbd>Esc</kbd>, or clicking the info button again.
|
|
29
29
|
|
|
30
30
|
Use an infotip to provide additional info about a nearby element or content.
|
|
31
|
-
|
|
32
|
-
Infotip consists of an icon button and the .tip-bg subcomponent. The info button has low and high emphasis variants and the `.tip` has 4 alignment variants.
|
|
31
|
+
The info button has low and high emphasis variants and the `Tip` has 4 alignment variants.
|
|
33
32
|
|
|
34
33
|
## Variants
|
|
35
34
|
|
|
@@ -57,16 +56,18 @@ This `floating` variant should only be used as needed.
|
|
|
57
56
|
|
|
58
57
|
### InfoTips with links or buttons
|
|
59
58
|
|
|
60
|
-
Links or buttons within InfoTips should be used sparingly and only when the information is critical to the user's understanding of the content.
|
|
59
|
+
Links or buttons within InfoTips should be used sparingly and only when the information is critical to the user's understanding of the content. When an InfoTip opens, focus automatically moves to the tip content, allowing keyboard users to immediately interact with any links or buttons inside.
|
|
61
60
|
|
|
62
61
|
<Canvas of={InfoTipStories.WithLinksOrButtons} />
|
|
63
62
|
|
|
64
|
-
###
|
|
63
|
+
### Automatic Focus Management
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
InfoTips automatically manage focus for optimal keyboard accessibility:
|
|
67
66
|
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
67
|
+
- **Opening**: Focus automatically moves to the tip content when opened
|
|
68
|
+
- **Tab (Floating)**: Navigate forward through focusable elements (links, buttons) inside the tip. When reaching the last element, wraps back to the InfoTip button
|
|
69
|
+
- **Shift+Tab (Floating)**: Navigate backward naturally - from the button, exits to the previous page element
|
|
70
|
+
- **Tab/Shift+Tab (Inline)**: Follows normal document flow
|
|
70
71
|
- **Escape**: Closes the tip and returns focus to the InfoTip button
|
|
71
72
|
|
|
72
73
|
<Canvas of={InfoTipStories.KeyboardNavigation} />
|
|
@@ -93,13 +94,13 @@ The InfoTip button's accessible label can be customized using either prop:
|
|
|
93
94
|
|
|
94
95
|
### Custom Role Description
|
|
95
96
|
|
|
96
|
-
The `InfoTipButton` uses [`aria-roledescription
|
|
97
|
+
The `InfoTipButton` uses [`aria-roledescription`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-roledescription) to provide additional context to screen reader users about the button's specific purpose. This defaults to `"More information button"` but can be customized via the `ariaRoleDescription` prop for translation or other accessibility needs.
|
|
97
98
|
|
|
98
99
|
<Canvas of={InfoTipStories.AriaLabel} />
|
|
99
100
|
|
|
100
|
-
##
|
|
101
|
+
## InfoTips and zIndex
|
|
101
102
|
|
|
102
|
-
You can
|
|
103
|
+
You can change the zIndex of your `InfoTip` with the zIndex property.
|
|
103
104
|
|
|
104
105
|
<Canvas of={InfoTipStories.ZIndex} />
|
|
105
106
|
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Anchor,
|
|
3
3
|
Box,
|
|
4
|
-
Coachmark,
|
|
5
4
|
FillButton,
|
|
6
5
|
FlexBox,
|
|
7
6
|
GridBox,
|
|
7
|
+
IconButton,
|
|
8
8
|
InfoTip,
|
|
9
9
|
Modal,
|
|
10
10
|
Text,
|
|
11
11
|
} from '@codecademy/gamut';
|
|
12
|
+
import { SparkleIcon } from '@codecademy/gamut-icons';
|
|
12
13
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
13
|
-
import {
|
|
14
|
+
import { useState } from 'react';
|
|
14
15
|
|
|
15
16
|
const meta: Meta<typeof InfoTip> = {
|
|
16
17
|
component: InfoTip,
|
|
17
18
|
args: {
|
|
18
19
|
alignment: 'top-left',
|
|
20
|
+
ariaLabel: 'More information',
|
|
19
21
|
info: `I am additional information about a nearby element or content.`,
|
|
20
22
|
},
|
|
21
23
|
};
|
|
@@ -29,7 +31,10 @@ export const Emphasis: Story = {
|
|
|
29
31
|
},
|
|
30
32
|
render: (args) => (
|
|
31
33
|
<FlexBox center m={24} py={64}>
|
|
32
|
-
<Text mr={4}>
|
|
34
|
+
<Text id="emphasis-text" mr={4}>
|
|
35
|
+
Some text that needs info
|
|
36
|
+
</Text>
|
|
37
|
+
<InfoTip {...args} ariaLabelledby="emphasis-text" />
|
|
33
38
|
</FlexBox>
|
|
34
39
|
),
|
|
35
40
|
};
|
|
@@ -39,10 +44,15 @@ export const Alignments: Story = {
|
|
|
39
44
|
<GridBox gap={24} gridTemplateColumns="1fr 1fr" ml={8} py={64}>
|
|
40
45
|
{(['top-right', 'top-left', 'bottom-right', 'bottom-left'] as const).map(
|
|
41
46
|
(alignment) => {
|
|
47
|
+
const labelId = `alignment-${alignment}`;
|
|
42
48
|
return (
|
|
43
49
|
<Box key={alignment}>
|
|
44
|
-
<Text>{alignment}</Text>
|
|
45
|
-
<InfoTip
|
|
50
|
+
<Text id={labelId}>{alignment}</Text>
|
|
51
|
+
<InfoTip
|
|
52
|
+
{...args}
|
|
53
|
+
alignment={alignment}
|
|
54
|
+
ariaLabelledby={labelId}
|
|
55
|
+
/>
|
|
46
56
|
</Box>
|
|
47
57
|
);
|
|
48
58
|
}
|
|
@@ -57,10 +67,51 @@ export const Placement: Story = {
|
|
|
57
67
|
},
|
|
58
68
|
render: (args) => (
|
|
59
69
|
<FlexBox center>
|
|
60
|
-
<Text mr={4}>
|
|
70
|
+
<Text id="placement-text" mr={4}>
|
|
61
71
|
This text is in a small space and needs floating placement
|
|
62
72
|
</Text>{' '}
|
|
63
|
-
<InfoTip {...args} />
|
|
73
|
+
<InfoTip {...args} ariaLabelledby="placement-text" />
|
|
74
|
+
</FlexBox>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const AriaLabel: Story = {
|
|
79
|
+
render: (args) => (
|
|
80
|
+
<FlexBox center column gap={24} my={48} width={1}>
|
|
81
|
+
<FlexBox alignItems="center" gap={8}>
|
|
82
|
+
<Text fontSize={16} fontWeight="bold">
|
|
83
|
+
Using ariaLabel (no visible label text):
|
|
84
|
+
</Text>
|
|
85
|
+
</FlexBox>
|
|
86
|
+
<FlexBox alignItems="center" gap={8}>
|
|
87
|
+
<IconButton
|
|
88
|
+
icon={SparkleIcon}
|
|
89
|
+
tip="This tool needs to be explained in the InfoTip"
|
|
90
|
+
tipProps={{ placement: 'floating' }}
|
|
91
|
+
onClick={() => null}
|
|
92
|
+
/>
|
|
93
|
+
<InfoTip
|
|
94
|
+
{...args}
|
|
95
|
+
ariaLabel="Learn more about this tool"
|
|
96
|
+
info="This is some helpful info about the tool represented by the IconButton"
|
|
97
|
+
/>
|
|
98
|
+
</FlexBox>
|
|
99
|
+
|
|
100
|
+
<FlexBox alignItems="center" gap={8}>
|
|
101
|
+
<Text fontSize={16} fontWeight="bold">
|
|
102
|
+
Using ariaLabelledby (references visible text):
|
|
103
|
+
</Text>
|
|
104
|
+
</FlexBox>
|
|
105
|
+
<FlexBox alignItems="center" gap={8}>
|
|
106
|
+
<Text id="custom-info-id">
|
|
107
|
+
I am some helpful yet concise text that needs more explanation
|
|
108
|
+
</Text>
|
|
109
|
+
<InfoTip
|
|
110
|
+
alignment="bottom-left"
|
|
111
|
+
ariaLabelledby="custom-info-id"
|
|
112
|
+
info="I am clarifying information related to the concise text."
|
|
113
|
+
/>
|
|
114
|
+
</FlexBox>
|
|
64
115
|
</FlexBox>
|
|
65
116
|
),
|
|
66
117
|
};
|
|
@@ -70,19 +121,16 @@ export const WithLinksOrButtons: Story = {
|
|
|
70
121
|
placement: 'floating',
|
|
71
122
|
},
|
|
72
123
|
render: function WithLinksOrButtons(args) {
|
|
73
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
74
|
-
|
|
75
|
-
const onClick = ({ isTipHidden }: { isTipHidden: boolean }) => {
|
|
76
|
-
if (!isTipHidden) ref.current?.focus();
|
|
77
|
-
};
|
|
78
|
-
|
|
79
124
|
return (
|
|
80
125
|
<FlexBox center py={64}>
|
|
81
|
-
<Text mr={4}>
|
|
126
|
+
<Text id="links-text" mr={4}>
|
|
127
|
+
This text is in a small space and needs info
|
|
128
|
+
</Text>{' '}
|
|
82
129
|
<InfoTip
|
|
83
130
|
{...args}
|
|
131
|
+
ariaLabelledby="links-text"
|
|
84
132
|
info={
|
|
85
|
-
<Text
|
|
133
|
+
<Text tabIndex={-1}>
|
|
86
134
|
Hey! Here is a{' '}
|
|
87
135
|
<Anchor href="https://giphy.com/search/nichijou">
|
|
88
136
|
cool link
|
|
@@ -94,7 +142,6 @@ export const WithLinksOrButtons: Story = {
|
|
|
94
142
|
that is also super important.
|
|
95
143
|
</Text>
|
|
96
144
|
}
|
|
97
|
-
onClick={onClick}
|
|
98
145
|
/>
|
|
99
146
|
</FlexBox>
|
|
100
147
|
);
|
|
@@ -103,42 +150,35 @@ export const WithLinksOrButtons: Story = {
|
|
|
103
150
|
|
|
104
151
|
export const KeyboardNavigation: Story = {
|
|
105
152
|
render: function KeyboardNavigation() {
|
|
106
|
-
const floatingRef = useRef<HTMLDivElement>(null);
|
|
107
|
-
const inlineRef = useRef<HTMLDivElement>(null);
|
|
108
|
-
|
|
109
153
|
const examples = [
|
|
110
154
|
{
|
|
111
155
|
title: 'Floating Placement',
|
|
112
156
|
placement: 'floating' as const,
|
|
113
|
-
ref: floatingRef,
|
|
114
157
|
links: ['Link 1', 'Link 2', 'Link 3'],
|
|
115
158
|
},
|
|
116
159
|
{
|
|
117
160
|
title: 'Inline Placement',
|
|
118
161
|
placement: 'inline' as const,
|
|
119
162
|
alignment: 'bottom-right' as const,
|
|
120
|
-
ref: inlineRef,
|
|
121
163
|
links: ['Link A', 'Link B'],
|
|
122
164
|
},
|
|
123
165
|
];
|
|
124
166
|
|
|
125
167
|
return (
|
|
126
|
-
<FlexBox center column gap={24} py={64}>
|
|
168
|
+
<FlexBox center flexDirection="column" gap={24} py={64}>
|
|
127
169
|
<GridBox gap={16} gridTemplateColumns="1fr 1fr">
|
|
128
|
-
{examples.map(({ title, placement, alignment,
|
|
129
|
-
const
|
|
130
|
-
if (!isTipHidden) ref.current?.focus();
|
|
131
|
-
};
|
|
132
|
-
|
|
170
|
+
{examples.map(({ title, placement, alignment, links }) => {
|
|
171
|
+
const labelId = `keyboard-nav-${placement}`;
|
|
133
172
|
return (
|
|
134
173
|
<FlexBox gap={8} key={placement}>
|
|
135
|
-
<Text fontSize={16} fontWeight="bold">
|
|
174
|
+
<Text fontSize={16} fontWeight="bold" id={labelId}>
|
|
136
175
|
{title}
|
|
137
176
|
</Text>
|
|
138
177
|
<InfoTip
|
|
139
178
|
alignment={alignment}
|
|
179
|
+
ariaLabelledby={labelId}
|
|
140
180
|
info={
|
|
141
|
-
<Text
|
|
181
|
+
<Text>
|
|
142
182
|
{links.map((label, idx) => (
|
|
143
183
|
<>
|
|
144
184
|
{idx > 0 && ', '}
|
|
@@ -151,7 +191,6 @@ export const KeyboardNavigation: Story = {
|
|
|
151
191
|
</Text>
|
|
152
192
|
}
|
|
153
193
|
placement={placement}
|
|
154
|
-
onClick={onClick}
|
|
155
194
|
/>
|
|
156
195
|
</FlexBox>
|
|
157
196
|
);
|
|
@@ -163,6 +202,10 @@ export const KeyboardNavigation: Story = {
|
|
|
163
202
|
Keyboard Navigation:
|
|
164
203
|
</Text>
|
|
165
204
|
<Box as="ul" fontSize={14} pl={16}>
|
|
205
|
+
<li>
|
|
206
|
+
<strong>Opening:</strong> Focus automatically moves to the tip
|
|
207
|
+
content when opened
|
|
208
|
+
</li>
|
|
166
209
|
<li>
|
|
167
210
|
<strong>Floating - Tab:</strong> Navigates forward through links,
|
|
168
211
|
then wraps to button (contained)
|
|
@@ -196,7 +239,6 @@ export const InfoTipInsideModal: Story = {
|
|
|
196
239
|
},
|
|
197
240
|
render: function InfoTipInsideModal(args) {
|
|
198
241
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
199
|
-
const [showCoachmark, setShowCoachmark] = useState(false);
|
|
200
242
|
|
|
201
243
|
return (
|
|
202
244
|
<FlexBox center flexDirection="column" gap={16} py={64}>
|
|
@@ -226,8 +268,10 @@ export const InfoTipInsideModal: Story = {
|
|
|
226
268
|
<Text>This modal contains an InfoTip below:</Text>
|
|
227
269
|
|
|
228
270
|
<FlexBox alignItems="center" gap={8}>
|
|
229
|
-
<Text
|
|
230
|
-
|
|
271
|
+
<Text id="modal-infotip-text">
|
|
272
|
+
Some text that needs explanation
|
|
273
|
+
</Text>
|
|
274
|
+
<InfoTip {...args} ariaLabelledby="modal-infotip-text" />
|
|
231
275
|
</FlexBox>
|
|
232
276
|
|
|
233
277
|
<Text color="text-disabled" fontSize={14}>
|
|
@@ -235,37 +279,6 @@ export const InfoTipInsideModal: Story = {
|
|
|
235
279
|
closing the modal itself. Inline placement works correctly.
|
|
236
280
|
</Text>
|
|
237
281
|
|
|
238
|
-
<FlexBox alignItems="center" borderTop={1} gap={8} mt={8} pt={16}>
|
|
239
|
-
<Text id="modal-coachmark-text">
|
|
240
|
-
This modal also contains a Coachmark
|
|
241
|
-
</Text>
|
|
242
|
-
<Coachmark
|
|
243
|
-
popoverProps={{ zIndex: 3 }}
|
|
244
|
-
renderPopover={() => (
|
|
245
|
-
<FlexBox
|
|
246
|
-
alignItems="flex-start"
|
|
247
|
-
flexDirection="column"
|
|
248
|
-
p={16}
|
|
249
|
-
>
|
|
250
|
-
<Text mb={8}>
|
|
251
|
-
This Coachmark is inside a Modal. Try pressing Escape!
|
|
252
|
-
</Text>
|
|
253
|
-
<FillButton
|
|
254
|
-
size="small"
|
|
255
|
-
onClick={() => setShowCoachmark(false)}
|
|
256
|
-
>
|
|
257
|
-
Got it
|
|
258
|
-
</FillButton>
|
|
259
|
-
</FlexBox>
|
|
260
|
-
)}
|
|
261
|
-
shouldShow={showCoachmark}
|
|
262
|
-
>
|
|
263
|
-
<FillButton size="small" onClick={() => setShowCoachmark(true)}>
|
|
264
|
-
Show Coachmark
|
|
265
|
-
</FillButton>
|
|
266
|
-
</Coachmark>
|
|
267
|
-
</FlexBox>
|
|
268
|
-
|
|
269
282
|
<FillButton onClick={() => setIsModalOpen(false)}>
|
|
270
283
|
Close Modal
|
|
271
284
|
</FillButton>
|
|
@@ -278,49 +291,22 @@ export const InfoTipInsideModal: Story = {
|
|
|
278
291
|
|
|
279
292
|
export const ZIndex: Story = {
|
|
280
293
|
args: {
|
|
281
|
-
info: 'I
|
|
294
|
+
info: 'I am inline, cool',
|
|
282
295
|
zIndex: 5,
|
|
283
296
|
},
|
|
284
297
|
render: (args) => (
|
|
285
|
-
<FlexBox center flexDirection="column"
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
ariaLabel="inline custom"
|
|
298
|
-
info="z-index: 5 (inline)"
|
|
299
|
-
/>
|
|
300
|
-
</FlexBox>
|
|
301
|
-
|
|
302
|
-
<Text variant="p-small" mt={24}>
|
|
303
|
-
Floating placement:
|
|
304
|
-
</Text>
|
|
305
|
-
<FlexBox alignItems="center" gap={8}>
|
|
306
|
-
<Box bg="paleGreen" p={8} zIndex={3} position="relative">
|
|
307
|
-
z-index: 3
|
|
308
|
-
</Box>
|
|
309
|
-
<InfoTip
|
|
310
|
-
ariaLabel="floating default"
|
|
311
|
-
info="Default z-index (floating)"
|
|
312
|
-
placement="floating"
|
|
313
|
-
/>
|
|
314
|
-
<Box bg="paleGreen" p={8} zIndex={3} position="relative">
|
|
315
|
-
z-index: 3
|
|
316
|
-
</Box>
|
|
317
|
-
<InfoTip
|
|
318
|
-
{...args}
|
|
319
|
-
ariaLabel="floating custom"
|
|
320
|
-
info="z-index: 5 (floating)"
|
|
321
|
-
placement="floating"
|
|
322
|
-
/>
|
|
323
|
-
</FlexBox>
|
|
298
|
+
<FlexBox center flexDirection="column" m={24} py={64}>
|
|
299
|
+
<Box bg="paleBlue" zIndex={3}>
|
|
300
|
+
I will not be behind the infotip, sad + unreadable
|
|
301
|
+
</Box>
|
|
302
|
+
<InfoTip
|
|
303
|
+
ariaLabel="z-index example without override"
|
|
304
|
+
info="I am inline, cool"
|
|
305
|
+
/>
|
|
306
|
+
<Box bg="paleBlue" zIndex={3}>
|
|
307
|
+
I will be behind the infotip, nice + great
|
|
308
|
+
</Box>
|
|
309
|
+
<InfoTip {...args} ariaLabel="z-index example with override" />
|
|
324
310
|
</FlexBox>
|
|
325
311
|
),
|
|
326
312
|
};
|
|
@@ -328,7 +314,10 @@ export const ZIndex: Story = {
|
|
|
328
314
|
export const Default: Story = {
|
|
329
315
|
render: (args) => (
|
|
330
316
|
<FlexBox center m={24} py={64}>
|
|
331
|
-
<Text mr={4}>
|
|
317
|
+
<Text id="default-text" mr={4}>
|
|
318
|
+
Some text that needs info
|
|
319
|
+
</Text>
|
|
320
|
+
<InfoTip {...args} ariaLabelledby="default-text" />
|
|
332
321
|
</FlexBox>
|
|
333
322
|
),
|
|
334
323
|
};
|
|
@@ -55,12 +55,6 @@ The `truncateLines` prop allows you to set the maximum number of lines that the
|
|
|
55
55
|
|
|
56
56
|
<Canvas of={PreviewTipStories.Truncation} />
|
|
57
57
|
|
|
58
|
-
## Z-Index
|
|
59
|
-
|
|
60
|
-
You can customize the `zIndex` of the PreviewTip's portal with the `zIndex` prop. This works for both `inline` and `floating` placements, and is useful when the tip needs to appear above other positioned elements.
|
|
61
|
-
|
|
62
|
-
<Canvas of={PreviewTipStories.ZIndex} />
|
|
63
|
-
|
|
64
58
|
## Playground
|
|
65
59
|
|
|
66
60
|
<Canvas sourceState="shown" of={PreviewTipStories.Default} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box, FlexBox, PreviewTip
|
|
1
|
+
import { Box, FlexBox, PreviewTip } from '@codecademy/gamut';
|
|
2
2
|
import { SmileyIndifferentIcon } from '@codecademy/gamut-icons';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
4
|
import { useState } from 'react';
|
|
@@ -110,70 +110,3 @@ const TruncationExample = (args: React.ComponentProps<typeof PreviewTip>) => {
|
|
|
110
110
|
export const Truncation: Story = {
|
|
111
111
|
render: (args) => <TruncationExample {...args} />,
|
|
112
112
|
};
|
|
113
|
-
|
|
114
|
-
export const ZIndex: Story = {
|
|
115
|
-
args: {
|
|
116
|
-
zIndex: 5,
|
|
117
|
-
},
|
|
118
|
-
render: (args) => (
|
|
119
|
-
<FlexBox center flexDirection="column" gap={16} py={64}>
|
|
120
|
-
<Text variant="p-small">Inline placement:</Text>
|
|
121
|
-
<FlexBox alignItems="center" gap={16}>
|
|
122
|
-
<Box bg="paleBlue" p={8} zIndex={3} position="relative">
|
|
123
|
-
z-index: 3
|
|
124
|
-
</Box>
|
|
125
|
-
<PreviewTip
|
|
126
|
-
{...args}
|
|
127
|
-
alignment="bottom-right"
|
|
128
|
-
href="#"
|
|
129
|
-
linkDescription="Default z-index (inline)"
|
|
130
|
-
>
|
|
131
|
-
Default z-index
|
|
132
|
-
</PreviewTip>
|
|
133
|
-
<Box bg="paleBlue" p={8} zIndex={3} position="relative">
|
|
134
|
-
z-index: 3
|
|
135
|
-
</Box>
|
|
136
|
-
<PreviewTip
|
|
137
|
-
{...args}
|
|
138
|
-
alignment="top-right"
|
|
139
|
-
href="#"
|
|
140
|
-
linkDescription="z-index: 5 (inline)"
|
|
141
|
-
zIndex={5}
|
|
142
|
-
>
|
|
143
|
-
z-index: 5
|
|
144
|
-
</PreviewTip>
|
|
145
|
-
</FlexBox>
|
|
146
|
-
|
|
147
|
-
<Text variant="p-small" mt={24}>
|
|
148
|
-
Floating placement:
|
|
149
|
-
</Text>
|
|
150
|
-
<FlexBox alignItems="center" gap={16}>
|
|
151
|
-
<Box bg="paleGreen" p={8} zIndex={3} position="relative">
|
|
152
|
-
z-index: 3
|
|
153
|
-
</Box>
|
|
154
|
-
<PreviewTip
|
|
155
|
-
{...args}
|
|
156
|
-
alignment="bottom-right"
|
|
157
|
-
href="#"
|
|
158
|
-
linkDescription="Default z-index (floating)"
|
|
159
|
-
placement="floating"
|
|
160
|
-
>
|
|
161
|
-
Default z-index
|
|
162
|
-
</PreviewTip>
|
|
163
|
-
<Box bg="paleGreen" p={8} zIndex={3} position="relative">
|
|
164
|
-
z-index: 3
|
|
165
|
-
</Box>
|
|
166
|
-
<PreviewTip
|
|
167
|
-
{...args}
|
|
168
|
-
alignment="top-right"
|
|
169
|
-
href="#"
|
|
170
|
-
linkDescription="z-index: 5 (floating)"
|
|
171
|
-
placement="floating"
|
|
172
|
-
zIndex={5}
|
|
173
|
-
>
|
|
174
|
-
z-index: 5
|
|
175
|
-
</PreviewTip>
|
|
176
|
-
</FlexBox>
|
|
177
|
-
</FlexBox>
|
|
178
|
-
),
|
|
179
|
-
};
|
|
@@ -63,12 +63,6 @@ When a Button is disabled with a tooltip, you must use the `aria-disabled` prop
|
|
|
63
63
|
|
|
64
64
|
<Canvas of={ToolTipStories.Disabled} />
|
|
65
65
|
|
|
66
|
-
## Z-Index
|
|
67
|
-
|
|
68
|
-
You can customize the `zIndex` of the ToolTip's portal with the `zIndex` prop. This works for both `inline` and `floating` placements, and is useful when the tip needs to appear above other positioned elements.
|
|
69
|
-
|
|
70
|
-
<Canvas of={ToolTipStories.ZIndex} />
|
|
71
|
-
|
|
72
66
|
## Playground
|
|
73
67
|
|
|
74
68
|
<Canvas sourceState="shown" of={ToolTipStories.Default} />
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Box,
|
|
3
2
|
FillButton,
|
|
4
3
|
FlexBox,
|
|
5
4
|
IconButton,
|
|
6
5
|
StrokeButton,
|
|
7
|
-
Text,
|
|
8
6
|
ToolTip,
|
|
9
7
|
} from '@codecademy/gamut';
|
|
10
8
|
import {
|
|
@@ -135,60 +133,3 @@ export const Disabled: Story = {
|
|
|
135
133
|
</FlexBox>
|
|
136
134
|
),
|
|
137
135
|
};
|
|
138
|
-
|
|
139
|
-
export const ZIndex: Story = {
|
|
140
|
-
render: () => (
|
|
141
|
-
<FlexBox center flexDirection="column" gap={16} m={24} py={64}>
|
|
142
|
-
<Text variant="p-small">Inline placement:</Text>
|
|
143
|
-
<FlexBox alignItems="center" gap={16}>
|
|
144
|
-
<Box bg="paleBlue" p={8} zIndex={3} position="relative">
|
|
145
|
-
z-index: 3
|
|
146
|
-
</Box>
|
|
147
|
-
<ToolTip id="zindex-inline-default" info="Default z-index (inline)">
|
|
148
|
-
<FillButton aria-describedby="zindex-inline-default">
|
|
149
|
-
Default
|
|
150
|
-
</FillButton>
|
|
151
|
-
</ToolTip>
|
|
152
|
-
<Box bg="paleBlue" p={8} zIndex={3} position="relative">
|
|
153
|
-
z-index: 3
|
|
154
|
-
</Box>
|
|
155
|
-
<ToolTip id="zindex-inline-custom" info="z-index: 5 (inline)" zIndex={5}>
|
|
156
|
-
<FillButton aria-describedby="zindex-inline-custom">
|
|
157
|
-
z-index: 5
|
|
158
|
-
</FillButton>
|
|
159
|
-
</ToolTip>
|
|
160
|
-
</FlexBox>
|
|
161
|
-
|
|
162
|
-
<Text variant="p-small" mt={24}>
|
|
163
|
-
Floating placement:
|
|
164
|
-
</Text>
|
|
165
|
-
<FlexBox alignItems="center" gap={16}>
|
|
166
|
-
<Box bg="paleGreen" p={8} zIndex={3} position="relative">
|
|
167
|
-
z-index: 3
|
|
168
|
-
</Box>
|
|
169
|
-
<ToolTip
|
|
170
|
-
id="zindex-floating-default"
|
|
171
|
-
info="Default z-index (floating)"
|
|
172
|
-
placement="floating"
|
|
173
|
-
>
|
|
174
|
-
<FillButton aria-describedby="zindex-floating-default">
|
|
175
|
-
Default
|
|
176
|
-
</FillButton>
|
|
177
|
-
</ToolTip>
|
|
178
|
-
<Box bg="paleGreen" p={8} zIndex={3} position="relative">
|
|
179
|
-
z-index: 3
|
|
180
|
-
</Box>
|
|
181
|
-
<ToolTip
|
|
182
|
-
id="zindex-floating-custom"
|
|
183
|
-
info="z-index: 5 (floating)"
|
|
184
|
-
placement="floating"
|
|
185
|
-
zIndex={5}
|
|
186
|
-
>
|
|
187
|
-
<FillButton aria-describedby="zindex-floating-custom">
|
|
188
|
-
z-index: 5
|
|
189
|
-
</FillButton>
|
|
190
|
-
</ToolTip>
|
|
191
|
-
</FlexBox>
|
|
192
|
-
</FlexBox>
|
|
193
|
-
),
|
|
194
|
-
};
|
|
@@ -61,6 +61,26 @@ A `ConnectedFormGroup` can be in one of three states: `default`, `error`, or `di
|
|
|
61
61
|
|
|
62
62
|
<Canvas of={ConnectedFormGroupStories.States} />
|
|
63
63
|
|
|
64
|
+
## InfoTip
|
|
65
|
+
|
|
66
|
+
A `ConnectedFormGroup` can include an `infotip` prop to provide additional context.
|
|
67
|
+
|
|
68
|
+
### Automatic labelling
|
|
69
|
+
|
|
70
|
+
InfoTip buttons are automatically labelled by string field labels for accessibility.
|
|
71
|
+
|
|
72
|
+
<Canvas of={ConnectedFormGroupStories.InfoTipAutoLabelling} />
|
|
73
|
+
|
|
74
|
+
### ReactNode labels
|
|
75
|
+
|
|
76
|
+
For ReactNode labels (e.g., styled text or icons), you have three options:
|
|
77
|
+
|
|
78
|
+
- `labelledByFieldLabel: true` - opt into automatic labelling by the field label
|
|
79
|
+
- `ariaLabel` - provide a custom accessible name
|
|
80
|
+
- `ariaLabelledby` - reference another element on the page, such as a section heading
|
|
81
|
+
|
|
82
|
+
<Canvas of={ConnectedFormGroupStories.InfoTipWithReactNodeLabel} />
|
|
83
|
+
|
|
64
84
|
## Playground
|
|
65
85
|
|
|
66
86
|
To see how a `ConnectedFormGroup` can be used in a `ConnectedForm`, check out the <LinkTo id="Organisms/ConnectedForm/ConnectedForm">ConnectedForm</LinkTo> page.
|
|
@@ -2,7 +2,9 @@ import {
|
|
|
2
2
|
ConnectedForm,
|
|
3
3
|
ConnectedFormGroup,
|
|
4
4
|
ConnectedFormGroupProps,
|
|
5
|
+
ConnectedInput,
|
|
5
6
|
ConnectedRadioGroupInput,
|
|
7
|
+
Text,
|
|
6
8
|
useConnectedForm,
|
|
7
9
|
} from '@codecademy/gamut';
|
|
8
10
|
import { action } from '@storybook/addon-actions';
|
|
@@ -119,3 +121,85 @@ export const States = () => {
|
|
|
119
121
|
</ConnectedForm>
|
|
120
122
|
);
|
|
121
123
|
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* InfoTip buttons are automatically labelled by string field labels for accessibility.
|
|
127
|
+
* Screen readers will announce "Field Label, button" when focusing the InfoTip.
|
|
128
|
+
*/
|
|
129
|
+
export const InfoTipAutoLabelling: Story = {
|
|
130
|
+
render: () => (
|
|
131
|
+
<ConnectedForm
|
|
132
|
+
defaultValues={{ email: '' }}
|
|
133
|
+
onSubmit={(values) => action('Form Submitted')(values)}
|
|
134
|
+
>
|
|
135
|
+
<ConnectedFormGroup
|
|
136
|
+
field={{ component: ConnectedInput, type: 'email' }}
|
|
137
|
+
infotip={{
|
|
138
|
+
info: 'We will never share your email with third parties.',
|
|
139
|
+
placement: 'floating',
|
|
140
|
+
}}
|
|
141
|
+
label="Email address"
|
|
142
|
+
name="email"
|
|
143
|
+
/>
|
|
144
|
+
</ConnectedForm>
|
|
145
|
+
),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* For ReactNode labels, you have three options for accessible InfoTip labelling:
|
|
150
|
+
* - `labelledByFieldLabel: true` - uses the field label
|
|
151
|
+
* - `ariaLabel` - provides a custom accessible name
|
|
152
|
+
* - `ariaLabelledby` - references another element on the page
|
|
153
|
+
*/
|
|
154
|
+
export const InfoTipWithReactNodeLabel: Story = {
|
|
155
|
+
render: () => (
|
|
156
|
+
<ConnectedForm
|
|
157
|
+
defaultValues={{ username: '', password: '', apiKey: '' }}
|
|
158
|
+
onSubmit={(values) => action('Form Submitted')(values)}
|
|
159
|
+
>
|
|
160
|
+
<Text as="h3" id="api-section-heading" mb={8}>
|
|
161
|
+
API Configuration
|
|
162
|
+
</Text>
|
|
163
|
+
<ConnectedFormGroup
|
|
164
|
+
field={{ component: ConnectedInput }}
|
|
165
|
+
infotip={{
|
|
166
|
+
alignment: 'top-left',
|
|
167
|
+
info: 'Choose a unique username between 3-20 characters.',
|
|
168
|
+
labelledByFieldLabel: true,
|
|
169
|
+
}}
|
|
170
|
+
label={
|
|
171
|
+
<Text as="span" fontWeight="bold">
|
|
172
|
+
Username (labelledByFieldLabel)
|
|
173
|
+
</Text>
|
|
174
|
+
}
|
|
175
|
+
name="username"
|
|
176
|
+
/>
|
|
177
|
+
<ConnectedFormGroup
|
|
178
|
+
field={{ component: ConnectedInput, type: 'password' }}
|
|
179
|
+
infotip={{
|
|
180
|
+
info: 'Password must be at least 8 characters with one uppercase letter and one number.',
|
|
181
|
+
ariaLabel: 'Password requirements',
|
|
182
|
+
}}
|
|
183
|
+
label={
|
|
184
|
+
<Text as="span" fontWeight="bold">
|
|
185
|
+
Password (ariaLabel)
|
|
186
|
+
</Text>
|
|
187
|
+
}
|
|
188
|
+
name="password"
|
|
189
|
+
/>
|
|
190
|
+
<ConnectedFormGroup
|
|
191
|
+
field={{ component: ConnectedInput }}
|
|
192
|
+
infotip={{
|
|
193
|
+
info: 'You can find your API key in the developer settings dashboard.',
|
|
194
|
+
ariaLabelledby: 'api-section-heading',
|
|
195
|
+
}}
|
|
196
|
+
label={
|
|
197
|
+
<Text as="span" fontWeight="bold">
|
|
198
|
+
API Key (ariaLabelledby)
|
|
199
|
+
</Text>
|
|
200
|
+
}
|
|
201
|
+
name="apiKey"
|
|
202
|
+
/>
|
|
203
|
+
</ConnectedForm>
|
|
204
|
+
),
|
|
205
|
+
};
|
|
@@ -95,3 +95,23 @@ Hidden inputs can be used to include data that users can't see or modify with th
|
|
|
95
95
|
We call it a "sweet container" so that bots do not immediately detect it as a honeypot input.
|
|
96
96
|
|
|
97
97
|
<Canvas of={FieldsStories.SweetContainer} />
|
|
98
|
+
|
|
99
|
+
## InfoTip
|
|
100
|
+
|
|
101
|
+
Any field can include an `infotip` prop to provide additional context to users.
|
|
102
|
+
|
|
103
|
+
### Automatic labelling
|
|
104
|
+
|
|
105
|
+
InfoTip buttons are automatically labelled by string field labels for accessibility.
|
|
106
|
+
|
|
107
|
+
<Canvas of={FieldsStories.InfoTipAutoLabelling} />
|
|
108
|
+
|
|
109
|
+
### ReactNode labels
|
|
110
|
+
|
|
111
|
+
For ReactNode labels, you have three options:
|
|
112
|
+
|
|
113
|
+
- `labelledByFieldLabel: true` - opt into automatic labelling by the field label
|
|
114
|
+
- `ariaLabel` - provide a custom accessible name
|
|
115
|
+
- `ariaLabelledby` - reference another element on the page, such as a section heading
|
|
116
|
+
|
|
117
|
+
<Canvas of={FieldsStories.InfoTipWithReactNodeLabel} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FormGroup, GridForm, Input } from '@codecademy/gamut';
|
|
1
|
+
import { FormGroup, GridForm, Input, Text } from '@codecademy/gamut';
|
|
2
2
|
import { action } from '@storybook/addon-actions';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
4
|
|
|
@@ -328,3 +328,75 @@ export const SweetContainer: Story = {
|
|
|
328
328
|
],
|
|
329
329
|
},
|
|
330
330
|
};
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* InfoTip buttons are automatically labelled by string field labels for accessibility.
|
|
334
|
+
* Screen readers will announce "Field Label, button" when focusing the InfoTip.
|
|
335
|
+
*/
|
|
336
|
+
export const InfoTipAutoLabelling: Story = {
|
|
337
|
+
args: {
|
|
338
|
+
fields: [
|
|
339
|
+
{
|
|
340
|
+
label: 'Email address',
|
|
341
|
+
name: 'email',
|
|
342
|
+
size: 9,
|
|
343
|
+
type: 'email',
|
|
344
|
+
infotip: {
|
|
345
|
+
info: 'We will never share your email with third parties.',
|
|
346
|
+
placement: 'floating',
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* For ReactNode labels, you have three options for accessible InfoTip labelling:
|
|
355
|
+
* - `labelledByFieldLabel: true` - uses the field label
|
|
356
|
+
* - `ariaLabel` - provides a custom accessible name
|
|
357
|
+
* - `ariaLabelledby` - references another element on the page
|
|
358
|
+
*/
|
|
359
|
+
export const InfoTipWithReactNodeLabel: Story = {
|
|
360
|
+
render: (args) => (
|
|
361
|
+
<>
|
|
362
|
+
<Text as="h3" id="api-section-heading" mb={8}>
|
|
363
|
+
API Configuration
|
|
364
|
+
</Text>
|
|
365
|
+
<GridForm {...args} />
|
|
366
|
+
</>
|
|
367
|
+
),
|
|
368
|
+
args: {
|
|
369
|
+
fields: [
|
|
370
|
+
{
|
|
371
|
+
label: <strong>Username (labelledByFieldLabel)</strong>,
|
|
372
|
+
name: 'username',
|
|
373
|
+
size: 9,
|
|
374
|
+
type: 'text',
|
|
375
|
+
infotip: {
|
|
376
|
+
info: 'Choose a unique username between 3-20 characters.',
|
|
377
|
+
labelledByFieldLabel: true,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
label: <strong>Password (ariaLabel)</strong>,
|
|
382
|
+
name: 'password',
|
|
383
|
+
size: 9,
|
|
384
|
+
type: 'password',
|
|
385
|
+
infotip: {
|
|
386
|
+
info: 'Password must be at least 8 characters with one uppercase letter and one number.',
|
|
387
|
+
ariaLabel: 'Password requirements',
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
label: <strong>API Key (ariaLabelledby)</strong>,
|
|
392
|
+
name: 'apiKey',
|
|
393
|
+
size: 9,
|
|
394
|
+
type: 'text',
|
|
395
|
+
infotip: {
|
|
396
|
+
info: 'You can find your API key in the developer settings dashboard.',
|
|
397
|
+
ariaLabelledby: 'api-section-heading',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
};
|
|
@@ -34,7 +34,7 @@ Solo field form should always have their solo input be required. They should aut
|
|
|
34
34
|
|
|
35
35
|
## InfoTips
|
|
36
36
|
|
|
37
|
-
A field can include our existing `InfoTip`. See the <LinkTo id="Molecules/Tips/InfoTip">InfoTip</LinkTo> story for more information on what props are available.
|
|
37
|
+
A field can include our existing `InfoTip`. See the <LinkTo id="Molecules/Tips/InfoTip">Fields</LinkTo> story for specific accessibility tooling and <LinkTo id="Molecules/Tips/InfoTip">InfoTip</LinkTo> story for more information on what props are available.
|
|
38
38
|
|
|
39
39
|
See the <LinkTo id="Atoms/FormInputs/Radio">Radio</LinkTo> story for an example of how to add a infotip to a radio option.
|
|
40
40
|
|