@lumx/react 3.9.4 → 3.9.5

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
@@ -6,8 +6,8 @@
6
6
  "url": "https://github.com/lumapps/design-system/issues"
7
7
  },
8
8
  "dependencies": {
9
- "@lumx/core": "^3.9.4",
10
- "@lumx/icons": "^3.9.4",
9
+ "@lumx/core": "^3.9.5",
10
+ "@lumx/icons": "^3.9.5",
11
11
  "@popperjs/core": "^2.5.4",
12
12
  "body-scroll-lock": "^3.1.5",
13
13
  "classnames": "^2.3.2",
@@ -111,5 +111,5 @@
111
111
  "build:storybook": "storybook build"
112
112
  },
113
113
  "sideEffects": false,
114
- "version": "3.9.4"
114
+ "version": "3.9.5"
115
115
  }
@@ -168,6 +168,21 @@ describe('<Chip />', () => {
168
168
  fireEvent.keyDown(chip, { key: 'A', code: 'KeyA' });
169
169
  expect(onKeyDown).toHaveBeenCalled();
170
170
  });
171
+
172
+ it('should forward key down event and trigger `onClick` when pressing Enter', async () => {
173
+ const user = userEvent.setup();
174
+ const onKeyDown = jest.fn();
175
+ const { chip } = setup({ onClick, onKeyDown });
176
+
177
+ await user.tab();
178
+ expect(chip).toHaveFocus();
179
+
180
+ await userEvent.keyboard('{Enter}');
181
+
182
+ expect(onKeyDown).toHaveBeenCalled();
183
+ expect(onClick).toHaveBeenCalled();
184
+ onClick.mockClear();
185
+ });
171
186
  });
172
187
 
173
188
  commonTestsSuiteRTL(setup, { baseClassName: CLASSNAME, forwardClassName: 'chip', forwardAttributes: 'chip' });
