@lumx/react 3.1.5 → 3.2.1-alpha.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/_internal/types.d.ts +16 -5
- package/index.d.ts +45 -4
- package/index.js +632 -423
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/alert-dialog/AlertDialog.stories.tsx +96 -165
- package/src/components/alert-dialog/AlertDialog.test.tsx +15 -23
- package/src/components/avatar/Avatar.stories.tsx +91 -62
- package/src/components/avatar/Avatar.test.tsx +9 -24
- package/src/components/badge/Badge.stories.tsx +63 -38
- package/src/components/button/Button.stories.tsx +147 -139
- package/src/components/button/IconButton.stories.tsx +45 -0
- package/src/components/checkbox/Checkbox.stories.tsx +37 -30
- package/src/components/chip/Chip.stories.tsx +77 -15
- package/src/components/comment-block/CommentBlock.stories.tsx +90 -25
- package/src/components/comment-block/CommentBlock.test.tsx +12 -17
- package/src/components/date-picker/DatePickerField.stories.tsx +52 -83
- package/src/components/dialog/Dialog.stories.tsx +131 -293
- package/src/components/dialog/Dialog.test.tsx +0 -32
- package/src/components/dropdown/Dropdown.stories.tsx +1 -186
- package/src/components/flag/Flag.stories.tsx +33 -18
- package/src/components/flag/Flag.test.tsx +1 -8
- package/src/components/flex-box/FlexBox.stories.tsx +151 -238
- package/src/components/flex-box/FlexBox.test.tsx +9 -49
- package/src/components/generic-block/GenericBlock.stories.jsx +1 -1
- package/src/components/grid-column/GridColumn.stories.tsx +46 -0
- package/src/components/heading/Heading.stories.tsx +57 -95
- package/src/components/icon/Icon.stories.tsx +67 -70
- package/src/components/image-block/ImageBlock.stories.tsx +103 -47
- package/src/components/image-block/ImageBlock.test.tsx +12 -17
- package/src/components/inline-list/InlineList.stories.tsx +45 -29
- package/src/components/input-helper/InputHelper.stories.tsx +31 -25
- package/src/components/input-label/InputLabel.stories.tsx +33 -10
- package/src/components/lightbox/Lightbox.stories.tsx +39 -77
- package/src/components/lightbox/Lightbox.test.tsx +12 -17
- package/src/components/link/Link.stories.tsx +98 -128
- package/src/components/link-preview/LinkPreview.stories.tsx +48 -75
- package/src/components/list/List.stories.tsx +59 -84
- package/src/components/list/List.test.tsx +8 -17
- package/src/components/list/ListDivider.stories.tsx +9 -4
- package/src/components/list/ListDivider.test.tsx +12 -17
- package/src/components/list/ListItem.stories.tsx +97 -59
- package/src/components/list/ListItem.test.tsx +12 -17
- package/src/components/list/ListSubheader.stories.tsx +8 -5
- package/src/components/list/ListSubheader.test.tsx +12 -18
- package/src/components/message/Message.stories.tsx +51 -22
- package/src/components/mosaic/Mosaic.stories.tsx +78 -74
- package/src/components/mosaic/Mosaic.test.tsx +0 -31
- package/src/components/navigation/Navigation.stories.tsx +67 -0
- package/src/components/navigation/Navigation.test.tsx +58 -0
- package/src/components/navigation/Navigation.tsx +62 -0
- package/src/components/navigation/NavigationItem.test.tsx +37 -0
- package/src/components/navigation/NavigationItem.tsx +89 -0
- package/src/components/navigation/NavigationSection.test.tsx +126 -0
- package/src/components/navigation/NavigationSection.tsx +109 -0
- package/src/components/navigation/context.tsx +6 -0
- package/src/components/navigation/index.ts +1 -0
- package/src/components/notification/Notifications.stories.tsx +52 -47
- package/src/components/popover/Popover.stories.tsx +68 -201
- package/src/components/popover/Popover.tsx +7 -9
- package/src/components/popover-dialog/PopoverDialog.stories.tsx +26 -65
- package/src/components/post-block/PostBlock.test.tsx +12 -17
- package/src/components/progress/ProgressCircular.stories.tsx +24 -12
- package/src/components/progress/ProgressLinear.stories.tsx +6 -2
- package/src/components/radio-button/RadioButton.stories.tsx +35 -24
- package/src/components/select/Select.stories.tsx +19 -23
- package/src/components/select/SelectMultiple.stories.tsx +105 -2
- package/src/components/select/WithSelectContext.tsx +10 -4
- package/src/components/skeleton/SkeletonCircle.stories.tsx +37 -21
- package/src/components/skeleton/SkeletonCircle.test.tsx +12 -17
- package/src/components/skeleton/SkeletonRectangle.stories.tsx +74 -99
- package/src/components/skeleton/SkeletonRectangle.test.tsx +12 -17
- package/src/components/skeleton/SkeletonTypography.test.tsx +12 -17
- package/src/components/slider/Slider.stories.tsx +41 -25
- package/src/components/slider/Slider.test.tsx +12 -18
- package/src/components/slideshow/Slideshow.stories.tsx +31 -61
- package/src/components/slideshow/Slideshow.test.tsx +15 -23
- package/src/components/slideshow/SlideshowControls.stories.tsx +4 -6
- package/src/components/switch/Switch.stories.tsx +35 -32
- package/src/components/table/Table.test.tsx +12 -17
- package/src/components/tabs/Tabs.stories.tsx +4 -3
- package/src/components/text/Text.stories.tsx +130 -0
- package/src/components/text-field/TextField.stories.tsx +114 -148
- package/src/components/thumbnail/Thumbnail.stories.tsx +106 -255
- package/src/components/thumbnail/Thumbnail.test.tsx +12 -35
- package/src/components/tooltip/Tooltip.stories.tsx +51 -136
- package/src/components/user-block/UserBlock.stories.tsx +67 -56
- package/src/components/user-block/UserBlock.test.tsx +1 -5
- package/src/hooks/useFocusTrap.ts +2 -2
- package/src/index.ts +1 -0
- package/src/stories/controls/color.ts +6 -0
- package/src/stories/controls/element.ts +6 -0
- package/src/stories/controls/focusPoint.ts +1 -0
- package/src/stories/controls/icons.ts +6 -0
- package/src/stories/{knobs → controls}/image.ts +6 -16
- package/src/stories/controls/selectArgType.ts +4 -0
- package/src/stories/controls/theme.ts +3 -0
- package/src/stories/controls/typography.ts +5 -0
- package/src/stories/controls/withUndefined.ts +1 -0
- package/src/stories/decorators/withChromaticForceScreenSize.tsx +8 -0
- package/src/stories/decorators/withCombinations.tsx +99 -0
- package/src/stories/decorators/withNestedProps.tsx +23 -0
- package/src/stories/{withResizableBox.tsx → decorators/withResizableBox.tsx} +6 -10
- package/src/stories/decorators/withValueOnChange.tsx +18 -0
- package/src/stories/decorators/withWrapper.tsx +19 -0
- package/src/stories/utils/CustomLink.tsx +8 -2
- package/src/stories/{knobs → utils}/lorem.ts +9 -9
- package/src/testing/utils/commonTestsSuiteRTL.ts +2 -3
- package/src/testing/utils/index.ts +0 -2
- package/src/untypped-modules.d.ts +0 -2
- package/src/utils/MaterialThemeSwitcher/MaterialThemeSwitcher.tsx +1 -1
- package/src/utils/ThemeContext.ts +4 -0
- package/src/utils/forwardRefPolymorphic.ts +9 -0
- package/src/utils/type.ts +28 -4
- package/src/components/alert-dialog/__snapshots__/AlertDialog.test.tsx.snap +0 -558
- package/src/components/avatar/__snapshots__/Avatar.test.tsx.snap +0 -681
- package/src/components/comment-block/__snapshots__/CommentBlock.test.tsx.snap +0 -92
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +0 -1133
- package/src/components/expansion-panel/ExpansionPanel.stories.tsx +0 -65
- package/src/components/flag/__snapshots__/Flag.test.tsx.snap +0 -133
- package/src/components/flex-box/__snapshots__/FlexBox.test.tsx.snap +0 -492
- package/src/components/grid-column/GridColumn.stories.jsx +0 -56
- package/src/components/image-block/__snapshots__/ImageBlock.test.tsx.snap +0 -64
- package/src/components/lightbox/__snapshots__/Lightbox.test.tsx.snap +0 -194
- package/src/components/list/__snapshots__/List.test.tsx.snap +0 -360
- package/src/components/list/__snapshots__/ListDivider.test.tsx.snap +0 -7
- package/src/components/list/__snapshots__/ListItem.test.tsx.snap +0 -160
- package/src/components/list/__snapshots__/ListSubheader.test.tsx.snap +0 -9
- package/src/components/mosaic/__snapshots__/Mosaic.test.tsx.snap +0 -357
- package/src/components/post-block/__snapshots__/PostBlock.test.tsx.snap +0 -139
- package/src/components/skeleton/__snapshots__/SkeletonCircle.test.tsx.snap +0 -54
- package/src/components/skeleton/__snapshots__/SkeletonRectangle.test.tsx.snap +0 -177
- package/src/components/skeleton/__snapshots__/SkeletonTypography.test.tsx.snap +0 -174
- package/src/components/slider/__snapshots__/Slider.test.tsx.snap +0 -122
- package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +0 -157
- package/src/components/table/__snapshots__/Table.test.tsx.snap +0 -263
- package/src/components/text/Text.stories.jsx +0 -75
- package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +0 -130
- package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +0 -362
- package/src/stories/chromaticForceScreenSize.tsx +0 -7
- package/src/stories/knobs/buttonKnob.ts +0 -9
- package/src/stories/knobs/emphasisKnob.ts +0 -8
- package/src/stories/knobs/enumKnob.ts +0 -14
- package/src/stories/knobs/focusKnob.ts +0 -3
- package/src/stories/knobs/sizeKnob.ts +0 -5
- package/src/stories/knobs/thumbnailsKnob.ts +0 -9
- package/src/testing/utils/itShouldRenderStories.tsx +0 -103
|
@@ -1,85 +1,89 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { Alignment, ImageBlock, Lightbox, Slideshow, SlideshowItem, Theme } from '@lumx/react';
|
|
4
|
-
import { boolean, number } from '@storybook/addon-knobs';
|
|
5
|
-
import { thumbnailsKnob } from '@lumx/react/stories/knobs/thumbnailsKnob';
|
|
1
|
+
import { withWrapper } from '@lumx/react/stories/decorators/withWrapper';
|
|
2
|
+
import { IMAGES } from '@lumx/react/stories/controls/image';
|
|
6
3
|
import { Mosaic } from './Mosaic';
|
|
7
4
|
|
|
8
|
-
export default {
|
|
5
|
+
export default {
|
|
6
|
+
title: 'LumX components/mosaic/Mosaic',
|
|
7
|
+
component: Mosaic,
|
|
8
|
+
args: Mosaic.defaultProps,
|
|
9
|
+
argTypes: {},
|
|
10
|
+
decorators: [withWrapper({ style: { width: 250 } })],
|
|
11
|
+
};
|
|
9
12
|
|
|
10
|
-
export const OneThumbnail =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
export const OneThumbnail = {
|
|
14
|
+
args: {
|
|
15
|
+
thumbnails: [{ image: IMAGES.landscape1 }],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
15
18
|
|
|
16
|
-
export const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export const OneThumbnailClickable = {
|
|
20
|
+
...OneThumbnail,
|
|
21
|
+
argTypes: {
|
|
22
|
+
onImageClick: { action: true },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
21
25
|
|
|
22
|
-
export const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
export const TwoThumbnail = {
|
|
27
|
+
args: {
|
|
28
|
+
thumbnails: [...OneThumbnail.args.thumbnails, { image: IMAGES.landscape2 }],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
27
31
|
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
export const TwoThumbnailClickable = {
|
|
33
|
+
...TwoThumbnail,
|
|
34
|
+
argTypes: {
|
|
35
|
+
onImageClick: { action: true },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
33
38
|
|
|
34
|
-
export const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
export const ThreeThumbnail = {
|
|
40
|
+
args: {
|
|
41
|
+
thumbnails: [...TwoThumbnail.args.thumbnails, { image: IMAGES.landscape3 }],
|
|
42
|
+
},
|
|
43
|
+
};
|
|
39
44
|
|
|
40
|
-
export const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
setActiveIndex(undefined);
|
|
47
|
-
}, [setActiveIndex]);
|
|
45
|
+
export const ThreeThumbnailClickable = {
|
|
46
|
+
...ThreeThumbnail,
|
|
47
|
+
argTypes: {
|
|
48
|
+
onImageClick: { action: true },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
export const FourThumbnail = {
|
|
53
|
+
args: {
|
|
54
|
+
thumbnails: [...ThreeThumbnail.args.thumbnails, { image: IMAGES.portrait1 }],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const FourThumbnailClickable = {
|
|
59
|
+
...FourThumbnail,
|
|
60
|
+
argTypes: {
|
|
61
|
+
onImageClick: { action: true },
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const FiveThumbnail = {
|
|
66
|
+
args: {
|
|
67
|
+
thumbnails: [...FourThumbnail.args.thumbnails, { image: IMAGES.portrait2 }],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const FiveThumbnailClickable = {
|
|
72
|
+
...FiveThumbnail,
|
|
73
|
+
argTypes: {
|
|
74
|
+
onImageClick: { action: true },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const SixThumbnail = {
|
|
79
|
+
args: {
|
|
80
|
+
thumbnails: [...FiveThumbnail.args.thumbnails, { image: IMAGES.portrait3 }],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
52
83
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
closeButtonProps={{ label: 'Close' }}
|
|
59
|
-
>
|
|
60
|
-
<Slideshow
|
|
61
|
-
activeIndex={activeIndex}
|
|
62
|
-
slideshowControlsProps={{
|
|
63
|
-
nextButtonProps: { label: 'Next' },
|
|
64
|
-
previousButtonProps: { label: 'Previous' },
|
|
65
|
-
}}
|
|
66
|
-
fillHeight
|
|
67
|
-
theme={Theme.dark}
|
|
68
|
-
>
|
|
69
|
-
{thumbnails.map((thumbnail, index) => (
|
|
70
|
-
<SlideshowItem key={`${thumbnail.alt}-${thumbnail.image}-${index}`}>
|
|
71
|
-
<ImageBlock
|
|
72
|
-
alt={thumbnail.alt}
|
|
73
|
-
image={thumbnail.image}
|
|
74
|
-
theme={Theme.dark}
|
|
75
|
-
align={Alignment.center}
|
|
76
|
-
fillHeight
|
|
77
|
-
/>
|
|
78
|
-
</SlideshowItem>
|
|
79
|
-
))}
|
|
80
|
-
</Slideshow>
|
|
81
|
-
</Lightbox>
|
|
82
|
-
)}
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
84
|
+
export const SixThumbnailClickable = {
|
|
85
|
+
...SixThumbnail,
|
|
86
|
+
argTypes: {
|
|
87
|
+
onImageClick: { action: true },
|
|
88
|
+
},
|
|
85
89
|
};
|
|
@@ -6,7 +6,6 @@ import 'jest-enzyme';
|
|
|
6
6
|
|
|
7
7
|
import React, { ReactElement } from 'react';
|
|
8
8
|
import { Theme } from '..';
|
|
9
|
-
import * as stories from './Mosaic.stories';
|
|
10
9
|
|
|
11
10
|
const CLASSNAME = Mosaic.className as string;
|
|
12
11
|
|
|
@@ -17,9 +16,6 @@ jest.mock('@lumx/react/hooks/useIntersectionObserver', () => ({
|
|
|
17
16
|
|
|
18
17
|
type SetupProps = Partial<MosaicProps>;
|
|
19
18
|
|
|
20
|
-
/**
|
|
21
|
-
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
22
|
-
*/
|
|
23
19
|
const setup = (propsOverride: SetupProps = {}, shallowRendering = true) => {
|
|
24
20
|
const props: any = { ...propsOverride };
|
|
25
21
|
const renderer: (el: ReactElement) => Wrapper = shallowRendering ? shallow : mount;
|
|
@@ -33,22 +29,6 @@ const setup = (propsOverride: SetupProps = {}, shallowRendering = true) => {
|
|
|
33
29
|
};
|
|
34
30
|
|
|
35
31
|
describe(`<${Mosaic.displayName}>`, () => {
|
|
36
|
-
// 1. Test render via snapshot.
|
|
37
|
-
describe('Snapshots and structure', () => {
|
|
38
|
-
// Do snapshot render test on every stories.
|
|
39
|
-
for (const [storyName, Story] of Object.entries(stories)) {
|
|
40
|
-
if (typeof Story !== 'function') {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
it(`should render story ${storyName}`, () => {
|
|
45
|
-
const wrapper = shallow(<Story />);
|
|
46
|
-
expect(wrapper.find('Mosaic').dive()).toMatchSnapshot();
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// 2. Test defaultProps value and important props custom values.
|
|
52
32
|
describe('Props', () => {
|
|
53
33
|
it('should pass theme prop to Thumbnails', () => {
|
|
54
34
|
const expectedTheme = Theme.dark;
|
|
@@ -67,7 +47,6 @@ describe(`<${Mosaic.displayName}>`, () => {
|
|
|
67
47
|
});
|
|
68
48
|
});
|
|
69
49
|
|
|
70
|
-
// 3. Test events.
|
|
71
50
|
describe('Events', () => {
|
|
72
51
|
it('should keep Thumbnail onClick', () => {
|
|
73
52
|
const onClick = jest.fn();
|
|
@@ -105,16 +84,6 @@ describe(`<${Mosaic.displayName}>`, () => {
|
|
|
105
84
|
});
|
|
106
85
|
});
|
|
107
86
|
|
|
108
|
-
// 4. Test conditions (i.e. things that display or not in the UI based on props).
|
|
109
|
-
describe('Conditions', () => {
|
|
110
|
-
// Nothing to do here.
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// 5. Test state.
|
|
114
|
-
describe('State', () => {
|
|
115
|
-
// Nothing to do here.
|
|
116
|
-
});
|
|
117
|
-
|
|
118
87
|
// Common tests suite.
|
|
119
88
|
commonTestsSuite(setup, { className: 'wrapper' }, { className: CLASSNAME });
|
|
120
89
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
mdiHome,
|
|
5
|
+
mdiMessageTextOutline,
|
|
6
|
+
mdiFolderGoogleDrive,
|
|
7
|
+
mdiTextBox,
|
|
8
|
+
mdiLink,
|
|
9
|
+
mdiGoogleCirclesExtended,
|
|
10
|
+
mdiFolder,
|
|
11
|
+
} from '@lumx/icons';
|
|
12
|
+
import { Navigation, Orientation } from '@lumx/react';
|
|
13
|
+
import { CustomLink } from '@lumx/react/stories/utils/CustomLink';
|
|
14
|
+
|
|
15
|
+
export default { title: 'LumX components/navigation/Navigation' };
|
|
16
|
+
|
|
17
|
+
export const Default = ({ theme, onClick, orientation }: any) => {
|
|
18
|
+
return (
|
|
19
|
+
<Navigation theme={theme} aria-label="navigation" orientation={orientation}>
|
|
20
|
+
<Navigation.Item isCurrentPage label="Homepage" icon={mdiHome} href="#" />
|
|
21
|
+
<Navigation.Item
|
|
22
|
+
label="Some very very very very very very very very very very very very very very very very very very very very very very very very very very very long text"
|
|
23
|
+
href="#"
|
|
24
|
+
/>
|
|
25
|
+
<Navigation.Item
|
|
26
|
+
label="Custom link element"
|
|
27
|
+
icon={mdiMessageTextOutline}
|
|
28
|
+
as={CustomLink}
|
|
29
|
+
// `to` prop is required in CustomLink
|
|
30
|
+
to="#"
|
|
31
|
+
/>
|
|
32
|
+
<Navigation.Item as="button" label="Button element" icon={mdiFolderGoogleDrive} onClick={onClick} />
|
|
33
|
+
<Navigation.Section label="Section 1" icon={mdiFolder}>
|
|
34
|
+
<Navigation.Item label="A content" href="#content" />
|
|
35
|
+
<Navigation.Item label="A button" icon={mdiLink} href="https://www.google.com" />
|
|
36
|
+
<Navigation.Item
|
|
37
|
+
label="Some very very very very very very very very very very very very very very very very very very very very very very very very very very very long text"
|
|
38
|
+
icon={mdiTextBox}
|
|
39
|
+
href="#content"
|
|
40
|
+
/>
|
|
41
|
+
<Navigation.Item label="A community" icon={mdiGoogleCirclesExtended} href="#community" />
|
|
42
|
+
<Navigation.Section label="Section 1.1" icon={mdiFolder}>
|
|
43
|
+
<Navigation.Item label="A content" icon={mdiTextBox} href="#content" />
|
|
44
|
+
<Navigation.Item
|
|
45
|
+
label="Some very very very very very very very very very very very very very very very very very very very very very very very very very very very long text"
|
|
46
|
+
icon={mdiTextBox}
|
|
47
|
+
href="#content"
|
|
48
|
+
/>
|
|
49
|
+
<Navigation.Item label="A link" icon={mdiLink} href="https://www.google.com" />
|
|
50
|
+
<Navigation.Item label="A community" icon={mdiGoogleCirclesExtended} href="#community" />
|
|
51
|
+
</Navigation.Section>
|
|
52
|
+
</Navigation.Section>
|
|
53
|
+
<Navigation.Section label="Section 2" icon={mdiFolder}>
|
|
54
|
+
<Navigation.Item label="A content" icon={mdiTextBox} href="#content" />
|
|
55
|
+
<Navigation.Item label="A link" icon={mdiLink} href="https://www.google.com" />
|
|
56
|
+
<Navigation.Item label="A community" icon={mdiGoogleCirclesExtended} href="#community" />
|
|
57
|
+
</Navigation.Section>
|
|
58
|
+
</Navigation>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
Default.argTypes = { onClick: { action: true } };
|
|
62
|
+
|
|
63
|
+
export const VerticalWithSection: any = Default.bind({});
|
|
64
|
+
VerticalWithSection.args = { orientation: Orientation.vertical };
|
|
65
|
+
|
|
66
|
+
export const HorizontalWithSection: any = Default.bind({});
|
|
67
|
+
HorizontalWithSection.args = { orientation: Orientation.horizontal };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
4
|
+
import { render } from '@testing-library/react';
|
|
5
|
+
import { getByClassName } from '@lumx/react/testing/utils/queries';
|
|
6
|
+
import { Navigation, NavigationProps } from '.';
|
|
7
|
+
import { Orientation } from '..';
|
|
8
|
+
|
|
9
|
+
const CLASSNAME = Navigation.className as string;
|
|
10
|
+
|
|
11
|
+
type SetupProps = Partial<NavigationProps>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const setup = (propsOverride: SetupProps = {}) => {
|
|
18
|
+
const props = { 'aria-label': 'navigation', ...propsOverride } as any;
|
|
19
|
+
const { container } = render(
|
|
20
|
+
<Navigation {...props}>
|
|
21
|
+
<Navigation.Item label="A link" href="" />
|
|
22
|
+
<Navigation.Item label="A link" as="button" />
|
|
23
|
+
<Navigation.Item label="A link" href="" />
|
|
24
|
+
</Navigation>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
container,
|
|
29
|
+
element: getByClassName(container, CLASSNAME),
|
|
30
|
+
props,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe(`<${Navigation.displayName}>`, () => {
|
|
35
|
+
it('should render default', () => {
|
|
36
|
+
const { element } = setup();
|
|
37
|
+
expect(element).toBeInTheDocument();
|
|
38
|
+
expect(element).toHaveClass(CLASSNAME);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should render vertically by default', () => {
|
|
42
|
+
const { element } = setup();
|
|
43
|
+
expect(element).toHaveClass(`${CLASSNAME}--orientation-vertical`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should render vertically when orientation is set to vertical', () => {
|
|
47
|
+
const { element } = setup({ orientation: Orientation.vertical });
|
|
48
|
+
expect(element).toHaveClass(`${CLASSNAME}--orientation-vertical`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should render horizontally when orientation is set to horizontal', () => {
|
|
52
|
+
const { element } = setup({ orientation: Orientation.horizontal });
|
|
53
|
+
expect(element).toHaveClass(`${CLASSNAME}--orientation-horizontal`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Common tests suite.
|
|
57
|
+
commonTestsSuiteRTL(setup, { baseClassName: CLASSNAME, forwardClassName: 'element', forwardAttributes: 'element' });
|
|
58
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { forwardRef, ReactNode } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { HasAriaLabelOrLabelledBy, HasClassName, HasTheme } from '@lumx/react/utils/type';
|
|
4
|
+
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
5
|
+
import { Orientation, Theme } from '@lumx/react';
|
|
6
|
+
import { ThemeContext } from '@lumx/react/utils/ThemeContext';
|
|
7
|
+
import { NavigationSection } from './NavigationSection';
|
|
8
|
+
import { NavigationItem } from './NavigationItem';
|
|
9
|
+
import { NavigationContext } from './context';
|
|
10
|
+
|
|
11
|
+
export type NavigationProps = React.ComponentProps<'nav'> &
|
|
12
|
+
HasClassName &
|
|
13
|
+
HasTheme & {
|
|
14
|
+
/** Content of the navigation. These components should be of type NavigationItem to be rendered */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
orientation?: Orientation;
|
|
17
|
+
} & HasAriaLabelOrLabelledBy;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Component display name.
|
|
21
|
+
*/
|
|
22
|
+
const COMPONENT_NAME = 'Navigation';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Component default class name and class prefix.
|
|
26
|
+
*/
|
|
27
|
+
const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
28
|
+
|
|
29
|
+
export const Navigation = Object.assign(
|
|
30
|
+
// eslint-disable-next-line react/display-name
|
|
31
|
+
forwardRef<HTMLElement, NavigationProps>((props, ref) => {
|
|
32
|
+
const { children, className, theme, orientation, ...forwardedProps } = props;
|
|
33
|
+
return (
|
|
34
|
+
<ThemeContext.Provider value={theme}>
|
|
35
|
+
<nav
|
|
36
|
+
className={classNames(
|
|
37
|
+
className,
|
|
38
|
+
handleBasicClasses({
|
|
39
|
+
prefix: CLASSNAME,
|
|
40
|
+
theme,
|
|
41
|
+
orientation,
|
|
42
|
+
}),
|
|
43
|
+
)}
|
|
44
|
+
ref={ref}
|
|
45
|
+
{...forwardedProps}
|
|
46
|
+
>
|
|
47
|
+
<NavigationContext.Provider value={{ orientation }}>
|
|
48
|
+
<ul className={`${CLASSNAME}__list`}>{children}</ul>
|
|
49
|
+
</NavigationContext.Provider>
|
|
50
|
+
</nav>
|
|
51
|
+
</ThemeContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}),
|
|
54
|
+
{
|
|
55
|
+
displayName: COMPONENT_NAME,
|
|
56
|
+
className: CLASSNAME,
|
|
57
|
+
defaultProps: { theme: Theme.light, orientation: Orientation.vertical },
|
|
58
|
+
// Sub components
|
|
59
|
+
Section: NavigationSection,
|
|
60
|
+
Item: NavigationItem,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
4
|
+
import { render } from '@testing-library/react';
|
|
5
|
+
import { getByClassName } from '@lumx/react/testing/utils/queries';
|
|
6
|
+
import { NavigationItem, NavigationItemProps } from './NavigationItem';
|
|
7
|
+
|
|
8
|
+
const CLASSNAME = NavigationItem.className as string;
|
|
9
|
+
|
|
10
|
+
type SetupProps = Partial<NavigationItemProps>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const setup = (propsOverride: SetupProps = {}) => {
|
|
17
|
+
const props = { ...propsOverride };
|
|
18
|
+
const { container } = render(<NavigationItem label="A link" href="" {...props} />);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
container,
|
|
22
|
+
element: getByClassName(container, CLASSNAME),
|
|
23
|
+
link: getByClassName(container, `${CLASSNAME}__link`),
|
|
24
|
+
props,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe(`<${NavigationItem.displayName}>`, () => {
|
|
29
|
+
it('should render default', () => {
|
|
30
|
+
const { element } = setup();
|
|
31
|
+
expect(element).toBeInTheDocument();
|
|
32
|
+
expect(element).toHaveClass(CLASSNAME);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Common tests suite.
|
|
36
|
+
commonTestsSuiteRTL(setup, { baseClassName: CLASSNAME, forwardClassName: 'element', forwardAttributes: 'link' });
|
|
37
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { ElementType, ReactNode, useState, useContext } from 'react';
|
|
2
|
+
import { Icon, Placement, Size, Tooltip, Text } from '@lumx/react';
|
|
3
|
+
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
4
|
+
import { ComponentRef, HasClassName, HasPolymorphicAs, HasTheme } from '@lumx/react/utils/type';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
import { forwardRefPolymorphic } from '@lumx/react/utils/forwardRefPolymorphic';
|
|
7
|
+
import { ThemeContext } from '@lumx/react/utils/ThemeContext';
|
|
8
|
+
|
|
9
|
+
type BaseNavigationItemProps = {
|
|
10
|
+
/* Icon (SVG path). */
|
|
11
|
+
icon?: string;
|
|
12
|
+
/** Label content. */
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
/** Mark as the current page link */
|
|
15
|
+
isCurrentPage?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Make `href` required when `as` is `a` */
|
|
19
|
+
type RequiredLinkHref<E> = E extends 'a' ? { href: string } : Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Navigation item props
|
|
23
|
+
*/
|
|
24
|
+
export type NavigationItemProps<E extends ElementType = 'a'> = HasPolymorphicAs<E> &
|
|
25
|
+
HasTheme &
|
|
26
|
+
HasClassName &
|
|
27
|
+
BaseNavigationItemProps &
|
|
28
|
+
RequiredLinkHref<E>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Component display name.
|
|
32
|
+
*/
|
|
33
|
+
const COMPONENT_NAME = 'NavigationItem';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Component default class name and class prefix.
|
|
37
|
+
*/
|
|
38
|
+
export const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
39
|
+
|
|
40
|
+
export const NavigationItem = Object.assign(
|
|
41
|
+
forwardRefPolymorphic(<E extends ElementType = 'a'>(props: NavigationItemProps<E>, ref: ComponentRef<E>) => {
|
|
42
|
+
const { className, icon, label, isCurrentPage, as: Element = 'a', ...forwardedProps } = props;
|
|
43
|
+
const theme = useContext(ThemeContext);
|
|
44
|
+
const [labelElement, setLabelElement] = useState<HTMLSpanElement | null>(null);
|
|
45
|
+
const tooltipLabel =
|
|
46
|
+
typeof label === 'string' && labelElement && labelElement.offsetWidth < labelElement.scrollWidth
|
|
47
|
+
? label
|
|
48
|
+
: null;
|
|
49
|
+
|
|
50
|
+
const buttonProps = Element === 'button' ? { type: 'button' } : {};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<li
|
|
54
|
+
className={classNames(
|
|
55
|
+
className,
|
|
56
|
+
handleBasicClasses({
|
|
57
|
+
prefix: CLASSNAME,
|
|
58
|
+
theme,
|
|
59
|
+
}),
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
<Tooltip label={tooltipLabel} placement={Placement.TOP}>
|
|
63
|
+
<Element
|
|
64
|
+
className={handleBasicClasses({
|
|
65
|
+
prefix: `${CLASSNAME}__link`,
|
|
66
|
+
isSelected: isCurrentPage,
|
|
67
|
+
})}
|
|
68
|
+
ref={ref}
|
|
69
|
+
aria-current={isCurrentPage ? 'page' : undefined}
|
|
70
|
+
{...buttonProps}
|
|
71
|
+
{...forwardedProps}
|
|
72
|
+
>
|
|
73
|
+
{icon ? (
|
|
74
|
+
<Icon className={`${CLASSNAME}__icon`} icon={icon} size={Size.xs} theme={theme} />
|
|
75
|
+
) : null}
|
|
76
|
+
|
|
77
|
+
<Text as="span" truncate className={`${CLASSNAME}__label`} ref={setLabelElement}>
|
|
78
|
+
{label}
|
|
79
|
+
</Text>
|
|
80
|
+
</Element>
|
|
81
|
+
</Tooltip>
|
|
82
|
+
</li>
|
|
83
|
+
);
|
|
84
|
+
}),
|
|
85
|
+
{
|
|
86
|
+
displayName: COMPONENT_NAME,
|
|
87
|
+
className: CLASSNAME,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import { NavigationItem } from './NavigationItem';
|
|
8
|
+
import { NavigationSection, NavigationSectionProps } from './NavigationSection';
|
|
9
|
+
import { NavigationContext } from './context';
|
|
10
|
+
import { Orientation } from '..';
|
|
11
|
+
|
|
12
|
+
const CLASSNAME = NavigationSection.className as string;
|
|
13
|
+
|
|
14
|
+
type SetupProps = Partial<NavigationSectionProps>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const setup = (propsOverride: SetupProps = {}, orientation: Orientation = Orientation.vertical) => {
|
|
21
|
+
const props = { ...propsOverride };
|
|
22
|
+
const { container } = render(
|
|
23
|
+
<NavigationContext.Provider value={{ orientation }}>
|
|
24
|
+
<NavigationSection label="Section 1" {...props}>
|
|
25
|
+
<NavigationItem label="A content" href="" />
|
|
26
|
+
<NavigationItem label="A link" href="" />
|
|
27
|
+
<NavigationItem label="A community" href="" />
|
|
28
|
+
</NavigationSection>
|
|
29
|
+
</NavigationContext.Provider>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
container,
|
|
34
|
+
element: getByClassName(container, CLASSNAME),
|
|
35
|
+
query: {
|
|
36
|
+
button: () =>
|
|
37
|
+
screen.getByRole('button', {
|
|
38
|
+
name: /section 1/i,
|
|
39
|
+
}),
|
|
40
|
+
content: () => queryByClassName(container, `${CLASSNAME}__drawer`),
|
|
41
|
+
popover: () => queryByClassName(container, `${CLASSNAME}__drawer--popover`),
|
|
42
|
+
},
|
|
43
|
+
props,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe(`<${NavigationSection.displayName}>`, () => {
|
|
48
|
+
it('should render default', () => {
|
|
49
|
+
const { element } = setup();
|
|
50
|
+
expect(element).toBeInTheDocument();
|
|
51
|
+
expect(element).toHaveClass(CLASSNAME);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should be closed by default in vertical mode', () => {
|
|
55
|
+
const { element, query } = setup();
|
|
56
|
+
expect(element).toBeInTheDocument();
|
|
57
|
+
expect(element).toHaveClass(CLASSNAME);
|
|
58
|
+
// Section is visible
|
|
59
|
+
expect(query.button()).toBeInTheDocument();
|
|
60
|
+
expect(query.button()).toHaveAttribute('aria-expanded', 'false');
|
|
61
|
+
// Content is not visible
|
|
62
|
+
expect(query.content()).not.toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should be closed by default in horizontal mode', () => {
|
|
66
|
+
const { element, query } = setup({}, Orientation.horizontal);
|
|
67
|
+
expect(element).toBeInTheDocument();
|
|
68
|
+
expect(element).toHaveClass(CLASSNAME);
|
|
69
|
+
// Section is visible
|
|
70
|
+
expect(query.button()).toBeInTheDocument();
|
|
71
|
+
expect(query.button()).toHaveAttribute('aria-expanded', 'false');
|
|
72
|
+
// Content is not visible
|
|
73
|
+
expect(query.popover()).not.toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should toggle on click in vertical mode', async () => {
|
|
77
|
+
const { query } = setup();
|
|
78
|
+
// Content is not visible
|
|
79
|
+
expect(query.content()).not.toBeInTheDocument();
|
|
80
|
+
// click to open
|
|
81
|
+
await userEvent.click(query.button() as any);
|
|
82
|
+
expect(query.button()).toBeInTheDocument();
|
|
83
|
+
expect(query.button()).toHaveAttribute('aria-expanded', 'true');
|
|
84
|
+
expect(query.button()).toHaveAttribute('aria-controls');
|
|
85
|
+
expect(query.content()).toBeInTheDocument();
|
|
86
|
+
expect(query.button().getAttribute('aria-controls')).toBe(query.content()?.getAttribute('id'));
|
|
87
|
+
// click to close
|
|
88
|
+
await userEvent.click(query.button() as any);
|
|
89
|
+
expect(query.button()).toBeInTheDocument();
|
|
90
|
+
expect(query.button()).toHaveAttribute('aria-expanded', 'false');
|
|
91
|
+
expect(query.content()).not.toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should be in a popover and toggle on click in horizontal mode', async () => {
|
|
95
|
+
const { query } = setup({}, Orientation.horizontal);
|
|
96
|
+
// Content is not visible
|
|
97
|
+
expect(query.popover()).not.toBeInTheDocument();
|
|
98
|
+
// click to open
|
|
99
|
+
await userEvent.click(query.button() as any);
|
|
100
|
+
expect(query.button()).toBeInTheDocument();
|
|
101
|
+
expect(query.button()).toHaveAttribute('aria-expanded', 'true');
|
|
102
|
+
expect(query.button()).toHaveAttribute('aria-controls');
|
|
103
|
+
expect(query.popover()).toBeInTheDocument();
|
|
104
|
+
expect(query.button().getAttribute('aria-controls')).toBe(query.popover()?.getAttribute('id'));
|
|
105
|
+
// click to close
|
|
106
|
+
await userEvent.click(query.button() as any);
|
|
107
|
+
expect(query.button()).toBeInTheDocument();
|
|
108
|
+
expect(query.button()).toHaveAttribute('aria-expanded', 'false');
|
|
109
|
+
expect(query.popover()).not.toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should also toggle on click away in horizontal mode', async () => {
|
|
113
|
+
const { query } = setup({}, Orientation.horizontal);
|
|
114
|
+
// Content is not visible
|
|
115
|
+
expect(query.popover()).not.toBeInTheDocument();
|
|
116
|
+
// click to open
|
|
117
|
+
await userEvent.click(query.button() as any);
|
|
118
|
+
expect(query.popover()).toBeInTheDocument();
|
|
119
|
+
// click away to close
|
|
120
|
+
await userEvent.click(document.body);
|
|
121
|
+
expect(query.popover()).not.toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Common tests suite.
|
|
125
|
+
commonTestsSuiteRTL(setup, { baseClassName: CLASSNAME, forwardClassName: 'element', forwardAttributes: 'element' });
|
|
126
|
+
});
|