@lumx/react 3.0.2-alpha-react-utils.3 → 3.0.3-alpha.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.0.2-alpha-react-utils.3",
11
- "@lumx/icons": "^3.0.2-alpha-react-utils.3",
10
+ "@lumx/core": "^3.0.3-alpha.1",
11
+ "@lumx/icons": "^3.0.3-alpha.1",
12
12
  "@popperjs/core": "^2.5.4",
13
13
  "body-scroll-lock": "^3.1.5",
14
14
  "classnames": "^2.2.6",
@@ -120,6 +120,6 @@
120
120
  "build:storybook": "cd storybook && ./build"
121
121
  },
122
122
  "sideEffects": false,
123
- "version": "3.0.2-alpha-react-utils.3",
124
- "gitHead": "190431ec45dfcb8ee619060ca97eeda6a88a9db4"
123
+ "version": "3.0.3-alpha.1",
124
+ "gitHead": "8bbdcaeec0b5b6556fdac9bdf81a56ef47c77bcc"
125
125
  }
@@ -6,6 +6,7 @@ import { Comp, isComponentType } from '@lumx/react/utils/type';
6
6
  import { getRootClassName } from '@lumx/react/utils/className';
7
7
  import { partitionMulti } from '@lumx/react/utils/partitionMulti';
8
8
  import { Orientation, Size, FlexBox, FlexBoxProps } from '@lumx/react';
9
+ import { GenericBlockGapSize } from '@lumx/react/components/generic-block/constants';
9
10
 
10
11
  export interface GenericBlockProps extends FlexBoxProps {
11
12
  /**
@@ -46,6 +47,10 @@ export interface GenericBlockProps extends FlexBoxProps {
46
47
  * props to forward to the figure element.
47
48
  */
48
49
  figureProps?: Omit<FlexBoxProps, 'children'>;
50
+ /**
51
+ * Gap space between sections.
52
+ */
53
+ gap?: GenericBlockGapSize;
49
54
  }
50
55
 
51
56
  /**
@@ -68,22 +73,29 @@ const DEFAULT_PROPS: Partial<GenericBlockProps> = {
68
73
 
69
74
  type BaseGenericBlock = Comp<GenericBlockProps, HTMLDivElement>;
70
75
 
76
+ interface GenericBlockSectionProps extends FlexBoxProps {
77
+ /**
78
+ * Gap space between items.
79
+ */
80
+ gap?: GenericBlockGapSize;
81
+ }
82
+
71
83
  interface GenericBlock extends BaseGenericBlock {
72
84
  /**
73
85
  * Use `GenericBlock.Figure` component as children of the `GenericBlock` component as an alternative way to inject
74
86
  * the "figure" section of the block (instead of using `figure` and `figureProps` props).
75
87
  */
76
- Figure: Comp<FlexBoxProps>;
88
+ Figure: Comp<GenericBlockSectionProps>;
77
89
  /**
78
90
  * Use `GenericBlock.Content` component as children of the `GenericBlock` component as an alternative way to inject
79
91
  * the "content" section of the block (instead of using `content` and `contentProps` props).
80
92
  */
81
- Content: Comp<FlexBoxProps>;
93
+ Content: Comp<GenericBlockSectionProps>;
82
94
  /**
83
95
  * Use `GenericBlock.Actions` component as children of the `GenericBlock` component as an alternative way to inject
84
96
  * the "actions" section of the block (instead of using `actions` and `actionsProps` props).
85
97
  */
86
- Actions: Comp<FlexBoxProps>;
98
+ Actions: Comp<GenericBlockSectionProps>;
87
99
  }
88
100
 
89
101
  const Figure = noop.bind({}) as Comp<FlexBoxProps>;
@@ -0,0 +1,9 @@
1
+ import pick from 'lodash/pick';
2
+ import { Size } from '@lumx/react';
3
+ import { ValueOf } from '@lumx/react/utils/type';
4
+
5
+ /**
6
+ * Accepted gap sizes for the generic block.
7
+ */
8
+ export const GenericBlockGapSize = pick(Size, ['tiny', 'regular', 'medium', 'big', 'huge']);
9
+ export type GenericBlockGapSize = ValueOf<typeof GenericBlockGapSize>;
@@ -1 +1,2 @@
1
1
  export * from './GenericBlock';