@@ -101,7 +101,7 @@ export const Chip: Comp<ChipProps, HTMLAnchorElement> = forwardRef((props, ref)
101
101
  const handleKeyDown = (evt: React.KeyboardEvent) => {
102
102
  onKeyDown?.(evt);
103
103
  if (hasOnClick) {
104
- onEnterPressed(onClick);
104
+ onEnterPressed(onClick)(evt);
105
105
  }
106
106
  };
107
107
 
@@ -6,6 +6,7 @@ import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/quer
6
6
  import userEvent from '@testing-library/user-event';
7
7
  import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
8
8
 
9
+ import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
9
10
  import { ExpansionPanel, ExpansionPanelProps } from '.';
10
11
 
11
12
  const CLASSNAME = ExpansionPanel.className as string;
@@ -14,16 +15,29 @@ jest.mock('@lumx/react/utils/isFocusVisible');
14
15
 
15
16
  const mockChildrenContent = 'children content';
16
17
 
18
+ /** Controlled component that uses a local state to toggle the component */
19
+ const ControlledComponent = (props: ExpansionPanelProps) => {
20
+ const [isOpen, handleClose, handleOpen] = useBooleanState(false);
21
+
22
+ return (
23
+ <ExpansionPanel isOpen={isOpen} onClose={handleClose} onOpen={handleOpen} {...props}>
24
+ {mockChildrenContent}
25
+ </ExpansionPanel>
26
+ );
27
+ };
28
+
17
29
  /**
18
30
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
19
31
  */
20
- const setup = (propsOverride: Partial<ExpansionPanelProps> = {}) => {
32
+ const setup = (propsOverride: Partial<ExpansionPanelProps> = {}, options: { controlled?: boolean } = {}) => {
21
33
  const props = {
22
34
  toggleButtonProps: { label: 'Toggle' },
23
35
  children: mockChildrenContent,
24
36
  ...propsOverride,
25
37
  };
26
- const { container } = render(<ExpansionPanel {...props} />);
38
+
39
+ const Component = options.controlled ? ControlledComponent : ExpansionPanel;
40
+ const { container } = render(<Component {...props} />);
27
41
 
28
42
  return {
29
43
  container,
@@ -116,6 +130,22 @@ describe(`<${ExpansionPanel.displayName}>`, () => {
116
130
  expect(onClose).toHaveBeenCalled();
117
131
  expect(onToggleOpen).toHaveBeenCalledWith(false, expect.anything());
118
132
  });
133
+
134
+ it('should hide children after toggling the expansion panel', async () => {
135
+ const user = userEvent.setup();
136
+ const { query } = setup({}, { controlled: true });
137
+
138
+ // Content is not visible by default
139
+ expect(query.content()).not.toBeInTheDocument();
140
+
141
+ await user.click(query.header() as any);
142
+
143
+ expect(query.content()).toBeInTheDocument();
144
+
145
+ await user.click(query.header() as any);
146
+
147
+ expect(query.content()).not.toBeInTheDocument();
148
+ });
119
149
  });
120
150
 
121
151
  // Common tests suite.
@@ -12,7 +12,8 @@ import { ColorPalette, DragHandle, Emphasis, IconButton, IconButtonProps, Theme
12
12
  import { Comp, GenericProps, HasTheme, isComponent } from '@lumx/react/utils/type';
13
13
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
14
14
  import { partitionMulti } from '@lumx/react/utils/partitionMulti';
15
- import { WINDOW } from '@lumx/react/constants';
15
+ import { useTransitionVisibility } from '@lumx/react/hooks/useTransitionVisibility';
16
+ import { EXPANSION_PANEL_TRANSITION_DURATION } from '@lumx/core/js/constants';
16
17
 
17
18
  /**
18
19
  * Defines the props of the component.
@@ -81,7 +82,6 @@ export const ExpansionPanel: Comp<ExpansionPanelProps, HTMLDivElement> = forward
81
82
  ...forwardedProps
82
83
  } = props;
83
84
 
84
- const [isChildrenVisible, setIsChildrenVisible] = useState(isOpen);
85
85
  const children: ReactNode[] = Children.toArray(anyChildren);
86
86
 
87
87
  // Partition children by types.
@@ -96,34 +96,16 @@ export const ExpansionPanel: Comp<ExpansionPanelProps, HTMLDivElement> = forward
96
96
  );
97
97
 
98
98
  const toggleOpen = (event: React.MouseEvent) => {
99
- const hasReducedMotionEnabled = WINDOW?.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches;
100
99
  const shouldOpen = !isOpen;
101
100
 
102
101
  if (isFunction(onOpen) && shouldOpen) {
103
102
  onOpen(event);
104
- // On open, we immediately show children
105
- setIsChildrenVisible(true);
106
103
  }
107
104
  if (isFunction(onClose) && !shouldOpen) {
108
105
  onClose(event);
109
- /**
110
- * On close, we only show children immediately if reduced motion is enabled
111
- * When disabled, the children will be hidden via the `onTransitionEnd` prop.
112
- */
113
- if (hasReducedMotionEnabled) {
114
- setIsChildrenVisible(false);
115
- }
116
106
  }
117
107
  if (isFunction(onToggleOpen)) {
118
108
  onToggleOpen(shouldOpen, event);
119
- /**
120
- * On toggle, we forward the show state if
121
- * * the toggle will open the expansion panel
122
- * * reduced motion is enabled. When disabled, the children will be hidden via the `onTransitionEnd` prop.
123
- * */
124
- if (shouldOpen || hasReducedMotionEnabled) {
125
- setIsChildrenVisible(shouldOpen);
126
- }
127
109
  }
128
110
  };
129
111
 
@@ -145,6 +127,9 @@ export const ExpansionPanel: Comp<ExpansionPanelProps, HTMLDivElement> = forward
145
127
 
146
128
  const wrapperRef = useRef<HTMLDivElement>(null);
147
129
 
130
+ /** Hide the children at the end of the transition */
131
+ const isChildrenVisible = useTransitionVisibility(wrapperRef, !!isOpen, EXPANSION_PANEL_TRANSITION_DURATION);
132
+
148
133
  // Switch max height on/off to activate the CSS transition (updates when children changes).
149
134
  const [maxHeight, setMaxHeight] = useState('0');
150
135
  useEffect(() => {
@@ -174,16 +159,7 @@ export const ExpansionPanel: Comp<ExpansionPanelProps, HTMLDivElement> = forward
174
159
  </header>
175
160
 
176
161
  {(isOpen || isChildrenVisible) && (
177
- <div
178
- className={`${CLASSNAME}__wrapper`}
179
- style={{ maxHeight }}
180
- // At the end of the closing transition, remove the children from the DOM
181
- onTransitionEnd={() => {
182
- if (!isOpen) {
183
- setIsChildrenVisible(false);
184
- }
185
- }}
186
- >
162
+ <div className={`${CLASSNAME}__wrapper`} style={{ maxHeight }}>
187
163
  <div className={`${CLASSNAME}__container`} ref={wrapperRef}>
188
164
  <div className={`${CLASSNAME}__content`}>{content}</div>
189
165