@codecademy/styleguide 78.5.6-alpha.a75de2.0 → 78.5.6-alpha.afe04a.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/.storybook/components/Elements/DocsContainer.tsx +4 -0
- package/.storybook/components/Elements/Markdown.tsx +0 -1
- package/.storybook/preview.ts +14 -0
- package/.storybook/theming/GamutThemeProvider.tsx +7 -1
- package/CHANGELOG.md +1 -1
- package/package.json +2 -2
- package/src/lib/Atoms/FormElements/FormGroup/FormGroup.mdx +0 -6
- package/src/lib/Atoms/FormElements/FormGroup/FormGroup.stories.tsx +0 -11
- package/src/lib/Foundations/System/About.mdx +1 -1
- package/src/lib/Foundations/System/Props/About.mdx +81 -0
- package/src/lib/Foundations/System/Props/Background.mdx +30 -0
- package/src/lib/Foundations/System/Props/Border.mdx +33 -0
- package/src/lib/Foundations/System/Props/Color.mdx +28 -0
- package/src/lib/Foundations/System/Props/Flex.mdx +28 -0
- package/src/lib/Foundations/System/Props/Grid.mdx +31 -0
- package/src/lib/Foundations/System/Props/Layout.mdx +34 -0
- package/src/lib/Foundations/System/Props/List.mdx +38 -0
- package/src/lib/Foundations/System/Props/Positioning.mdx +29 -0
- package/src/lib/Foundations/System/Props/Shadow.mdx +31 -0
- package/src/lib/Foundations/System/Props/Space.mdx +44 -0
- package/src/lib/Foundations/System/Props/Space.stories.tsx +48 -0
- package/src/lib/Foundations/System/Props/Typography.mdx +28 -0
- package/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.mdx +3 -3
- package/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.stories.tsx +1 -0
- package/src/lib/Foundations/shared/elements.tsx +69 -19
- package/src/lib/Meta/About.mdx +3 -1
- package/src/lib/Meta/Logical and physical CSS properties.mdx +123 -0
- package/src/lib/Meta/Usage Guide.mdx +6 -1
- package/src/lib/Molecules/Tips/InfoTip/InfoTip.mdx +8 -24
- package/src/lib/Molecules/Tips/InfoTip/InfoTip.stories.tsx +34 -86
- package/src/lib/Organisms/ConnectedForm/ConnectedFormGroup/ConnectedFormGroup.mdx +0 -20
- package/src/lib/Organisms/ConnectedForm/ConnectedFormGroup/ConnectedFormGroup.stories.tsx +0 -84
- package/src/lib/Organisms/GridForm/Fields.mdx +0 -20
- package/src/lib/Organisms/GridForm/Fields.stories.tsx +1 -73
- package/src/lib/Organisms/GridForm/Layout.mdx +1 -1
- package/src/static/meta/toolbar.png +0 -0
- package/src/lib/Foundations/System/Props.mdx +0 -230
|
@@ -6,17 +6,17 @@ import { breakpoint } from '../../shared/elements';
|
|
|
6
6
|
import * as ResponsivePropertiesStories from './ResponsiveProperties.stories';
|
|
7
7
|
|
|
8
8
|
export const parameters = {
|
|
9
|
-
title: 'Responsive
|
|
9
|
+
title: 'Responsive properties',
|
|
10
10
|
subtitle:
|
|
11
11
|
'All system props accept a syntax to generate responsive styles on a per prop basis',
|
|
12
12
|
source: {
|
|
13
13
|
repo: 'variance',
|
|
14
14
|
githubLink:
|
|
15
|
-
'https://github.com/Codecademy/gamut/blob/
|
|
15
|
+
'https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variables/responsive.ts',
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
<Meta
|
|
19
|
+
<Meta of={ResponsivePropertiesStories} />
|
|
20
20
|
|
|
21
21
|
<AboutHeader {...parameters} />
|
|
22
22
|
|
|
@@ -2,22 +2,32 @@ import { Anchor, Box } from '@codecademy/gamut';
|
|
|
2
2
|
import {
|
|
3
3
|
Background,
|
|
4
4
|
coreSwatches,
|
|
5
|
+
css,
|
|
5
6
|
lxStudioColors,
|
|
6
7
|
theme,
|
|
7
8
|
trueColors,
|
|
8
9
|
} from '@codecademy/gamut-styles';
|
|
9
10
|
// eslint-disable-next-line gamut/import-paths
|
|
10
11
|
import * as ALL_PROPS from '@codecademy/gamut-styles/src/variance/config';
|
|
12
|
+
import { useTheme } from '@emotion/react';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
11
14
|
import kebabCase from 'lodash/kebabCase';
|
|
12
15
|
|
|
13
16
|
import { Code, ColorScale, LinkTo, TokenTable } from '~styleguide/blocks';
|
|
14
17
|
|
|
15
18
|
import { applyCorrectNotation } from './applyCorrectNotation';
|
|
16
19
|
|
|
20
|
+
const AnchorCode = styled(Code)(
|
|
21
|
+
css({
|
|
22
|
+
textDecoration: 'underline',
|
|
23
|
+
mx: 4,
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
|
|
17
27
|
export const PROP_COLUMN = {
|
|
18
28
|
key: 'key',
|
|
19
29
|
name: 'Prop',
|
|
20
|
-
size: '
|
|
30
|
+
size: 'lg',
|
|
21
31
|
render: ({ id }: any) => <Code>{id}</Code>,
|
|
22
32
|
};
|
|
23
33
|
|
|
@@ -403,26 +413,61 @@ export const DarkModeTable = () => (
|
|
|
403
413
|
);
|
|
404
414
|
/* eslint-disable gamut/import-paths */
|
|
405
415
|
|
|
416
|
+
const PropertiesRenderer = ({
|
|
417
|
+
property,
|
|
418
|
+
properties,
|
|
419
|
+
resolveProperty,
|
|
420
|
+
}: {
|
|
421
|
+
property: string | { physical: string; logical: string };
|
|
422
|
+
properties?: string[] | { physical: string[]; logical: string[] };
|
|
423
|
+
resolveProperty?: (useLogicalProperties: boolean) => 'logical' | 'physical';
|
|
424
|
+
}) => {
|
|
425
|
+
const currentTheme = useTheme() as { useLogicalProperties?: boolean };
|
|
426
|
+
const useLogicalProperties = currentTheme?.useLogicalProperties ?? true;
|
|
427
|
+
|
|
428
|
+
const mode = resolveProperty
|
|
429
|
+
? resolveProperty(useLogicalProperties)
|
|
430
|
+
: 'physical';
|
|
431
|
+
|
|
432
|
+
const resolvedProperty =
|
|
433
|
+
typeof property === 'string' ? property : property[mode];
|
|
434
|
+
|
|
435
|
+
let resolvedProperties: string[];
|
|
436
|
+
if (!properties) {
|
|
437
|
+
resolvedProperties = [resolvedProperty];
|
|
438
|
+
} else if (Array.isArray(properties)) {
|
|
439
|
+
resolvedProperties = properties;
|
|
440
|
+
} else {
|
|
441
|
+
resolvedProperties = properties[mode];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<>
|
|
446
|
+
{resolvedProperties.map((prop) => (
|
|
447
|
+
<Anchor
|
|
448
|
+
href={`https://developer.mozilla.org/en-US/docs/Web/CSS/${kebabCase(
|
|
449
|
+
prop
|
|
450
|
+
)}`}
|
|
451
|
+
key={prop}
|
|
452
|
+
rel=""
|
|
453
|
+
target="_blank"
|
|
454
|
+
>
|
|
455
|
+
<AnchorCode>{kebabCase(prop)}</AnchorCode>
|
|
456
|
+
</Anchor>
|
|
457
|
+
))}
|
|
458
|
+
</>
|
|
459
|
+
);
|
|
460
|
+
};
|
|
461
|
+
|
|
406
462
|
const PROPERTIES_COLUMN = {
|
|
407
463
|
key: 'properties',
|
|
408
464
|
name: 'Properties',
|
|
409
465
|
size: 'xl',
|
|
410
|
-
render: ({
|
|
411
|
-
property
|
|
412
|
-
properties
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
properties: string[];
|
|
416
|
-
}) =>
|
|
417
|
-
properties.map((property) => (
|
|
418
|
-
<Anchor
|
|
419
|
-
href={`https://developer.mozilla.org/en-US/docs/Web/CSS/${property}`}
|
|
420
|
-
rel=""
|
|
421
|
-
target="_blank"
|
|
422
|
-
>
|
|
423
|
-
<Code key={property}>{kebabCase(property)}</Code>
|
|
424
|
-
</Anchor>
|
|
425
|
-
)),
|
|
466
|
+
render: (props: {
|
|
467
|
+
property: string | { physical: string; logical: string };
|
|
468
|
+
properties?: string[] | { physical: string[]; logical: string[] };
|
|
469
|
+
resolveProperty?: (useLogicalProperties: boolean) => 'logical' | 'physical';
|
|
470
|
+
}) => <PropertiesRenderer {...props} />,
|
|
426
471
|
};
|
|
427
472
|
|
|
428
473
|
const SCALE_COLUMN = {
|
|
@@ -430,7 +475,7 @@ const SCALE_COLUMN = {
|
|
|
430
475
|
name: 'Scale',
|
|
431
476
|
size: 'lg',
|
|
432
477
|
render: ({ scale }: { scale: string }) => (
|
|
433
|
-
<LinkTo id=
|
|
478
|
+
<LinkTo id="Foundations/Theme/Core Theme">{scale}</LinkTo>
|
|
434
479
|
),
|
|
435
480
|
};
|
|
436
481
|
|
|
@@ -438,7 +483,12 @@ const TRANSFORM_COLUMN = {
|
|
|
438
483
|
key: 'transform',
|
|
439
484
|
name: 'Transform',
|
|
440
485
|
size: 'fill',
|
|
441
|
-
render: ({ transform }: any) =>
|
|
486
|
+
render: ({ transform, resolveProperty }: any) => (
|
|
487
|
+
<>
|
|
488
|
+
{transform && <Code>{transform?.name}</Code>}
|
|
489
|
+
{resolveProperty && <Code>{resolveProperty?.name}</Code>}
|
|
490
|
+
</>
|
|
491
|
+
),
|
|
442
492
|
};
|
|
443
493
|
|
|
444
494
|
export const defaultColumns = [
|
package/src/lib/Meta/About.mdx
CHANGED
|
@@ -13,6 +13,7 @@ import { parameters as deepControlsParameters } from './Deep Controls Add-On.mdx
|
|
|
13
13
|
import { parameters as eslintRulesParameters } from './ESLint rules.mdx';
|
|
14
14
|
import { parameters as faqsParameters } from './FAQs.mdx';
|
|
15
15
|
import { parameters as installationParameters } from './Installation.mdx';
|
|
16
|
+
import { parameters as logicalPhysicalParameters } from './Logical and physical CSS properties.mdx';
|
|
16
17
|
import { parameters as storiesParameters } from './Stories.mdx';
|
|
17
18
|
import { parameters as usageGuideParameters } from './Usage Guide.mdx';
|
|
18
19
|
|
|
@@ -34,9 +35,10 @@ export const parameters = {
|
|
|
34
35
|
deepControlsParameters,
|
|
35
36
|
eslintRulesParameters,
|
|
36
37
|
faqsParameters,
|
|
38
|
+
installationParameters,
|
|
39
|
+
logicalPhysicalParameters,
|
|
37
40
|
storiesParameters,
|
|
38
41
|
brandParameters,
|
|
39
|
-
installationParameters,
|
|
40
42
|
usageGuideParameters,
|
|
41
43
|
])}
|
|
42
44
|
/>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AboutHeader,
|
|
5
|
+
Callout,
|
|
6
|
+
Code,
|
|
7
|
+
ImageWrapper,
|
|
8
|
+
TokenTable,
|
|
9
|
+
} from '~styleguide/blocks';
|
|
10
|
+
|
|
11
|
+
export const parameters = {
|
|
12
|
+
id: 'Meta/Logical and physical CSS properties',
|
|
13
|
+
title: 'Logical and physical CSS properties',
|
|
14
|
+
subtitle:
|
|
15
|
+
'Understanding CSS logical and physical properties and how Gamut supports both modes.',
|
|
16
|
+
status: 'static',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
<Meta title="Meta/Logical and physical CSS properties" />
|
|
20
|
+
|
|
21
|
+
<AboutHeader {...parameters} />
|
|
22
|
+
|
|
23
|
+
## What are CSS logical properties?
|
|
24
|
+
|
|
25
|
+
CSS logical properties are a modern approach to styling that adapts to the writing mode and text direction of your content, rather than being tied to physical screen directions. More information can be found on[MDN: CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values)
|
|
26
|
+
|
|
27
|
+
### Physical Properties (Traditional)
|
|
28
|
+
|
|
29
|
+
Physical properties reference the physical dimensions of the viewport. For example:
|
|
30
|
+
|
|
31
|
+
- `margin-left`, `margin-right`, `margin-top`, `margin-bottom`
|
|
32
|
+
- `padding-left`, `padding-right`, `padding-top`, `padding-bottom`
|
|
33
|
+
|
|
34
|
+
These work well for left-to-right (LTR) languages but require manual overrides for right-to-left (RTL) languages like Arabic or Hebrew.
|
|
35
|
+
|
|
36
|
+
### Logical Properties (Modern)
|
|
37
|
+
|
|
38
|
+
Logical properties reference the flow of content:
|
|
39
|
+
|
|
40
|
+
- **Inline axis** (text direction): `margin-inline-start`, `margin-inline-end`
|
|
41
|
+
- **Block axis** (reading direction): `margin-block-start`, `margin-block-end`
|
|
42
|
+
|
|
43
|
+
## Using `useLogicalProperties` in Gamut
|
|
44
|
+
|
|
45
|
+
Gamut supports both physical and logical CSS properties through the `useLogicalProperties` prop on `GamutProvider`. This allows you to choose which mode your application uses. By default, `useLogicalProperties` is set to `true`, meaning Gamut will use logical CSS properties. If you want to use physical CSS properties, you have to set `useLogicalProperties` to `false`.
|
|
46
|
+
|
|
47
|
+
### Affected Props
|
|
48
|
+
|
|
49
|
+
Here are some examples of how physical and logical properties are affected by the `useLogicalProperties` prop:
|
|
50
|
+
|
|
51
|
+
<TokenTable
|
|
52
|
+
idKey="prop"
|
|
53
|
+
columns={[
|
|
54
|
+
{
|
|
55
|
+
key: 'prop',
|
|
56
|
+
name: 'Prop',
|
|
57
|
+
size: 'sm',
|
|
58
|
+
render: ({ prop }) => <Code>{prop}</Code>,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: 'physical',
|
|
62
|
+
name: 'Physical',
|
|
63
|
+
size: 'xl',
|
|
64
|
+
render: ({ physical }) =>
|
|
65
|
+
physical.map((p) => (
|
|
66
|
+
<>
|
|
67
|
+
<Code key={p}>{p}</Code>{' '}
|
|
68
|
+
</>
|
|
69
|
+
)),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'logical',
|
|
73
|
+
name: 'Logical',
|
|
74
|
+
size: 'xl',
|
|
75
|
+
render: ({ logical }) =>
|
|
76
|
+
logical.map((l) => (
|
|
77
|
+
<>
|
|
78
|
+
<Code key={l}>{l}</Code>{' '}
|
|
79
|
+
</>
|
|
80
|
+
)),
|
|
81
|
+
},
|
|
82
|
+
]}
|
|
83
|
+
rows={[
|
|
84
|
+
{
|
|
85
|
+
prop: 'mx',
|
|
86
|
+
physical: ['margin-left', 'margin-right'],
|
|
87
|
+
logical: ['margin-inline-start', 'margin-inline-end'],
|
|
88
|
+
},
|
|
89
|
+
{ prop: 'mt', physical: ['margin-top'], logical: ['margin-block-start'] },
|
|
90
|
+
{
|
|
91
|
+
prop: 'py',
|
|
92
|
+
physical: ['padding-top', 'padding-bottom'],
|
|
93
|
+
logical: ['padding-block-start', 'padding-block-end'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
prop: 'pb',
|
|
97
|
+
physical: ['padding-bottom'],
|
|
98
|
+
logical: ['padding-block-end'],
|
|
99
|
+
},
|
|
100
|
+
]}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
<Callout
|
|
104
|
+
text={
|
|
105
|
+
<>
|
|
106
|
+
Props like <code>m</code> and <code>p</code> (which set all four sides at
|
|
107
|
+
once) are not affected by this setting, as the CSS <code>margin</code> and{' '}
|
|
108
|
+
<code>padding</code> shorthands work identically in both modes.
|
|
109
|
+
</>
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
## Previewing in Storybook
|
|
114
|
+
|
|
115
|
+
You can toggle between logical and physical properties in Storybook using the **LogicalProps** toolbar button:
|
|
116
|
+
|
|
117
|
+
<ImageWrapper
|
|
118
|
+
src="./meta/toolbar.png"
|
|
119
|
+
alt="The Storybook toolbar with the LogicalProps toggle highlighted."
|
|
120
|
+
height="auto"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
This allows you to preview how components render with either property mode without changing any code.
|
|
@@ -39,7 +39,8 @@ On each page is a toolbar located on the top:
|
|
|
39
39
|
1. Grid (the 2x2 collection of squares) - applies a grid to the preview
|
|
40
40
|
2. Color mode selector (the circle icon) - toggles between light and dark mode for rendered code examples
|
|
41
41
|
3. Theme switcher (the paintbrush icon) - switches between different design system themes (Core, Admin, LX Studio, Percipio)
|
|
42
|
-
4.
|
|
42
|
+
4. LogicalProps (the two arrows icon) - toggles between physical and logical CSS properties
|
|
43
|
+
5. Outline (dotted square) - applies outlines to elements in the rendered code examples
|
|
43
44
|
|
|
44
45
|
### Theme Switcher
|
|
45
46
|
|
|
@@ -58,6 +59,10 @@ Available themes:
|
|
|
58
59
|
|
|
59
60
|
The theme switcher works in combination with the color mode selector, so you can test both light and dark variants of each theme.
|
|
60
61
|
|
|
62
|
+
### LogicalProps
|
|
63
|
+
|
|
64
|
+
The LogicalProps button (two arrows icon) provides a menu to select between Logical and Physical CSS properties.
|
|
65
|
+
|
|
61
66
|
### Showing code
|
|
62
67
|
|
|
63
68
|
On the bottom right of each canvas (a rendered code example) is a button to show its code:
|
|
@@ -28,7 +28,8 @@ export const parameters = {
|
|
|
28
28
|
A tip is triggered by clicking on an information icon button and can be closed by clicking outside, pressing <KeyboardKey>Esc</KeyboardKey>, or clicking the info button again.
|
|
29
29
|
|
|
30
30
|
Use an infotip to provide additional info about a nearby element or content.
|
|
31
|
-
|
|
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.
|
|
32
33
|
|
|
33
34
|
## Variants
|
|
34
35
|
|
|
@@ -56,19 +57,17 @@ This `floating` variant should only be used as needed.
|
|
|
56
57
|
|
|
57
58
|
### InfoTips with links or buttons
|
|
58
59
|
|
|
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.
|
|
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. If an infotip _absolutely requires_ a link or button, it needs to provide a programmatic focus by way of the `onClick` prop. The `onClick` prop accepts a function that can accept an `{isTipHidden}` argument and using that you can set programmatic focus as needed.
|
|
60
61
|
|
|
61
62
|
<Canvas of={InfoTipStories.WithLinksOrButtons} />
|
|
62
63
|
|
|
63
|
-
###
|
|
64
|
+
### Floating placement
|
|
64
65
|
|
|
65
|
-
InfoTips
|
|
66
|
+
When using `placement="floating"`, InfoTips implements focus management for easier navigation:
|
|
66
67
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
- **<KeyboardKey>
|
|
70
|
-
- **<KeyboardKey>Tab</KeyboardKey> or <KeyboardKey>Shift</KeyboardKey> +<KeyboardKey>Tab</KeyboardKey> (Inline)**: Follows normal document flow
|
|
71
|
-
- **<KeyboardKey>Escape</KeyboardKey>**: Closes the tip and returns focus to the InfoTip button
|
|
68
|
+
- **<KeyboardKey>Tab</KeyboardKey>**: Navigate forward through focusable elements (links, buttons) inside the tip. When reaching the last element, wraps back to the InfoTip button for convenience
|
|
69
|
+
- **<KeyboardKey>Shift</KeyboardKey>+<KeyboardKey>Tab</KeyboardKey>**: Navigate backward naturally through the page
|
|
70
|
+
- **<KeyboardKey>Esc</KeyboardKey>**: Closes the tip and returns focus to the InfoTip button
|
|
72
71
|
|
|
73
72
|
<Canvas of={InfoTipStories.KeyboardNavigation} />
|
|
74
73
|
|
|
@@ -83,21 +82,6 @@ InfoTips have intelligent Escape key handling that works correctly both inside a
|
|
|
83
82
|
|
|
84
83
|
<Canvas of={InfoTipStories.InfoTipInsideModal} />
|
|
85
84
|
|
|
86
|
-
## Custom Accessible Labeling
|
|
87
|
-
|
|
88
|
-
Provide either `ariaLabel` or `ariaLabelledby` to ensure screen reader users understand the purpose of the InfoTip button.
|
|
89
|
-
|
|
90
|
-
The InfoTip button's accessible label can be customized using either prop:
|
|
91
|
-
|
|
92
|
-
- **`ariaLabel`**: Directly sets the accessible label text. Useful when you want to provide a custom label without referencing another element.
|
|
93
|
-
- **`ariaLabelledby`**: References the ID of another element to use as the label. Useful when you want the InfoTip button to be labeled by visible text elsewhere on the page. This is useful for when the `InfoTip` is beside text that contextualizes it.
|
|
94
|
-
|
|
95
|
-
### Custom Role Description
|
|
96
|
-
|
|
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.
|
|
98
|
-
|
|
99
|
-
<Canvas of={InfoTipStories.AriaLabel} />
|
|
100
|
-
|
|
101
85
|
## InfoTips and zIndex
|
|
102
86
|
|
|
103
87
|
You can change the zIndex of your `InfoTip` with the zIndex property.
|
|
@@ -4,20 +4,17 @@ import {
|
|
|
4
4
|
FillButton,
|
|
5
5
|
FlexBox,
|
|
6
6
|
GridBox,
|
|
7
|
-
IconButton,
|
|
8
7
|
InfoTip,
|
|
9
8
|
Modal,
|
|
10
9
|
Text,
|
|
11
10
|
} from '@codecademy/gamut';
|
|
12
|
-
import { SparkleIcon } from '@codecademy/gamut-icons';
|
|
13
11
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
14
|
-
import { useState } from 'react';
|
|
12
|
+
import { useRef, useState } from 'react';
|
|
15
13
|
|
|
16
14
|
const meta: Meta<typeof InfoTip> = {
|
|
17
15
|
component: InfoTip,
|
|
18
16
|
args: {
|
|
19
17
|
alignment: 'top-left',
|
|
20
|
-
ariaLabel: 'More information',
|
|
21
18
|
info: `I am additional information about a nearby element or content.`,
|
|
22
19
|
},
|
|
23
20
|
};
|
|
@@ -31,10 +28,7 @@ export const Emphasis: Story = {
|
|
|
31
28
|
},
|
|
32
29
|
render: (args) => (
|
|
33
30
|
<FlexBox center m={24} py={64}>
|
|
34
|
-
<Text
|
|
35
|
-
Some text that needs info
|
|
36
|
-
</Text>
|
|
37
|
-
<InfoTip {...args} ariaLabelledby="emphasis-text" />
|
|
31
|
+
<Text mr={4}>Some text that needs info</Text> <InfoTip {...args} />
|
|
38
32
|
</FlexBox>
|
|
39
33
|
),
|
|
40
34
|
};
|
|
@@ -44,15 +38,10 @@ export const Alignments: Story = {
|
|
|
44
38
|
<GridBox gap={24} gridTemplateColumns="1fr 1fr" ml={8} py={64}>
|
|
45
39
|
{(['top-right', 'top-left', 'bottom-right', 'bottom-left'] as const).map(
|
|
46
40
|
(alignment) => {
|
|
47
|
-
const labelId = `alignment-${alignment}`;
|
|
48
41
|
return (
|
|
49
42
|
<Box key={alignment}>
|
|
50
|
-
<Text
|
|
51
|
-
<InfoTip
|
|
52
|
-
{...args}
|
|
53
|
-
alignment={alignment}
|
|
54
|
-
ariaLabelledby={labelId}
|
|
55
|
-
/>
|
|
43
|
+
<Text>{alignment}</Text>
|
|
44
|
+
<InfoTip {...args} alignment={alignment} />
|
|
56
45
|
</Box>
|
|
57
46
|
);
|
|
58
47
|
}
|
|
@@ -67,51 +56,10 @@ export const Placement: Story = {
|
|
|
67
56
|
},
|
|
68
57
|
render: (args) => (
|
|
69
58
|
<FlexBox center>
|
|
70
|
-
<Text
|
|
59
|
+
<Text mr={4}>
|
|
71
60
|
This text is in a small space and needs floating placement
|
|
72
61
|
</Text>{' '}
|
|
73
|
-
<InfoTip {...args}
|
|
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>
|
|
62
|
+
<InfoTip {...args} />
|
|
115
63
|
</FlexBox>
|
|
116
64
|
),
|
|
117
65
|
};
|
|
@@ -121,16 +69,19 @@ export const WithLinksOrButtons: Story = {
|
|
|
121
69
|
placement: 'floating',
|
|
122
70
|
},
|
|
123
71
|
render: function WithLinksOrButtons(args) {
|
|
72
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
73
|
+
|
|
74
|
+
const onClick = ({ isTipHidden }: { isTipHidden: boolean }) => {
|
|
75
|
+
if (!isTipHidden) ref.current?.focus();
|
|
76
|
+
};
|
|
77
|
+
|
|
124
78
|
return (
|
|
125
79
|
<FlexBox center py={64}>
|
|
126
|
-
<Text
|
|
127
|
-
This text is in a small space and needs info
|
|
128
|
-
</Text>{' '}
|
|
80
|
+
<Text mr={4}>This text is in a small space and needs info </Text>{' '}
|
|
129
81
|
<InfoTip
|
|
130
82
|
{...args}
|
|
131
|
-
ariaLabelledby="links-text"
|
|
132
83
|
info={
|
|
133
|
-
<Text tabIndex={-1}>
|
|
84
|
+
<Text ref={ref} tabIndex={-1}>
|
|
134
85
|
Hey! Here is a{' '}
|
|
135
86
|
<Anchor href="https://giphy.com/search/nichijou">
|
|
136
87
|
cool link
|
|
@@ -142,6 +93,7 @@ export const WithLinksOrButtons: Story = {
|
|
|
142
93
|
that is also super important.
|
|
143
94
|
</Text>
|
|
144
95
|
}
|
|
96
|
+
onClick={onClick}
|
|
145
97
|
/>
|
|
146
98
|
</FlexBox>
|
|
147
99
|
);
|
|
@@ -150,35 +102,42 @@ export const WithLinksOrButtons: Story = {
|
|
|
150
102
|
|
|
151
103
|
export const KeyboardNavigation: Story = {
|
|
152
104
|
render: function KeyboardNavigation() {
|
|
105
|
+
const floatingRef = useRef<HTMLDivElement>(null);
|
|
106
|
+
const inlineRef = useRef<HTMLDivElement>(null);
|
|
107
|
+
|
|
153
108
|
const examples = [
|
|
154
109
|
{
|
|
155
110
|
title: 'Floating Placement',
|
|
156
111
|
placement: 'floating' as const,
|
|
112
|
+
ref: floatingRef,
|
|
157
113
|
links: ['Link 1', 'Link 2', 'Link 3'],
|
|
158
114
|
},
|
|
159
115
|
{
|
|
160
116
|
title: 'Inline Placement',
|
|
161
117
|
placement: 'inline' as const,
|
|
162
118
|
alignment: 'bottom-right' as const,
|
|
119
|
+
ref: inlineRef,
|
|
163
120
|
links: ['Link A', 'Link B'],
|
|
164
121
|
},
|
|
165
122
|
];
|
|
166
123
|
|
|
167
124
|
return (
|
|
168
|
-
<FlexBox center
|
|
125
|
+
<FlexBox center column gap={24} py={64}>
|
|
169
126
|
<GridBox gap={16} gridTemplateColumns="1fr 1fr">
|
|
170
|
-
{examples.map(({ title, placement, alignment, links }) => {
|
|
171
|
-
const
|
|
127
|
+
{examples.map(({ title, placement, alignment, ref, links }) => {
|
|
128
|
+
const onClick = ({ isTipHidden }: { isTipHidden: boolean }) => {
|
|
129
|
+
if (!isTipHidden) ref.current?.focus();
|
|
130
|
+
};
|
|
131
|
+
|
|
172
132
|
return (
|
|
173
133
|
<FlexBox gap={8} key={placement}>
|
|
174
|
-
<Text fontSize={16} fontWeight="bold"
|
|
134
|
+
<Text fontSize={16} fontWeight="bold">
|
|
175
135
|
{title}
|
|
176
136
|
</Text>
|
|
177
137
|
<InfoTip
|
|
178
138
|
alignment={alignment}
|
|
179
|
-
ariaLabelledby={labelId}
|
|
180
139
|
info={
|
|
181
|
-
<Text>
|
|
140
|
+
<Text ref={ref} tabIndex={-1}>
|
|
182
141
|
{links.map((label, idx) => (
|
|
183
142
|
<>
|
|
184
143
|
{idx > 0 && ', '}
|
|
@@ -191,6 +150,7 @@ export const KeyboardNavigation: Story = {
|
|
|
191
150
|
</Text>
|
|
192
151
|
}
|
|
193
152
|
placement={placement}
|
|
153
|
+
onClick={onClick}
|
|
194
154
|
/>
|
|
195
155
|
</FlexBox>
|
|
196
156
|
);
|
|
@@ -202,10 +162,6 @@ export const KeyboardNavigation: Story = {
|
|
|
202
162
|
Keyboard Navigation:
|
|
203
163
|
</Text>
|
|
204
164
|
<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>
|
|
209
165
|
<li>
|
|
210
166
|
<strong>Floating - Tab:</strong> Navigates forward through links,
|
|
211
167
|
then wraps to button (contained)
|
|
@@ -268,10 +224,8 @@ export const InfoTipInsideModal: Story = {
|
|
|
268
224
|
<Text>This modal contains an InfoTip below:</Text>
|
|
269
225
|
|
|
270
226
|
<FlexBox alignItems="center" gap={8}>
|
|
271
|
-
<Text
|
|
272
|
-
|
|
273
|
-
</Text>
|
|
274
|
-
<InfoTip {...args} ariaLabelledby="modal-infotip-text" />
|
|
227
|
+
<Text>Some text that needs explanation</Text>
|
|
228
|
+
<InfoTip {...args} />
|
|
275
229
|
</FlexBox>
|
|
276
230
|
|
|
277
231
|
<Text color="text-disabled" fontSize={14}>
|
|
@@ -299,14 +253,11 @@ export const ZIndex: Story = {
|
|
|
299
253
|
<Box bg="paleBlue" zIndex={3}>
|
|
300
254
|
I will not be behind the infotip, sad + unreadable
|
|
301
255
|
</Box>
|
|
302
|
-
<InfoTip
|
|
303
|
-
ariaLabel="z-index example without override"
|
|
304
|
-
info="I am inline, cool"
|
|
305
|
-
/>
|
|
256
|
+
<InfoTip info="I am inline, cool" />
|
|
306
257
|
<Box bg="paleBlue" zIndex={3}>
|
|
307
258
|
I will be behind the infotip, nice + great
|
|
308
259
|
</Box>
|
|
309
|
-
<InfoTip {...args}
|
|
260
|
+
<InfoTip {...args} />
|
|
310
261
|
</FlexBox>
|
|
311
262
|
),
|
|
312
263
|
};
|
|
@@ -314,10 +265,7 @@ export const ZIndex: Story = {
|
|
|
314
265
|
export const Default: Story = {
|
|
315
266
|
render: (args) => (
|
|
316
267
|
<FlexBox center m={24} py={64}>
|
|
317
|
-
<Text
|
|
318
|
-
Some text that needs info
|
|
319
|
-
</Text>
|
|
320
|
-
<InfoTip {...args} ariaLabelledby="default-text" />
|
|
268
|
+
<Text mr={4}>Some text that needs info</Text> <InfoTip {...args} />
|
|
321
269
|
</FlexBox>
|
|
322
270
|
),
|
|
323
271
|
};
|
|
@@ -61,26 +61,6 @@ 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
|
-
|
|
84
64
|
## Playground
|
|
85
65
|
|
|
86
66
|
To see how a `ConnectedFormGroup` can be used in a `ConnectedForm`, check out the <LinkTo id="Organisms/ConnectedForm/ConnectedForm">ConnectedForm</LinkTo> page.
|