@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/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.0",
11
- "@lumx/icons": "^3.7.0",
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.0"
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: string;
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`}>{label}</span>
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(className, CLASSNAME, fontColorClassName, typographyClassName)}
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, useState, useContext } from 'react';
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 [labelElement, setLabelElement] = useState<HTMLSpanElement | null>(null);
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={setLabelElement}>
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 { Alignment, AspectRatio, Badge, FlexBox, Icon, Size, Thumbnail, ThumbnailVariant } from '@lumx/react';
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
+ };