2
+ export { GenericBlockGapSize } from './constants';
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { mdiEarth } from '@lumx/icons';
3
+ import { ColorPalette, ColorVariant, Icon, Text, TypographyCustom, TypographyInterface } from '@lumx/react';
4
+ import { withResizableBox } from '@lumx/react/stories/withResizableBox';
5
+ import { InlineList } from '.';
6
+
7
+ const ALL_TYPOGRAPHY = [undefined, ...Object.values(TypographyInterface), ...Object.values(TypographyCustom)];
8
+
9
+ export default {
10
+ title: 'LumX components/inline-list/InlineList',
11
+ argTypes: {
12
+ typography: { control: 'select', options: ALL_TYPOGRAPHY },
13
+ color: { control: 'select', options: ColorPalette },
14
+ colorVariant: { control: 'select', options: ColorVariant },
15
+ },
16
+ };
17
+
18
+ export const Default = (args: any) => (
19
+ <InlineList as="p" {...args}>
20
+ <span>Some text</span>
21
+ <span>Some other text</span>
22
+ <span>Some other other text</span>
23
+ </InlineList>
24
+ );
25
+
26
+ export const MixedNoWrapAndTruncate = (args: any) => (
27
+ <InlineList typography="body1" color="dark" colorVariant="L2" {...args} style={{ width: '100%' }}>
28
+ <Text as="span" truncate>
29
+ Very very very very very long text
30
+ </Text>
31
+ <Text as="span" noWrap>
32
+ <Icon icon={mdiEarth} />
33
+ Some text
34
+ </Text>
35
+ <Text as="span" truncate>
36
+ Very very very very very long text
37
+ </Text>
38
+ </InlineList>
39
+ );
40
+ MixedNoWrapAndTruncate.decorators = [withResizableBox({ width: 400 })];
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ import { shallow } from 'enzyme';
4
+ import 'jest-enzyme';
5
+
6
+ import { commonTestsSuite } from '@lumx/react/testing/utils';
7
+ import { InlineList, InlineListProps } from '.';
8
+
9
+ const setup = (props: Partial<InlineListProps> = {}) => {
10
+ const wrapper = shallow(<InlineList {...props} />);
11
+ return { props, wrapper };
12
+ };
13
+
14
+ describe(`<${InlineList.displayName}>`, () => {
15
+ describe('Snapshots and structure', () => {
16
+ it('should render default', () => {
17
+ const { wrapper } = setup({ children: ['Some text', 'Some other text'] });
18
+ expect(wrapper).toHaveDisplayName('ul');
19
+ expect(wrapper.prop('className')).toBe(InlineList.className);
20
+ expect(wrapper.find('li')).toHaveLength(2);
21
+ });
22
+
23
+ it('should render with typography', () => {
24
+ const { wrapper } = setup({ typography: 'body2', children: 'Some text' });
25
+ expect(wrapper).toHaveClassName('lumx-typography-body2');
26
+ });
27
+
28
+ it('should render with color', () => {
29
+ const { wrapper } = setup({ color: 'blue', children: 'Some text' });
30
+ expect(wrapper).toHaveClassName('lumx-color-font-blue-N');
31
+ });
32
+
33
+ it('should render with color and variant', () => {
34
+ const { wrapper } = setup({ color: 'blue', colorVariant: 'D2', children: 'Some text' });
35
+ expect(wrapper).toHaveClassName('lumx-color-font-blue-D2');
36
+ });
37
+ });
38
+
39
+ // Common tests suite.
40
+ commonTestsSuite(setup, { className: 'wrapper', prop: 'wrapper' }, { className: InlineList.className });
41
+ });
@@ -0,0 +1,80 @@
1
+ import React, { Children, forwardRef, isValidElement } from 'react';
2
+
3
+ import classNames from 'classnames';
4
+
5
+ import { ColorPalette, ColorVariant, Typography } from '@lumx/react';
6
+ import { Comp, GenericProps } from '@lumx/react/utils/type';
7
+ import { getFontColorClassName, getRootClassName, getTypographyClassName } from '@lumx/react/utils/className';
8
+
9
+ /**
10
+ * Defines the props of the component.
11
+ */
12
+ export interface InlineListProps extends GenericProps {
13
+ /**
14
+ * Text color.
15
+ */
16
+ color?: ColorPalette;
17
+ /**
18
+ * Lightened or darkened variant of the selected color.
19
+ */
20
+ colorVariant?: ColorVariant;
21
+ /**
22
+ * Typography variant.
23
+ */
24
+ typography?: Typography;
25
+ }
26
+
27
+ /**
28
+ * Component display name.
29
+ */
30
+ const COMPONENT_NAME = 'InlineList';
31
+
32
+ /**
33
+ * Component default class name and class prefix.
34
+ */
35
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
36
+
37
+ /**
38
+ * Component default props.
39
+ */
40
+ const DEFAULT_PROPS = {} as const;
41
+
42
+ /**
43
+ * InlineList component.
44
+ *
45
+ * @param props Component props.
46
+ * @param ref Component ref.
47
+ * @return React element.
48
+ */
49
+ export const InlineList: Comp<InlineListProps> = forwardRef((props, ref) => {
50
+ const { className, color, colorVariant, typography, children, ...forwardedProps } = props;
51
+ const fontColorClassName = color && getFontColorClassName(color, colorVariant);
52
+ const typographyClassName = typography && getTypographyClassName(typography);
53
+ return (
54
+ // eslint-disable-next-line jsx-a11y/no-redundant-roles
55
+ <ul
56
+ {...forwardedProps}
57
+ ref={ref as any}
58
+ className={classNames(className, CLASSNAME, fontColorClassName, typographyClassName)}
59
+ // Lists with removed bullet style can lose their a11y list role on some browsers
60
+ role="list"
61
+ >
62
+ {Children.toArray(children).map((child, index) => {
63
+ const key = (isValidElement(child) && child.key) || index;
64
+ return (
65
+ <li key={key} className={`${CLASSNAME}__item`}>
66
+ {index !== 0 && (
67
+ <span className={`${CLASSNAME}__item-separator`} aria-hidden="true">
68
+ {'\u00A0•\u00A0'}
69
+ </span>
70
+ )}
71
+ {child}
72
+ </li>
73
+ );
74
+ })}
75
+ </ul>
76
+ );
77
+ });
78
+ InlineList.displayName = COMPONENT_NAME;
79
+ InlineList.className = CLASSNAME;
80
+ InlineList.defaultProps = DEFAULT_PROPS;
@@ -0,0 +1 @@
1
+ export * from './InlineList';
@@ -1,38 +1,37 @@
1
1
  import React from 'react';
