@faststore/components 2.0.54-alpha.0 → 2.0.56-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/dist/hooks/UIProvider.d.ts +33 -0
- package/dist/hooks/UIProvider.js +74 -0
- package/dist/hooks/UIProvider.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useFadeEffect.d.ts +5 -0
- package/dist/hooks/useFadeEffect.js +18 -0
- package/dist/hooks/useFadeEffect.js.map +1 -0
- package/dist/hooks/useTrapFocus.d.ts +8 -0
- package/dist/hooks/useTrapFocus.js +75 -0
- package/dist/hooks/useTrapFocus.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/molecules/Modal/Modal.d.ts +34 -0
- package/dist/molecules/Modal/Modal.js +38 -0
- package/dist/molecules/Modal/Modal.js.map +1 -0
- package/dist/molecules/Modal/ModalBody.d.ts +6 -0
- package/dist/molecules/Modal/ModalBody.js +4 -0
- package/dist/molecules/Modal/ModalBody.js.map +1 -0
- package/dist/molecules/Modal/ModalContent.d.ts +10 -0
- package/dist/molecules/Modal/ModalContent.js +23 -0
- package/dist/molecules/Modal/ModalContent.js.map +1 -0
- package/dist/molecules/Modal/ModalHeader.d.ts +19 -0
- package/dist/molecules/Modal/ModalHeader.js +11 -0
- package/dist/molecules/Modal/ModalHeader.js.map +1 -0
- package/dist/molecules/Modal/index.d.ts +5 -0
- package/dist/molecules/Modal/index.js +4 -0
- package/dist/molecules/Modal/index.js.map +1 -0
- package/dist/molecules/Table/Table.d.ts +1 -1
- package/package.json +2 -2
- package/src/hooks/UIProvider.tsx +152 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFadeEffect.ts +21 -0
- package/src/hooks/useTrapFocus.ts +108 -0
- package/src/index.ts +5 -0
- package/src/molecules/Modal/Modal.tsx +106 -0
- package/src/molecules/Modal/ModalBody.tsx +13 -0
- package/src/molecules/Modal/ModalContent.tsx +90 -0
- package/src/molecules/Modal/ModalHeader.tsx +47 -0
- package/src/molecules/Modal/index.tsx +5 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react';
|
|
2
|
+
interface Toast {
|
|
3
|
+
message: string;
|
|
4
|
+
status: 'ERROR' | 'WARNING' | 'INFO';
|
|
5
|
+
title?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
}
|
|
8
|
+
interface State {
|
|
9
|
+
/** Cart sidebar */
|
|
10
|
+
cart: boolean;
|
|
11
|
+
/** Region modal */
|
|
12
|
+
modal: boolean;
|
|
13
|
+
/** Menu slider */
|
|
14
|
+
navbar: boolean;
|
|
15
|
+
/** Search page filter slider */
|
|
16
|
+
filter: boolean;
|
|
17
|
+
toasts: Toast[];
|
|
18
|
+
}
|
|
19
|
+
interface Context extends State {
|
|
20
|
+
closeNavbar: () => void;
|
|
21
|
+
openNavbar: () => void;
|
|
22
|
+
closeFilter: () => void;
|
|
23
|
+
openFilter: () => void;
|
|
24
|
+
openCart: () => void;
|
|
25
|
+
closeCart: () => void;
|
|
26
|
+
openModal: () => void;
|
|
27
|
+
closeModal: () => void;
|
|
28
|
+
pushToast: (data: Toast) => void;
|
|
29
|
+
popToast: () => void;
|
|
30
|
+
}
|
|
31
|
+
declare function UIProvider({ children }: PropsWithChildren): JSX.Element;
|
|
32
|
+
export declare function useUI(): Context;
|
|
33
|
+
export default UIProvider;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createContext, useContext, useMemo, useReducer } from 'react';
|
|
3
|
+
const reducer = (state, action) => {
|
|
4
|
+
const { type } = action;
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 'open': {
|
|
7
|
+
const { payload } = action;
|
|
8
|
+
document.body.classList.add('no-scroll');
|
|
9
|
+
return {
|
|
10
|
+
...state,
|
|
11
|
+
[payload]: true,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
case 'close': {
|
|
15
|
+
const { payload } = action;
|
|
16
|
+
document.body.classList.remove('no-scroll');
|
|
17
|
+
return {
|
|
18
|
+
...state,
|
|
19
|
+
[payload]: false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
case 'pushToast': {
|
|
23
|
+
return {
|
|
24
|
+
...state,
|
|
25
|
+
toasts: [...state.toasts, action.payload],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
case 'popToast': {
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
toasts: state.toasts.slice(1),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
default:
|
|
35
|
+
throw new Error(`Action ${type} not implemented`);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const initializer = () => ({
|
|
39
|
+
cart: false,
|
|
40
|
+
modal: false,
|
|
41
|
+
navbar: false,
|
|
42
|
+
filter: false,
|
|
43
|
+
toasts: [],
|
|
44
|
+
});
|
|
45
|
+
const UIContext = createContext(undefined);
|
|
46
|
+
function UIProvider({ children }) {
|
|
47
|
+
const [ui, dispatch] = useReducer(reducer, undefined, initializer);
|
|
48
|
+
const callbacks = useMemo(() => ({
|
|
49
|
+
openFilter: () => dispatch({ type: 'open', payload: 'filter' }),
|
|
50
|
+
closeFilter: () => dispatch({ type: 'close', payload: 'filter' }),
|
|
51
|
+
openNavbar: () => dispatch({ type: 'open', payload: 'navbar' }),
|
|
52
|
+
closeNavbar: () => dispatch({ type: 'close', payload: 'navbar' }),
|
|
53
|
+
openCart: () => dispatch({ type: 'open', payload: 'cart' }),
|
|
54
|
+
closeCart: () => dispatch({ type: 'close', payload: 'cart' }),
|
|
55
|
+
openModal: () => dispatch({ type: 'open', payload: 'modal' }),
|
|
56
|
+
closeModal: () => dispatch({ type: 'close', payload: 'modal' }),
|
|
57
|
+
pushToast: (toast) => dispatch({ type: 'pushToast', payload: toast }),
|
|
58
|
+
popToast: () => dispatch({ type: 'popToast' }),
|
|
59
|
+
}), []);
|
|
60
|
+
const value = useMemo(() => ({
|
|
61
|
+
...ui,
|
|
62
|
+
...callbacks,
|
|
63
|
+
}), [callbacks, ui]);
|
|
64
|
+
return React.createElement(UIContext.Provider, { value: value }, children);
|
|
65
|
+
}
|
|
66
|
+
export function useUI() {
|
|
67
|
+
const context = useContext(UIContext);
|
|
68
|
+
if (context === undefined) {
|
|
69
|
+
throw new Error('Missing UI context on React tree');
|
|
70
|
+
}
|
|
71
|
+
return context;
|
|
72
|
+
}
|
|
73
|
+
export default UIProvider;
|
|
74
|
+
//# sourceMappingURL=UIProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UIProvider.js","sourceRoot":"","sources":["../../src/hooks/UIProvider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
export const useFadeEffect = () => {
|
|
3
|
+
const [fade, setFade] = useState('out');
|
|
4
|
+
const fadeOut = useCallback(() => setFade('out'), []);
|
|
5
|
+
const fadeIn = useCallback(() => setFade('in'), []);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
fadeIn();
|
|
8
|
+
return () => {
|
|
9
|
+
fadeOut();
|
|
10
|
+
};
|
|
11
|
+
}, [fadeIn, fadeOut]);
|
|
12
|
+
return {
|
|
13
|
+
fade,
|
|
14
|
+
fadeIn,
|
|
15
|
+
fadeOut,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=useFadeEffect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFadeEffect.js","sourceRoot":"","sources":["../../src/hooks/useFadeEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAExD,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE;IAChC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAe,KAAK,CAAC,CAAA;IACrD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAEnD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,CAAA;QAER,OAAO,GAAG,EAAE;YACV,OAAO,EAAE,CAAA;QACX,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAErB,OAAO;QACL,IAAI;QACJ,MAAM;QACN,OAAO;KACR,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
interface TrapFocusParams {
|
|
3
|
+
beforeElementRef: RefObject<HTMLElement>;
|
|
4
|
+
trapFocusRef: RefObject<HTMLElement>;
|
|
5
|
+
afterElementRef: RefObject<HTMLElement>;
|
|
6
|
+
}
|
|
7
|
+
export declare const useTrapFocus: ({ trapFocusRef, beforeElementRef, afterElementRef, }: TrapFocusParams) => void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { tabbable } from 'tabbable';
|
|
3
|
+
/*
|
|
4
|
+
* Element that will maintain the focus inside trapFocusRef, focus the first element,
|
|
5
|
+
* and focus back on the element that was in focus when useTrapFocus was triggered.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by Reakit useTrapFocus https://github.com/reakit/reakit/blob/a211d94da9f3b683182568a56479b91afb1b85ae/packages/reakit/src/Dialog/__utils/useFocusTrap.ts
|
|
8
|
+
*/
|
|
9
|
+
export const useTrapFocus = ({ trapFocusRef, beforeElementRef, afterElementRef, }) => {
|
|
10
|
+
const tabbableNodesRef = useRef();
|
|
11
|
+
const nodeToRestoreRef = useRef(document.hasFocus() ? document.activeElement : null);
|
|
12
|
+
// Focus back on the element that was focused when useTrapFocus is triggered.
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const nodeToRestore = nodeToRestoreRef.current;
|
|
15
|
+
return () => {
|
|
16
|
+
nodeToRestore?.focus();
|
|
17
|
+
};
|
|
18
|
+
}, [nodeToRestoreRef]);
|
|
19
|
+
// Set focus on first tabbable element
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!trapFocusRef.current) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!tabbableNodesRef.current) {
|
|
25
|
+
tabbableNodesRef.current = tabbable(trapFocusRef.current);
|
|
26
|
+
}
|
|
27
|
+
const [firstTabbable] = tabbableNodesRef.current;
|
|
28
|
+
if (!firstTabbable) {
|
|
29
|
+
trapFocusRef.current.focus();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
firstTabbable.focus();
|
|
33
|
+
}, [trapFocusRef]);
|
|
34
|
+
// Handle loop focus. Set keydown and focusin event listeners
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!trapFocusRef.current ||
|
|
37
|
+
!beforeElementRef.current ||
|
|
38
|
+
!afterElementRef.current) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const beforeElement = beforeElementRef.current;
|
|
42
|
+
const afterElement = afterElementRef.current;
|
|
43
|
+
const trapFocus = trapFocusRef.current;
|
|
44
|
+
const handleLoopFocus = (nativeEvent) => {
|
|
45
|
+
if (!document.hasFocus()) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
tabbableNodesRef.current = tabbable(trapFocusRef.current);
|
|
49
|
+
if (!tabbableNodesRef.current.length) {
|
|
50
|
+
trapFocus.focus();
|
|
51
|
+
}
|
|
52
|
+
/*
|
|
53
|
+
* Handle loop focus from beforeElementRef. This node can only be focused if the user press shift tab.
|
|
54
|
+
* It will focus the last element of the trapFocusRef.
|
|
55
|
+
*/
|
|
56
|
+
if (nativeEvent.target === beforeElement) {
|
|
57
|
+
tabbableNodesRef.current[tabbableNodesRef.current.length - 1]?.focus();
|
|
58
|
+
}
|
|
59
|
+
/*
|
|
60
|
+
* Handle loop focus from afterElementRef. This node can only be focused if the user press tab.
|
|
61
|
+
* It will focus the first element of the trapFocusRef.
|
|
62
|
+
*/
|
|
63
|
+
if (nativeEvent.target === afterElement) {
|
|
64
|
+
tabbableNodesRef.current[0]?.focus();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
beforeElement?.addEventListener('focusin', handleLoopFocus);
|
|
68
|
+
afterElement?.addEventListener('focusin', handleLoopFocus);
|
|
69
|
+
return () => {
|
|
70
|
+
beforeElement?.removeEventListener('focusin', handleLoopFocus);
|
|
71
|
+
afterElement?.removeEventListener('focusin', handleLoopFocus);
|
|
72
|
+
};
|
|
73
|
+
}, [tabbableNodesRef, afterElementRef, beforeElementRef, trapFocusRef]);
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=useTrapFocus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTrapFocus.js","sourceRoot":"","sources":["../../src/hooks/useTrapFocus.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './assets';
|
|
2
|
+
export * from './hooks';
|
|
2
3
|
export { default as Badge } from './atoms/Badge';
|
|
3
4
|
export type { BadgeProps } from './atoms/Badge';
|
|
4
5
|
export { default as Button } from './atoms/Button';
|
|
@@ -47,6 +48,8 @@ export { default as InputField } from './molecules/InputField';
|
|
|
47
48
|
export type { InputFieldProps } from './molecules/InputField';
|
|
48
49
|
export { default as LinkButton } from './molecules/LinkButton';
|
|
49
50
|
export type { LinkButtonProps } from './molecules/LinkButton';
|
|
51
|
+
export { default as Modal, ModalHeader, ModalBody } from './molecules/Modal';
|
|
52
|
+
export type { ModalProps, ModalHeaderProps } from './molecules/Modal';
|
|
50
53
|
export { default as RadioField } from './molecules/RadioField';
|
|
51
54
|
export type { RadioFieldProps } from './molecules/RadioField';
|
|
52
55
|
export { default as RadioGroup, RadioOption, } from './molecules/RadioGroup';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Assets
|
|
2
2
|
export * from './assets';
|
|
3
|
+
// Hooks
|
|
4
|
+
export * from './hooks';
|
|
3
5
|
// Atoms
|
|
4
6
|
export { default as Badge } from './atoms/Badge';
|
|
5
7
|
export { default as Button } from './atoms/Button';
|
|
@@ -27,6 +29,7 @@ export { default as Dropdown, DropdownButton, DropdownItem, DropdownMenu, } from
|
|
|
27
29
|
export { default as Gift, GiftContent, GiftImage, } from './molecules/Gift';
|
|
28
30
|
export { default as InputField } from './molecules/InputField';
|
|
29
31
|
export { default as LinkButton } from './molecules/LinkButton';
|
|
32
|
+
export { default as Modal, ModalHeader, ModalBody } from './molecules/Modal';
|
|
30
33
|
export { default as RadioField } from './molecules/RadioField';
|
|
31
34
|
export { default as RadioGroup, RadioOption, } from './molecules/RadioGroup';
|
|
32
35
|
export { default as Rating } from './molecules/Rating';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AriaAttributes, ReactNode } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { ModalContentProps } from './ModalContent';
|
|
4
|
+
export type ModalChildrenProps = {
|
|
5
|
+
fade: 'in' | 'out';
|
|
6
|
+
fadeOut: () => void;
|
|
7
|
+
fadeIn: () => void;
|
|
8
|
+
};
|
|
9
|
+
type ModalChildrenFunction = (props: ModalChildrenProps) => ReactNode;
|
|
10
|
+
export interface ModalProps extends Omit<ModalContentProps, 'children'> {
|
|
11
|
+
/**
|
|
12
|
+
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
13
|
+
*/
|
|
14
|
+
testId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Identifies the element (or elements) that labels the current element.
|
|
17
|
+
* @see aria-labelledby https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby
|
|
18
|
+
*/
|
|
19
|
+
'aria-labelledby'?: AriaAttributes['aria-label'];
|
|
20
|
+
/**
|
|
21
|
+
* A boolean value that represents the state of the Modal
|
|
22
|
+
*/
|
|
23
|
+
isOpen?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Event emitted when the modal is closed
|
|
26
|
+
*/
|
|
27
|
+
onDismiss?: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Children or function as a children
|
|
30
|
+
*/
|
|
31
|
+
children: ModalChildrenFunction | ReactNode;
|
|
32
|
+
}
|
|
33
|
+
declare const Modal: ({ children, testId, isOpen, onDismiss, ...otherProps }: ModalProps) => React.ReactPortal | null;
|
|
34
|
+
export default Modal;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { Overlay } from '../..';
|
|
4
|
+
import { useFadeEffect, useUI } from '../../hooks';
|
|
5
|
+
import ModalContent from './ModalContent';
|
|
6
|
+
/*
|
|
7
|
+
* This component is based on @reach/dialog.
|
|
8
|
+
* https://github.com/reach/reach-ui/blob/main/packages/dialog/src/index.tsx
|
|
9
|
+
* https://reach.tech/dialog
|
|
10
|
+
*/
|
|
11
|
+
const Modal = ({ children, testId = 'fs-modal', isOpen = true, onDismiss, ...otherProps }) => {
|
|
12
|
+
const { closeModal } = useUI();
|
|
13
|
+
const { fade, fadeOut, fadeIn } = useFadeEffect();
|
|
14
|
+
const handleBackdropClick = (event) => {
|
|
15
|
+
if (event.defaultPrevented) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
event.stopPropagation();
|
|
19
|
+
fadeOut?.();
|
|
20
|
+
onDismiss?.();
|
|
21
|
+
};
|
|
22
|
+
const handleBackdropKeyDown = (event) => {
|
|
23
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
fadeOut?.();
|
|
28
|
+
onDismiss?.();
|
|
29
|
+
};
|
|
30
|
+
return isOpen
|
|
31
|
+
? createPortal(React.createElement(Overlay, { onClick: handleBackdropClick, onKeyDown: handleBackdropKeyDown },
|
|
32
|
+
React.createElement(ModalContent, { onTransitionEnd: () => fade === 'out' && closeModal(), "data-fs-modal": true, "data-fs-modal-state": fade, testId: testId, ...otherProps }, typeof children === 'function'
|
|
33
|
+
? children({ fade, fadeOut, fadeIn })
|
|
34
|
+
: children)), document.body)
|
|
35
|
+
: null;
|
|
36
|
+
};
|
|
37
|
+
export default Modal;
|
|
38
|
+
//# sourceMappingURL=Modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../src/molecules/Modal/Modal.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAElD,OAAO,YAAY,MAAM,gBAAgB,CAAA;AAkCzC;;;;GAIG;AAEH,MAAM,KAAK,GAAG,CAAC,EACb,QAAQ,EACR,MAAM,GAAG,UAAU,EACnB,MAAM,GAAG,IAAI,EACb,SAAS,EACT,GAAG,UAAU,EACF,EAAE,EAAE;IACf,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,CAAA;IAC9B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAAA;IAEjD,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAE,EAAE;QAChD,IAAI,KAAK,CAAC,gBAAgB,EAAE;YAC1B,OAAM;SACP;QAED,KAAK,CAAC,eAAe,EAAE,CAAA;QACvB,OAAO,EAAE,EAAE,CAAA;QACX,SAAS,EAAE,EAAE,CAAA;IACf,CAAC,CAAA;IAED,MAAM,qBAAqB,GAAG,CAAC,KAAoB,EAAE,EAAE;QACrD,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,gBAAgB,EAAE;YACpD,OAAM;SACP;QAED,KAAK,CAAC,eAAe,EAAE,CAAA;QACvB,OAAO,EAAE,EAAE,CAAA;QACX,SAAS,EAAE,EAAE,CAAA;IACf,CAAC,CAAA;IAED,OAAO,MAAM;QACX,CAAC,CAAC,YAAY,CACV,oBAAC,OAAO,IACN,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,qBAAqB;YAEhC,oBAAC,YAAY,IACX,eAAe,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,KAAK,IAAI,UAAU,EAAE,gDAEhC,IAAI,EACzB,MAAM,EAAE,MAAM,KACV,UAAU,IAEb,OAAO,QAAQ,KAAK,UAAU;gBAC7B,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;gBACrC,CAAC,CAAC,QAAQ,CACC,CACP,EACV,QAAQ,CAAC,IAAI,CACd;QACH,CAAC,CAAC,IAAI,CAAA;AACV,CAAC,CAAA;AAED,eAAe,KAAK,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalBody.js","sourceRoot":"","sources":["../../../src/molecules/Modal/ModalBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAA;AAMxD,MAAM,SAAS,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAkB,EAAE,EAAE,CAAC,CACjE,4DAA4B,UAAU,IACnC,QAAQ,CACL,CACP,CAAA;AAED,eAAe,SAAS,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DetailedHTMLProps, HTMLAttributes, RefObject } from 'react';
|
|
2
|
+
interface ModalContentPureProps extends Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'> {
|
|
3
|
+
beforeElementRef: RefObject<HTMLDivElement>;
|
|
4
|
+
trapFocusRef: RefObject<HTMLDivElement>;
|
|
5
|
+
afterElementRef: RefObject<HTMLDivElement>;
|
|
6
|
+
testId?: string;
|
|
7
|
+
}
|
|
8
|
+
export type ModalContentProps = Omit<ModalContentPureProps, 'trapFocusRef' | 'onClick' | 'beforeElementRef' | 'afterElementRef'>;
|
|
9
|
+
declare const ModalContent: ({ children, ...otherProps }: ModalContentProps) => JSX.Element;
|
|
10
|
+
export default ModalContent;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { useTrapFocus } from '../../hooks';
|
|
3
|
+
const ModalContentPure = ({ beforeElementRef, trapFocusRef, afterElementRef, testId = 'store-modal-content', children, ...otherProps }) => {
|
|
4
|
+
return (React.createElement(React.Fragment, null,
|
|
5
|
+
React.createElement("div", { ref: beforeElementRef, "data-testid": "beforeElement", tabIndex: 0, "aria-hidden": "true" }),
|
|
6
|
+
React.createElement("div", { "data-fs-modal-content": true, "data-testid": testId, ref: trapFocusRef, "aria-modal": "true", role: "dialog", tabIndex: -1, ...otherProps }, children),
|
|
7
|
+
React.createElement("div", { ref: afterElementRef, "data-testid": "afterElement", tabIndex: 0, "aria-hidden": "true" })));
|
|
8
|
+
};
|
|
9
|
+
const ModalContent = ({ children, ...otherProps }) => {
|
|
10
|
+
const trapFocusRef = useRef(null);
|
|
11
|
+
const beforeElementRef = useRef(null);
|
|
12
|
+
const afterElementRef = useRef(null);
|
|
13
|
+
useTrapFocus({
|
|
14
|
+
beforeElementRef,
|
|
15
|
+
trapFocusRef,
|
|
16
|
+
afterElementRef,
|
|
17
|
+
});
|
|
18
|
+
return (React.createElement(ModalContentPure, { ...otherProps, trapFocusRef: trapFocusRef, beforeElementRef: beforeElementRef, afterElementRef: afterElementRef, onClick: (event) => {
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
} }, children));
|
|
21
|
+
};
|
|
22
|
+
export default ModalContent;
|
|
23
|
+
//# sourceMappingURL=ModalContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalContent.js","sourceRoot":"","sources":["../../../src/molecules/Modal/ModalContent.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAErC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAa1C,MAAM,gBAAgB,GAAG,CAAC,EACxB,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,MAAM,GAAG,qBAAqB,EAC9B,QAAQ,EACR,GAAG,UAAU,EACS,EAAE,EAAE;IAC1B,OAAO,CACL;QACE,6BACE,GAAG,EAAE,gBAAgB,iBACT,eAAe,EAC3B,QAAQ,EAAE,CAAC,iBACC,MAAM,GAClB;QACF,2EAEe,MAAM,EACnB,GAAG,EAAE,YAAY,gBACN,MAAM,EACjB,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,KACR,UAAU,IAEb,QAAQ,CACL;QACN,6BACE,GAAG,EAAE,eAAe,iBACR,cAAc,EAC1B,QAAQ,EAAE,CAAC,iBACC,MAAM,GAClB,CACD,CACJ,CAAA;AACH,CAAC,CAAA;AAOD,MAAM,YAAY,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAqB,EAAE,EAAE;IACtE,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IACjD,MAAM,gBAAgB,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IACrD,MAAM,eAAe,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAEpD,YAAY,CAAC;QACX,gBAAgB;QAChB,YAAY;QACZ,eAAe;KAChB,CAAC,CAAA;IAEF,OAAO,CACL,oBAAC,gBAAgB,OACX,UAAU,EACd,YAAY,EAAE,YAAY,EAC1B,gBAAgB,EAAE,gBAAgB,EAClC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,CAAC,KAAiB,EAAE,EAAE;YAC7B,KAAK,CAAC,eAAe,EAAE,CAAA;QACzB,CAAC,IAEA,QAAQ,CACQ,CACpB,CAAA;AACH,CAAC,CAAA;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { IconButtonProps } from '../IconButton';
|
|
3
|
+
export interface ModalHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
/**
|
|
5
|
+
* Title for header modal.
|
|
6
|
+
*/
|
|
7
|
+
title: string;
|
|
8
|
+
/**
|
|
9
|
+
* Description for header modal.
|
|
10
|
+
*/
|
|
11
|
+
description?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Props for the Close Button component.
|
|
14
|
+
*/
|
|
15
|
+
closeButtonProps?: Partial<Omit<IconButtonProps, 'onClick'>>;
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
}
|
|
18
|
+
declare const ModalHeader: ({ onClose, title, closeButtonProps, description, }: ModalHeaderProps) => JSX.Element;
|
|
19
|
+
export default ModalHeader;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { X } from '../../assets';
|
|
3
|
+
import IconButton from '../IconButton';
|
|
4
|
+
const ModalHeader = ({ onClose, title, closeButtonProps = {}, description, }) => {
|
|
5
|
+
return (React.createElement("header", { "data-fs-modal-header": true },
|
|
6
|
+
onClose && (React.createElement(IconButton, { onClick: () => onClose?.(), "data-fs-modal-header-close-button": true, icon: React.createElement(X, null), "aria-label": "Close modal", ...closeButtonProps })),
|
|
7
|
+
React.createElement("p", { "data-fs-modal-header-title": true }, title),
|
|
8
|
+
description && React.createElement("p", { "data-fs-modal-header-description": true }, description)));
|
|
9
|
+
};
|
|
10
|
+
export default ModalHeader;
|
|
11
|
+
//# sourceMappingURL=ModalHeader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalHeader.js","sourceRoot":"","sources":["../../../src/molecules/Modal/ModalHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyB,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,UAA+B,MAAM,eAAe,CAAA;AAqB3D,MAAM,WAAW,GAAG,CAAC,EACnB,OAAO,EACP,KAAK,EACL,gBAAgB,GAAG,EAAE,EACrB,WAAW,GACM,EAAE,EAAE;IACrB,OAAO,CACL;QACG,OAAO,IAAI,CACV,oBAAC,UAAU,IACT,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,6CAE1B,IAAI,EAAE,oBAAC,CAAC,OAAG,gBACA,aAAa,KACpB,gBAAgB,GACpB,CACH;QACD,iEAA+B,KAAK,CAAK;QACxC,WAAW,IAAI,uEAAqC,WAAW,CAAK,CAC9D,CACV,CAAA;AACH,CAAC,CAAA;AAED,eAAe,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/molecules/Modal/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -22,5 +22,5 @@ export interface TableProps extends DetailedHTMLProps<TableHTMLAttributes<HTMLTa
|
|
|
22
22
|
*/
|
|
23
23
|
nonce?: string | undefined;
|
|
24
24
|
}
|
|
25
|
-
declare const Table: React.ForwardRefExoticComponent<Pick<PropsWithChildren<TableProps>, "slot" | "style" | "summary" | "title" | "testId" | "variant" | "aria-label" | "
|
|
25
|
+
declare const Table: React.ForwardRefExoticComponent<Pick<PropsWithChildren<TableProps>, "slot" | "style" | "summary" | "title" | "children" | "testId" | "variant" | "aria-label" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "id" | "lang" | "nonce" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onResize" | "onResizeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "width" | "key" | "align" | "bgcolor" | "border" | "cellPadding" | "cellSpacing" | "frame" | "rules"> & React.RefAttributes<HTMLTableElement>>;
|
|
26
26
|
export default Table;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/components",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.56-alpha.0",
|
|
4
4
|
"module": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
6
6
|
"author": "Emerson Laurentino @emersonlaurentino",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"node": "16.18.0",
|
|
31
31
|
"yarn": "1.19.1"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "6afa204ec28032832e3867f805fbc5bcbbed4aab"
|
|
34
34
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { createContext, useContext, useMemo, useReducer } from 'react'
|
|
3
|
+
import type { PropsWithChildren } from 'react'
|
|
4
|
+
|
|
5
|
+
interface Toast {
|
|
6
|
+
message: string
|
|
7
|
+
status: 'ERROR' | 'WARNING' | 'INFO'
|
|
8
|
+
title?: string
|
|
9
|
+
icon?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface State {
|
|
13
|
+
/** Cart sidebar */
|
|
14
|
+
cart: boolean
|
|
15
|
+
/** Region modal */
|
|
16
|
+
modal: boolean
|
|
17
|
+
/** Menu slider */
|
|
18
|
+
navbar: boolean
|
|
19
|
+
/** Search page filter slider */
|
|
20
|
+
filter: boolean
|
|
21
|
+
toasts: Toast[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type UIElement = 'navbar' | 'cart' | 'modal' | 'filter'
|
|
25
|
+
|
|
26
|
+
type Action =
|
|
27
|
+
| {
|
|
28
|
+
type: 'open'
|
|
29
|
+
payload: UIElement
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
type: 'close'
|
|
33
|
+
payload: UIElement
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
type: 'pushToast'
|
|
37
|
+
payload: Toast
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
type: 'popToast'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const reducer = (state: State, action: Action): State => {
|
|
44
|
+
const { type } = action
|
|
45
|
+
|
|
46
|
+
switch (type) {
|
|
47
|
+
case 'open': {
|
|
48
|
+
const { payload } = action
|
|
49
|
+
|
|
50
|
+
document.body.classList.add('no-scroll')
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...state,
|
|
54
|
+
[payload]: true,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case 'close': {
|
|
59
|
+
const { payload } = action
|
|
60
|
+
|
|
61
|
+
document.body.classList.remove('no-scroll')
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
[payload]: false,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case 'pushToast': {
|
|
70
|
+
return {
|
|
71
|
+
...state,
|
|
72
|
+
toasts: [...state.toasts, action.payload],
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case 'popToast': {
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
toasts: state.toasts.slice(1),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Action ${type} not implemented`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const initializer = (): State => ({
|
|
89
|
+
cart: false,
|
|
90
|
+
modal: false,
|
|
91
|
+
navbar: false,
|
|
92
|
+
filter: false,
|
|
93
|
+
toasts: [],
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
interface Context extends State {
|
|
97
|
+
closeNavbar: () => void
|
|
98
|
+
openNavbar: () => void
|
|
99
|
+
closeFilter: () => void
|
|
100
|
+
openFilter: () => void
|
|
101
|
+
openCart: () => void
|
|
102
|
+
closeCart: () => void
|
|
103
|
+
openModal: () => void
|
|
104
|
+
closeModal: () => void
|
|
105
|
+
pushToast: (data: Toast) => void
|
|
106
|
+
popToast: () => void
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const UIContext = createContext<Context | undefined>(undefined)
|
|
110
|
+
|
|
111
|
+
function UIProvider({ children }: PropsWithChildren) {
|
|
112
|
+
const [ui, dispatch] = useReducer(reducer, undefined, initializer)
|
|
113
|
+
|
|
114
|
+
const callbacks = useMemo(
|
|
115
|
+
() => ({
|
|
116
|
+
openFilter: () => dispatch({ type: 'open', payload: 'filter' }),
|
|
117
|
+
closeFilter: () => dispatch({ type: 'close', payload: 'filter' }),
|
|
118
|
+
openNavbar: () => dispatch({ type: 'open', payload: 'navbar' }),
|
|
119
|
+
closeNavbar: () => dispatch({ type: 'close', payload: 'navbar' }),
|
|
120
|
+
openCart: () => dispatch({ type: 'open', payload: 'cart' }),
|
|
121
|
+
closeCart: () => dispatch({ type: 'close', payload: 'cart' }),
|
|
122
|
+
openModal: () => dispatch({ type: 'open', payload: 'modal' }),
|
|
123
|
+
closeModal: () => dispatch({ type: 'close', payload: 'modal' }),
|
|
124
|
+
pushToast: (toast: Toast) =>
|
|
125
|
+
dispatch({ type: 'pushToast', payload: toast }),
|
|
126
|
+
popToast: () => dispatch({ type: 'popToast' }),
|
|
127
|
+
}),
|
|
128
|
+
[]
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const value = useMemo(
|
|
132
|
+
() => ({
|
|
133
|
+
...ui,
|
|
134
|
+
...callbacks,
|
|
135
|
+
}),
|
|
136
|
+
[callbacks, ui]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return <UIContext.Provider value={value}>{children}</UIContext.Provider>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function useUI() {
|
|
143
|
+
const context = useContext(UIContext)
|
|
144
|
+
|
|
145
|
+
if (context === undefined) {
|
|
146
|
+
throw new Error('Missing UI context on React tree')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return context
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default UIProvider
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export const useFadeEffect = () => {
|
|
4
|
+
const [fade, setFade] = useState<'in' | 'out'>('out')
|
|
5
|
+
const fadeOut = useCallback(() => setFade('out'), [])
|
|
6
|
+
const fadeIn = useCallback(() => setFade('in'), [])
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
fadeIn()
|
|
10
|
+
|
|
11
|
+
return () => {
|
|
12
|
+
fadeOut()
|
|
13
|
+
}
|
|
14
|
+
}, [fadeIn, fadeOut])
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
fade,
|
|
18
|
+
fadeIn,
|
|
19
|
+
fadeOut,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import type { RefObject } from 'react'
|
|
3
|
+
import type { FocusableElement } from 'tabbable'
|
|
4
|
+
import { tabbable } from 'tabbable'
|
|
5
|
+
|
|
6
|
+
interface TrapFocusParams {
|
|
7
|
+
beforeElementRef: RefObject<HTMLElement>
|
|
8
|
+
trapFocusRef: RefObject<HTMLElement>
|
|
9
|
+
afterElementRef: RefObject<HTMLElement>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Element that will maintain the focus inside trapFocusRef, focus the first element,
|
|
14
|
+
* and focus back on the element that was in focus when useTrapFocus was triggered.
|
|
15
|
+
*
|
|
16
|
+
* Inspired by Reakit useTrapFocus https://github.com/reakit/reakit/blob/a211d94da9f3b683182568a56479b91afb1b85ae/packages/reakit/src/Dialog/__utils/useFocusTrap.ts
|
|
17
|
+
*/
|
|
18
|
+
export const useTrapFocus = ({
|
|
19
|
+
trapFocusRef,
|
|
20
|
+
beforeElementRef,
|
|
21
|
+
afterElementRef,
|
|
22
|
+
}: TrapFocusParams) => {
|
|
23
|
+
const tabbableNodesRef = useRef<FocusableElement[]>()
|
|
24
|
+
const nodeToRestoreRef = useRef<HTMLElement | null>(
|
|
25
|
+
document.hasFocus() ? (document.activeElement as HTMLElement) : null
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
// Focus back on the element that was focused when useTrapFocus is triggered.
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const nodeToRestore = nodeToRestoreRef.current
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
nodeToRestore?.focus()
|
|
34
|
+
}
|
|
35
|
+
}, [nodeToRestoreRef])
|
|
36
|
+
|
|
37
|
+
// Set focus on first tabbable element
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!trapFocusRef.current) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!tabbableNodesRef.current) {
|
|
44
|
+
tabbableNodesRef.current = tabbable(trapFocusRef.current)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [firstTabbable] = tabbableNodesRef.current
|
|
48
|
+
|
|
49
|
+
if (!firstTabbable) {
|
|
50
|
+
trapFocusRef.current.focus()
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
firstTabbable.focus()
|
|
56
|
+
}, [trapFocusRef])
|
|
57
|
+
|
|
58
|
+
// Handle loop focus. Set keydown and focusin event listeners
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (
|
|
61
|
+
!trapFocusRef.current ||
|
|
62
|
+
!beforeElementRef.current ||
|
|
63
|
+
!afterElementRef.current
|
|
64
|
+
) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const beforeElement = beforeElementRef.current
|
|
69
|
+
const afterElement = afterElementRef.current
|
|
70
|
+
const trapFocus = trapFocusRef.current
|
|
71
|
+
|
|
72
|
+
const handleLoopFocus = (nativeEvent: FocusEvent) => {
|
|
73
|
+
if (!document.hasFocus()) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
tabbableNodesRef.current = tabbable(trapFocusRef.current!)
|
|
78
|
+
|
|
79
|
+
if (!tabbableNodesRef.current.length) {
|
|
80
|
+
trapFocus.focus()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* Handle loop focus from beforeElementRef. This node can only be focused if the user press shift tab.
|
|
85
|
+
* It will focus the last element of the trapFocusRef.
|
|
86
|
+
*/
|
|
87
|
+
if (nativeEvent.target === beforeElement) {
|
|
88
|
+
tabbableNodesRef.current[tabbableNodesRef.current.length - 1]?.focus()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* Handle loop focus from afterElementRef. This node can only be focused if the user press tab.
|
|
93
|
+
* It will focus the first element of the trapFocusRef.
|
|
94
|
+
*/
|
|
95
|
+
if (nativeEvent.target === afterElement) {
|
|
96
|
+
tabbableNodesRef.current[0]?.focus()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
beforeElement?.addEventListener('focusin', handleLoopFocus)
|
|
101
|
+
afterElement?.addEventListener('focusin', handleLoopFocus)
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
beforeElement?.removeEventListener('focusin', handleLoopFocus)
|
|
105
|
+
afterElement?.removeEventListener('focusin', handleLoopFocus)
|
|
106
|
+
}
|
|
107
|
+
}, [tabbableNodesRef, afterElementRef, beforeElementRef, trapFocusRef])
|
|
108
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Assets
|
|
2
2
|
export * from './assets'
|
|
3
3
|
|
|
4
|
+
// Hooks
|
|
5
|
+
export * from './hooks'
|
|
6
|
+
|
|
4
7
|
// Atoms
|
|
5
8
|
export { default as Badge } from './atoms/Badge'
|
|
6
9
|
export type { BadgeProps } from './atoms/Badge'
|
|
@@ -80,6 +83,8 @@ export { default as InputField } from './molecules/InputField'
|
|
|
80
83
|
export type { InputFieldProps } from './molecules/InputField'
|
|
81
84
|
export { default as LinkButton } from './molecules/LinkButton'
|
|
82
85
|
export type { LinkButtonProps } from './molecules/LinkButton'
|
|
86
|
+
export { default as Modal, ModalHeader, ModalBody } from './molecules/Modal'
|
|
87
|
+
export type { ModalProps, ModalHeaderProps } from './molecules/Modal'
|
|
83
88
|
export { default as RadioField } from './molecules/RadioField'
|
|
84
89
|
export type { RadioFieldProps } from './molecules/RadioField'
|
|
85
90
|
export {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AriaAttributes,
|
|
3
|
+
KeyboardEvent,
|
|
4
|
+
MouseEvent,
|
|
5
|
+
ReactNode,
|
|
6
|
+
} from 'react'
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import { createPortal } from 'react-dom'
|
|
9
|
+
|
|
10
|
+
import { Overlay } from '../..'
|
|
11
|
+
import { useFadeEffect, useUI } from '../../hooks'
|
|
12
|
+
import type { ModalContentProps } from './ModalContent'
|
|
13
|
+
import ModalContent from './ModalContent'
|
|
14
|
+
|
|
15
|
+
export type ModalChildrenProps = {
|
|
16
|
+
fade: 'in' | 'out'
|
|
17
|
+
fadeOut: () => void
|
|
18
|
+
fadeIn: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ModalChildrenFunction = (props: ModalChildrenProps) => ReactNode
|
|
22
|
+
|
|
23
|
+
export interface ModalProps extends Omit<ModalContentProps, 'children'> {
|
|
24
|
+
/**
|
|
25
|
+
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
26
|
+
*/
|
|
27
|
+
testId?: string
|
|
28
|
+
/**
|
|
29
|
+
* Identifies the element (or elements) that labels the current element.
|
|
30
|
+
* @see aria-labelledby https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby
|
|
31
|
+
*/
|
|
32
|
+
'aria-labelledby'?: AriaAttributes['aria-label']
|
|
33
|
+
/**
|
|
34
|
+
* A boolean value that represents the state of the Modal
|
|
35
|
+
*/
|
|
36
|
+
isOpen?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Event emitted when the modal is closed
|
|
39
|
+
*/
|
|
40
|
+
onDismiss?: () => void
|
|
41
|
+
/**
|
|
42
|
+
* Children or function as a children
|
|
43
|
+
*/
|
|
44
|
+
children: ModalChildrenFunction | ReactNode
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* This component is based on @reach/dialog.
|
|
49
|
+
* https://github.com/reach/reach-ui/blob/main/packages/dialog/src/index.tsx
|
|
50
|
+
* https://reach.tech/dialog
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
const Modal = ({
|
|
54
|
+
children,
|
|
55
|
+
testId = 'fs-modal',
|
|
56
|
+
isOpen = true,
|
|
57
|
+
onDismiss,
|
|
58
|
+
...otherProps
|
|
59
|
+
}: ModalProps) => {
|
|
60
|
+
const { closeModal } = useUI()
|
|
61
|
+
const { fade, fadeOut, fadeIn } = useFadeEffect()
|
|
62
|
+
|
|
63
|
+
const handleBackdropClick = (event: MouseEvent) => {
|
|
64
|
+
if (event.defaultPrevented) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
event.stopPropagation()
|
|
69
|
+
fadeOut?.()
|
|
70
|
+
onDismiss?.()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handleBackdropKeyDown = (event: KeyboardEvent) => {
|
|
74
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
event.stopPropagation()
|
|
79
|
+
fadeOut?.()
|
|
80
|
+
onDismiss?.()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return isOpen
|
|
84
|
+
? createPortal(
|
|
85
|
+
<Overlay
|
|
86
|
+
onClick={handleBackdropClick}
|
|
87
|
+
onKeyDown={handleBackdropKeyDown}
|
|
88
|
+
>
|
|
89
|
+
<ModalContent
|
|
90
|
+
onTransitionEnd={() => fade === 'out' && closeModal()}
|
|
91
|
+
data-fs-modal
|
|
92
|
+
data-fs-modal-state={fade}
|
|
93
|
+
testId={testId}
|
|
94
|
+
{...otherProps}
|
|
95
|
+
>
|
|
96
|
+
{typeof children === 'function'
|
|
97
|
+
? children({ fade, fadeOut, fadeIn })
|
|
98
|
+
: children}
|
|
99
|
+
</ModalContent>
|
|
100
|
+
</Overlay>,
|
|
101
|
+
document.body
|
|
102
|
+
)
|
|
103
|
+
: null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default Modal
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { ReactNode, HTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface ModalBodyProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const ModalBody = ({ children, ...otherProps }: ModalBodyProps) => (
|
|
8
|
+
<div data-fs-modal-body {...otherProps}>
|
|
9
|
+
{children}
|
|
10
|
+
</div>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export default ModalBody
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DetailedHTMLProps,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
MouseEvent,
|
|
5
|
+
RefObject,
|
|
6
|
+
} from 'react'
|
|
7
|
+
import React, { useRef } from 'react'
|
|
8
|
+
|
|
9
|
+
import { useTrapFocus } from '../../hooks'
|
|
10
|
+
|
|
11
|
+
interface ModalContentPureProps
|
|
12
|
+
extends Omit<
|
|
13
|
+
DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
|
|
14
|
+
'ref'
|
|
15
|
+
> {
|
|
16
|
+
beforeElementRef: RefObject<HTMLDivElement>
|
|
17
|
+
trapFocusRef: RefObject<HTMLDivElement>
|
|
18
|
+
afterElementRef: RefObject<HTMLDivElement>
|
|
19
|
+
testId?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ModalContentPure = ({
|
|
23
|
+
beforeElementRef,
|
|
24
|
+
trapFocusRef,
|
|
25
|
+
afterElementRef,
|
|
26
|
+
testId = 'store-modal-content',
|
|
27
|
+
children,
|
|
28
|
+
...otherProps
|
|
29
|
+
}: ModalContentPureProps) => {
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<div
|
|
33
|
+
ref={beforeElementRef}
|
|
34
|
+
data-testid="beforeElement"
|
|
35
|
+
tabIndex={0}
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
/>
|
|
38
|
+
<div
|
|
39
|
+
data-fs-modal-content
|
|
40
|
+
data-testid={testId}
|
|
41
|
+
ref={trapFocusRef}
|
|
42
|
+
aria-modal="true"
|
|
43
|
+
role="dialog"
|
|
44
|
+
tabIndex={-1}
|
|
45
|
+
{...otherProps}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
<div
|
|
50
|
+
ref={afterElementRef}
|
|
51
|
+
data-testid="afterElement"
|
|
52
|
+
tabIndex={0}
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
/>
|
|
55
|
+
</>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ModalContentProps = Omit<
|
|
60
|
+
ModalContentPureProps,
|
|
61
|
+
'trapFocusRef' | 'onClick' | 'beforeElementRef' | 'afterElementRef'
|
|
62
|
+
>
|
|
63
|
+
|
|
64
|
+
const ModalContent = ({ children, ...otherProps }: ModalContentProps) => {
|
|
65
|
+
const trapFocusRef = useRef<HTMLDivElement>(null)
|
|
66
|
+
const beforeElementRef = useRef<HTMLDivElement>(null)
|
|
67
|
+
const afterElementRef = useRef<HTMLDivElement>(null)
|
|
68
|
+
|
|
69
|
+
useTrapFocus({
|
|
70
|
+
beforeElementRef,
|
|
71
|
+
trapFocusRef,
|
|
72
|
+
afterElementRef,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<ModalContentPure
|
|
77
|
+
{...otherProps}
|
|
78
|
+
trapFocusRef={trapFocusRef}
|
|
79
|
+
beforeElementRef={beforeElementRef}
|
|
80
|
+
afterElementRef={afterElementRef}
|
|
81
|
+
onClick={(event: MouseEvent) => {
|
|
82
|
+
event.stopPropagation()
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</ModalContentPure>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default ModalContent
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { HTMLAttributes } from 'react'
|
|
2
|
+
import { X } from '../../assets'
|
|
3
|
+
import IconButton, { IconButtonProps } from '../IconButton'
|
|
4
|
+
|
|
5
|
+
export interface ModalHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
/**
|
|
7
|
+
* Title for header modal.
|
|
8
|
+
*/
|
|
9
|
+
title: string
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Description for header modal.
|
|
13
|
+
*/
|
|
14
|
+
description?: string
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Props for the Close Button component.
|
|
18
|
+
*/
|
|
19
|
+
closeButtonProps?: Partial<Omit<IconButtonProps, 'onClick'>>
|
|
20
|
+
|
|
21
|
+
onClose?: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ModalHeader = ({
|
|
25
|
+
onClose,
|
|
26
|
+
title,
|
|
27
|
+
closeButtonProps = {},
|
|
28
|
+
description,
|
|
29
|
+
}: ModalHeaderProps) => {
|
|
30
|
+
return (
|
|
31
|
+
<header data-fs-modal-header>
|
|
32
|
+
{onClose && (
|
|
33
|
+
<IconButton
|
|
34
|
+
onClick={() => onClose?.()}
|
|
35
|
+
data-fs-modal-header-close-button
|
|
36
|
+
icon={<X />}
|
|
37
|
+
aria-label="Close modal"
|
|
38
|
+
{...closeButtonProps}
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
<p data-fs-modal-header-title>{title}</p>
|
|
42
|
+
{description && <p data-fs-modal-header-description>{description}</p>}
|
|
43
|
+
</header>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default ModalHeader
|