@lumx/react 3.12.0 → 3.12.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/index.d.ts +10 -2
- package/index.js +105 -144
- package/index.js.map +1 -1
- package/package.json +11 -11
- package/src/components/button/Button.test.tsx +2 -1
- package/src/components/button/Button.tsx +13 -3
- package/src/components/input-label/InputLabel.stories.tsx +9 -0
- package/src/components/input-label/InputLabel.test.tsx +8 -1
- package/src/components/input-label/InputLabel.tsx +11 -4
- package/src/components/link/Link.stories.tsx +54 -23
- package/src/components/link/Link.test.tsx +10 -8
- package/src/components/link/Link.tsx +41 -88
- package/src/components/text/Text.tsx +5 -11
- package/src/components/text-field/TextField.stories.tsx +15 -1
- package/src/stories/controls/icons.ts +1 -0
- package/src/testing/utils/commonTestsSuiteRTL.tsx +5 -3
- package/src/utils/react/wrapChildrenIconWithSpaces.test.tsx +40 -0
- package/src/utils/react/wrapChildrenIconWithSpaces.tsx +22 -0
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"url": "https://github.com/lumapps/design-system/issues"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@lumx/core": "^3.12.0",
|
|
10
|
-
"@lumx/icons": "^3.12.0",
|
|
9
|
+
"@lumx/core": "^3.12.1-alpha.0",
|
|
10
|
+
"@lumx/icons": "^3.12.1-alpha.0",
|
|
11
11
|
"@popperjs/core": "^2.5.4",
|
|
12
12
|
"body-scroll-lock": "^3.1.5",
|
|
13
13
|
"classnames": "^2.3.2",
|
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
"react-popper": "^2.2.4"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@babel/core": "^7.
|
|
18
|
+
"@babel/core": "^7.26.10",
|
|
19
19
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
20
|
-
"@babel/plugin-proposal-export-default-from": "^7.
|
|
20
|
+
"@babel/plugin-proposal-export-default-from": "^7.25.9",
|
|
21
21
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
|
22
|
-
"@babel/plugin-proposal-object-rest-spread": "^7.
|
|
23
|
-
"@babel/plugin-proposal-optional-chaining": "^7.
|
|
22
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
23
|
+
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
|
24
24
|
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
|
25
|
-
"@babel/plugin-proposal-private-property-in-object": "^7.
|
|
26
|
-
"@babel/preset-env": "^7.
|
|
27
|
-
"@babel/preset-react": "^7.
|
|
28
|
-
"@babel/preset-typescript": "^7.
|
|
25
|
+
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
|
26
|
+
"@babel/preset-env": "^7.26.9",
|
|
27
|
+
"@babel/preset-react": "^7.26.3",
|
|
28
|
+
"@babel/preset-typescript": "^7.26.0",
|
|
29
29
|
"@rollup/plugin-babel": "^6.0.4",
|
|
30
30
|
"@rollup/plugin-commonjs": "^19.0.2",
|
|
31
31
|
"@rollup/plugin-node-resolve": "16.0.0",
|
|
@@ -110,5 +110,5 @@
|
|
|
110
110
|
"build:storybook": "storybook build"
|
|
111
111
|
},
|
|
112
112
|
"sideEffects": false,
|
|
113
|
-
"version": "3.12.0"
|
|
113
|
+
"version": "3.12.1-alpha.0"
|
|
114
114
|
}
|
|
@@ -72,10 +72,11 @@ describe(`<${Button.displayName}>`, () => {
|
|
|
72
72
|
forwardAttributes: 'button',
|
|
73
73
|
forwardRef: 'button',
|
|
74
74
|
applyTheme: {
|
|
75
|
-
affects: [{ element: 'button' }],
|
|
75
|
+
affects: [{ element: 'button' }, { not: { element: 'icons' } }],
|
|
76
76
|
viaProp: true,
|
|
77
77
|
viaContext: true,
|
|
78
78
|
defaultTheme: 'light',
|
|
79
|
+
defaultProps: { rightIcon: mdiPlus, leftIcon: mdiCheck },
|
|
79
80
|
},
|
|
80
81
|
});
|
|
81
82
|
});
|
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import isEmpty from 'lodash/isEmpty';
|
|
5
5
|
|
|
6
|
-
import { Emphasis, Icon, Size, Theme, Text } from '@lumx/react';
|
|
6
|
+
import { Emphasis, Icon, Size, Theme, Text, ThemeProvider } from '@lumx/react';
|
|
7
7
|
import { isComponent } from '@lumx/react/utils/type';
|
|
8
8
|
import { getBasicClass, getRootClassName } from '@lumx/react/utils/className';
|
|
9
9
|
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
@@ -73,9 +73,19 @@ export const Button = forwardRef<ButtonProps, HTMLButtonElement | HTMLAnchorElem
|
|
|
73
73
|
className={buttonClassName}
|
|
74
74
|
variant="button"
|
|
75
75
|
>
|
|
76
|
-
{leftIcon && !isEmpty(leftIcon) &&
|
|
76
|
+
{leftIcon && !isEmpty(leftIcon) && (
|
|
77
|
+
// Theme is handled in the button scss
|
|
78
|
+
<ThemeProvider value={undefined}>
|
|
79
|
+
<Icon icon={leftIcon} />
|
|
80
|
+
</ThemeProvider>
|
|
81
|
+
)}
|
|
77
82
|
{children && (isComponent(Text)(children) ? children : <span>{children}</span>)}
|
|
78
|
-
{rightIcon && !isEmpty(rightIcon) &&
|
|
83
|
+
{rightIcon && !isEmpty(rightIcon) && (
|
|
84
|
+
// Theme is handled in the button scss
|
|
85
|
+
<ThemeProvider value={undefined}>
|
|
86
|
+
<Icon icon={rightIcon} />
|
|
87
|
+
</ThemeProvider>
|
|
88
|
+
)}
|
|
79
89
|
</ButtonRoot>
|
|
80
90
|
);
|
|
81
91
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
import { Typography } from '@lumx/react';
|
|
4
|
+
|
|
3
5
|
import { InputLabel } from './InputLabel';
|
|
4
6
|
|
|
5
7
|
export default {
|
|
@@ -36,3 +38,10 @@ export const Default = {};
|
|
|
36
38
|
export const IsRequired = {
|
|
37
39
|
args: { isRequired: true },
|
|
38
40
|
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Default input label
|
|
44
|
+
*/
|
|
45
|
+
export const WithCustomTypography = {
|
|
46
|
+
args: { typography: Typography.subtitle1 },
|
|
47
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import { Theme } from '@lumx/react';
|
|
3
|
+
import { Theme, Typography } from '@lumx/react';
|
|
4
4
|
import { getByClassName } from '@lumx/react/testing/utils/queries';
|
|
5
|
+
import { getTypographyClassName } from '@lumx/react/utils/className';
|
|
5
6
|
import { render } from '@testing-library/react';
|
|
6
7
|
import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
|
|
7
8
|
import { InputLabel, InputLabelProps } from './InputLabel';
|
|
@@ -38,6 +39,12 @@ describe(`<${InputLabel.displayName}>`, () => {
|
|
|
38
39
|
expect(label).toHaveClass(`${CLASSNAME}--theme-dark`);
|
|
39
40
|
expect(label).toHaveClass(`${CLASSNAME}--is-required`);
|
|
40
41
|
});
|
|
42
|
+
|
|
43
|
+
it('should render typography', () => {
|
|
44
|
+
const { label } = setup({ children: 'The label', typography: Typography.body1 });
|
|
45
|
+
expect(label).toHaveClass(CLASSNAME);
|
|
46
|
+
expect(label).toHaveClass(getTypographyClassName(Typography.body1));
|
|
47
|
+
});
|
|
41
48
|
});
|
|
42
49
|
|
|
43
50
|
commonTestsSuiteRTL(setup, {
|
|
@@ -2,9 +2,9 @@ import React, { ReactNode } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
5
|
-
import { Theme } from '@lumx/react';
|
|
5
|
+
import { Theme, Typography } from '@lumx/react';
|
|
6
6
|
import { GenericProps, HasTheme } from '@lumx/react/utils/type';
|
|
7
|
-
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
7
|
+
import { getRootClassName, handleBasicClasses, getTypographyClassName } from '@lumx/react/utils/className';
|
|
8
8
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
9
9
|
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
10
10
|
|
|
@@ -12,6 +12,8 @@ import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
|
12
12
|
* Defines the props of the component.
|
|
13
13
|
*/
|
|
14
14
|
export interface InputLabelProps extends GenericProps, HasTheme {
|
|
15
|
+
/** Typography variant. */
|
|
16
|
+
typography?: Typography;
|
|
15
17
|
/** Label content. */
|
|
16
18
|
children: string | ReactNode;
|
|
17
19
|
/** Native htmlFor property. */
|
|
@@ -44,14 +46,19 @@ const DEFAULT_PROPS: Partial<InputLabelProps> = {};
|
|
|
44
46
|
*/
|
|
45
47
|
export const InputLabel = forwardRef<InputLabelProps, HTMLLabelElement>((props, ref) => {
|
|
46
48
|
const defaultTheme = useTheme() || Theme.light;
|
|
47
|
-
const { children, className, htmlFor, isRequired, theme = defaultTheme, ...forwardedProps } = props;
|
|
49
|
+
const { children, className, htmlFor, isRequired, theme = defaultTheme, typography, ...forwardedProps } = props;
|
|
50
|
+
const typographyClass = typography && getTypographyClassName(typography);
|
|
48
51
|
|
|
49
52
|
return (
|
|
50
53
|
<label
|
|
51
54
|
ref={ref}
|
|
52
55
|
{...forwardedProps}
|
|
53
56
|
htmlFor={htmlFor}
|
|
54
|
-
className={classNames(
|
|
57
|
+
className={classNames(
|
|
58
|
+
className,
|
|
59
|
+
handleBasicClasses({ prefix: CLASSNAME, isRequired, theme, hasCustomTypography: Boolean(typography) }),
|
|
60
|
+
typographyClass,
|
|
61
|
+
)}
|
|
55
62
|
>
|
|
56
63
|
{children}
|
|
57
64
|
</label>
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ColorPalette,
|
|
3
|
+
ColorVariant,
|
|
4
|
+
FlexBox,
|
|
5
|
+
Icon,
|
|
6
|
+
Link,
|
|
7
|
+
Typography,
|
|
8
|
+
TypographyInterface,
|
|
9
|
+
TypographyTitleCustom,
|
|
10
|
+
} from '@lumx/react';
|
|
2
11
|
import React from 'react';
|
|
3
12
|
import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
|
|
4
13
|
import { colorArgType, colorVariantArgType } from '@lumx/react/stories/controls/color';
|
|
@@ -6,6 +15,9 @@ import { iconArgType } from '@lumx/react/stories/controls/icons';
|
|
|
6
15
|
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
7
16
|
import { withUndefined } from '@lumx/react/stories/controls/withUndefined';
|
|
8
17
|
import { CustomLink } from '@lumx/react/stories/utils/CustomLink';
|
|
18
|
+
import { withWrapper } from '@lumx/react/stories/decorators/withWrapper';
|
|
19
|
+
import { withThemedBackground } from '@lumx/react/stories/decorators/withThemedBackground';
|
|
20
|
+
import { mdiEarth, mdiFoodApple, mdiPencil } from '@lumx/icons/override/generated';
|
|
9
21
|
|
|
10
22
|
const linkTypographies = { ...TypographyInterface, ...TypographyTitleCustom };
|
|
11
23
|
|
|
@@ -29,28 +41,6 @@ export const Default = {
|
|
|
29
41
|
args: { href: 'https://example.com', target: '_blank' },
|
|
30
42
|
};
|
|
31
43
|
|
|
32
|
-
/**
|
|
33
|
-
* Disabled
|
|
34
|
-
*/
|
|
35
|
-
export const Disabled = {
|
|
36
|
-
args: { ...Default.args, children: 'Link (disabled)', isDisabled: true },
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Using onClick transforms the link into a <button> in DOM
|
|
41
|
-
*/
|
|
42
|
-
export const ButtonLink = {
|
|
43
|
-
argTypes: { onClick: { action: true } },
|
|
44
|
-
args: { children: 'Button link' },
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Button link disabled
|
|
49
|
-
*/
|
|
50
|
-
export const ButtonLinkDisabled = {
|
|
51
|
-
args: { ...ButtonLink.args, children: 'Button link (disabled)', isDisabled: true },
|
|
52
|
-
};
|
|
53
|
-
|
|
54
44
|
/**
|
|
55
45
|
* Use custom component instead of <a> or <button>
|
|
56
46
|
*/
|
|
@@ -76,10 +66,51 @@ export const WithCustomizableTypography = {
|
|
|
76
66
|
</>
|
|
77
67
|
),
|
|
78
68
|
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Show state combinations
|
|
72
|
+
*/
|
|
73
|
+
export const AllStates = {
|
|
74
|
+
argTypes: {
|
|
75
|
+
isDisabled: { control: false },
|
|
76
|
+
},
|
|
77
|
+
decorators: [
|
|
78
|
+
withThemedBackground(),
|
|
79
|
+
withCombinations({
|
|
80
|
+
combinations: {
|
|
81
|
+
sections: {
|
|
82
|
+
Default: {},
|
|
83
|
+
'with icon': {
|
|
84
|
+
children: ['Link', <Icon key="icon" icon={mdiEarth} />, 'with icon'],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
cols: {
|
|
88
|
+
Default: {},
|
|
89
|
+
Disabled: { isDisabled: true },
|
|
90
|
+
Focused: { 'data-focus-visible-added': true },
|
|
91
|
+
Hovered: { 'data-lumx-hover': true },
|
|
92
|
+
},
|
|
93
|
+
rows: {
|
|
94
|
+
Default: {},
|
|
95
|
+
'color=red': { color: 'red' },
|
|
96
|
+
'theme=dark': { theme: 'dark' },
|
|
97
|
+
'theme=dark & color=red': { theme: 'dark', color: 'red' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
withWrapper({ orientation: 'horizontal', vAlign: 'space-evenly', wrap: true, gap: 'huge' }, FlexBox),
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
79
105
|
/**
|
|
80
106
|
* Show all typographies
|
|
81
107
|
*/
|
|
82
108
|
export const AllTypography = {
|
|
109
|
+
args: {
|
|
110
|
+
children: ['Link', <Icon key="icon" icon={mdiEarth} />, 'with icon'],
|
|
111
|
+
rightIcon: mdiPencil,
|
|
112
|
+
leftIcon: mdiFoodApple,
|
|
113
|
+
},
|
|
83
114
|
argTypes: {
|
|
84
115
|
typography: { control: false },
|
|
85
116
|
},
|
|
@@ -15,11 +15,9 @@ const CLASSNAME = Link.className as string;
|
|
|
15
15
|
const setup = (props: LinkProps = {}) => {
|
|
16
16
|
render(<Link {...props} />);
|
|
17
17
|
const link = getByClassName(document.body, CLASSNAME);
|
|
18
|
-
const content = queryByClassName(link, `${CLASSNAME}__content`);
|
|
19
18
|
const rightIcon = queryByClassName(link, `${CLASSNAME}__right-icon`);
|
|
20
19
|
const leftIcon = queryByClassName(link, `${CLASSNAME}__left-icon`);
|
|
21
|
-
|
|
22
|
-
return { props, link, content, rightIcon, leftIcon };
|
|
20
|
+
return { props, link, rightIcon, leftIcon };
|
|
23
21
|
};
|
|
24
22
|
|
|
25
23
|
describe(`<${Link.displayName}>`, () => {
|
|
@@ -42,14 +40,12 @@ describe(`<${Link.displayName}>`, () => {
|
|
|
42
40
|
color: ColorPalette.primary,
|
|
43
41
|
colorVariant: ColorVariant.D1,
|
|
44
42
|
});
|
|
45
|
-
expect(link.className).
|
|
46
|
-
'"lumx-link lumx-link--color-primary lumx-link--color-variant-D1"',
|
|
47
|
-
);
|
|
43
|
+
expect(link.className).toBe('lumx-link lumx-link--color-primary lumx-link--color-variant-D1');
|
|
48
44
|
});
|
|
49
45
|
|
|
50
46
|
it('should render typography', () => {
|
|
51
|
-
const {
|
|
52
|
-
expect(
|
|
47
|
+
const { link } = setup({ href: 'https://google.com', typography: Typography.title });
|
|
48
|
+
expect(link.className).toBe('lumx-link lumx-typography-title');
|
|
53
49
|
});
|
|
54
50
|
|
|
55
51
|
it('should render a button', () => {
|
|
@@ -59,6 +55,12 @@ describe(`<${Link.displayName}>`, () => {
|
|
|
59
55
|
expect(link).toBe(screen.queryByRole('button', { name }));
|
|
60
56
|
});
|
|
61
57
|
|
|
58
|
+
it('should render disabled link as button', () => {
|
|
59
|
+
const name = 'Link';
|
|
60
|
+
const { link } = setup({ href: 'https://google.com', isDisabled: true, children: name });
|
|
61
|
+
expect(link).toBe(screen.queryByRole('button', { name }));
|
|
62
|
+
});
|
|
63
|
+
|
|
62
64
|
it('should render with icons', () => {
|
|
63
65
|
const { rightIcon, leftIcon } = setup({ rightIcon: mdiPlus, leftIcon: mdiCheck });
|
|
64
66
|
expect(rightIcon).toBeInTheDocument();
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import React
|
|
2
|
-
|
|
3
|
-
import isEmpty from 'lodash/isEmpty';
|
|
1
|
+
import React from 'react';
|
|
4
2
|
|
|
5
3
|
import classNames from 'classnames';
|
|
6
4
|
|
|
7
|
-
import { ColorPalette, ColorVariant, Icon,
|
|
5
|
+
import { ColorPalette, ColorVariant, Icon, Typography } from '@lumx/react';
|
|
8
6
|
import { GenericProps } from '@lumx/react/utils/type';
|
|
9
|
-
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
10
|
-
import { renderLink } from '@lumx/react/utils/react/renderLink';
|
|
7
|
+
import { getRootClassName, getTypographyClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
11
8
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
9
|
+
import { wrapChildrenIconWithSpaces } from '@lumx/react/utils/react/wrapChildrenIconWithSpaces';
|
|
12
10
|
|
|
13
11
|
type HTMLAnchorProps = React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
14
12
|
|
|
@@ -24,11 +22,17 @@ export interface LinkProps extends GenericProps {
|
|
|
24
22
|
href?: HTMLAnchorProps['href'];
|
|
25
23
|
/** Whether the component is disabled or not. */
|
|
26
24
|
isDisabled?: boolean;
|
|
27
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Left icon (SVG path).
|
|
27
|
+
* @deprecated Just nest `<Icon />` in the children
|
|
28
|
+
*/
|
|
28
29
|
leftIcon?: string;
|
|
29
30
|
/** Custom react component for the link (can be used to inject react router Link). */
|
|
30
31
|
linkAs?: 'a' | any;
|
|
31
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Right icon (SVG path).
|
|
34
|
+
* @deprecated Just nest `<Icon />` in the children
|
|
35
|
+
*/
|
|
32
36
|
rightIcon?: string;
|
|
33
37
|
/** Link target. */
|
|
34
38
|
target?: HTMLAnchorProps['target'];
|
|
@@ -48,36 +52,6 @@ const COMPONENT_NAME = 'Link';
|
|
|
48
52
|
*/
|
|
49
53
|
const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
50
54
|
|
|
51
|
-
const getIconSize = (typography?: Typography) => {
|
|
52
|
-
switch (typography) {
|
|
53
|
-
case Typography.display1:
|
|
54
|
-
return Size.m;
|
|
55
|
-
|
|
56
|
-
case Typography.headline:
|
|
57
|
-
case Typography.title:
|
|
58
|
-
case Typography.custom.title1:
|
|
59
|
-
case Typography.custom.title2:
|
|
60
|
-
case Typography.custom.title3:
|
|
61
|
-
case Typography.custom.title4:
|
|
62
|
-
case Typography.custom.title5:
|
|
63
|
-
case Typography.custom.title6:
|
|
64
|
-
case Typography.body2:
|
|
65
|
-
case Typography.subtitle2:
|
|
66
|
-
return Size.s;
|
|
67
|
-
|
|
68
|
-
case Typography.body1:
|
|
69
|
-
case Typography.subtitle1:
|
|
70
|
-
return Size.xs;
|
|
71
|
-
|
|
72
|
-
case Typography.caption:
|
|
73
|
-
case Typography.overline:
|
|
74
|
-
return Size.xxs;
|
|
75
|
-
|
|
76
|
-
default:
|
|
77
|
-
return Size.s;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
55
|
/**
|
|
82
56
|
* Link component.
|
|
83
57
|
*
|
|
@@ -101,58 +75,37 @@ export const Link = forwardRef<LinkProps, HTMLAnchorElement | HTMLButtonElement>
|
|
|
101
75
|
typography,
|
|
102
76
|
...forwardedProps
|
|
103
77
|
} = props;
|
|
104
|
-
const renderedChildren = useMemo(
|
|
105
|
-
() => (
|
|
106
|
-
<>
|
|
107
|
-
{leftIcon && !isEmpty(leftIcon) && (
|
|
108
|
-
<Icon icon={leftIcon} className={`${CLASSNAME}__left-icon`} size={getIconSize(typography)} />
|
|
109
|
-
)}
|
|
110
|
-
|
|
111
|
-
{children && (
|
|
112
|
-
<span
|
|
113
|
-
className={classNames(`${CLASSNAME}__content`, {
|
|
114
|
-
[`lumx-typography-${typography}`]: typography,
|
|
115
|
-
})}
|
|
116
|
-
>
|
|
117
|
-
{children}
|
|
118
|
-
</span>
|
|
119
|
-
)}
|
|
120
|
-
|
|
121
|
-
{rightIcon && !isEmpty(rightIcon) && (
|
|
122
|
-
<Icon icon={rightIcon} className={`${CLASSNAME}__right-icon`} size={getIconSize(typography)} />
|
|
123
|
-
)}
|
|
124
|
-
</>
|
|
125
|
-
),
|
|
126
|
-
[leftIcon, typography, children, rightIcon],
|
|
127
|
-
);
|
|
128
78
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
ref={ref as RefObject<HTMLButtonElement>}
|
|
139
|
-
disabled={isDisabled}
|
|
140
|
-
className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, color, colorVariant }))}
|
|
141
|
-
>
|
|
142
|
-
{renderedChildren}
|
|
143
|
-
</button>
|
|
144
|
-
);
|
|
79
|
+
const isLink = linkAs || href;
|
|
80
|
+
const Component = isLink && !isDisabled ? linkAs || 'a' : 'button';
|
|
81
|
+
const baseProps: React.ComponentProps<typeof Component> = {};
|
|
82
|
+
if (Component === 'button') {
|
|
83
|
+
baseProps.type = 'button';
|
|
84
|
+
baseProps.disabled = isDisabled;
|
|
85
|
+
} else if (isLink) {
|
|
86
|
+
baseProps.href = href;
|
|
87
|
+
baseProps.target = target;
|
|
145
88
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
className
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Component
|
|
92
|
+
ref={ref}
|
|
93
|
+
{...forwardedProps}
|
|
94
|
+
{...baseProps}
|
|
95
|
+
className={classNames(
|
|
96
|
+
className,
|
|
97
|
+
handleBasicClasses({ prefix: CLASSNAME, color, colorVariant }),
|
|
98
|
+
typography && getTypographyClassName(typography),
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
{wrapChildrenIconWithSpaces(
|
|
102
|
+
<>
|
|
103
|
+
{leftIcon && <Icon icon={leftIcon} className={`${CLASSNAME}__left-icon`} />}
|
|
104
|
+
{children}
|
|
105
|
+
{rightIcon && <Icon icon={rightIcon} className={`${CLASSNAME}__right-icon`} />}
|
|
106
|
+
</>,
|
|
107
|
+
)}
|
|
108
|
+
</Component>
|
|
156
109
|
);
|
|
157
110
|
});
|
|
158
111
|
Link.displayName = COMPONENT_NAME;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { GenericProps, TextElement
|
|
5
|
+
import { ColorPalette, ColorVariant, Typography, WhiteSpace } from '@lumx/react';
|
|
6
|
+
import { GenericProps, TextElement } from '@lumx/react/utils/type';
|
|
7
7
|
import {
|
|
8
8
|
getFontColorClassName,
|
|
9
9
|
getRootClassName,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { useOverflowTooltipLabel } from '@lumx/react/hooks/useOverflowTooltipLabel';
|
|
14
14
|
import { useMergeRefs } from '@lumx/react/utils/react/mergeRefs';
|
|
15
15
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
16
|
+
import { wrapChildrenIconWithSpaces } from '@lumx/react/utils/react/wrapChildrenIconWithSpaces';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Defines the props of the component.
|
|
@@ -132,14 +133,7 @@ export const Text = forwardRef<TextProps>((props, ref) => {
|
|
|
132
133
|
style={{ ...truncateLinesStyle, ...whiteSpaceStyle, ...style }}
|
|
133
134
|
{...forwardedProps}
|
|
134
135
|
>
|
|
135
|
-
{children
|
|
136
|
-
Children.toArray(children).map((child, index) => {
|
|
137
|
-
// Force wrap spaces around icons to make sure they are never stuck against text.
|
|
138
|
-
if (isComponent(Icon)(child)) {
|
|
139
|
-
return <Fragment key={child.key || index}> {child} </Fragment>;
|
|
140
|
-
}
|
|
141
|
-
return child;
|
|
142
|
-
})}
|
|
136
|
+
{wrapChildrenIconWithSpaces(children)}
|
|
143
137
|
</Component>
|
|
144
138
|
);
|
|
145
139
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { mdiTranslate } from '@lumx/icons';
|
|
3
|
-
import { Chip, IconButton, TextField } from '@lumx/react';
|
|
3
|
+
import { Chip, IconButton, TextField, Typography } from '@lumx/react';
|
|
4
4
|
import { withValueOnChange } from '@lumx/react/stories/decorators/withValueOnChange';
|
|
5
5
|
import { loremIpsum } from '@lumx/react/stories/utils/lorem';
|
|
6
6
|
|
|
@@ -47,6 +47,20 @@ export const LabelAndHelper = {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* With custom label and helper
|
|
52
|
+
*/
|
|
53
|
+
export const CustomLabelAndHelper = {
|
|
54
|
+
args: {
|
|
55
|
+
...Default.args,
|
|
56
|
+
label: 'Textfield label',
|
|
57
|
+
labelProps: {
|
|
58
|
+
typography: Typography.subtitle1,
|
|
59
|
+
},
|
|
60
|
+
helper: loremIpsum('tiny'),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
50
64
|
/**
|
|
51
65
|
* With clear button
|
|
52
66
|
*/
|
|
@@ -30,6 +30,8 @@ interface Options<S extends CommonSetup> {
|
|
|
30
30
|
viaContext: boolean;
|
|
31
31
|
/** Apply a default theme if no prop or context was provided */
|
|
32
32
|
defaultTheme?: Theme;
|
|
33
|
+
/** Default props to apply when testing theme */
|
|
34
|
+
defaultProps?: S['props'];
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -128,7 +130,7 @@ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<
|
|
|
128
130
|
it.each(testElements)(
|
|
129
131
|
`should $apply default theme (${defaultTheme}) to \`$element\``,
|
|
130
132
|
async (affectedElement) => {
|
|
131
|
-
const wrappers = await setup();
|
|
133
|
+
const wrappers = await setup(applyTheme.defaultProps);
|
|
132
134
|
expectTheme(wrappers, affectedElement, defaultTheme);
|
|
133
135
|
},
|
|
134
136
|
);
|
|
@@ -140,7 +142,7 @@ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<
|
|
|
140
142
|
it.each(affectedElements)(
|
|
141
143
|
`should not apply default theme (${defaultTheme}) to \`$element\``,
|
|
142
144
|
async (affectedElement) => {
|
|
143
|
-
const wrappers = await setup();
|
|
145
|
+
const wrappers = await setup(applyTheme.defaultProps);
|
|
144
146
|
expectTheme(wrappers, affectedElement, Theme.light, { shouldHaveModifier: false });
|
|
145
147
|
expectTheme(wrappers, affectedElement, Theme.dark, { shouldHaveModifier: false });
|
|
146
148
|
},
|
|
@@ -152,7 +154,7 @@ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<
|
|
|
152
154
|
it.each(testElements)(
|
|
153
155
|
`should $apply prop theme=${theme} to \`$element\``,
|
|
154
156
|
async (affectedElement) => {
|
|
155
|
-
const wrappers = await setup({ theme });
|
|
157
|
+
const wrappers = await setup({ ...applyTheme.defaultProps, theme });
|
|
156
158
|
expectTheme(wrappers, affectedElement, theme);
|
|
157
159
|
},
|
|
158
160
|
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@lumx/react';
|
|
4
|
+
import { mdiEarth, mdiFoodApple, mdiPencil } from '@lumx/icons';
|
|
5
|
+
import { wrapChildrenIconWithSpaces } from './wrapChildrenIconWithSpaces';
|
|
6
|
+
|
|
7
|
+
describe(wrapChildrenIconWithSpaces, () => {
|
|
8
|
+
it('should ignore null or undefined children', () => {
|
|
9
|
+
expect(wrapChildrenIconWithSpaces(undefined)).toBeUndefined();
|
|
10
|
+
expect(wrapChildrenIconWithSpaces(null)).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should wrap icons with spaces', () => {
|
|
14
|
+
expect(
|
|
15
|
+
wrapChildrenIconWithSpaces(
|
|
16
|
+
<>
|
|
17
|
+
<Icon icon={mdiEarth} />a string
|
|
18
|
+
<>
|
|
19
|
+
some more string with
|
|
20
|
+
<Icon icon={mdiFoodApple} />
|
|
21
|
+
</>
|
|
22
|
+
{['array with', <Icon key="custom-key" icon={mdiPencil} />]}
|
|
23
|
+
</>,
|
|
24
|
+
),
|
|
25
|
+
).toEqual([
|
|
26
|
+
' ',
|
|
27
|
+
<Icon key=".0" icon={mdiEarth} />,
|
|
28
|
+
' ',
|
|
29
|
+
'a string',
|
|
30
|
+
'some more string with',
|
|
31
|
+
' ',
|
|
32
|
+
<Icon key=".1" icon={mdiFoodApple} />,
|
|
33
|
+
' ',
|
|
34
|
+
'array with',
|
|
35
|
+
' ',
|
|
36
|
+
<Icon key=".3:$custom-key" icon={mdiPencil} />,
|
|
37
|
+
' ',
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React, { Children } from 'react';
|
|
2
|
+
import { isComponentType } from '@lumx/react/utils/type';
|
|
3
|
+
import { Icon } from '@lumx/react';
|
|
4
|
+
|
|
5
|
+
/** Force wrap spaces around icons to make sure they are never stuck against text. */
|
|
6
|
+
export function wrapChildrenIconWithSpaces(children: React.ReactNode): React.ReactNode {
|
|
7
|
+
if (children === null || children === undefined) return undefined;
|
|
8
|
+
return Children.toArray(children).flatMap((child) => {
|
|
9
|
+
if (!React.isValidElement(child)) {
|
|
10
|
+
return child;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (child.type === React.Fragment) {
|
|
14
|
+
return wrapChildrenIconWithSpaces(child.props.children);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isComponentType(Icon)(child)) {
|
|
18
|
+
return [' ', child, ' '];
|
|
19
|
+
}
|
|
20
|
+
return child;
|
|
21
|
+
});
|
|
22
|
+
}
|