@lumx/react 3.7.0 → 3.7.1
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 +9 -1
- package/index.d.ts +12 -4
- package/index.js +81 -14
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/flag/Flag.stories.tsx +10 -0
- package/src/components/flag/Flag.tsx +10 -5
- package/src/components/inline-list/InlineList.stories.tsx +22 -0
- package/src/components/inline-list/InlineList.tsx +12 -2
- package/src/components/navigation/NavigationItem.tsx +4 -7
- package/src/components/text/Text.tsx +6 -1
- package/src/components/thumbnail/Thumbnail.stories.tsx +37 -1
- package/src/components/thumbnail/Thumbnail.tsx +5 -1
- package/src/components/thumbnail/types.ts +9 -0
- package/src/components/tooltip/Tooltip.tsx +2 -1
- package/src/components/tooltip/context.tsx +17 -0
- package/src/hooks/useOverflowTooltipLabel.tsx +29 -0
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^3.7.
|
|
11
|
-
"@lumx/icons": "^3.7.
|
|
10
|
+
"@lumx/core": "^3.7.1",
|
|
11
|
+
"@lumx/icons": "^3.7.1",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.3.2",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"build:storybook": "storybook build"
|
|
113
113
|
},
|
|
114
114
|
"sideEffects": false,
|
|
115
|
-
"version": "3.7.
|
|
115
|
+
"version": "3.7.1"
|
|
116
116
|
}
|
|
@@ -4,6 +4,8 @@ import { colorArgType } from '@lumx/react/stories/controls/color';
|
|
|
4
4
|
import { iconArgType } from '@lumx/react/stories/controls/icons';
|
|
5
5
|
import { withUndefined } from '@lumx/react/stories/controls/withUndefined';
|
|
6
6
|
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
7
|
+
import { withResizableBox } from '@lumx/react/stories/decorators/withResizableBox';
|
|
8
|
+
import { loremIpsum } from '@lumx/react/stories/utils/lorem';
|
|
7
9
|
|
|
8
10
|
export default {
|
|
9
11
|
title: 'LumX components/flag/Flag',
|
|
@@ -36,3 +38,11 @@ export const AllColors = {
|
|
|
36
38
|
}),
|
|
37
39
|
],
|
|
38
40
|
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Truncate text option
|
|
44
|
+
*/
|
|
45
|
+
export const Truncate = {
|
|
46
|
+
args: { label: loremIpsum('tiny'), truncate: true },
|
|
47
|
+
decorators: [withResizableBox()],
|
|
48
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
|
|
4
|
-
import { ColorPalette, Icon, Size, Theme } from '@lumx/react';
|
|
4
|
+
import { ColorPalette, Icon, Size, Theme, Text } from '@lumx/react';
|
|
5
5
|
import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
|
|
6
6
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
7
7
|
|
|
@@ -11,7 +11,9 @@ export interface FlagProps extends GenericProps, HasTheme {
|
|
|
11
11
|
/** Icon to use before the label. */
|
|
12
12
|
icon?: string;
|
|
13
13
|
/** Text label of the flag. */
|
|
14
|
-
label:
|
|
14
|
+
label: React.ReactNode;
|
|
15
|
+
/** Enable text truncate on overflow */
|
|
16
|
+
truncate?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const COMPONENT_NAME = 'Flag';
|
|
@@ -28,17 +30,20 @@ const DEFAULT_PROPS: Partial<FlagProps> = {
|
|
|
28
30
|
* @return React element.
|
|
29
31
|
*/
|
|
30
32
|
export const Flag: Comp<FlagProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
31
|
-
const { label, icon, color, className, theme, ...forwardedProps } = props;
|
|
33
|
+
const { label, icon, color, className, theme, truncate, ...forwardedProps } = props;
|
|
32
34
|
const flagColor = color || (theme === Theme.light ? ColorPalette.dark : ColorPalette.light);
|
|
35
|
+
const isTruncated = !!truncate;
|
|
33
36
|
|
|
34
37
|
return (
|
|
35
38
|
<div
|
|
36
39
|
{...forwardedProps}
|
|
37
|
-
className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, color: flagColor }))}
|
|
40
|
+
className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, color: flagColor, isTruncated }))}
|
|
38
41
|
ref={ref}
|
|
39
42
|
>
|
|
40
43
|
{icon && <Icon icon={icon} size={Size.xxs} className={`${CLASSNAME}__icon`} />}
|
|
41
|
-
<span className={`${CLASSNAME}__label`}>
|
|
44
|
+
<Text as="span" truncate={isTruncated} typography="overline" className={`${CLASSNAME}__label`}>
|
|
45
|
+
{label}
|
|
46
|
+
</Text>
|
|
42
47
|
</div>
|
|
43
48
|
);
|
|
44
49
|
});
|
|
@@ -54,3 +54,25 @@ export const MixedNoWrapAndTruncate = {
|
|
|
54
54
|
},
|
|
55
55
|
decorators: [withResizableBox({ width: 400 })],
|
|
56
56
|
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Line wrap on overflow
|
|
60
|
+
*/
|
|
61
|
+
export const Wrap = {
|
|
62
|
+
args: {
|
|
63
|
+
wrap: true,
|
|
64
|
+
children: [
|
|
65
|
+
<Text key="1" as="span">
|
|
66
|
+
Very very very very very long text
|
|
67
|
+
</Text>,
|
|
68
|
+
<Text key="2" as="span">
|
|
69
|
+
<Icon icon={mdiEarth} />
|
|
70
|
+
Some text
|
|
71
|
+
</Text>,
|
|
72
|
+
<Text key="3" as="span">
|
|
73
|
+
Very very very very very long text
|
|
74
|
+
</Text>,
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
decorators: [withResizableBox({ width: 400 })],
|
|
78
|
+
};
|
|
@@ -22,6 +22,10 @@ export interface InlineListProps extends GenericProps {
|
|
|
22
22
|
* Typography variant.
|
|
23
23
|
*/
|
|
24
24
|
typography?: Typography;
|
|
25
|
+
/**
|
|
26
|
+
* Activate line wrap on overflow.
|
|
27
|
+
*/
|
|
28
|
+
wrap?: boolean;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -47,7 +51,7 @@ const DEFAULT_PROPS = {} as const;
|
|
|
47
51
|
* @return React element.
|
|
48
52
|
*/
|
|
49
53
|
export const InlineList: Comp<InlineListProps> = forwardRef((props, ref) => {
|
|
50
|
-
const { className, color, colorVariant, typography, children, ...forwardedProps } = props;
|
|
54
|
+
const { className, color, colorVariant, typography, children, wrap, ...forwardedProps } = props;
|
|
51
55
|
const fontColorClassName = color && getFontColorClassName(color, colorVariant);
|
|
52
56
|
const typographyClassName = typography && getTypographyClassName(typography);
|
|
53
57
|
return (
|
|
@@ -55,7 +59,13 @@ export const InlineList: Comp<InlineListProps> = forwardRef((props, ref) => {
|
|
|
55
59
|
<ul
|
|
56
60
|
{...forwardedProps}
|
|
57
61
|
ref={ref as any}
|
|
58
|
-
className={classNames(
|
|
62
|
+
className={classNames(
|
|
63
|
+
className,
|
|
64
|
+
CLASSNAME,
|
|
65
|
+
wrap && `${CLASSNAME}--wrap`,
|
|
66
|
+
fontColorClassName,
|
|
67
|
+
typographyClassName,
|
|
68
|
+
)}
|
|
59
69
|
// Lists with removed bullet style can lose their a11y list role on some browsers
|
|
60
70
|
role="list"
|
|
61
71
|
>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { ElementType, ReactNode,
|
|
1
|
+
import React, { ElementType, ReactNode, useContext } from 'react';
|
|
2
2
|
import { Icon, Placement, Size, Tooltip, Text } from '@lumx/react';
|
|
3
3
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
4
4
|
import { ComponentRef, HasClassName, HasPolymorphicAs, HasTheme } from '@lumx/react/utils/type';
|
|
5
5
|
import classNames from 'classnames';
|
|
6
6
|
import { forwardRefPolymorphic } from '@lumx/react/utils/forwardRefPolymorphic';
|
|
7
7
|
import { ThemeContext } from '@lumx/react/utils/ThemeContext';
|
|
8
|
+
import { useOverflowTooltipLabel } from '@lumx/react/hooks/useOverflowTooltipLabel';
|
|
8
9
|
|
|
9
10
|
type BaseNavigationItemProps = {
|
|
10
11
|
/** Icon (SVG path). */
|
|
@@ -41,11 +42,7 @@ export const NavigationItem = Object.assign(
|
|
|
41
42
|
forwardRefPolymorphic(<E extends ElementType = 'a'>(props: NavigationItemProps<E>, ref: ComponentRef<E>) => {
|
|
42
43
|
const { className, icon, label, isCurrentPage, as: Element = 'a', ...forwardedProps } = props;
|
|
43
44
|
const theme = useContext(ThemeContext);
|
|
44
|
-
const
|
|
45
|
-
const tooltipLabel =
|
|
46
|
-
typeof label === 'string' && labelElement && labelElement.offsetWidth < labelElement.scrollWidth
|
|
47
|
-
? label
|
|
48
|
-
: null;
|
|
45
|
+
const { tooltipLabel, labelRef } = useOverflowTooltipLabel();
|
|
49
46
|
|
|
50
47
|
const buttonProps = Element === 'button' ? { type: 'button' } : {};
|
|
51
48
|
|
|
@@ -74,7 +71,7 @@ export const NavigationItem = Object.assign(
|
|
|
74
71
|
<Icon className={`${CLASSNAME}__icon`} icon={icon} size={Size.xs} theme={theme} />
|
|
75
72
|
) : null}
|
|
76
73
|
|
|
77
|
-
<Text as="span" truncate className={`${CLASSNAME}__label`} ref={
|
|
74
|
+
<Text as="span" truncate className={`${CLASSNAME}__label`} ref={labelRef}>
|
|
78
75
|
{label}
|
|
79
76
|
</Text>
|
|
80
77
|
</Element>
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getTypographyClassName,
|
|
10
10
|
} from '@lumx/react/utils/className';
|
|
11
11
|
import classNames from 'classnames';
|
|
12
|
+
import { useOverflowTooltipLabel } from '@lumx/react/hooks/useOverflowTooltipLabel';
|
|
13
|
+
import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Defines the props of the component.
|
|
@@ -104,9 +106,11 @@ export const Text: Comp<TextProps> = forwardRef((props, ref) => {
|
|
|
104
106
|
!(isTruncated && !isTruncatedMultiline) &&
|
|
105
107
|
whiteSpace && { '--lumx-text-white-space': whiteSpace };
|
|
106
108
|
|
|
109
|
+
const { tooltipLabel, labelRef } = useOverflowTooltipLabel();
|
|
110
|
+
|
|
107
111
|
return (
|
|
108
112
|
<Component
|
|
109
|
-
ref={ref as React.Ref<any
|
|
113
|
+
ref={useMergeRefs(ref as React.Ref<any>, labelRef)}
|
|
110
114
|
className={classNames(
|
|
111
115
|
className,
|
|
112
116
|
handleBasicClasses({
|
|
@@ -118,6 +122,7 @@ export const Text: Comp<TextProps> = forwardRef((props, ref) => {
|
|
|
118
122
|
colorClass,
|
|
119
123
|
noWrap && `${CLASSNAME}--no-wrap`,
|
|
120
124
|
)}
|
|
125
|
+
title={tooltipLabel}
|
|
121
126
|
style={{ ...truncateLinesStyle, ...whiteSpaceStyle, ...style }}
|
|
122
127
|
{...forwardedProps}
|
|
123
128
|
>
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { mdiAbTesting } from '@lumx/icons';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Alignment,
|
|
6
|
+
AspectRatio,
|
|
7
|
+
Badge,
|
|
8
|
+
FlexBox,
|
|
9
|
+
GridColumn,
|
|
10
|
+
Icon,
|
|
11
|
+
Size,
|
|
12
|
+
Thumbnail,
|
|
13
|
+
ThumbnailObjectFit,
|
|
14
|
+
ThumbnailVariant,
|
|
15
|
+
} from '@lumx/react';
|
|
5
16
|
import { CustomLink } from '@lumx/react/stories/utils/CustomLink';
|
|
6
17
|
import { IMAGE_SIZES, imageArgType, IMAGES } from '@lumx/react/stories/controls/image';
|
|
7
18
|
import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
|
|
@@ -375,3 +386,28 @@ export const Square = () => (
|
|
|
375
386
|
</FlexBox>
|
|
376
387
|
</>
|
|
377
388
|
);
|
|
389
|
+
|
|
390
|
+
export const ObjectFit = {
|
|
391
|
+
args: { size: Size.xl },
|
|
392
|
+
decorators: [
|
|
393
|
+
withCombinations({
|
|
394
|
+
cellStyle: { border: '1px solid lightgray' },
|
|
395
|
+
combinations: {
|
|
396
|
+
cols: {
|
|
397
|
+
'Default (cover)': {},
|
|
398
|
+
contain: { objectFit: ThumbnailObjectFit.contain },
|
|
399
|
+
},
|
|
400
|
+
rows: {
|
|
401
|
+
'Ratio square': { aspectRatio: AspectRatio.square },
|
|
402
|
+
'Ratio wide': { aspectRatio: AspectRatio.wide },
|
|
403
|
+
'Ratio vertical': { aspectRatio: AspectRatio.vertical },
|
|
404
|
+
},
|
|
405
|
+
sections: {
|
|
406
|
+
'Portrait image': { image: IMAGES.portrait1 },
|
|
407
|
+
'Landscape image': { image: IMAGES.landscape1 },
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
withWrapper({ maxColumns: 3, itemMinWidth: 350 }, GridColumn),
|
|
412
|
+
],
|
|
413
|
+
};
|
|
@@ -11,7 +11,7 @@ import React, {
|
|
|
11
11
|
} from 'react';
|
|
12
12
|
import classNames from 'classnames';
|
|
13
13
|
|
|
14
|
-
import { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react';
|
|
14
|
+
import { AspectRatio, HorizontalAlignment, Icon, Size, Theme, ThumbnailObjectFit } from '@lumx/react';
|
|
15
15
|
|
|
16
16
|
import { Comp, Falsy, GenericProps, HasTheme } from '@lumx/react/utils/type';
|
|
17
17
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
@@ -52,6 +52,8 @@ export interface ThumbnailProps extends GenericProps, HasTheme {
|
|
|
52
52
|
imgRef?: Ref<HTMLImageElement>;
|
|
53
53
|
/** Set to true to force the display of the loading skeleton. */
|
|
54
54
|
isLoading?: boolean;
|
|
55
|
+
/** Set how the image should fit when its aspect ratio is constrained */
|
|
56
|
+
objectFit?: ThumbnailObjectFit;
|
|
55
57
|
/** Size variant of the component. */
|
|
56
58
|
size?: ThumbnailSize;
|
|
57
59
|
/** Image loading mode. */
|
|
@@ -111,6 +113,7 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
111
113
|
imgProps,
|
|
112
114
|
imgRef: propImgRef,
|
|
113
115
|
isLoading: isLoadingProp,
|
|
116
|
+
objectFit,
|
|
114
117
|
loading,
|
|
115
118
|
size,
|
|
116
119
|
theme,
|
|
@@ -175,6 +178,7 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
175
178
|
hasIconErrorFallback,
|
|
176
179
|
hasCustomErrorFallback,
|
|
177
180
|
isLoading,
|
|
181
|
+
objectFit,
|
|
178
182
|
hasBadge: !!badge,
|
|
179
183
|
}),
|
|
180
184
|
fillHeight && `${CLASSNAME}--fill-height`,
|
|
@@ -37,3 +37,12 @@ export const ThumbnailVariant = {
|
|
|
37
37
|
rounded: 'rounded',
|
|
38
38
|
} as const;
|
|
39
39
|
export type ThumbnailVariant = ValueOf<typeof ThumbnailVariant>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Thumbnail object fit.
|
|
43
|
+
*/
|
|
44
|
+
export const ThumbnailObjectFit = {
|
|
45
|
+
cover: 'cover',
|
|
46
|
+
contain: 'contain',
|
|
47
|
+
} as const;
|
|
48
|
+
export type ThumbnailObjectFit = ValueOf<typeof ThumbnailObjectFit>;
|
|
@@ -12,6 +12,7 @@ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/classNam
|
|
|
12
12
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
13
13
|
import { Placement } from '@lumx/react/components/popover';
|
|
14
14
|
|
|
15
|
+
import { TooltipContextProvider } from '@lumx/react/components/tooltip/context';
|
|
15
16
|
import { useInjectTooltipRef } from './useInjectTooltipRef';
|
|
16
17
|
import { useTooltipOpen } from './useTooltipOpen';
|
|
17
18
|
|
|
@@ -91,7 +92,7 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
91
92
|
|
|
92
93
|
return (
|
|
93
94
|
<>
|
|
94
|
-
{wrappedChildren}
|
|
95
|
+
<TooltipContextProvider>{wrappedChildren}</TooltipContextProvider>
|
|
95
96
|
{isOpen &&
|
|
96
97
|
createPortal(
|
|
97
98
|
<div
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/** Empty object */
|
|
4
|
+
type TooltipContextValue = NonNullable<unknown>;
|
|
5
|
+
|
|
6
|
+
const DEFAULT_VALUE = {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tooltip react context simply used as a marker to know when parent react node have a tooltip
|
|
10
|
+
*/
|
|
11
|
+
const TooltipContext = React.createContext<TooltipContextValue | undefined>(undefined);
|
|
12
|
+
|
|
13
|
+
export const TooltipContextProvider: React.FC = ({ children }) => (
|
|
14
|
+
<TooltipContext.Provider value={DEFAULT_VALUE}>{children}</TooltipContext.Provider>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const useTooltipContext = () => React.useContext(TooltipContext);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTooltipContext } from '@lumx/react/components/tooltip/context';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compute a tooltip label based on a label element `innerText` if the text overflows.
|
|
6
|
+
*
|
|
7
|
+
* Warning: only works on first render, does not update on label element resize.
|
|
8
|
+
*/
|
|
9
|
+
export const useOverflowTooltipLabel = () => {
|
|
10
|
+
const parentTooltip = useTooltipContext();
|
|
11
|
+
const [tooltipLabel, setTooltipLabel] = React.useState<string | undefined>(undefined);
|
|
12
|
+
const labelRef = React.useCallback(
|
|
13
|
+
(labelElement: HTMLElement | null) => {
|
|
14
|
+
if (!labelElement || !!parentTooltip) {
|
|
15
|
+
// Skip if label element is unknown
|
|
16
|
+
// Skip if the parent has a tooltip
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Label overflowing
|
|
21
|
+
if (labelElement.offsetWidth < labelElement.scrollWidth) {
|
|
22
|
+
setTooltipLabel(labelElement.innerText);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
[parentTooltip],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return { labelRef, tooltipLabel };
|
|
29
|
+
};
|