2
- import { ColorPalette, ColorVariant, TypographyCustom, TypographyInterface } from '@lumx/react';
2
+ import { ColorPalette, ColorVariant, Icon, TypographyCustom, TypographyInterface } from '@lumx/react';
3
+ import { mdiEarth, mdiHeart } from '@lumx/icons';
4
+ import { withResizableBox } from '@lumx/react/stories/withResizableBox';
3
5
  import { Text } from './Text';
4
6
 
5
7
  export default { title: 'LumX components/text/Text' };
6
8
 
7
9
  export const Default = () => <Text as="p">Some text</Text>;
8
10
 
9
- const withResizableBox = (Story: any) => (
10
- <div
11
- style={{
12
- width: 150,
13
- height: 60,
14
- border: '1px solid red',
15
- resize: 'both',
16
- overflow: 'hidden',
17
- }}
18
- >
19
- <Story />
20
- </div>
21
- );
22
-
23
- export const Truncate = () => (
24
- <Text as="p" truncate>
25
- Some very very very long text
11
+ export const WithIcon = (args) => (
12
+ <Text as="p" {...args}>
13
+ Some text <Icon icon={mdiHeart} /> with icons <Icon icon={mdiEarth} />
26
14
  </Text>
27
15
  );
28
- Truncate.decorators = [withResizableBox];
29
16
 
30
- export const TruncateMultiline = () => (
31
- <Text as="p" truncate={{ lines: 2 }}>
17
+ export const LongText = (args) => (
18
+ <Text as="p" {...args}>
32
19
  Some very very very very very very very very very long text
33
20
  </Text>
34
21
  );
35
- TruncateMultiline.decorators = [withResizableBox];
22
+ LongText.decorators = [withResizableBox()];
23
+
24
+ export const NoWrap = LongText.bind({});
25
+ NoWrap.args = { noWrap: true };
26
+ NoWrap.decorators = [withResizableBox()];
27
+
28
+ export const Truncate = LongText.bind({});
29
+ Truncate.args = { truncate: true };
30
+ Truncate.decorators = [withResizableBox()];
31
+
32
+ export const TruncateMultiline = LongText.bind({});
33
+ TruncateMultiline.args = { truncate: { lines: 2 } };
34
+ TruncateMultiline.decorators = [withResizableBox()];
36
35
 
37
36
  export const AllTypography = () => {
38
37
  const typographies = [undefined, ...Object.values(TypographyInterface), ...Object.values(TypographyCustom)];
@@ -42,9 +41,7 @@ export const AllTypography = () => {
42
41
  <tr key={typography}>
43
42
  <td>{typography}</td>
44
43
  <td>
45
- <Text as="p" typography={typography}>
46
- Some text
47
- </Text>
44
+ <WithIcon typography={typography} />
48
45
  </td>
49
46
  </tr>
50
47
  ))}
@@ -68,9 +65,7 @@ export const AllColor = () => {
68
65
  <td>{color}</td>
69
66
  {colorVariants.map((colorVariant) => (
70
67
  <td key={colorVariant}>
71
- <Text as="p" color={color} colorVariant={colorVariant}>
72
- Some text
73
- </Text>
68
+ <WithIcon color={color} colorVariant={colorVariant} />
74
69
  </td>
75
70
  ))}
76
71
  </tr>
@@ -4,7 +4,9 @@ import { shallow } from 'enzyme';
4
4
  import 'jest-enzyme';
5
5
 
6
6
  import { commonTestsSuite } from '@lumx/react/testing/utils';
7
- import { Text, TextProps } from './Text';
7
+ import { mdiEarth } from '@lumx/icons';
8
+ import { Icon } from '@lumx/react';
9
+ import { Text, TextProps } from '.';
8
10
 
9
11
  const setup = (props: Partial<TextProps> = {}) => {
10
12
  const wrapper = shallow(<Text as="span" {...props} />);
@@ -55,6 +57,18 @@ describe(`<${Text.displayName}>`, () => {
55
57
  expect(wrapper).toHaveClassName('lumx-text--is-truncated-multiline');
56
58
  expect(wrapper).toHaveStyle({ '--lumx-text-truncate-lines': 2 });
57
59
  });
60
+
61
+ it('should render noWrap', () => {
62
+ const { wrapper } = setup({ noWrap: true });
63
+ expect(wrapper).toHaveDisplayName('span');
64
+ expect(wrapper).toHaveClassName('lumx-text--no-wrap');
65
+ });
66
+
67
+ it('should wrap icons with spaces', () => {
68
+ const { wrapper } = setup({ children: ['Some text', <Icon key="icon" icon={mdiEarth} />, 'with icon'] });
69
+ // Spaces have been inserted around the icon.
70
+ expect(wrapper).toHaveText('Some text with icon');
71
+ });
58
72
  });
59
73
 
60
74
  // Common tests suite.
@@ -1,12 +1,15 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { Children, Fragment, forwardRef } from 'react';
2
2
 
3
- import { Color, ColorVariant, Typography } from '@lumx/react';
4
- import { Comp, GenericProps, HeadingElement } from '@lumx/react/utils/type';
5
- import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
3
+ import { Icon, ColorPalette, ColorVariant, Typography } from '@lumx/react';
4
+ import { Comp, GenericProps, TextElement, isComponent } from '@lumx/react/utils/type';
5
+ import {
6
+ getFontColorClassName,
7
+ getRootClassName,
8
+ handleBasicClasses,
9
+ getTypographyClassName,
10
+ } from '@lumx/react/utils/className';
6
11
  import classNames from 'classnames';
7
12
 
8
- type TextComponents = 'span' | 'p' | HeadingElement;
9
-
10
13
  /**
11
14
  * Defines the props of the component.
12
15
  */
@@ -14,7 +17,7 @@ export interface TextProps extends GenericProps {
14
17
  /**
15
18
  * Color variant.
16
19
  */
17
- color?: Color;
20
+ color?: ColorPalette;
18
21
  /**
19
22
  * Lightened or darkened variant of the selected color.
20
23
  */
@@ -26,13 +29,18 @@ export interface TextProps extends GenericProps {
26
29
  /**
27
30
  * Custom component to render the text.
28
31
  */
29
- as: TextComponents;
32
+ as: TextElement;
30
33
  /**
31
34
  * Control whether the text should truncate or not.
32
35
  * Setting as `true` will make the text truncate on a single line.
33
36
  * Setting as `{ lines: number }` will make the text truncate on a multiple lines.
34
37
  */
35
38
  truncate?: boolean | { lines: number };
39
+ /**
40
+ * Prevents text to wrap on multiple lines
41
+ * (automatically activated when single line text truncate is activated).
42
+ */
43
+ noWrap?: boolean;
36
44
  }
37
45
 
38
46
  /**
@@ -58,11 +66,21 @@ const DEFAULT_PROPS = {} as const;
58
66
  * @return React element.
59
67
  */
60
68
  export const Text: Comp<TextProps> = forwardRef((props, ref) => {
61
- const { as, children, className, color, colorVariant, typography, truncate, style, ...forwardedProps } = props;
69
+ const {
70
+ as: Component,
71
+ children,
72
+ className,
73
+ color,
74
+ colorVariant,
75
+ noWrap,
76
+ typography,
77
+ truncate,
78
+ style,
79
+ ...forwardedProps
80
+ } = props;
62
81
 
63
- const Component = as as TextComponents;
64
- const colorClass = color && `lumx-color-font-${color}-${colorVariant || ColorVariant.N}`;
65
- const typographyClass = typography && `lumx-typography-${typography}`;
82
+ const colorClass = color && getFontColorClassName(color, colorVariant);
83
+ const typographyClass = typography && getTypographyClassName(typography);
66
84
 
67
85
  // Truncate mode
68
86
  const truncateLinesStyle = typeof truncate === 'object' &&
@@ -82,11 +100,18 @@ export const Text: Comp<TextProps> = forwardRef((props, ref) => {
82
100
  }),
83
101
  typographyClass,
84
102
  colorClass,
103
+ noWrap && `${CLASSNAME}--no-wrap`,
85
104
  )}
86
105
  style={{ ...truncateLinesStyle, ...style }}
87
106
  {...forwardedProps}
88
107
  >
89
- {children}
108
+ {Children.toArray(children).map((child, index) => {
109
+ // Force wrap spaces around icons to make sure they are never stuck against text.
110
+ if (isComponent(Icon)(child)) {
111
+ return <Fragment key={child.key || index}> {child} </Fragment>;
112
+ }
113
+ return child;
114
+ })}
90
115
  </Component>
91
116
  );
92
117
  });
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export * from './components/heading';
24
24
  export * from './components/grid';
