design-react-kit 5.9.3 → 5.10.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/package.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "bugs": {
26
26
  "url": "https://github.com/italia/design-react-kit/issues"
27
27
  },
28
- "version": "5.9.3",
28
+ "version": "5.10.0",
29
29
  "license": "BSD-3",
30
30
  "type": "module",
31
31
  "module": "./dist/index.js",
@@ -72,7 +72,7 @@
72
72
  "postversion": "echo \"You can now publish your version using 'git push --follow-tags'\""
73
73
  },
74
74
  "peerDependencies": {
75
- "bootstrap-italia": "^2.17.4",
75
+ "bootstrap-italia": "^2.18.0",
76
76
  "react": ">=18.2.0"
77
77
  },
78
78
  "browserslist": [
@@ -105,7 +105,7 @@
105
105
  "@types/react-dom": "^18.2.24",
106
106
  "@types/react-transition-group": "^4.4.10",
107
107
  "@types/uuid": "^10.0.0",
108
- "bootstrap-italia": "^2.17.4",
108
+ "bootstrap-italia": "^2.18.0",
109
109
  "browserslist-config-design-italia": "^1.0.0",
110
110
  "conventional-changelog": "^7.1.1",
111
111
  "eslint": "^9.10.0",
@@ -1,5 +1,5 @@
1
1
  import classNames from 'classnames';
2
- import React, { ElementType, FC, HTMLAttributes, Ref } from 'react';
2
+ import React, { ElementType, FC, HTMLAttributes, Ref, useEffect, useRef, useState } from 'react';
3
3
 
4
4
  import { Collapse as CollapseBase } from 'reactstrap';
5
5
  import { CSSModule } from 'reactstrap/types/lib/utils';
@@ -63,25 +63,98 @@ export const Collapse: FC<CollapseProps> = ({
63
63
  'navbar-collapse': 'navbar-collapsable',
64
64
  ...cssModule
65
65
  };
66
+
67
+ // Two-phase state to allow CSS transitions to play.
68
+ // isVisible controls display:block/none; isExpanded controls the animation class.
69
+ const [isVisible, setIsVisible] = useState(isOpen);
70
+ const [isExpanded, setIsExpanded] = useState(isOpen);
71
+ const panelRef = useRef<HTMLElement>(null);
72
+ const triggerRef = useRef<Element | null>(null);
73
+
74
+ useEffect(() => {
75
+ if (!(megamenu || navbar)) return;
76
+ if (isOpen) {
77
+ triggerRef.current = document.activeElement;
78
+ setIsVisible(true);
79
+ // Double rAF ensures the browser has painted display:block before adding
80
+ // the expanded class, so the CSS transform transition can fire.
81
+ requestAnimationFrame(() => {
82
+ requestAnimationFrame(() => {
83
+ setIsExpanded(true);
84
+ panelRef.current?.focus();
85
+ });
86
+ });
87
+ return;
88
+ }
89
+ setIsExpanded(false);
90
+ // Wait for the CSS transition to complete before hiding.
91
+ const timer = setTimeout(() => {
92
+ setIsVisible(false);
93
+ (triggerRef.current as HTMLElement | null)?.focus();
94
+ }, PANEL_TRANSITION_MS);
95
+ return () => clearTimeout(timer);
96
+ }, [isOpen, megamenu, navbar]);
97
+
98
+ useEffect(() => {
99
+ if (!(megamenu || navbar)) return;
100
+ if (isOpen) {
101
+ document.body.style.overflow = 'hidden';
102
+ } else {
103
+ document.body.style.overflow = '';
104
+ }
105
+ return () => { document.body.style.overflow = ''; };
106
+ }, [isOpen, megamenu, navbar]);
107
+
108
+ useEffect(() => {
109
+ if (!(megamenu || navbar)) return;
110
+ const main = document.querySelector('main');
111
+ if (!main) return;
112
+ if (isOpen) {
113
+ main.setAttribute('inert', '');
114
+ } else {
115
+ main.removeAttribute('inert');
116
+ }
117
+ return () => main.removeAttribute('inert');
118
+ }, [isOpen, megamenu, navbar]);
119
+
120
+ useEffect(() => {
121
+ if (!(megamenu || navbar)) return;
122
+ const handleKeyDown = (e: KeyboardEvent) => {
123
+ if (e.key === 'Escape' && isOpen) {
124
+ onOverlayClick?.();
125
+ }
126
+ };
127
+ document.addEventListener('keydown', handleKeyDown);
128
+ return () => document.removeEventListener('keydown', handleKeyDown);
129
+ }, [isOpen, megamenu, navbar, onOverlayClick]);
130
+
131
+ // Must match the longest CSS transition on .navbar-collapsable (currently 0.3s + buffer).
132
+ const PANEL_TRANSITION_MS = 350;
133
+
66
134
  if (megamenu || navbar) {
67
135
  const classes = classNames(className, 'navbar-collapse', {
68
- expanded: isOpen
136
+ expanded: isExpanded
69
137
  });
70
- const style = { display: isOpen ? 'block' : 'none' };
71
138
  const overlayClasses = classNames('overlay', {
72
- fade: isOpen,
73
- show: isOpen
139
+ fade: isVisible,
140
+ show: isExpanded
74
141
  });
142
+ const displayStyle = { display: isVisible ? 'block' : 'none' };
75
143
  return (
76
144
  <CollapseBase
77
145
  className={classes}
78
146
  cssModule={newCssModule}
79
147
  navbar={navbar}
80
- style={style}
148
+ style={displayStyle}
149
+ tabIndex={-1}
150
+ innerRef={panelRef}
151
+ role={isOpen ? 'dialog' : undefined}
152
+ aria-modal={isOpen ? true : undefined}
153
+ aria-label={isOpen ? 'Menu di navigazione' : undefined}
81
154
  data-testid={testId}
82
155
  {...attributes}
83
156
  >
84
- <div className={overlayClasses} style={style} onClick={onOverlayClick}></div>
157
+ <div className={overlayClasses} style={displayStyle} onClick={onOverlayClick}></div>
85
158
  <div className='close-div'>
86
159
  <button className='btn close-menu' type='button' onClick={onOverlayClick}>
87
160
  <span className='visually-hidden'>{closeSrText}</span>
@@ -5,7 +5,6 @@ import React, {
5
5
  ReactNode,
6
6
  Ref,
7
7
  useCallback,
8
- useEffect,
9
8
  useRef,
10
9
  useState
11
10
  } from 'react';
@@ -152,19 +151,7 @@ export const Input = ({
152
151
  toggleIcon(!hasIcon);
153
152
  }, [hasIcon, isHidden]);
154
153
 
155
- const divResizeRef = useRef<HTMLDivElement>(null);
156
154
  const inputRef = useRef<HTMLInputElement>(null);
157
- const [width, setWidth] = useState('100');
158
-
159
- useEffect(() => {
160
- if (divResizeRef.current != null && divResizeRef.current.classList.contains('input-number-adaptive')) {
161
- if (!value) {
162
- setWidth(`calc(70px)`);
163
- } else {
164
- setWidth(`calc(70px + ${`${value}`.length}ch)`);
165
- }
166
- }
167
- }, [value]);
168
155
 
169
156
  // eslint-disable-next-line prefer-const
170
157
  let { bsSize, valid, ...rest } = attributes;
@@ -312,8 +299,7 @@ export const Input = ({
312
299
  disabled: rest.disabled,
313
300
  'input-number-adaptive': type === 'adaptive'
314
301
  })}
315
- style={{ width }}
316
- ref={divResizeRef}
302
+ style={type === 'adaptive' ? { width: !value ? '70px' : `min(calc(70px + ${`${value}`.length}ch), 100%)` } : undefined}
317
303
  >
318
304
  {['currency', 'percentage'].includes(type) && (
319
305
  <span className='input-group-text fw-semibold'>{addonText}</span>