@faststore/components 3.42.0 → 3.45.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/cjs/hooks/UIProvider.d.ts +10 -1
- package/dist/cjs/hooks/UIProvider.js +24 -0
- package/dist/cjs/hooks/UIProvider.js.map +1 -1
- package/dist/cjs/hooks/index.d.ts +2 -1
- package/dist/cjs/hooks/index.js +5 -3
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/hooks/useOnClickOutside.d.ts +4 -0
- package/dist/cjs/hooks/useOnClickOutside.js +33 -0
- package/dist/cjs/hooks/useOnClickOutside.js.map +1 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/molecules/Accordion/AccordionButton.js +1 -1
- package/dist/cjs/molecules/Accordion/AccordionButton.js.map +1 -1
- package/dist/cjs/molecules/Modal/Modal.d.ts +14 -6
- package/dist/cjs/molecules/Modal/Modal.js +13 -4
- package/dist/cjs/molecules/Modal/Modal.js.map +1 -1
- package/dist/cjs/molecules/Popover/Popover.d.ts +67 -0
- package/dist/cjs/molecules/Popover/Popover.js +65 -0
- package/dist/cjs/molecules/Popover/Popover.js.map +1 -0
- package/dist/cjs/molecules/Popover/index.d.ts +2 -0
- package/dist/cjs/molecules/Popover/index.js +9 -0
- package/dist/cjs/molecules/Popover/index.js.map +1 -0
- package/dist/cjs/molecules/RegionBar/RegionBar.d.ts +13 -4
- package/dist/cjs/molecules/RegionBar/RegionBar.js +5 -3
- package/dist/cjs/molecules/RegionBar/RegionBar.js.map +1 -1
- package/dist/cjs/organisms/RegionModal/RegionModal.d.ts +10 -1
- package/dist/cjs/organisms/RegionModal/RegionModal.js +15 -8
- package/dist/cjs/organisms/RegionModal/RegionModal.js.map +1 -1
- package/dist/cjs/organisms/ShippingSimulation/ShippingSimulation.d.ts +2 -2
- package/dist/cjs/organisms/SlideOver/SlideOverHeader.d.ts +1 -1
- package/dist/esm/hooks/UIProvider.d.ts +10 -1
- package/dist/esm/hooks/UIProvider.js +24 -0
- package/dist/esm/hooks/UIProvider.js.map +1 -1
- package/dist/esm/hooks/index.d.ts +2 -1
- package/dist/esm/hooks/index.js +2 -1
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/hooks/useOnClickOutside.d.ts +4 -0
- package/dist/esm/hooks/useOnClickOutside.js +29 -0
- package/dist/esm/hooks/useOnClickOutside.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/molecules/Accordion/AccordionButton.js +1 -1
- package/dist/esm/molecules/Accordion/AccordionButton.js.map +1 -1
- package/dist/esm/molecules/Modal/Modal.d.ts +14 -6
- package/dist/esm/molecules/Modal/Modal.js +13 -4
- package/dist/esm/molecules/Modal/Modal.js.map +1 -1
- package/dist/esm/molecules/Popover/Popover.d.ts +67 -0
- package/dist/esm/molecules/Popover/Popover.js +62 -0
- package/dist/esm/molecules/Popover/Popover.js.map +1 -0
- package/dist/esm/molecules/Popover/index.d.ts +2 -0
- package/dist/esm/molecules/Popover/index.js +2 -0
- package/dist/esm/molecules/Popover/index.js.map +1 -0
- package/dist/esm/molecules/RegionBar/RegionBar.d.ts +13 -4
- package/dist/esm/molecules/RegionBar/RegionBar.js +5 -3
- package/dist/esm/molecules/RegionBar/RegionBar.js.map +1 -1
- package/dist/esm/organisms/RegionModal/RegionModal.d.ts +10 -1
- package/dist/esm/organisms/RegionModal/RegionModal.js +15 -8
- package/dist/esm/organisms/RegionModal/RegionModal.js.map +1 -1
- package/dist/esm/organisms/ShippingSimulation/ShippingSimulation.d.ts +2 -2
- package/dist/esm/organisms/SlideOver/SlideOverHeader.d.ts +1 -1
- package/package.json +2 -2
- package/src/hooks/UIProvider.tsx +48 -1
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useOnClickOutside.ts +40 -0
- package/src/index.ts +2 -0
- package/src/molecules/Accordion/AccordionButton.tsx +1 -1
- package/src/molecules/Modal/Modal.tsx +29 -8
- package/src/molecules/Popover/Popover.tsx +209 -0
- package/src/molecules/Popover/index.tsx +2 -0
- package/src/molecules/RegionBar/RegionBar.tsx +21 -5
- package/src/organisms/RegionModal/RegionModal.tsx +29 -7
- package/src/organisms/ShippingSimulation/ShippingSimulation.tsx +2 -2
- package/src/organisms/SlideOver/SlideOverHeader.tsx +1 -1
|
@@ -43,6 +43,10 @@ export interface RegionModalProps extends Omit<ModalProps, 'children'> {
|
|
|
43
43
|
* Postal code input's label.
|
|
44
44
|
*/
|
|
45
45
|
inputLabel?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The text displayed on the InputField Button. Suggestion: maximum 9 characters.
|
|
48
|
+
*/
|
|
49
|
+
inputButtonActionText?: string;
|
|
46
50
|
/**
|
|
47
51
|
* Enables fadeOut effect on modal after onSubmit function
|
|
48
52
|
*/
|
|
@@ -67,6 +71,11 @@ export interface RegionModalProps extends Omit<ModalProps, 'children'> {
|
|
|
67
71
|
* Callback function when the input clear button is clicked.
|
|
68
72
|
*/
|
|
69
73
|
onClear?: () => void;
|
|
74
|
+
/**
|
|
75
|
+
* Determines if the modal can be dismissed using the close button or the Escape key.
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
dismissible?: boolean;
|
|
70
79
|
}
|
|
71
|
-
declare function RegionModal({ testId, title, description, closeButtonAriaLabel, idkPostalCodeLinkProps, errorMessage, inputRef, inputValue, inputLabel, fadeOutOnSubmit, overlayProps, onClose, onInput, onSubmit, onClear, ...otherProps }: RegionModalProps): React.JSX.Element;
|
|
80
|
+
declare function RegionModal({ testId, title, description, closeButtonAriaLabel, idkPostalCodeLinkProps, errorMessage, inputRef, inputValue, inputLabel, inputButtonActionText, fadeOutOnSubmit, overlayProps, onClose, onInput, onSubmit, onClear, dismissible, ...otherProps }: RegionModalProps): React.JSX.Element;
|
|
72
81
|
export default RegionModal;
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { InputField, Link, Modal, ModalBody, ModalHeader } from '../..';
|
|
3
|
-
function RegionModal({ testId = 'fs-region-modal', title = 'Set your location', description = '
|
|
4
|
-
return (React.createElement(Modal, { "data-fs-region-modal": true, testId: testId, overlayProps: overlayProps, title: "Region modal", "aria-label": "Region modal",
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
function RegionModal({ testId = 'fs-region-modal', title = 'Set your location', description = 'Offers and availability vary by location.', closeButtonAriaLabel = 'Close Region Modal', idkPostalCodeLinkProps, errorMessage, inputRef, inputValue, inputLabel = 'Postal Code', inputButtonActionText = 'Apply', fadeOutOnSubmit, overlayProps, onClose, onInput, onSubmit, onClear, dismissible = true, ...otherProps }) {
|
|
4
|
+
return (React.createElement(Modal, { "data-fs-region-modal": true, testId: testId, overlayProps: overlayProps, title: "Region modal", "aria-label": "Region modal", disableEscapeKeyDown: !dismissible, onEntered: () => {
|
|
5
|
+
if (inputRef?.current) {
|
|
6
|
+
inputRef.current.focus();
|
|
7
|
+
}
|
|
8
|
+
}, ...otherProps }, ({ fadeOut }) => (React.createElement(React.Fragment, null,
|
|
9
|
+
React.createElement(ModalHeader, { ...(dismissible && {
|
|
10
|
+
onClose: () => {
|
|
11
|
+
fadeOut();
|
|
12
|
+
onClear?.();
|
|
13
|
+
onClose?.();
|
|
14
|
+
},
|
|
15
|
+
}), title: title, description: description, closeBtnProps: {
|
|
9
16
|
'aria-label': closeButtonAriaLabel,
|
|
10
17
|
} }),
|
|
11
18
|
React.createElement(ModalBody, null,
|
|
12
|
-
React.createElement(InputField, { "data-fs-region-modal-input": true, id: `${testId}-input-field`, inputRef: inputRef, label: inputLabel, actionable: true, value: inputValue, onInput: (event) => onInput?.(event), onSubmit: () => {
|
|
19
|
+
React.createElement(InputField, { "data-fs-region-modal-input": true, id: `${testId}-input-field`, inputRef: inputRef, label: inputLabel, actionable: true, value: inputValue, buttonActionText: inputButtonActionText, onInput: (event) => onInput?.(event), onSubmit: () => {
|
|
13
20
|
onSubmit?.();
|
|
14
21
|
fadeOutOnSubmit ? fadeOut() : null;
|
|
15
22
|
}, onClear: () => onClear?.(), error: errorMessage }),
|
|
16
|
-
React.createElement(Link, { "data-fs-region-modal-link": true, ...idkPostalCodeLinkProps }))))));
|
|
23
|
+
idkPostalCodeLinkProps && (React.createElement(Link, { "data-fs-region-modal-link": true, ...idkPostalCodeLinkProps })))))));
|
|
17
24
|
}
|
|
18
25
|
export default RegionModal;
|
|
19
26
|
//# sourceMappingURL=RegionModal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegionModal.js","sourceRoot":"","sources":["../../../../src/organisms/RegionModal/RegionModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"RegionModal.js","sourceRoot":"","sources":["../../../../src/organisms/RegionModal/RegionModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAqFvE,SAAS,WAAW,CAAC,EACnB,MAAM,GAAG,iBAAiB,EAC1B,KAAK,GAAG,mBAAmB,EAC3B,WAAW,GAAG,2CAA2C,EACzD,oBAAoB,GAAG,oBAAoB,EAC3C,sBAAsB,EACtB,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,UAAU,GAAG,aAAa,EAC1B,qBAAqB,GAAG,OAAO,EAC/B,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAAO,EACP,WAAW,GAAG,IAAI,EAClB,GAAG,UAAU,EACI;IACjB,OAAO,CACL,oBAAC,KAAK,kCAEJ,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAC,cAAc,gBACT,cAAc,EACzB,oBAAoB,EAAE,CAAC,WAAW,EAClC,SAAS,EAAE,GAAG,EAAE;YACd,IAAI,QAAQ,EAAE,OAAO,EAAE,CAAC;gBACtB,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;YAC1B,CAAC;QACH,CAAC,KACG,UAAU,IAEb,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAChB;QACE,oBAAC,WAAW,OACN,CAAC,WAAW,IAAI;gBAClB,OAAO,EAAE,GAAG,EAAE;oBACZ,OAAO,EAAE,CAAA;oBACT,OAAO,EAAE,EAAE,CAAA;oBACX,OAAO,EAAE,EAAE,CAAA;gBACb,CAAC;aACF,CAAC,EACF,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE;gBACb,YAAY,EAAE,oBAAoB;aACnC,GACD;QACF,oBAAC,SAAS;YACR,oBAAC,UAAU,wCAET,EAAE,EAAE,GAAG,MAAM,cAAc,EAC3B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,UAAU,EACjB,UAAU,QACV,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,qBAAqB,EACvC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,EACpC,QAAQ,EAAE,GAAG,EAAE;oBACb,QAAQ,EAAE,EAAE,CAAA;oBACZ,eAAe,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;gBACpC,CAAC,EACD,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAC1B,KAAK,EAAE,YAAY,GACnB;YACD,sBAAsB,IAAI,CACzB,oBAAC,IAAI,0CAA+B,sBAAsB,GAAI,CAC/D,CACS,CACX,CACJ,CACK,CACT,CAAA;AACH,CAAC;AAED,eAAe,WAAW,CAAA"}
|
|
@@ -54,9 +54,9 @@ interface Address {
|
|
|
54
54
|
*/
|
|
55
55
|
reference?: string;
|
|
56
56
|
/**
|
|
57
|
-
* Address geoCoordinates
|
|
57
|
+
* Address geoCoordinates. [longitude, latitude]
|
|
58
58
|
*/
|
|
59
|
-
geoCoordinates?: [number];
|
|
59
|
+
geoCoordinates?: [number, number];
|
|
60
60
|
}
|
|
61
61
|
export interface ShippingSimulationProps extends HTMLAttributes<HTMLDivElement> {
|
|
62
62
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/components",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.45.0",
|
|
4
4
|
"main": "dist/cjs/index.js",
|
|
5
5
|
"module": "dist/esm/index.js",
|
|
6
6
|
"typings": "dist/esm/index.d.ts",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"volta": {
|
|
57
57
|
"extends": "../../package.json"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "7a888c79aa95c02416c95820f1a912f829fa379c"
|
|
60
60
|
}
|
package/src/hooks/UIProvider.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PropsWithChildren, ReactNode } from 'react'
|
|
1
|
+
import type { PropsWithChildren, ReactNode, RefObject } from 'react'
|
|
2
2
|
import React, { createContext, useContext, useMemo, useReducer } from 'react'
|
|
3
3
|
|
|
4
4
|
export interface Toast {
|
|
@@ -8,6 +8,11 @@ export interface Toast {
|
|
|
8
8
|
icon?: ReactNode
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface Popover {
|
|
12
|
+
isOpen: boolean
|
|
13
|
+
triggerRef?: RefObject<HTMLElement>
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
interface State {
|
|
12
17
|
/** Cart sidebar */
|
|
13
18
|
cart: boolean
|
|
@@ -17,7 +22,10 @@ interface State {
|
|
|
17
22
|
navbar: boolean
|
|
18
23
|
/** Search page filter slider */
|
|
19
24
|
filter: boolean
|
|
25
|
+
/** Toast notifications */
|
|
20
26
|
toasts: Toast[]
|
|
27
|
+
/** Region Popover */
|
|
28
|
+
popover: Popover
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
type UIElement = 'navbar' | 'cart' | 'modal' | 'filter'
|
|
@@ -38,6 +46,16 @@ type Action =
|
|
|
38
46
|
| {
|
|
39
47
|
type: 'popToast'
|
|
40
48
|
}
|
|
49
|
+
| {
|
|
50
|
+
type: 'openPopover'
|
|
51
|
+
payload: {
|
|
52
|
+
isOpen: boolean
|
|
53
|
+
triggerRef?: RefObject<HTMLElement>
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
type: 'closePopover'
|
|
58
|
+
}
|
|
41
59
|
|
|
42
60
|
const reducer = (state: State, action: Action): State => {
|
|
43
61
|
const { type } = action
|
|
@@ -89,6 +107,26 @@ const reducer = (state: State, action: Action): State => {
|
|
|
89
107
|
}
|
|
90
108
|
}
|
|
91
109
|
|
|
110
|
+
case 'openPopover': {
|
|
111
|
+
return {
|
|
112
|
+
...state,
|
|
113
|
+
popover: {
|
|
114
|
+
isOpen: true,
|
|
115
|
+
triggerRef: action.payload.triggerRef,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case 'closePopover': {
|
|
121
|
+
return {
|
|
122
|
+
...state,
|
|
123
|
+
popover: {
|
|
124
|
+
isOpen: false,
|
|
125
|
+
triggerRef: undefined,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
92
130
|
default:
|
|
93
131
|
throw new Error(`Action ${type} not implemented`)
|
|
94
132
|
}
|
|
@@ -100,6 +138,10 @@ const initializer = (): State => ({
|
|
|
100
138
|
navbar: false,
|
|
101
139
|
filter: false,
|
|
102
140
|
toasts: [],
|
|
141
|
+
popover: {
|
|
142
|
+
isOpen: false,
|
|
143
|
+
triggerRef: undefined,
|
|
144
|
+
},
|
|
103
145
|
})
|
|
104
146
|
|
|
105
147
|
interface Context extends State {
|
|
@@ -113,6 +155,8 @@ interface Context extends State {
|
|
|
113
155
|
closeModal: () => void
|
|
114
156
|
pushToast: (data: Toast) => void
|
|
115
157
|
popToast: () => void
|
|
158
|
+
openPopover: (popover: Popover) => void
|
|
159
|
+
closePopover: () => void
|
|
116
160
|
}
|
|
117
161
|
|
|
118
162
|
const UIContext = createContext<Context | undefined>(undefined)
|
|
@@ -133,6 +177,9 @@ function UIProvider({ children }: PropsWithChildren<unknown>) {
|
|
|
133
177
|
pushToast: (toast: Toast) =>
|
|
134
178
|
dispatch({ type: 'pushToast', payload: toast }),
|
|
135
179
|
popToast: () => dispatch({ type: 'popToast' }),
|
|
180
|
+
openPopover: (popover: Popover) =>
|
|
181
|
+
dispatch({ type: 'openPopover', payload: popover }),
|
|
182
|
+
closePopover: () => dispatch({ type: 'closePopover' }),
|
|
136
183
|
}),
|
|
137
184
|
[]
|
|
138
185
|
)
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as UIProvider, Toast as ToastProps, useUI } from './UIProvider'
|
|
2
2
|
export { useFadeEffect } from './useFadeEffect'
|
|
3
|
-
export {
|
|
3
|
+
export { useOnClickOutside } from './useOnClickOutside'
|
|
4
4
|
export { useSearch } from './useSearch'
|
|
5
5
|
export { useSKUMatrix } from './useSKUMatrix'
|
|
6
6
|
export { useScrollDirection } from './useScrollDirection'
|
|
@@ -12,3 +12,4 @@ export type {
|
|
|
12
12
|
SlideDirection,
|
|
13
13
|
} from './useSlider'
|
|
14
14
|
export { useSlideVisibility } from './useSlideVisibility'
|
|
15
|
+
export { useTrapFocus } from './useTrapFocus'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RefObject } from 'react'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
type Handler = (event: any) => void
|
|
5
|
+
|
|
6
|
+
export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
|
|
7
|
+
ref: RefObject<T> | undefined,
|
|
8
|
+
handler: Handler
|
|
9
|
+
) {
|
|
10
|
+
useEffect(
|
|
11
|
+
() => {
|
|
12
|
+
if (!ref?.current) return
|
|
13
|
+
|
|
14
|
+
const listener: Handler = (event) => {
|
|
15
|
+
if (!ref?.current || ref.current.contains(event.target)) {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
handler(event)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
document.addEventListener('mousedown', listener)
|
|
23
|
+
document.addEventListener('touchstart', listener)
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
document.removeEventListener('mousedown', listener)
|
|
27
|
+
document.removeEventListener('touchstart', listener)
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Add ref and handler to effect dependencies.
|
|
32
|
+
* It's worth noting that because passed in handler is a new
|
|
33
|
+
* function on every render that will cause this effect
|
|
34
|
+
* callback/cleanup to run every render. It's not a big deal
|
|
35
|
+
* but to optimize you can wrap handler in useCallback before
|
|
36
|
+
* passing it into this hook.
|
|
37
|
+
*/
|
|
38
|
+
[ref, handler]
|
|
39
|
+
)
|
|
40
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -131,6 +131,8 @@ export type {
|
|
|
131
131
|
} from './molecules/NavbarLinks'
|
|
132
132
|
export { default as OrderSummary } from './molecules/OrderSummary'
|
|
133
133
|
export type { OrderSummaryProps } from './molecules/OrderSummary'
|
|
134
|
+
export { default as Popover } from './molecules/Popover'
|
|
135
|
+
export type { PopoverProps } from './molecules/Popover'
|
|
134
136
|
export {
|
|
135
137
|
default as ProductCard,
|
|
136
138
|
ProductCardImage,
|
|
@@ -78,7 +78,7 @@ const AccordionButton = forwardRef<HTMLButtonElement, AccordionButtonProps>(
|
|
|
78
78
|
ref={ref}
|
|
79
79
|
id={button}
|
|
80
80
|
variant="tertiary"
|
|
81
|
-
data-fs-accordion-button
|
|
81
|
+
data-fs-accordion-button={indices.has(index) ? 'expanded' : 'collapsed'}
|
|
82
82
|
aria-expanded={indices.has(index)}
|
|
83
83
|
icon={indices.has(index) ? expandedIcon : collapsedIcon}
|
|
84
84
|
iconPosition="right"
|
|
@@ -20,7 +20,8 @@ export type ModalChildrenProps = {
|
|
|
20
20
|
|
|
21
21
|
type ModalChildrenFunction = (props: ModalChildrenProps) => ReactNode
|
|
22
22
|
|
|
23
|
-
export interface ModalProps
|
|
23
|
+
export interface ModalProps
|
|
24
|
+
extends Omit<ModalContentProps, 'children' | 'onEntered'> {
|
|
24
25
|
/**
|
|
25
26
|
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
26
27
|
*/
|
|
@@ -31,21 +32,29 @@ export interface ModalProps extends Omit<ModalContentProps, 'children'> {
|
|
|
31
32
|
*/
|
|
32
33
|
'aria-labelledby'?: AriaAttributes['aria-label']
|
|
33
34
|
/**
|
|
34
|
-
* A boolean value that represents the state of the Modal
|
|
35
|
+
* A boolean value that represents the state of the Modal.
|
|
35
36
|
*/
|
|
36
37
|
isOpen?: boolean
|
|
37
38
|
/**
|
|
38
|
-
* Event emitted when the modal is closed
|
|
39
|
+
* Event emitted when the modal is closed.
|
|
39
40
|
*/
|
|
40
41
|
onDismiss?: () => void
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* Callback function when the modal is opened.
|
|
44
|
+
*/
|
|
45
|
+
onEntered?: () => void
|
|
46
|
+
/**
|
|
47
|
+
* Props forwarded to the `Overlay` component.
|
|
43
48
|
*/
|
|
44
49
|
overlayProps?: OverlayProps
|
|
45
50
|
/**
|
|
46
|
-
* Children or function as a children
|
|
51
|
+
* Children or function as a children.
|
|
47
52
|
*/
|
|
48
53
|
children: ModalChildrenFunction | ReactNode
|
|
54
|
+
/**
|
|
55
|
+
* Disable being closed using the Escape key.
|
|
56
|
+
*/
|
|
57
|
+
disableEscapeKeyDown?: boolean
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
/*
|
|
@@ -60,13 +69,15 @@ const Modal = ({
|
|
|
60
69
|
isOpen = true,
|
|
61
70
|
onDismiss,
|
|
62
71
|
overlayProps,
|
|
72
|
+
disableEscapeKeyDown = false,
|
|
73
|
+
onEntered,
|
|
63
74
|
...otherProps
|
|
64
75
|
}: ModalProps) => {
|
|
65
76
|
const { closeModal } = useUI()
|
|
66
77
|
const { fade, fadeOut, fadeIn } = useFadeEffect()
|
|
67
78
|
|
|
68
79
|
const handleBackdropClick = (event: MouseEvent) => {
|
|
69
|
-
if (event.defaultPrevented) {
|
|
80
|
+
if (disableEscapeKeyDown || event.defaultPrevented) {
|
|
70
81
|
return
|
|
71
82
|
}
|
|
72
83
|
|
|
@@ -76,7 +87,11 @@ const Modal = ({
|
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
const handleBackdropKeyDown = (event: KeyboardEvent) => {
|
|
79
|
-
if (
|
|
90
|
+
if (
|
|
91
|
+
disableEscapeKeyDown ||
|
|
92
|
+
event.key !== 'Escape' ||
|
|
93
|
+
event.defaultPrevented
|
|
94
|
+
) {
|
|
80
95
|
return
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -93,7 +108,13 @@ const Modal = ({
|
|
|
93
108
|
{...overlayProps}
|
|
94
109
|
>
|
|
95
110
|
<ModalContent
|
|
96
|
-
onTransitionEnd={() =>
|
|
111
|
+
onTransitionEnd={() => {
|
|
112
|
+
if (fade === 'out') {
|
|
113
|
+
closeModal()
|
|
114
|
+
} else if (fade === 'in' && onEntered) {
|
|
115
|
+
onEntered()
|
|
116
|
+
}
|
|
117
|
+
}}
|
|
97
118
|
data-fs-modal
|
|
98
119
|
data-fs-modal-state={fade}
|
|
99
120
|
testId={testId}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type HTMLAttributes,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
type RefObject,
|
|
11
|
+
} from 'react'
|
|
12
|
+
import Icon from '../../atoms/Icon'
|
|
13
|
+
import IconButton from '../IconButton'
|
|
14
|
+
|
|
15
|
+
import { useOnClickOutside, useUI } from '../../hooks'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Specifies Popover position.
|
|
19
|
+
*/
|
|
20
|
+
export type Side = 'bottom' | 'top'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Specifies tooltip alignment.
|
|
24
|
+
*/
|
|
25
|
+
export type Alignment = 'start' | 'center' | 'end'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Combines side + alignment (e.g., "top-start").
|
|
29
|
+
*/
|
|
30
|
+
export type Placement = `${Side}-${Alignment}`
|
|
31
|
+
|
|
32
|
+
export interface PopoverProps
|
|
33
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
|
|
34
|
+
/**
|
|
35
|
+
* The Popover header's title.
|
|
36
|
+
*/
|
|
37
|
+
title?: string
|
|
38
|
+
/**
|
|
39
|
+
* Content of the Popover.
|
|
40
|
+
*/
|
|
41
|
+
content: ReactNode
|
|
42
|
+
/**
|
|
43
|
+
* Defines the side or side-alignment (e.g., "bottom-start", "bottom-center") of the Popover.
|
|
44
|
+
*/
|
|
45
|
+
placement?: Placement
|
|
46
|
+
/**
|
|
47
|
+
* If the Popover can be closed by a button.
|
|
48
|
+
*/
|
|
49
|
+
dismissible?: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Called when the Popover is dismissed.
|
|
52
|
+
*/
|
|
53
|
+
onDismiss?: () => void
|
|
54
|
+
/**
|
|
55
|
+
* Callback when the Popover is fully rendered and positioned.
|
|
56
|
+
*/
|
|
57
|
+
onEntered?: () => void
|
|
58
|
+
/**
|
|
59
|
+
* Close button aria-label.
|
|
60
|
+
*/
|
|
61
|
+
closeButtonAriaLabel?: string
|
|
62
|
+
/**
|
|
63
|
+
* Controls whether the Popover is open.
|
|
64
|
+
*/
|
|
65
|
+
isOpen: boolean
|
|
66
|
+
/**
|
|
67
|
+
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
68
|
+
*/
|
|
69
|
+
testId?: string
|
|
70
|
+
/**
|
|
71
|
+
* Offset value for top position (e.g.: 12).
|
|
72
|
+
* @default '8'
|
|
73
|
+
*/
|
|
74
|
+
offsetTop?: number
|
|
75
|
+
/**
|
|
76
|
+
* Offset value for left position (e.g.: 12).
|
|
77
|
+
* @default '0'
|
|
78
|
+
*/
|
|
79
|
+
offsetLeft?: number
|
|
80
|
+
/**
|
|
81
|
+
* Reference to the trigger element that opens the Popover.
|
|
82
|
+
*/
|
|
83
|
+
triggerRef?: RefObject<HTMLElement>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const calculatePosition = (
|
|
87
|
+
rect: DOMRect,
|
|
88
|
+
placement: Placement,
|
|
89
|
+
offsetTop: number,
|
|
90
|
+
offsetLeft: number
|
|
91
|
+
) => {
|
|
92
|
+
const { top, left, height } = rect
|
|
93
|
+
|
|
94
|
+
switch (true) {
|
|
95
|
+
case placement.startsWith('top'):
|
|
96
|
+
return {
|
|
97
|
+
top: top + height + window.scrollY - offsetTop,
|
|
98
|
+
left: left + window.scrollX + offsetLeft,
|
|
99
|
+
}
|
|
100
|
+
case placement.startsWith('bottom'):
|
|
101
|
+
return {
|
|
102
|
+
top: top + height + window.scrollY + offsetTop,
|
|
103
|
+
left: left + window.scrollX + offsetLeft,
|
|
104
|
+
}
|
|
105
|
+
default:
|
|
106
|
+
return { top: 0, left: 0 }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const Popover = forwardRef<HTMLDivElement, PopoverProps>(function Popover(
|
|
111
|
+
{
|
|
112
|
+
title,
|
|
113
|
+
content,
|
|
114
|
+
placement = 'bottom-start',
|
|
115
|
+
dismissible = false,
|
|
116
|
+
onDismiss,
|
|
117
|
+
isOpen,
|
|
118
|
+
triggerRef: propTriggerRef,
|
|
119
|
+
offsetTop = 8,
|
|
120
|
+
offsetLeft = 0,
|
|
121
|
+
closeButtonAriaLabel = 'Close Popover',
|
|
122
|
+
testId = 'fs-popover',
|
|
123
|
+
style,
|
|
124
|
+
onEntered,
|
|
125
|
+
...otherProps
|
|
126
|
+
},
|
|
127
|
+
ref
|
|
128
|
+
) {
|
|
129
|
+
// Use forwarded ref or internal ref for fallback
|
|
130
|
+
const popoverRef = ref || useRef<HTMLDivElement>(null)
|
|
131
|
+
|
|
132
|
+
const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0 })
|
|
133
|
+
const { popover, closePopover } = useUI()
|
|
134
|
+
|
|
135
|
+
const contextTriggerRef = popover.triggerRef
|
|
136
|
+
|
|
137
|
+
// Use the propTriggerRef if provided, otherwise fallback to contextTriggerRef
|
|
138
|
+
const triggerRef = propTriggerRef || contextTriggerRef
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!isOpen || !triggerRef?.current) return
|
|
142
|
+
|
|
143
|
+
// Set the position according to the trigger element and placement
|
|
144
|
+
const rect = triggerRef.current.getBoundingClientRect()
|
|
145
|
+
|
|
146
|
+
setPopoverPosition(
|
|
147
|
+
calculatePosition(rect, placement, offsetTop, offsetLeft)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Trigger the onEntered callback after positioning
|
|
151
|
+
if (onEntered) {
|
|
152
|
+
onEntered()
|
|
153
|
+
}
|
|
154
|
+
}, [isOpen, triggerRef, offsetTop, offsetLeft, placement])
|
|
155
|
+
|
|
156
|
+
const handleDismiss = useCallback(() => {
|
|
157
|
+
closePopover()
|
|
158
|
+
onDismiss?.()
|
|
159
|
+
}, [closePopover, onDismiss])
|
|
160
|
+
|
|
161
|
+
useOnClickOutside(
|
|
162
|
+
isOpen ? (popoverRef as RefObject<HTMLDivElement>) : undefined,
|
|
163
|
+
handleDismiss
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const handleKeyDown = useCallback(
|
|
167
|
+
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
168
|
+
if (event.key === 'Escape') {
|
|
169
|
+
handleDismiss()
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
[handleDismiss]
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if (!isOpen) {
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div
|
|
181
|
+
data-fs-popover
|
|
182
|
+
role="dialog"
|
|
183
|
+
ref={popoverRef}
|
|
184
|
+
data-fs-popover-placement={placement}
|
|
185
|
+
onKeyDown={handleKeyDown}
|
|
186
|
+
data-testid={testId}
|
|
187
|
+
style={{ position: 'absolute', ...popoverPosition, ...style }}
|
|
188
|
+
{...otherProps}
|
|
189
|
+
>
|
|
190
|
+
<header data-fs-popover-header>
|
|
191
|
+
{title && <h3 data-fs-popover-header-title>{title}</h3>}
|
|
192
|
+
{dismissible && (
|
|
193
|
+
<IconButton
|
|
194
|
+
data-fs-popover-header-dismiss-button
|
|
195
|
+
size="small"
|
|
196
|
+
variant="tertiary"
|
|
197
|
+
icon={<Icon name="X" width={20} height={20} />}
|
|
198
|
+
aria-label={closeButtonAriaLabel}
|
|
199
|
+
onClick={handleDismiss}
|
|
200
|
+
/>
|
|
201
|
+
)}
|
|
202
|
+
</header>
|
|
203
|
+
<div data-fs-popover-content>{content}</div>
|
|
204
|
+
<span data-fs-popover-indicator aria-hidden="true" />
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
export default Popover
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import type { HTMLAttributes, ReactNode } from 'react'
|
|
1
|
+
import type { HTMLAttributes, ReactNode, RefAttributes } from 'react'
|
|
2
2
|
import React, { forwardRef } from 'react'
|
|
3
3
|
|
|
4
4
|
import { Button } from '../../'
|
|
5
5
|
|
|
6
|
-
export interface RegionBarProps
|
|
6
|
+
export interface RegionBarProps
|
|
7
|
+
extends HTMLAttributes<HTMLDivElement>,
|
|
8
|
+
RefAttributes<HTMLDivElement> {
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
10
|
+
* City to be displayed in the component.
|
|
11
|
+
*/
|
|
12
|
+
city?: string
|
|
13
|
+
/**
|
|
14
|
+
* Postal code string to be display in the component.
|
|
9
15
|
*/
|
|
10
16
|
postalCode?: string
|
|
11
17
|
/**
|
|
@@ -28,16 +34,23 @@ export interface RegionBarProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
28
34
|
* A React component that will be rendered as an icon.
|
|
29
35
|
*/
|
|
30
36
|
buttonIcon?: ReactNode
|
|
37
|
+
/**
|
|
38
|
+
* Boolean to control whether postal code should be visible or not.
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
shouldDisplayPostalCode?: boolean
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
const RegionBar = forwardRef<HTMLDivElement, RegionBarProps>(function RegionBar(
|
|
34
45
|
{
|
|
46
|
+
city,
|
|
35
47
|
postalCode,
|
|
36
48
|
icon,
|
|
37
49
|
label,
|
|
38
50
|
editLabel,
|
|
39
51
|
buttonIcon,
|
|
40
52
|
onButtonClick,
|
|
53
|
+
shouldDisplayPostalCode = true,
|
|
41
54
|
...otherProps
|
|
42
55
|
},
|
|
43
56
|
ref
|
|
@@ -51,9 +64,12 @@ const RegionBar = forwardRef<HTMLDivElement, RegionBarProps>(function RegionBar(
|
|
|
51
64
|
icon={buttonIcon}
|
|
52
65
|
>
|
|
53
66
|
{!!icon && icon}
|
|
54
|
-
{postalCode ? (
|
|
67
|
+
{city && postalCode ? (
|
|
55
68
|
<>
|
|
56
|
-
<span data-fs-region-bar-postal-code>
|
|
69
|
+
<span data-fs-region-bar-postal-code>
|
|
70
|
+
{city}
|
|
71
|
+
{shouldDisplayPostalCode && `, ${postalCode}`}
|
|
72
|
+
</span>
|
|
57
73
|
{!!editLabel && <span data-fs-region-bar-cta>{editLabel}</span>}
|
|
58
74
|
</>
|
|
59
75
|
) : (
|