25
25
  export * from './components/icon';
26
26
  export * from './components/image-block';
27
+ export * from './components/inline-list';
27
28
  export * from './components/input-helper';
28
29
  export * from './components/input-label';
29
30
  export * from './components/lightbox';
@@ -3,4 +3,6 @@
3
3
  */
4
4
  export default { title: 'LumX components/generic-block/GenericBlock Demos' };
5
5
 
6
- export { App as Default } from './default';
6
+ export { App as HorizontalAlignment } from './horizontal-alignment';
7
+ export { App as Sizes } from './sizes';
8
+ export { App as VerticalAlignment } from './vertical-alignment';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * File generated when storybook is started. Do not edit directly!
3
+ */
4
+ export default { title: 'LumX components/heading/Heading Demos' };
5
+
6
+ export { App as AutomaticHeadingLevel } from './automatic-heading-level';
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ /** Storybook decorator wrapping story in a resizable box */
4
+ // eslint-disable-next-line react/display-name
5
+ export const withResizableBox = ({ width = 150, height = 50 } = {}) => (Story: any) => (
6
+ <div
7
+ style={{
8
+ display: 'flex',
9
+ width,
10
+ height,
11
+ border: '1px solid red',
12
+ resize: 'both',
13
+ overflow: 'hidden',
14
+ }}
15
+ >
16
+ <Story />
17
+ </div>
18
+ );
@@ -1,6 +1,7 @@
1
1
  import { CSS_PREFIX } from '@lumx/react/constants';
