@lumx/react 2.2.0 → 2.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/esm/_internal/Avatar2.js +5 -1
- package/esm/_internal/Avatar2.js.map +1 -1
- package/esm/_internal/Thumbnail2.js +2 -0
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/Tooltip2.js +0 -5
- package/esm/_internal/Tooltip2.js.map +1 -1
- package/esm/_internal/UserBlock.js +41 -17
- package/esm/_internal/UserBlock.js.map +1 -1
- package/esm/_internal/user-block.js +2 -0
- package/esm/_internal/user-block.js.map +1 -1
- package/package.json +4 -4
- package/src/components/avatar/Avatar.stories.tsx +30 -53
- package/src/components/avatar/Avatar.tsx +8 -0
- package/src/components/avatar/__snapshots__/Avatar.test.tsx.snap +193 -330
- package/src/components/thumbnail/Thumbnail.stories.tsx +2 -7
- package/src/components/thumbnail/Thumbnail.test.tsx +1 -7
- package/src/components/thumbnail/Thumbnail.tsx +2 -0
- package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +4 -2
- package/src/components/tooltip/Tooltip.stories.tsx +7 -4
- package/src/components/tooltip/useInjectTooltipRef.tsx +1 -3
- package/src/components/user-block/UserBlock.stories.tsx +65 -105
- package/src/components/user-block/UserBlock.test.tsx +6 -0
- package/src/components/user-block/UserBlock.tsx +50 -25
- package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +113 -144
- package/src/stories/generated/Thumbnail/Demos.stories.tsx +1 -0
- package/src/stories/utils/CustomLink.tsx +7 -0
- package/types.d.ts +14 -4
|
@@ -5,13 +5,7 @@ import 'jest-enzyme';
|
|
|
5
5
|
import { commonTestsSuite, itShouldRenderStories } from '@lumx/react/testing/utils';
|
|
6
6
|
|
|
7
7
|
import { Thumbnail, ThumbnailProps } from './Thumbnail';
|
|
8
|
-
import {
|
|
9
|
-
Clickable,
|
|
10
|
-
ClickableCustomLink,
|
|
11
|
-
ClickableLink,
|
|
12
|
-
Default,
|
|
13
|
-
WithBadge,
|
|
14
|
-
} from './Thumbnail.stories';
|
|
8
|
+
import { Clickable, ClickableCustomLink, ClickableLink, Default, WithBadge } from './Thumbnail.stories';
|
|
15
9
|
|
|
16
10
|
const CLASSNAME = Thumbnail.className as string;
|
|
17
11
|
|
|
@@ -151,6 +151,8 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
151
151
|
Object.assign(wrapperProps, linkProps);
|
|
152
152
|
} else if (isButton) {
|
|
153
153
|
Wrapper = 'button';
|
|
154
|
+
wrapperProps.type = forwardedProps.type || 'button';
|
|
155
|
+
wrapperProps['aria-label'] = forwardedProps['aria-label'] || alt;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
return (
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`<Thumbnail> Snapshots and structure should render story 'Clickable' 1`] = `
|
|
4
4
|
<button
|
|
5
|
+
aria-label="Click me"
|
|
5
6
|
className="lumx-thumbnail lumx-thumbnail--aspect-ratio-original lumx-thumbnail--size-xxl lumx-thumbnail--theme-light lumx-thumbnail--is-clickable lumx-thumbnail--is-loading"
|
|
6
7
|
onClick={[Function]}
|
|
8
|
+
type="button"
|
|
7
9
|
>
|
|
8
10
|
<div
|
|
9
11
|
className="lumx-thumbnail__background"
|
|
@@ -20,7 +22,7 @@ exports[`<Thumbnail> Snapshots and structure should render story 'Clickable' 1`]
|
|
|
20
22
|
`;
|
|
21
23
|
|
|
22
24
|
exports[`<Thumbnail> Snapshots and structure should render story 'ClickableCustomLink' 1`] = `
|
|
23
|
-
<
|
|
25
|
+
<CustomLink
|
|
24
26
|
className="custom-class-name lumx-thumbnail lumx-thumbnail--aspect-ratio-original lumx-thumbnail--size-xxl lumx-thumbnail--theme-light lumx-thumbnail--is-clickable lumx-thumbnail--is-loading"
|
|
25
27
|
href="https://google.fr"
|
|
26
28
|
>
|
|
@@ -35,7 +37,7 @@ exports[`<Thumbnail> Snapshots and structure should render story 'ClickableCusto
|
|
|
35
37
|
style={Object {}}
|
|
36
38
|
/>
|
|
37
39
|
</div>
|
|
38
|
-
</
|
|
40
|
+
</CustomLink>
|
|
39
41
|
`;
|
|
40
42
|
|
|
41
43
|
exports[`<Thumbnail> Snapshots and structure should render story 'ClickableLink' 1`] = `
|
|
@@ -76,13 +76,16 @@ export const EmptyTooltip = () => (
|
|
|
76
76
|
);
|
|
77
77
|
|
|
78
78
|
export const TooltipWithDropdown = () => {
|
|
79
|
-
const
|
|
79
|
+
const [button, setButton] = useState<HTMLElement | null>(null);
|
|
80
|
+
const [isOpen, setOpen] = useState(false);
|
|
80
81
|
return (
|
|
81
82
|
<>
|
|
82
|
-
<Tooltip label=
|
|
83
|
-
<Button ref={
|
|
83
|
+
<Tooltip label={!isOpen && 'Tooltip'} placement="top">
|
|
84
|
+
<Button ref={setButton} onClick={() => setOpen((o) => !o)}>
|
|
85
|
+
Anchor
|
|
86
|
+
</Button>
|
|
84
87
|
</Tooltip>
|
|
85
|
-
<Dropdown anchorRef={
|
|
88
|
+
<Dropdown anchorRef={{ current: button }} isOpen={isOpen}>
|
|
86
89
|
Dropdown
|
|
87
90
|
</Dropdown>
|
|
88
91
|
</>
|
|
@@ -28,9 +28,7 @@ export const useInjectTooltipRef = (
|
|
|
28
28
|
get(children, 'props.isDisabled') !== true
|
|
29
29
|
) {
|
|
30
30
|
const element = children as any;
|
|
31
|
-
|
|
32
|
-
setAnchorElement(element.ref.current);
|
|
33
|
-
}
|
|
31
|
+
|
|
34
32
|
return cloneElement(element, {
|
|
35
33
|
...element.props,
|
|
36
34
|
...ariaProps,
|
|
@@ -1,116 +1,76 @@
|
|
|
1
|
-
import { mdiStar } from '@lumx/icons';
|
|
2
|
-
import { Badge, ColorPalette, Icon, List, ListItem, Size } from '@lumx/react';
|
|
3
|
-
import { AVATAR_IMAGES, avatarImageKnob } from '@lumx/react/stories/knobs/image';
|
|
4
1
|
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { mdiStar } from '@lumx/icons';
|
|
4
|
+
import { Badge, ColorPalette, Icon, Size } from '@lumx/react';
|
|
5
|
+
import { avatarImageKnob } from '@lumx/react/stories/knobs/image';
|
|
6
|
+
import { CustomLink } from '@lumx/react/stories/utils/CustomLink';
|
|
7
|
+
|
|
5
8
|
import { UserBlock } from './UserBlock';
|
|
6
9
|
|
|
7
10
|
export default { title: 'LumX components/user-block/UserBlock' };
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
const logAction = (action: string) => () => console.log(action);
|
|
13
|
+
const sizes = [Size.s, Size.m, Size.l];
|
|
14
|
+
|
|
15
|
+
export const Default = ({ theme }: any) => (
|
|
16
|
+
<UserBlock
|
|
17
|
+
theme={theme}
|
|
18
|
+
name="Emmitt O. Lum"
|
|
19
|
+
fields={['Creative developer', 'Denpasar']}
|
|
20
|
+
avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
|
|
21
|
+
onMouseEnter={logAction('Mouse entered')}
|
|
22
|
+
onMouseLeave={logAction('Mouse left')}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const Sizes = ({ theme }: any) =>
|
|
27
|
+
sizes.map((size) => (
|
|
28
|
+
<UserBlock
|
|
29
|
+
key={size}
|
|
30
|
+
theme={theme}
|
|
31
|
+
name="Emmitt O. Lum"
|
|
32
|
+
fields={['Creative developer', 'Denpasar']}
|
|
33
|
+
avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
|
|
34
|
+
size={size}
|
|
35
|
+
onMouseEnter={logAction('Mouse entered')}
|
|
36
|
+
onMouseLeave={logAction('Mouse left')}
|
|
37
|
+
/>
|
|
23
38
|
));
|
|
24
|
-
};
|
|
25
39
|
|
|
26
|
-
export const
|
|
27
|
-
const
|
|
40
|
+
export const Clickable = ({ theme }: any) => {
|
|
41
|
+
const baseProps = {
|
|
42
|
+
theme,
|
|
43
|
+
name: 'Emmitt O. Lum',
|
|
44
|
+
fields: ['Creative developer', 'Denpasar'],
|
|
45
|
+
avatarProps: { image: avatarImageKnob(), alt: 'Avatar' },
|
|
46
|
+
} as any;
|
|
28
47
|
return (
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
fields={['Creative developer', 'Denpasar']}
|
|
33
|
-
avatarProps={{
|
|
34
|
-
image: avatarImageKnob(),
|
|
35
|
-
alt: 'Avatar',
|
|
36
|
-
badge: (
|
|
37
|
-
<Badge color={ColorPalette.blue}>
|
|
38
|
-
<Icon icon={mdiStar} />
|
|
39
|
-
</Badge>
|
|
40
|
-
),
|
|
41
|
-
}}
|
|
42
|
-
size={Size.m}
|
|
43
|
-
onMouseEnter={logAction('Mouse entered')}
|
|
44
|
-
onMouseLeave={logAction('Mouse left')}
|
|
45
|
-
onClick={logAction('UserBlock clicked')}
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
};
|
|
48
|
+
<>
|
|
49
|
+
<p>As a button</p>
|
|
50
|
+
<UserBlock {...baseProps} onClick={logAction('UserBlock clicked')} />
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
<UserBlock
|
|
58
|
-
name="Emmitt O. Lum"
|
|
59
|
-
fields={['Creative developer', 'Denpasar']}
|
|
60
|
-
avatarProps={{
|
|
61
|
-
image: avatarImageKnob('Avatar 1', AVATAR_IMAGES.avatar1),
|
|
62
|
-
alt: 'Avatar',
|
|
63
|
-
badge: (
|
|
64
|
-
<Badge color={ColorPalette.blue}>
|
|
65
|
-
<Icon icon={mdiStar} />
|
|
66
|
-
</Badge>
|
|
67
|
-
),
|
|
68
|
-
}}
|
|
69
|
-
size={Size.m}
|
|
70
|
-
onMouseEnter={logAction('Mouse entered')}
|
|
71
|
-
onMouseLeave={logAction('Mouse left')}
|
|
72
|
-
onClick={logAction('UserBlock clicked')}
|
|
73
|
-
/>
|
|
74
|
-
</ListItem>
|
|
75
|
-
<ListItem className="lumx-color-background-dark-L6" size={Size.big}>
|
|
76
|
-
<UserBlock
|
|
77
|
-
name="Emmitt O. Lum"
|
|
78
|
-
fields={['Creative developer', 'Denpasar']}
|
|
79
|
-
avatarProps={{
|
|
80
|
-
image: avatarImageKnob('Avatar 2', AVATAR_IMAGES.avatar2),
|
|
81
|
-
alt: 'Avatar',
|
|
82
|
-
badge: (
|
|
83
|
-
<Badge color={ColorPalette.blue}>
|
|
84
|
-
<Icon icon={mdiStar} />
|
|
85
|
-
</Badge>
|
|
86
|
-
),
|
|
87
|
-
}}
|
|
88
|
-
size={Size.m}
|
|
89
|
-
onMouseEnter={logAction('Mouse entered')}
|
|
90
|
-
onMouseLeave={logAction('Mouse left')}
|
|
91
|
-
onClick={logAction('UserBlock clicked')}
|
|
92
|
-
/>
|
|
93
|
-
</ListItem>
|
|
94
|
-
<ListItem className="lumx-color-background-dark-L6" size={Size.big}>
|
|
95
|
-
<UserBlock
|
|
96
|
-
name="Emmitt O. Lum"
|
|
97
|
-
fields={['Creative developer', 'Denpasar']}
|
|
98
|
-
avatarProps={{
|
|
99
|
-
image: avatarImageKnob('Avatar 3', AVATAR_IMAGES.avatar3),
|
|
100
|
-
alt: 'Avatar',
|
|
101
|
-
badge: (
|
|
102
|
-
<Badge color={ColorPalette.blue}>
|
|
103
|
-
<Icon icon={mdiStar} />
|
|
104
|
-
</Badge>
|
|
105
|
-
),
|
|
106
|
-
}}
|
|
107
|
-
size={Size.m}
|
|
108
|
-
onMouseEnter={logAction('Mouse entered')}
|
|
109
|
-
onMouseLeave={logAction('Mouse left')}
|
|
110
|
-
onClick={logAction('UserBlock clicked')}
|
|
111
|
-
/>
|
|
112
|
-
</ListItem>
|
|
113
|
-
</List>
|
|
114
|
-
</div>
|
|
52
|
+
<p>As a link</p>
|
|
53
|
+
<UserBlock {...baseProps} linkProps={{ href: 'https://example.com' }} />
|
|
54
|
+
|
|
55
|
+
<p>As a custom link component</p>
|
|
56
|
+
<UserBlock {...baseProps} linkAs={CustomLink} />
|
|
57
|
+
</>
|
|
115
58
|
);
|
|
116
59
|
};
|
|
60
|
+
|
|
61
|
+
export const WithBadge = ({ theme }: any) => (
|
|
62
|
+
<UserBlock
|
|
63
|
+
theme={theme}
|
|
64
|
+
name="Emmitt O. Lum"
|
|
65
|
+
fields={['Creative developer', 'Denpasar']}
|
|
66
|
+
avatarProps={{
|
|
67
|
+
image: avatarImageKnob(),
|
|
68
|
+
alt: 'Avatar',
|
|
69
|
+
badge: (
|
|
70
|
+
<Badge color={ColorPalette.blue}>
|
|
71
|
+
<Icon icon={mdiStar} />
|
|
72
|
+
</Badge>
|
|
73
|
+
),
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
@@ -25,6 +25,12 @@ describe(`<${UserBlock.displayName}>`, () => {
|
|
|
25
25
|
// 1. Test render via snapshot.
|
|
26
26
|
describe('Snapshots and structure', () => {
|
|
27
27
|
itShouldRenderStories(stories, UserBlock);
|
|
28
|
+
|
|
29
|
+
it('should forward name props', () => {
|
|
30
|
+
const { wrapper } = setup({ name: 'John Doe', nameProps: { 'data-custom-attribute': true } });
|
|
31
|
+
|
|
32
|
+
expect(wrapper.find('.lumx-user-block__name[data-custom-attribute]')).toHaveLength(1);
|
|
33
|
+
});
|
|
28
34
|
});
|
|
29
35
|
|
|
30
36
|
// Common tests suite.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { forwardRef, ReactNode } from 'react';
|
|
2
|
-
|
|
2
|
+
import isEmpty from 'lodash/isEmpty';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
5
|
-
import { Avatar, Orientation, Size, Theme } from '@lumx/react';
|
|
6
|
-
|
|
5
|
+
import { Avatar, ColorPalette, Link, Orientation, Size, Theme } from '@lumx/react';
|
|
7
6
|
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
7
|
+
|
|
8
8
|
import { AvatarProps } from '../avatar/Avatar';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -18,16 +18,22 @@ export type UserBlockSize = Extract<Size, 's' | 'm' | 'l'>;
|
|
|
18
18
|
export interface UserBlockProps extends GenericProps {
|
|
19
19
|
/** Props to pass to the avatar. */
|
|
20
20
|
avatarProps?: AvatarProps;
|
|
21
|
-
/** Simple action toolbar content. */
|
|
22
|
-
simpleAction?: ReactNode;
|
|
23
|
-
/** Multiple action toolbar content. */
|
|
24
|
-
multipleActions?: ReactNode;
|
|
25
21
|
/** Additional fields used to describe the user. */
|
|
26
22
|
fields?: string[];
|
|
23
|
+
/** Props to pass to the link wrapping the avatar thumbnail. */
|
|
24
|
+
linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
25
|
+
/** Custom react component for the link (can be used to inject react router Link). */
|
|
26
|
+
linkAs?: 'a' | any;
|
|
27
|
+
/** Multiple action toolbar content. */
|
|
28
|
+
multipleActions?: ReactNode;
|
|
27
29
|
/** User name. */
|
|
28
30
|
name?: string;
|
|
31
|
+
/** Props to pass to the name block. */
|
|
32
|
+
nameProps?: GenericProps;
|
|
29
33
|
/** Orientation. */
|
|
30
34
|
orientation?: Orientation;
|
|
35
|
+
/** Simple action toolbar content. */
|
|
36
|
+
simpleAction?: ReactNode;
|
|
31
37
|
/** Size variant. */
|
|
32
38
|
size?: UserBlockSize;
|
|
33
39
|
/** Theme adapting the component to light or dark background. */
|
|
@@ -71,8 +77,11 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
|
|
|
71
77
|
avatarProps,
|
|
72
78
|
className,
|
|
73
79
|
fields,
|
|
80
|
+
linkProps,
|
|
81
|
+
linkAs,
|
|
74
82
|
multipleActions,
|
|
75
83
|
name,
|
|
84
|
+
nameProps,
|
|
76
85
|
onClick,
|
|
77
86
|
onMouseEnter,
|
|
78
87
|
onMouseLeave,
|
|
@@ -91,18 +100,34 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
|
|
|
91
100
|
|
|
92
101
|
const shouldDisplayActions: boolean = orientation === Orientation.vertical;
|
|
93
102
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
const isLink = Boolean(linkProps?.href || linkAs);
|
|
104
|
+
const isClickable = !!onClick || isLink;
|
|
105
|
+
|
|
106
|
+
const nameBlock: ReactNode = React.useMemo(() => {
|
|
107
|
+
if (isEmpty(name)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
let NameComponent: any = 'span';
|
|
111
|
+
const nProps: any = {
|
|
112
|
+
...nameProps,
|
|
113
|
+
className: classNames(`${CLASSNAME}__name`, linkProps?.className, nameProps?.className),
|
|
114
|
+
};
|
|
115
|
+
if (isClickable) {
|
|
116
|
+
NameComponent = Link;
|
|
117
|
+
Object.assign(nProps, {
|
|
118
|
+
...linkProps,
|
|
119
|
+
linkAs,
|
|
120
|
+
color: ColorPalette.dark,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return <NameComponent {...nProps}>{name}</NameComponent>;
|
|
124
|
+
}, [isClickable, linkAs, linkProps, name, nameProps]);
|
|
100
125
|
|
|
101
126
|
const fieldsBlock: ReactNode = fields && componentSize !== Size.s && (
|
|
102
127
|
<div className={`${CLASSNAME}__fields`}>
|
|
103
|
-
{fields.map((
|
|
128
|
+
{fields.map((field: string, idx: number) => (
|
|
104
129
|
<span key={idx} className={`${CLASSNAME}__field`}>
|
|
105
|
-
{
|
|
130
|
+
{field}
|
|
106
131
|
</span>
|
|
107
132
|
))}
|
|
108
133
|
</div>
|
|
@@ -114,21 +139,21 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
|
|
|
114
139
|
{...forwardedProps}
|
|
115
140
|
className={classNames(
|
|
116
141
|
className,
|
|
117
|
-
handleBasicClasses({ prefix: CLASSNAME, orientation, size: componentSize, theme }),
|
|
142
|
+
handleBasicClasses({ prefix: CLASSNAME, orientation, size: componentSize, theme, isClickable }),
|
|
118
143
|
)}
|
|
119
144
|
onMouseLeave={onMouseLeave}
|
|
120
145
|
onMouseEnter={onMouseEnter}
|
|
121
146
|
>
|
|
122
147
|
{avatarProps && (
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
<Avatar
|
|
149
|
+
linkAs={linkAs}
|
|
150
|
+
linkProps={linkProps}
|
|
151
|
+
{...avatarProps}
|
|
152
|
+
className={classNames(`${CLASSNAME}__avatar`, avatarProps.className)}
|
|
153
|
+
size={componentSize}
|
|
154
|
+
onClick={onClick}
|
|
155
|
+
theme={theme}
|
|
156
|
+
/>
|
|
132
157
|
)}
|
|
133
158
|
{(fields || name) && (
|
|
134
159
|
<div className={`${CLASSNAME}__wrapper`}>
|