@lumx/react 2.2.17 → 2.2.19

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.
Files changed (30) hide show
  1. package/esm/_internal/SideNavigationItem.js +8 -4
  2. package/esm/_internal/SideNavigationItem.js.map +1 -1
  3. package/esm/_internal/Tooltip2.js +10 -12
  4. package/esm/_internal/Tooltip2.js.map +1 -1
  5. package/esm/_internal/UserBlock.js +9 -2
  6. package/esm/_internal/UserBlock.js.map +1 -1
  7. package/esm/_internal/useFocusTrap.js +22 -13
  8. package/esm/_internal/useFocusTrap.js.map +1 -1
  9. package/esm/_internal/user-block.js +1 -0
  10. package/esm/_internal/user-block.js.map +1 -1
  11. package/esm/index.js +1 -0
  12. package/esm/index.js.map +1 -1
  13. package/package.json +5 -5
  14. package/src/components/dialog/Dialog.stories.tsx +4 -1
  15. package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +85 -77
  16. package/src/components/side-navigation/SideNavigation.stories.tsx +26 -0
  17. package/src/components/side-navigation/SideNavigationItem.test.tsx +19 -2
  18. package/src/components/side-navigation/SideNavigationItem.tsx +10 -2
  19. package/src/components/side-navigation/__snapshots__/SideNavigationItem.test.tsx.snap +1 -1
  20. package/src/components/tooltip/Tooltip.tsx +2 -5
  21. package/src/components/tooltip/useTooltipOpen.tsx +7 -4
  22. package/src/components/user-block/UserBlock.stories.tsx +4 -4
  23. package/src/components/user-block/UserBlock.tsx +9 -3
  24. package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +51 -8
  25. package/src/hooks/useBooleanState.tsx +4 -10
  26. package/src/hooks/useFocusTrap.ts +2 -28
  27. package/src/stories/generated/Dialog/Demos.stories.tsx +1 -0
  28. package/src/utils/focus/getFirstAndLastFocusable.test.ts +128 -0
  29. package/src/utils/focus/getFirstAndLastFocusable.ts +27 -0
  30. package/types.d.ts +6 -1
@@ -1,82 +1,6 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`<Dialog> Snapshots and structure should render story DialogWithAlertDialog 1`] = `
4
- <Fragment>
5
- <Button
6
- emphasis="high"
7
- onClick={[Function]}
8
- size="m"
9
- theme="light"
10
- >
11
- Open dialog
12
- </Button>
13
- <Dialog
14
- isOpen={true}
15
- onClose={[Function]}
16
- parentElement={
17
- Object {
18
- "current": undefined,
19
- }
20
- }
21
- size="big"
22
- >
23
- <div
24
- className="lumx-spacing-padding"
25
- >
26
-
27
- Nihil hic munitissimus habendi senatus locus, nihil horum? At nos hinc posthac, sitientis piros
28
- Afros. Magna pars studiorum, prodita quaerimus. Integer legentibus erat a ante historiarum
29
- dapibus. Praeterea iter est quasdam res quas ex communi. Ullamco laboris nisi ut aliquid ex ea
30
- commodi consequat. Inmensae subtilitatis, obscuris et malesuada fames. Me non paenitet nullum
31
- festiviorem excogitasse ad hoc. Cum ceteris in veneratione tui montes, nascetur mus. Etiam
32
- habebis sem dicantur magna mollis euismod. Quis aute iure reprehenderit in voluptate velit esse.
33
- Phasellus laoreet lorem vel dolor tempus vehicula. Ambitioni dedisse scripsisse iudicaretur.
34
- Paullum deliquit, ponderibus modulisque suis ratio utitur. Ab illo tempore, ab est sed
35
- immemorabili. Nec dubitamus multa iter quae et nos invenerat. Tu quoque, Brute, fili mi, nihil
36
- timor populi, nihil! Morbi fringilla convallis sapien, id pulvinar odio volutpat. Cras mattis
37
- iudicium purus sit amet fermentum. Vivamus sagittis lacus vel augue laoreet rutrum faucibus.
38
- Quisque ut dolor gravida, placerat libero vel, euismod. Unam incolunt Belgae, aliam Aquitani,
39
- tertiam. Cras mattis iudicium purus sit amet fermentum
40
- </div>
41
- <footer>
42
- <Toolbar
43
- after={
44
- <Button
45
- emphasis="low"
46
- onClick={[Function]}
47
- size="m"
48
- theme="light"
49
- >
50
- Close
51
- </Button>
52
- }
53
- />
54
- </footer>
55
- </Dialog>
56
- <AlertDialog
57
- confirmProps={
58
- Object {
59
- "label": "Confirm",
60
- "onClick": [Function],
61
- }
62
- }
63
- isOpen={false}
64
- kind="info"
65
- onClose={[Function]}
66
- parentElement={
67
- Object {
68
- "current": undefined,
69
- }
70
- }
71
- size="tiny"
72
- title="Default (info)"
73
- >
74
- Consequat deserunt officia aute laborum tempor anim sint est.
75
- </AlertDialog>
76
- </Fragment>
77
- `;
78
-
79
- exports[`<Dialog> Snapshots and structure should render story DialogWithFocusableElements 1`] = `
3
+ exports[`<Dialog> Snapshots and structure should render story DialogFocusTrap 1`] = `
80
4
  <Fragment>
