@faststore/components 3.41.3 → 3.44.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/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/molecules/SearchProducts/SearchProductItemContent.d.ts +24 -0
- package/dist/cjs/molecules/SearchProducts/SearchProductItemContent.js +9 -3
- package/dist/cjs/molecules/SearchProducts/SearchProductItemContent.js.map +1 -1
- package/dist/cjs/molecules/SearchProducts/SearchProductItemControl.d.ts +50 -0
- package/dist/cjs/molecules/SearchProducts/SearchProductItemControl.js +62 -0
- package/dist/cjs/molecules/SearchProducts/SearchProductItemControl.js.map +1 -0
- 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/SKUMatrix/SKUMatrixSidebar.js +1 -1
- package/dist/cjs/organisms/SKUMatrix/SKUMatrixSidebar.js.map +1 -1
- package/dist/cjs/organisms/ShippingSimulation/ShippingSimulation.d.ts +2 -2
- 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/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/molecules/SearchProducts/SearchProductItemContent.d.ts +24 -0
- package/dist/esm/molecules/SearchProducts/SearchProductItemContent.js +10 -4
- package/dist/esm/molecules/SearchProducts/SearchProductItemContent.js.map +1 -1
- package/dist/esm/molecules/SearchProducts/SearchProductItemControl.d.ts +50 -0
- package/dist/esm/molecules/SearchProducts/SearchProductItemControl.js +59 -0
- package/dist/esm/molecules/SearchProducts/SearchProductItemControl.js.map +1 -0
- 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/SKUMatrix/SKUMatrixSidebar.js +1 -1
- package/dist/esm/organisms/SKUMatrix/SKUMatrixSidebar.js.map +1 -1
- package/dist/esm/organisms/ShippingSimulation/ShippingSimulation.d.ts +2 -2
- 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/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/molecules/SearchProducts/SearchProductItemContent.tsx +61 -10
- package/src/molecules/SearchProducts/SearchProductItemControl.tsx +199 -0
- package/src/organisms/RegionModal/RegionModal.tsx +29 -7
- package/src/organisms/SKUMatrix/SKUMatrixSidebar.tsx +1 -1
- package/src/organisms/ShippingSimulation/ShippingSimulation.tsx +2 -2
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,
|
|
@@ -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
|
) : (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { forwardRef } from 'react'
|
|
1
|
+
import React, { forwardRef, useCallback } from 'react'
|
|
2
2
|
import { ProductPrice } from '../..'
|
|
3
|
+
import SearchProductItemControl from './SearchProductItemControl'
|
|
3
4
|
|
|
4
5
|
import type { PriceDefinition } from '../../typings/PriceDefinition'
|
|
5
6
|
|
|
@@ -12,23 +13,73 @@ export interface SearchProductItemContentProps {
|
|
|
12
13
|
* Specifies product's prices.
|
|
13
14
|
*/
|
|
14
15
|
price: PriceDefinition
|
|
16
|
+
/**
|
|
17
|
+
* Quick order settings.
|
|
18
|
+
*/
|
|
19
|
+
quickOrder?: {
|
|
20
|
+
enabled: boolean
|
|
21
|
+
outOfStockLabel: string
|
|
22
|
+
availability: boolean
|
|
23
|
+
hasVariants: boolean
|
|
24
|
+
skuMatrixControl: React.ReactNode
|
|
25
|
+
quantity: number
|
|
26
|
+
min?: number
|
|
27
|
+
max?: number
|
|
28
|
+
onChangeQuantity(value: number): void
|
|
29
|
+
buyProps?: {
|
|
30
|
+
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
|
31
|
+
'data-testid': string
|
|
32
|
+
'data-sku': string
|
|
33
|
+
'data-seller': string
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Event emitted when value is out of the min and max bounds
|
|
38
|
+
*/
|
|
39
|
+
onValidateBlur?: (min: number, maxValue: number, quantity: number) => void
|
|
15
40
|
}
|
|
16
41
|
|
|
17
42
|
const SearchProductItemContent = forwardRef<
|
|
18
43
|
HTMLElement,
|
|
19
44
|
SearchProductItemContentProps
|
|
20
|
-
>(function SearchProductItemContent(
|
|
45
|
+
>(function SearchProductItemContent(
|
|
46
|
+
{ price, title, quickOrder, onValidateBlur, ...otherProps },
|
|
47
|
+
ref
|
|
48
|
+
) {
|
|
49
|
+
const renderProductItemContent = useCallback(() => {
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<p data-fs-search-product-item-title>{title}</p>
|
|
53
|
+
{price.value !== 0 && (
|
|
54
|
+
<ProductPrice
|
|
55
|
+
data-fs-search-product-item-prices
|
|
56
|
+
listPrice={price.listPrice}
|
|
57
|
+
value={price.value}
|
|
58
|
+
formatter={price.formatter}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
</>
|
|
62
|
+
)
|
|
63
|
+
}, [price.formatter, price.listPrice, price.value, title])
|
|
64
|
+
|
|
21
65
|
return (
|
|
22
66
|
<section ref={ref} data-fs-search-product-item-content {...otherProps}>
|
|
23
|
-
|
|
67
|
+
{!quickOrder?.enabled && renderProductItemContent()}
|
|
24
68
|
|
|
25
|
-
{
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
69
|
+
{quickOrder?.enabled && (
|
|
70
|
+
<SearchProductItemControl
|
|
71
|
+
outOfStockLabel={quickOrder.outOfStockLabel}
|
|
72
|
+
availability={quickOrder.availability}
|
|
73
|
+
hasVariants={quickOrder.hasVariants}
|
|
74
|
+
skuMatrixControl={quickOrder.skuMatrixControl}
|
|
75
|
+
quantity={quickOrder.quantity}
|
|
76
|
+
onChangeQuantity={quickOrder.onChangeQuantity}
|
|
77
|
+
max={quickOrder.max}
|
|
78
|
+
onValidateBlur={onValidateBlur}
|
|
79
|
+
{...quickOrder.buyProps}
|
|
80
|
+
>
|
|
81
|
+
{renderProductItemContent()}
|
|
82
|
+
</SearchProductItemControl>
|
|
32
83
|
)}
|
|
33
84
|
</section>
|
|
34
85
|
)
|