2
2
 
3
3
  import kebabCase from 'lodash/kebabCase';
4
+ import { ColorPalette, ColorVariant, Typography } from '@lumx/react/components';
4
5
 
5
6
  // See https://regex101.com/r/YjS1uI/3
6
7
  const LAST_PART_CLASSNAME = /^(.*)-(.+)$/gi;
@@ -25,3 +26,19 @@ export function getRootClassName(componentName: string, subComponent?: boolean):
25
26
  }
26
27
  return formattedClassName;
27
28
  }
29
+
30
+ /**
31
+ * Returns the classname associated to the given color and variant.
32
+ * For example, for 'dark' and 'L2' it returns `lumx-color-font-dark-l2`
33
+ */
34
+ export const getFontColorClassName = (color: ColorPalette, colorVariant: ColorVariant = ColorVariant.N) => {
35
+ return `lumx-color-font-${color}-${colorVariant}`;
36
+ };
37
+
38
+ /**
39
+ * Returns the classname associated to the given typography.
40
+ * For example, for `Typography.title` it returns `lumx-typography-title`
41
+ */
42
+ export const getTypographyClassName = (typography: Typography) => {
43
+ return `lumx-typography-${typography}`;
44
+ };
package/src/utils/type.ts CHANGED
@@ -35,6 +35,9 @@ export type Comp<P, T = HTMLElement> = {
35
35
  /** Union type of all heading elements */
36
36
  export type HeadingElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
37
37
 
38
+ /** Union type of all text elements */
39
+ export type TextElement = 'span' | 'p' | HeadingElement;
40
+
38
41
  export interface HasTheme {
39
42
  /**
40
43
  * Theme adapting the component to light or dark background.