81
5
  <Button
82
6
  emphasis="high"
@@ -226,11 +150,95 @@ exports[`<Dialog> Snapshots and structure should render story DialogWithFocusabl
226
150
  >
227
151
  Focus div
228
152
  </div>
153
+ <Button
154
+ emphasis="high"
155
+ isDisabled={false}
156
+ size="m"
157
+ theme="light"
158
+ >
159
+ Button explicitly not disabled (should focus)
160
+ </Button>
229
161
  </div>
230
162
  </Dialog>
231
163
  </Fragment>
232
164
  `;
233
165
 
166
+ exports[`<Dialog> Snapshots and structure should render story DialogWithAlertDialog 1`] = `
167
+ <Fragment>
168
+ <Button
169
+ emphasis="high"
170
+ onClick={[Function]}
171
+ size="m"
172
+ theme="light"
173
+ >
174
+ Open dialog
175
+ </Button>
176
+ <Dialog
177
+ isOpen={true}
178
+ onClose={[Function]}
179
+ parentElement={
180
+ Object {
181
+ "current": undefined,
182
+ }
183
+ }
184
+ size="big"
185
+ >
186
+ <div
187
+ className="lumx-spacing-padding"
188
+ >
189
+
190
+ Nihil hic munitissimus habendi senatus locus, nihil horum? At nos hinc posthac, sitientis piros
191
+ Afros. Magna pars studiorum, prodita quaerimus. Integer legentibus erat a ante historiarum
192
+ dapibus. Praeterea iter est quasdam res quas ex communi. Ullamco laboris nisi ut aliquid ex ea
193
+ commodi consequat. Inmensae subtilitatis, obscuris et malesuada fames. Me non paenitet nullum
194
+ festiviorem excogitasse ad hoc. Cum ceteris in veneratione tui montes, nascetur mus. Etiam
195
+ habebis sem dicantur magna mollis euismod. Quis aute iure reprehenderit in voluptate velit esse.
196
+ Phasellus laoreet lorem vel dolor tempus vehicula. Ambitioni dedisse scripsisse iudicaretur.
197
+ Paullum deliquit, ponderibus modulisque suis ratio utitur. Ab illo tempore, ab est sed
198
+ immemorabili. Nec dubitamus multa iter quae et nos invenerat. Tu quoque, Brute, fili mi, nihil
199
+ timor populi, nihil! Morbi fringilla convallis sapien, id pulvinar odio volutpat. Cras mattis
200
+ iudicium purus sit amet fermentum. Vivamus sagittis lacus vel augue laoreet rutrum faucibus.
201
+ Quisque ut dolor gravida, placerat libero vel, euismod. Unam incolunt Belgae, aliam Aquitani,
202
+ tertiam. Cras mattis iudicium purus sit amet fermentum
203
+ </div>
204
+ <footer>
205
+ <Toolbar
206
+ after={
207
+ <Button
208
+ emphasis="low"
209
+ onClick={[Function]}
210
+ size="m"
211
+ theme="light"
212
+ >
213
+ Close
214
+ </Button>
215
+ }
216
+ />
217
+ </footer>
218
+ </Dialog>
219
+ <AlertDialog
220
+ confirmProps={
221
+ Object {
222
+ "label": "Confirm",
223
+ "onClick": [Function],
224
+ }
225
+ }
226
+ isOpen={false}
227
+ kind="info"
228
+ onClose={[Function]}
229
+ parentElement={
230
+ Object {
231
+ "current": undefined,
232
+ }
233
+ }
234
+ size="tiny"
235
+ title="Default (info)"
236
+ >
237
+ Consequat deserunt officia aute laborum tempor anim sint est.
238
+ </AlertDialog>
239
+ </Fragment>
240
+ `;
241
+
234
242
  exports[`<Dialog> Snapshots and structure should render story DialogWithHeaderFooterAndDivider 1`] = `
235
243
  <Fragment>
236
244
  <Button
@@ -163,3 +163,29 @@ export const With3LevelsAndMultiActions = () => {
163
163
  </SideNavigation>
164
164
  );
165
165
  };
166
+
167
+ /** Using closeMode="hide" keeps children in DOM on close */
168
+ export const CloseModeHide = () => {
169
+ const [isOpen, setIsOpen] = React.useState(false);
170
+ const toggleL1 = () => setIsOpen(!isOpen);
171
+
172
+ return (
173
+ <SideNavigation>
174
+ <SideNavigationItem
175
+ closeMode="hide"
176
+ label="Level 1"
177
+ emphasis={Emphasis.high}
178
+ isOpen={isOpen}
179
+ onClick={toggleL1}
180
+ toggleButtonProps={{ label: 'Toggle' }}
181
+ >
182
+ <SideNavigationItem
183
+ closeMode="hide"
184
+ label="Level 2"
185
+ emphasis={Emphasis.medium}
186
+ toggleButtonProps={{ label: 'Toggle' }}
187
+ />
188
+ </SideNavigationItem>
189
+ </SideNavigation>
190
+ );
191
+ };
@@ -2,6 +2,7 @@ import React, { ReactElement } from 'react';
2
2
 
3
3
  import { mount, shallow } from 'enzyme';
4
4
  import 'jest-enzyme';
5
+ import without from 'lodash/without';
5
6
 
6
7
  import { commonTestsSuite, Wrapper } from '@lumx/react/testing/utils';
7
8
  import { getBasicClass } from '@lumx/react/utils';
@@ -51,13 +52,28 @@ describe(`<${SideNavigationItem.displayName}>`, () => {
51
52
  expect(root).toHaveClassName(CLASSNAME);
52
53
  });
53
54
 
54
- it('should render correctly with splitted actions', () => {
55
+ it('should render correctly with split actions', () => {
55
56
  const { root, wrapper } = setup({ linkProps: { href: 'http://toto.com' }, onClick: () => null });
56
57
  expect(wrapper).toMatchSnapshot();
57
58
 
58
59
  expect(root).toExist();
59
60
  expect(root).toHaveClassName(CLASSNAME);
60
61
  });
62
+
63
+ it('should unmount children by default when closed', () => {
64
+ const { children } = setup({
65
+ children: <SideNavigationItem label="Child 1" toggleButtonProps={{ label: 'Toggle' }} />,
66
+ });
67
+ expect(children).not.toExist();
68
+ });
69
+
70
+ it('should keep children in DOM when closed and with closeMode="hide"', () => {
71
+ const { children } = setup({
72
+ closeMode: 'hide',
73
+ children: <SideNavigationItem key="1" label="Child 1" toggleButtonProps={{ label: 'Toggle' }} />,
74
+ });
75
+ expect(children).toExist();
76
+ });
61
77
  });
62
78
 
63
79
  // 2. Test defaultProps value and important props custom values.
@@ -67,7 +83,8 @@ describe(`<${SideNavigationItem.displayName}>`, () => {
67
83
  it('should use default props', () => {
68
84
  const { root } = setup();
69
85
 
70
- for (const prop of Object.keys(DEFAULT_PROPS)) {
86
+ const propNames = without(Object.keys(DEFAULT_PROPS), 'closeMode');
87
+ for (const prop of propNames) {
71
88
  const className = getBasicClass({ prefix: CLASSNAME, type: prop, value: DEFAULT_PROPS[prop] });
72
89
  if (className) {
73
90
  expect(root).toHaveClassName(className);
@@ -41,6 +41,11 @@ export interface SideNavigationItemProps extends GenericProps {
41
41
  /** Props to pass to the toggle button (minus those already set by the SideNavigationItem props). */
42
42
  toggleButtonProps: Pick<IconButtonProps, 'label'> &
43
43
  Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis' | 'color' | 'size'>;
44
+ /**
45
+ * Choose how the children are hidden when closed
46
+ * ('hide' keeps the children in DOM but hide them, 'unmount' remove the children from the DOM).
47
+ */
48
+ closeMode?: 'hide' | 'unmount';
44
49
  /** On action button click callback. */
45
50
  onActionClick?(evt: React.MouseEvent): void;
46
51
  /** On click callback. */
@@ -62,6 +67,7 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
62
67
  */
63
68
  const DEFAULT_PROPS: Partial<SideNavigationItemProps> = {
64
69
  emphasis: Emphasis.high,
70
+ closeMode: 'unmount',
65
71
  };
66
72
 
67
73
  /**
@@ -85,12 +91,14 @@ export const SideNavigationItem: Comp<SideNavigationItemProps, HTMLLIElement> =
85
91
  onActionClick,
86
92
  onClick,
87
93
  toggleButtonProps,
94
+ closeMode = 'unmount',
88
95
  ...forwardedProps
89
96
  } = props;
90
97
 
91
98
  const content = children && Children.toArray(children).filter(isComponent(SideNavigationItem));
92
99
  const hasContent = !isEmpty(content);
93
100
  const shouldSplitActions = Boolean(onActionClick);
101
+ const showChildren = hasContent && isOpen;
94
102
 
95
103
  return (
96
104
  <li
@@ -100,7 +108,7 @@ export const SideNavigationItem: Comp<SideNavigationItemProps, HTMLLIElement> =
100
108
  className,
101
109
  handleBasicClasses({
102
110
  emphasis,
103
- isOpen,
111
+ isOpen: showChildren,
104
112
  isSelected,
105
113
  prefix: CLASSNAME,
106
114
  }),
@@ -151,7 +159,7 @@ export const SideNavigationItem: Comp<SideNavigationItemProps, HTMLLIElement> =
151
159
  )
152
160
  )}
153
161
 
154
- {hasContent && isOpen && <ul className={`${CLASSNAME}__children`}>{content}</ul>}
162
+ {(closeMode === 'hide' || showChildren) && <ul className={`${CLASSNAME}__children`}>{content}</ul>}
155
163
  </li>
156
164
  );
157
165
  });
@@ -13,7 +13,7 @@ exports[`<SideNavigationItem> Snapshots and structure should render correctly 1`
13
13
  </li>
14
14
  `;
15
15
 
16
- exports[`<SideNavigationItem> Snapshots and structure should render correctly with splitted actions 1`] = `
16
+ exports[`<SideNavigationItem> Snapshots and structure should render correctly with split actions 1`] = `
17
17
  <li
18
18
  className="lumx-side-navigation-item lumx-side-navigation-item--emphasis-high"
19
19
  >
@@ -65,12 +65,9 @@ const ARROW_SIZE = 8;
65
65
  * @return React element.
66
66
  */
67
67
  export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, ref) => {
68
- if (!DOCUMENT) {
69
- // Can't render in SSR.
70
- return null;
71
- }
72
68
  const { label, children, className, delay, placement, forceOpen, ...forwardedProps } = props;
73
- if (!label) {
69
+ // Disable in SSR or without a label.
70
+ if (!DOCUMENT || !label) {
74
71
  return <>{children}</>;
75
72
  }
76
73
 
@@ -95,13 +95,16 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
95
95
  );
96
96
 
97
97
  // Attach events
98
- for (const [node, eventType, evenHandler] of events) {
99
- node.addEventListener(eventType, evenHandler);
98
+ for (const [node, eventType, eventHandler] of events) {
99
+ node.addEventListener(eventType, eventHandler);
100
100
  }
101
101
  return () => {
102
+ // Clear pending timers.
103
+ if (timer) clearTimeout(timer);
104
+
102
105
  // Detach events.
103
- for (const [node, eventType, evenHandler] of events) {
104
- node.removeEventListener(eventType, evenHandler);
106
+ for (const [node, eventType, eventHandler] of events) {
107
+ node.removeEventListener(eventType, eventHandler);
105
108
  }
106
109
  };
107
110
  }, [anchorElement, delay]);
@@ -17,7 +17,7 @@ export const Default = ({ theme }: any) => (
17
17
  theme={theme}
18
18
  name="Emmitt O. Lum"
19
19
  fields={['Creative developer', 'Denpasar']}
20
- avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
20
+ avatarProps={{ image: avatarImageKnob() }}
21
21
  onMouseEnter={logAction('Mouse entered')}
22
22
  onMouseLeave={logAction('Mouse left')}
23
23
  />
@@ -30,7 +30,7 @@ export const Sizes = ({ theme }: any) =>
30
30
  theme={theme}
31
31
  name="Emmitt O. Lum"
32
32
  fields={['Creative developer', 'Denpasar']}
33
- avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
33
+ avatarProps={{ image: avatarImageKnob() }}
34
34
  size={size}
35
35
  onMouseEnter={logAction('Mouse entered')}
36
36
  onMouseLeave={logAction('Mouse left')}
@@ -41,8 +41,9 @@ export const Clickable = ({ theme }: any) => {
41
41
  const baseProps = {
42
42
  theme,
43
43
  name: 'Emmitt O. Lum',
44
+ nameProps: { 'aria-label': 'Emmitt O. Lum - open user profile' },
44
45
  fields: ['Creative developer', 'Denpasar'],
45
- avatarProps: { image: avatarImageKnob(), alt: 'Avatar' },
46
+ avatarProps: { image: avatarImageKnob() },
46
47
  } as any;
47
48
  return (
48
49
  <>
@@ -65,7 +66,6 @@ export const WithBadge = ({ theme }: any) => (
65
66
  fields={['Creative developer', 'Denpasar']}
66
67
  avatarProps={{
67
68
  image: avatarImageKnob(),
68
- alt: 'Avatar',
69
69
  badge: (
70
70
  <Badge color={ColorPalette.blue}>
71
71
  <Icon icon={mdiStar} />
@@ -1,6 +1,7 @@
1
1
  import React, { forwardRef, ReactNode } from 'react';
2
2
  import isEmpty from 'lodash/isEmpty';
3
3
  import classNames from 'classnames';
4
+ import set from 'lodash/set';
4
5
 
5
6
  import { Avatar, ColorPalette, Link, Orientation, Size, Theme } from '@lumx/react';
6
7
  import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
@@ -17,7 +18,7 @@ export type UserBlockSize = Extract<Size, 's' | 'm' | 'l'>;
17
18
  */
18
19
  export interface UserBlockProps extends GenericProps {
19
20
  /** Props to pass to the avatar. */
20
- avatarProps?: AvatarProps;
21
+ avatarProps?: Omit<AvatarProps, 'alt'>;
21
22
  /** Additional fields used to describe the user. */
22
23
  fields?: string[];
23
24
  /** Props to pass to the link wrapping the avatar thumbnail. */
@@ -121,8 +122,12 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
121
122
  color: ColorPalette.dark,
122
123
  });
123
124
  }
125
+ // Disable avatar focus since the name block is the same link / same button.
126
+ if (avatarProps) {
127
+ set(avatarProps, ['thumbnailProps', 'tabIndex'], -1);
128
+ }
124
129
  return <NameComponent {...nProps}>{name}</NameComponent>;
125
- }, [isClickable, linkAs, linkProps, name, nameProps, onClick]);
130
+ }, [avatarProps, isClickable, linkAs, linkProps, name, nameProps, onClick]);
126
131
 
127
132
  const fieldsBlock: ReactNode = fields && componentSize !== Size.s && (
128
133
  <div className={`${CLASSNAME}__fields`}>
@@ -149,7 +154,8 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
149
154
  <Avatar
150
155
  linkAs={linkAs}
151
156
  linkProps={linkProps}
152
- {...avatarProps}
157
+ alt=""
158
+ {...(avatarProps as any)}
153
159
  className={classNames(`${CLASSNAME}__avatar`, avatarProps.className)}
154
160
  size={componentSize}
155
161
  onClick={onClick}
@@ -6,17 +6,23 @@ Array [
6
6
  className="lumx-user-block lumx-user-block--orientation-horizontal lumx-user-block--size-m lumx-user-block--theme-light lumx-user-block--is-clickable"
7
7
  >
8
8
  <Avatar
9
- alt="Avatar"
9
+ alt=""
10
10
  className="lumx-user-block__avatar"
11
11
  image="/demo-assets/avatar1.jpg"
12
12
  onClick={[Function]}
13
13
  size="m"
14
14
  theme="light"
15
+ thumbnailProps={
16
+ Object {
17
+ "tabIndex": -1,
18
+ }
19
+ }
15
20
  />
16
21
  <div
17
22
  className="lumx-user-block__wrapper"
18
23
  >
19
24
  <Link
25
+ aria-label="Emmitt O. Lum - open user profile"
20
26
  className="lumx-user-block__name"
21
27
  color="dark"
22
28
  onClick={[Function]}
@@ -45,7 +51,7 @@ Array [
45
51
  className="lumx-user-block lumx-user-block--orientation-horizontal lumx-user-block--size-m lumx-user-block--theme-light lumx-user-block--is-clickable"
46
52
  >
47
53
  <Avatar
48
- alt="Avatar"
54
+ alt=""
49
55
  className="lumx-user-block__avatar"
50
56
  image="/demo-assets/avatar1.jpg"
51
57
  linkProps={
@@ -55,11 +61,17 @@ Array [
55
61
  }
56
62
  size="m"
57
63
  theme="light"
64
+ thumbnailProps={
65
+ Object {
66
+ "tabIndex": -1,
67
+ }
68
+ }
58
69
  />
59
70
  <div
60
71
  className="lumx-user-block__wrapper"
61
72
  >
62
73
  <Link
74
+ aria-label="Emmitt O. Lum - open user profile"
63
75
  className="lumx-user-block__name"
64
76
  color="dark"
65
77
  href="https://example.com"
@@ -88,17 +100,23 @@ Array [
88
100
  className="lumx-user-block lumx-user-block--orientation-horizontal lumx-user-block--size-m lumx-user-block--theme-light lumx-user-block--is-clickable"
89
101
  >
90
102
  <Avatar
91
- alt="Avatar"
103
+ alt=""
92
104
  className="lumx-user-block__avatar"
93
105
  image="/demo-assets/avatar1.jpg"
94
106
  linkAs={[Function]}
95
107
  size="m"
96
108
  theme="light"
109
+ thumbnailProps={
110
+ Object {
111
+ "tabIndex": -1,
112
+ }
113
+ }
97
114
  />
98
115
  <div
99
116
  className="lumx-user-block__wrapper"
100
117
  >
101
118
  <Link
119
+ aria-label="Emmitt O. Lum - open user profile"
102
120
  className="lumx-user-block__name"
103
121
  color="dark"
104
122
  linkAs={[Function]}
@@ -133,11 +151,16 @@ exports[`<UserBlock> Snapshots and structure should render story 'Default' 1`] =
133
151
  onMouseLeave={[Function]}
134
152
  >
135
153
  <Avatar
136
- alt="Avatar"
154
+ alt=""
137
155
  className="lumx-user-block__avatar"
138
156
  image="/demo-assets/avatar1.jpg"
139
157
  size="m"
140
158
  theme="light"
159
+ thumbnailProps={
160
+ Object {
161
+ "tabIndex": -1,
162
+ }
163
+ }
141
164
  />
142
165
  <div
143
166
  className="lumx-user-block__wrapper"
@@ -175,11 +198,16 @@ Array [
175
198
  onMouseLeave={[Function]}
176
199
  >
177
200
  <Avatar
178
- alt="Avatar"
201
+ alt=""
179
202
  className="lumx-user-block__avatar"
180
203
  image="/demo-assets/avatar1.jpg"
181
204
  size="s"
182
205
  theme="light"
206
+ thumbnailProps={
207
+ Object {
208
+ "tabIndex": -1,
209
+ }
210
+ }
183
211
  />
184
212
  <div
185
213
  className="lumx-user-block__wrapper"
@@ -197,11 +225,16 @@ Array [
197
225
  onMouseLeave={[Function]}
198
226
  >
199
227
  <Avatar
200
- alt="Avatar"
228
+ alt=""
201
229
  className="lumx-user-block__avatar"
202
230
  image="/demo-assets/avatar1.jpg"
203
231
  size="m"
204
232
  theme="light"
233
+ thumbnailProps={
234
+ Object {
235
+ "tabIndex": -1,
236
+ }
237
+ }
205
238
  />
206
239
  <div
207
240
  className="lumx-user-block__wrapper"
@@ -235,11 +268,16 @@ Array [
235
268
  onMouseLeave={[Function]}
236
269
  >
237
270
  <Avatar
238
- alt="Avatar"
271
+ alt=""
239
272
  className="lumx-user-block__avatar"
240
273
  image="/demo-assets/avatar1.jpg"
241
274
  size="l"
242
275
  theme="light"
276
+ thumbnailProps={
277
+ Object {
278
+ "tabIndex": -1,
279
+ }
280
+ }
243
281
  />
244
282
  <div
245
283
  className="lumx-user-block__wrapper"
@@ -275,7 +313,7 @@ exports[`<UserBlock> Snapshots and structure should render story 'WithBadge' 1`]
275
313
  className="lumx-user-block lumx-user-block--orientation-horizontal lumx-user-block--size-m lumx-user-block--theme-light"
276
314
  >
277
315
  <Avatar
278
- alt="Avatar"
316
+ alt=""
279
317
  badge={
280
318
  <Badge
281
319
  color="blue"
@@ -289,6 +327,11 @@ exports[`<UserBlock> Snapshots and structure should render story 'WithBadge' 1`]
289
327
  image="/demo-assets/avatar1.jpg"
290
328
  size="m"
291
329
  theme="light"
330
+ thumbnailProps={
331
+ Object {
332
+ "tabIndex": -1,
333
+ }
334
+ }
292
335
  />
293
336
  <div
294
337
  className="lumx-user-block__wrapper"
@@ -1,19 +1,13 @@
1
- import { useState } from 'react';
1
+ import { useCallback, useState } from 'react';
2
2
 
3
3
  export const useBooleanState = (defaultValue: boolean): [boolean, () => void, () => void, () => void] => {
4
4
  const [booleanValue, setBoolean] = useState<boolean>(defaultValue);
5
5
 
6
- const setToFalse = () => {
7
- setBoolean(false);
8
- };
6
+ const setToFalse = useCallback(() => setBoolean(false), []);
9
7
 
10
- const setToTrue = () => {
11
- setBoolean(true);
12
- };
8
+ const setToTrue = useCallback(() => setBoolean(true), []);
13
9
 
14
- const toggleBoolean = () => {
15
- setBoolean(!booleanValue);
16
- };
10
+ const toggleBoolean = useCallback(() => setBoolean((previousValue) => !previousValue), []);
17
11
 
18
12
  return [booleanValue, setToFalse, setToTrue, toggleBoolean];
19
13
  };
@@ -1,33 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
3
  import { DOCUMENT } from '@lumx/react/constants';
4
-
5
- /** CSS selector listing all tabbable elements. */
6
- const TABBABLE_ELEMENTS_SELECTOR = `a[href]:not([tabindex="-1"], [disabled], [aria-disabled]),
7
- button:not([tabindex="-1"], [disabled], [aria-disabled]),
8
- textarea:not([tabindex="-1"], [disabled], [aria-disabled]),
9
- input[type="text"]:not([tabindex="-1"], [disabled], [aria-disabled]),
10
- input[type="radio"]:not([tabindex="-1"], [disabled], [aria-disabled]),
11
- input[type="checkbox"]:not([tabindex="-1"], [disabled], [aria-disabled]),
12
- [tabindex]:not([tabindex="-1"], [disabled], [aria-disabled])`;
13
-
14
- /**
15
- * Get first and last elements focusable in an element.
16
- *
17
- * @param parentElement The element in which to search focusable elements.
18
- * @return first and last focusable elements
19
- */
20
- function getFocusable(parentElement: HTMLElement) {
21
- const focusableElements = parentElement.querySelectorAll<HTMLElement>(TABBABLE_ELEMENTS_SELECTOR);
22
-
23
- if (focusableElements.length <= 0) {
24
- return {};
25
- }
26
-
27
- const first = focusableElements[0];
28
- const last = focusableElements[focusableElements.length - 1];
29
- return { first, last };
30
- }
4
+ import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
31
5
 
32
6
  /**
33
7
  * Add a key down event handler to the given root element (document.body by default) to trap the move of focus
@@ -55,7 +29,7 @@ export function useFocusTrap(
55
29
  if (key !== 'Tab') {
56
30
  return;
57
31
  }
58
- const focusable = getFocusable(focusZoneElement);
32
+ const focusable = getFirstAndLastFocusable(focusZoneElement);
59
33
 
60
34
  // Prevent focus switch if no focusable available.
61
35
  if (!focusable.first) {
@@ -6,4 +6,5 @@ export default { title: 'LumX components/dialog/Dialog Demos' };
6
6
  export { App as Alert } from './alert';
7
7
  export { App as Confirm } from './confirm';
8
8
  export { App as Default } from './default';
9
+ export { App as Isloading } from './isloading';
9
10
  export { App as Sizes } from './sizes';