@lumx/react 3.6.1-alpha.1 → 3.6.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 +13 -1
- package/index.d.ts +8 -2
- package/index.js +25 -4
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/index.ts +13 -0
- package/src/components/text/Text.stories.tsx +26 -1
- package/src/components/text/Text.test.tsx +6 -0
- package/src/components/text/Text.tsx +18 -2
- package/src/components/tooltip/Tooltip.test.tsx +32 -35
- package/src/components/tooltip/useTooltipOpen.tsx +1 -1
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.6.1
|
|
11
|
-
"@lumx/icons": "^3.6.1
|
|
10
|
+
"@lumx/core": "^3.6.1",
|
|
11
|
+
"@lumx/icons": "^3.6.1",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.3.2",
|
|
@@ -113,5 +113,5 @@
|
|
|
113
113
|
"build:storybook": "storybook build"
|
|
114
114
|
},
|
|
115
115
|
"sideEffects": false,
|
|
116
|
-
"version": "3.6.1
|
|
116
|
+
"version": "3.6.1"
|
|
117
117
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -172,6 +172,19 @@ export const Kind = {
|
|
|
172
172
|
} as const;
|
|
173
173
|
export type Kind = ValueOf<typeof Kind>;
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* All available white-space values
|
|
177
|
+
* */
|
|
178
|
+
export const WhiteSpace = {
|
|
179
|
+
normal: 'normal',
|
|
180
|
+
nowrap: 'nowrap',
|
|
181
|
+
pre: 'pre',
|
|
182
|
+
'pre-wrap': 'pre-wrap',
|
|
183
|
+
'pre-line': 'pre-line',
|
|
184
|
+
'break-spaces': 'break-spaces',
|
|
185
|
+
};
|
|
186
|
+
export type WhiteSpace = ValueOf<typeof WhiteSpace>;
|
|
187
|
+
|
|
175
188
|
/**
|
|
176
189
|
* Re-exporting some utils types.
|
|
177
190
|
*/
|
|
@@ -6,9 +6,10 @@ import { textElementArgType } from '@lumx/react/stories/controls/element';
|
|
|
6
6
|
import { withUndefined } from '@lumx/react/stories/controls/withUndefined';
|
|
7
7
|
import { loremIpsum } from '@lumx/react/stories/utils/lorem';
|
|
8
8
|
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
9
|
-
import { ColorPalette, ColorVariant, Icon } from '@lumx/react';
|
|
9
|
+
import { ColorPalette, ColorVariant, Icon, WhiteSpace } from '@lumx/react';
|
|
10
10
|
import { mdiEarth, mdiHeart } from '@lumx/icons';
|
|
11
11
|
import { withResizableBox } from '@lumx/react/stories/decorators/withResizableBox';
|
|
12
|
+
import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
|
|
12
13
|
|
|
13
14
|
import { Text } from './Text';
|
|
14
15
|
|
|
@@ -22,6 +23,7 @@ export default {
|
|
|
22
23
|
typography: allTypographyArgType,
|
|
23
24
|
color: colorArgType,
|
|
24
25
|
colorVariant: colorVariantArgType,
|
|
26
|
+
whiteSpace: getSelectArgType(WhiteSpace),
|
|
25
27
|
},
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -71,6 +73,29 @@ export const NoWrap = {
|
|
|
71
73
|
},
|
|
72
74
|
};
|
|
73
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Long text with line breaks
|
|
78
|
+
*/
|
|
79
|
+
export const AllWhiteSpace = {
|
|
80
|
+
args: {
|
|
81
|
+
...Default.args,
|
|
82
|
+
children: `
|
|
83
|
+
But ere she from the church-door stepped She smiled and told us why: 'It was a wicked woman's curse,' Quoth she,
|
|
84
|
+
'and what care I?' She smiled, and smiled, and passed it off Ere from the door she stept—
|
|
85
|
+
`,
|
|
86
|
+
},
|
|
87
|
+
decorators: [
|
|
88
|
+
withCombinations({
|
|
89
|
+
combinations: {
|
|
90
|
+
rows: { key: 'whiteSpace', options: Object.values(WhiteSpace) },
|
|
91
|
+
},
|
|
92
|
+
tableStyle: { width: 500, tableLayout: 'fixed' },
|
|
93
|
+
firstColStyle: { width: 100 },
|
|
94
|
+
cellStyle: { border: '1px solid #000', width: '100%' },
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
|
|
74
99
|
/**
|
|
75
100
|
* Long text with single line truncate ellipsis
|
|
76
101
|
*/
|
|
@@ -61,6 +61,12 @@ describe(`<${Text.displayName}>`, () => {
|
|
|
61
61
|
expect(element).toHaveClass('lumx-text--no-wrap');
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
it('should render with custom whiteSpace', () => {
|
|
65
|
+
const { element } = setup({ whiteSpace: 'pre-wrap' });
|
|
66
|
+
expect(element.tagName).toBe('SPAN');
|
|
67
|
+
expect(element).toHaveStyle({ '--lumx-text-white-space': 'pre-wrap' });
|
|
68
|
+
});
|
|
69
|
+
|
|
64
70
|
it('should wrap icons with spaces', () => {
|
|
65
71
|
const { element } = setup({ children: ['Some text', <Icon key="icon" icon={mdiEarth} />, 'with icon'] });
|
|
66
72
|
// Spaces have been inserted around the icon.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { Children, Fragment, forwardRef } from 'react';
|
|
2
2
|
|
|
3
|
-
import { Icon, ColorPalette, ColorVariant, Typography } from '@lumx/react';
|
|
3
|
+
import { Icon, ColorPalette, ColorVariant, Typography, WhiteSpace } from '@lumx/react';
|
|
4
4
|
import { Comp, GenericProps, TextElement, isComponent } from '@lumx/react/utils/type';
|
|
5
5
|
import {
|
|
6
6
|
getFontColorClassName,
|
|
@@ -41,6 +41,12 @@ export interface TextProps extends GenericProps {
|
|
|
41
41
|
* (automatically activated when single line text truncate is activated).
|
|
42
42
|
*/
|
|
43
43
|
noWrap?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* WhiteSpace variant
|
|
46
|
+
* Ignored when `noWrap` is set to true
|
|
47
|
+
* Ignored when `truncate` is set to true or lines: 1
|
|
48
|
+
* */
|
|
49
|
+
whiteSpace?: WhiteSpace;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
@@ -75,6 +81,7 @@ export const Text: Comp<TextProps> = forwardRef((props, ref) => {
|
|
|
75
81
|
noWrap,
|
|
76
82
|
typography,
|
|
77
83
|
truncate,
|
|
84
|
+
whiteSpace,
|
|
78
85
|
style,
|
|
79
86
|
...forwardedProps
|
|
80
87
|
} = props;
|
|
@@ -88,6 +95,15 @@ export const Text: Comp<TextProps> = forwardRef((props, ref) => {
|
|
|
88
95
|
const isTruncatedMultiline = !!truncateLinesStyle;
|
|
89
96
|
const isTruncated = !!truncate;
|
|
90
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Add custom white-space style if specified
|
|
100
|
+
* Disabled if noWrap is specified
|
|
101
|
+
* Disabled if truncated on one-line
|
|
102
|
+
* */
|
|
103
|
+
const whiteSpaceStyle = !noWrap &&
|
|
104
|
+
!(isTruncated && !isTruncatedMultiline) &&
|
|
105
|
+
whiteSpace && { '--lumx-text-white-space': whiteSpace };
|
|
106
|
+
|
|
91
107
|
return (
|
|
92
108
|
<Component
|
|
93
109
|
ref={ref as React.Ref<any>}
|
|
@@ -102,7 +118,7 @@ export const Text: Comp<TextProps> = forwardRef((props, ref) => {
|
|
|
102
118
|
colorClass,
|
|
103
119
|
noWrap && `${CLASSNAME}--no-wrap`,
|
|
104
120
|
)}
|
|
105
|
-
style={{ ...truncateLinesStyle, ...style }}
|
|
121
|
+
style={{ ...truncateLinesStyle, ...whiteSpaceStyle, ...style }}
|
|
106
122
|
{...forwardedProps}
|
|
107
123
|
>
|
|
108
124
|
{children &&
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { Button } from '@lumx/react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
findByClassName,
|
|
7
|
-
getByClassName,
|
|
8
|
-
queryAllByTagName,
|
|
9
|
-
queryByClassName,
|
|
10
|
-
} from '@lumx/react/testing/utils/queries';
|
|
4
|
+
import { screen, render, waitFor } from '@testing-library/react';
|
|
5
|
+
import { queryAllByTagName, queryByClassName } from '@lumx/react/testing/utils/queries';
|
|
11
6
|
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
12
7
|
import userEvent from '@testing-library/user-event';
|
|
13
8
|
|
|
@@ -16,23 +11,19 @@ import { Tooltip, TooltipProps } from './Tooltip';
|
|
|
16
11
|
const CLASSNAME = Tooltip.className as string;
|
|
17
12
|
|
|
18
13
|
jest.mock('uid', () => ({ uid: () => 'uid' }));
|
|
14
|
+
// Skip delays
|
|
15
|
+
jest.mock('@lumx/react/constants', () => ({
|
|
16
|
+
...jest.requireActual('@lumx/react/constants'),
|
|
17
|
+
TOOLTIP_HOVER_DELAY: { open: 0, close: 0 },
|
|
18
|
+
}));
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
22
22
|
*/
|
|
23
23
|
const setup = async (propsOverride: Partial<TooltipProps> = {}) => {
|
|
24
|
-
const props: any = { forceOpen: true, ...propsOverride };
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Promise.resolve(
|
|
28
|
-
render(
|
|
29
|
-
<Tooltip label="Tooltip" {...props}>
|
|
30
|
-
{props.children || 'Anchor'}
|
|
31
|
-
</Tooltip>,
|
|
32
|
-
),
|
|
33
|
-
),
|
|
34
|
-
);
|
|
35
|
-
const tooltip = queryByClassName(document.body, CLASSNAME);
|
|
24
|
+
const props: any = { forceOpen: true, label: 'Tooltip label', children: 'Anchor', ...propsOverride };
|
|
25
|
+
render(<Tooltip {...props} />);
|
|
26
|
+
const tooltip = screen.queryByRole('tooltip', { name: props.label });
|
|
36
27
|
const anchorWrapper = queryByClassName(document.body, 'lumx-tooltip-anchor-wrapper');
|
|
37
28
|
return { props, tooltip, anchorWrapper };
|
|
38
29
|
};
|
|
@@ -67,7 +58,7 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
67
58
|
});
|
|
68
59
|
expect(tooltip).toBeInTheDocument();
|
|
69
60
|
expect(anchorWrapper).not.toBeInTheDocument();
|
|
70
|
-
const button =
|
|
61
|
+
const button = screen.queryByRole('button', { name: 'Anchor' });
|
|
71
62
|
expect(button).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
72
63
|
});
|
|
73
64
|
|
|
@@ -80,7 +71,7 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
80
71
|
expect(tooltip).toBeInTheDocument();
|
|
81
72
|
expect(anchorWrapper).toBeInTheDocument();
|
|
82
73
|
expect(anchorWrapper).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
83
|
-
const button =
|
|
74
|
+
const button = screen.queryByRole('button', { name: 'Anchor' });
|
|
84
75
|
expect(button?.parentElement).toBe(anchorWrapper);
|
|
85
76
|
});
|
|
86
77
|
|
|
@@ -108,11 +99,11 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
108
99
|
expect(tooltip).not.toBeInTheDocument();
|
|
109
100
|
|
|
110
101
|
// Hover anchor button
|
|
111
|
-
const button =
|
|
102
|
+
const button = screen.getByRole('button', { name: 'Anchor' });
|
|
112
103
|
await userEvent.hover(button);
|
|
113
104
|
|
|
114
105
|
// Tooltip opened
|
|
115
|
-
tooltip = await
|
|
106
|
+
tooltip = await screen.findByRole('tooltip', { name: 'Tooltip label' });
|
|
116
107
|
expect(tooltip).toBeInTheDocument();
|
|
117
108
|
expect(button).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
118
109
|
|
|
@@ -135,11 +126,11 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
135
126
|
expect(tooltip).not.toBeInTheDocument();
|
|
136
127
|
|
|
137
128
|
// Hover anchor button
|
|
138
|
-
const button =
|
|
129
|
+
const button = screen.getByRole('button', { name: 'Anchor' });
|
|
139
130
|
await userEvent.hover(button);
|
|
140
131
|
|
|
141
132
|
// Tooltip opened
|
|
142
|
-
tooltip = await
|
|
133
|
+
tooltip = await screen.findByRole('tooltip', { name: 'Tooltip label' });
|
|
143
134
|
expect(tooltip).toBeInTheDocument();
|
|
144
135
|
expect(button).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
145
136
|
|
|
@@ -157,7 +148,7 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
157
148
|
});
|
|
158
149
|
});
|
|
159
150
|
|
|
160
|
-
it('should activate on anchor focus', async () => {
|
|
151
|
+
it('should activate on anchor focus and close on escape', async () => {
|
|
161
152
|
let { tooltip } = await setup({
|
|
162
153
|
label: 'Tooltip label',
|
|
163
154
|
children: <Button>Anchor</Button>,
|
|
@@ -168,21 +159,27 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
168
159
|
|
|
169
160
|
// Focus anchor button
|
|
170
161
|
await userEvent.tab();
|
|
171
|
-
const button =
|
|
162
|
+
const button = screen.getByRole('button', { name: 'Anchor' });
|
|
172
163
|
expect(button).toHaveFocus();
|
|
173
164
|
|
|
174
165
|
// Tooltip opened
|
|
175
|
-
tooltip = await
|
|
166
|
+
tooltip = await screen.findByRole('tooltip', { name: 'Tooltip label' });
|
|
176
167
|
expect(tooltip).toBeInTheDocument();
|
|
177
168
|
expect(button).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
178
169
|
|
|
179
|
-
// Focus next element
|
|
180
|
-
userEvent.tab();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
});
|
|
170
|
+
// Focus next element => close tooltip
|
|
171
|
+
await userEvent.tab();
|
|
172
|
+
expect(button).not.toHaveFocus();
|
|
173
|
+
expect(tooltip).not.toBeInTheDocument();
|
|
174
|
+
|
|
175
|
+
// Focus again
|
|
176
|
+
await userEvent.tab({ shift: true });
|
|
177
|
+
tooltip = await screen.findByRole('tooltip', { name: 'Tooltip label' });
|
|
178
|
+
expect(tooltip).toBeInTheDocument();
|
|
179
|
+
|
|
180
|
+
// Escape pressed => close tooltip
|
|
181
|
+
await userEvent.keyboard('{Escape}');
|
|
182
|
+
expect(tooltip).not.toBeInTheDocument();
|
|
186
183
|
});
|
|
187
184
|
});
|
|
188
185
|
|
|
@@ -17,7 +17,7 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
|
|
|
17
17
|
|
|
18
18
|
// Global close on escape
|
|
19
19
|
const [closeCallback, setCloseCallback] = useState<undefined | (() => void)>(undefined);
|
|
20
|
-
useCallbackOnEscape(closeCallback);
|
|
20
|
+
useCallbackOnEscape(isOpen ? closeCallback : undefined);
|
|
21
21
|
|
|
22
22
|
useEffect(() => {
|
|
23
23
|
if (!anchorElement) {
|