playbook_ui 11.1.2 → 11.2.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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/_playbook.scss +1 -0
- data/app/pb_kits/playbook/data/menu.yml +14 -0
- data/app/pb_kits/playbook/index.js +1 -0
- data/app/pb_kits/playbook/pb_bread_crumbs/docs/_description.md +1 -1
- data/app/pb_kits/playbook/pb_button_toolbar/{_button_toolbar.jsx → _button_toolbar.tsx} +3 -3
- data/app/pb_kits/playbook/pb_caption/_caption.tsx +1 -1
- data/app/pb_kits/playbook/pb_contact/_contact.jsx +7 -6
- data/app/pb_kits/playbook/pb_contact/contact.rb +4 -0
- data/app/pb_kits/playbook/pb_contact/contact.test.js +87 -0
- data/app/pb_kits/playbook/pb_contact/docs/_contact_with_detail.html.erb +6 -0
- data/app/pb_kits/playbook/pb_contact/docs/_contact_with_detail.jsx +6 -0
- data/app/pb_kits/playbook/pb_dialog/_dialog.jsx +61 -12
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_default.jsx +1 -1
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_stacked_alert.jsx +153 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_stacked_alert.md +1 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_status.jsx +130 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_status.md +1 -0
- data/app/pb_kits/playbook/pb_dialog/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_dialog/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_flex/_flex_item.tsx +50 -0
- data/app/pb_kits/playbook/pb_icon/_icon.tsx +15 -13
- data/app/pb_kits/playbook/pb_lightbox/Carousel/Slide.tsx +53 -0
- data/app/pb_kits/playbook/pb_lightbox/Carousel/Slides.tsx +35 -0
- data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnail.tsx +39 -0
- data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnails.tsx +75 -0
- data/app/pb_kits/playbook/pb_lightbox/Carousel/index.tsx +57 -0
- data/app/pb_kits/playbook/pb_lightbox/_lightbox.tsx +115 -0
- data/app/pb_kits/playbook/pb_lightbox/_lightbox_context.tsx +3 -0
- data/app/pb_kits/playbook/pb_lightbox/_lightbox_header.tsx +109 -0
- data/app/pb_kits/playbook/pb_lightbox/_lightbox_header_icon.tsx +27 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_compound_component.jsx +66 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_compound_component.md +1 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.jsx +65 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.md +1 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_multiple.jsx +91 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/example.yml +6 -0
- data/app/pb_kits/playbook/pb_lightbox/docs/index.js +3 -0
- data/app/pb_kits/playbook/pb_lightbox/hooks/useKbdControls.ts +34 -0
- data/app/pb_kits/playbook/pb_lightbox/hooks/useVisibility.js +21 -0
- data/app/pb_kits/playbook/pb_lightbox/hooks/useWindowSize.ts +30 -0
- data/app/pb_kits/playbook/pb_lightbox/lightbox.scss +222 -0
- data/app/pb_kits/playbook/pb_lightbox/lightbox.test.jsx +124 -0
- data/app/pb_kits/playbook/playbook-doc.js +2 -0
- data/app/pb_kits/playbook/utilities/globalProps.ts +1 -1
- data/dist/reset.css +60 -1
- data/lib/playbook/version.rb +2 -2
- metadata +30 -3
@@ -0,0 +1,130 @@
|
|
1
|
+
/* eslint-disable react/jsx-handler-names */
|
2
|
+
/* @flow */
|
3
|
+
|
4
|
+
import React, { useState } from "react"
|
5
|
+
import { Button, Dialog, Flex } from "../.."
|
6
|
+
|
7
|
+
const useDialog = (visible = false) => {
|
8
|
+
const [opened, setOpened] = useState(visible)
|
9
|
+
const toggle = () => setOpened(!opened)
|
10
|
+
return [opened, toggle]
|
11
|
+
|
12
|
+
}
|
13
|
+
|
14
|
+
const DialogStatus = () => {
|
15
|
+
const [infoAlertOpened, toggleInfoAlert] = useDialog()
|
16
|
+
const [cautionAlertOpened, toggleCautionAlert] = useDialog()
|
17
|
+
const [successAlertOpened, toggleSuccessAlert] = useDialog()
|
18
|
+
const [errorAlertOpened, toggleErrorAlert] = useDialog()
|
19
|
+
const [deleteAlertOpened, toggleDeleteAlert] = useDialog()
|
20
|
+
|
21
|
+
const dialogs = [
|
22
|
+
{
|
23
|
+
status: "info",
|
24
|
+
text: "Text explaining why there is an alert",
|
25
|
+
title: "Are you Sure?",
|
26
|
+
toggle: toggleInfoAlert,
|
27
|
+
visible: infoAlertOpened,
|
28
|
+
buttonOneText:"No, Cancel",
|
29
|
+
buttonTwoText: "Yes, Action"
|
30
|
+
},
|
31
|
+
{
|
32
|
+
status: "caution",
|
33
|
+
text: "This is the action you will be taking",
|
34
|
+
title: "Are you Sure?",
|
35
|
+
toggle: toggleCautionAlert,
|
36
|
+
visible: cautionAlertOpened,
|
37
|
+
buttonOneText:"No, Cancel",
|
38
|
+
buttonTwoText: "Yes, Action"
|
39
|
+
},
|
40
|
+
{
|
41
|
+
status: "delete",
|
42
|
+
text: "You are about to delete ...",
|
43
|
+
title: "Delete",
|
44
|
+
toggle: toggleDeleteAlert,
|
45
|
+
visible: deleteAlertOpened,
|
46
|
+
buttonOneText:"No, Cancel",
|
47
|
+
buttonTwoText: "Yes, Delete"
|
48
|
+
},
|
49
|
+
{
|
50
|
+
status: "error",
|
51
|
+
text: "Text explaining the error",
|
52
|
+
title: "Error Message",
|
53
|
+
toggle: toggleErrorAlert,
|
54
|
+
visible: errorAlertOpened,
|
55
|
+
buttonOneText:"No, Cancel",
|
56
|
+
buttonTwoText: "Ok, Thanks"
|
57
|
+
},
|
58
|
+
{
|
59
|
+
status: "success",
|
60
|
+
text: "Text explaining what is successful",
|
61
|
+
title: "Success!",
|
62
|
+
toggle: toggleSuccessAlert,
|
63
|
+
visible: successAlertOpened,
|
64
|
+
buttonOneText:"No, Cancel",
|
65
|
+
buttonTwoText: "Ok, Thanks"
|
66
|
+
},
|
67
|
+
]
|
68
|
+
|
69
|
+
return (
|
70
|
+
<div>
|
71
|
+
<Flex>
|
72
|
+
<Button
|
73
|
+
marginRight="md"
|
74
|
+
onClick={toggleInfoAlert}
|
75
|
+
>
|
76
|
+
{"Information Status"}
|
77
|
+
</Button>
|
78
|
+
<Button
|
79
|
+
marginRight="md"
|
80
|
+
onClick={toggleCautionAlert}
|
81
|
+
>
|
82
|
+
{"Caution Status"}
|
83
|
+
</Button>
|
84
|
+
<Button
|
85
|
+
marginRight="md"
|
86
|
+
onClick={toggleSuccessAlert}
|
87
|
+
>
|
88
|
+
{"Success Status"}
|
89
|
+
</Button>
|
90
|
+
<Button onClick={toggleErrorAlert}>
|
91
|
+
{"Error Status"}
|
92
|
+
</Button>
|
93
|
+
<Button
|
94
|
+
marginRight="md"
|
95
|
+
onClick={toggleDeleteAlert}
|
96
|
+
>
|
97
|
+
{"Delete Status"}
|
98
|
+
</Button>
|
99
|
+
</Flex>
|
100
|
+
<Flex>
|
101
|
+
{dialogs.map((dialog) => (
|
102
|
+
<Dialog
|
103
|
+
key={dialog.status}
|
104
|
+
onClose={dialog.toggle}
|
105
|
+
opened={dialog.visible}
|
106
|
+
status={dialog.status}
|
107
|
+
text={dialog.text}
|
108
|
+
title={dialog.title}
|
109
|
+
>
|
110
|
+
<Dialog.Footer>
|
111
|
+
<Button
|
112
|
+
onClick={dialog.toggle}
|
113
|
+
variant="secondary"
|
114
|
+
>
|
115
|
+
{dialog.buttonOneText}
|
116
|
+
</Button>
|
117
|
+
<Button
|
118
|
+
onClick={dialog.toggle}
|
119
|
+
>
|
120
|
+
{dialog.buttonTwoText}
|
121
|
+
</Button>
|
122
|
+
</Dialog.Footer>
|
123
|
+
</Dialog>
|
124
|
+
))}
|
125
|
+
</Flex>
|
126
|
+
</div>
|
127
|
+
)
|
128
|
+
}
|
129
|
+
|
130
|
+
export default DialogStatus
|
@@ -0,0 +1 @@
|
|
1
|
+
This shows the different alert statuses that can be used for dialog boxes. These all have two buttons which is a default
|
@@ -4,3 +4,5 @@ export { default as DialogSizes } from './_dialog_sizes.jsx'
|
|
4
4
|
export { default as DialogScrollable } from './_dialog_scrollable.jsx'
|
5
5
|
export { default as DialogSeparators } from './_dialog_separators.jsx'
|
6
6
|
export { default as DialogShouldCloseOnOverlay } from './_dialog_should_close_on_overlay.jsx'
|
7
|
+
export { default as DialogStatus } from './_dialog_status.jsx'
|
8
|
+
export { default as DialogStackedAlert } from './_dialog_stacked_alert.jsx'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import classnames from 'classnames'
|
3
|
+
import { buildCss } from '../utilities/props'
|
4
|
+
import { globalProps, GlobalProps } from '../utilities/globalProps'
|
5
|
+
type FlexItemPropTypes = {
|
6
|
+
children: React.ReactNode[] | React.ReactNode,
|
7
|
+
fixedSize?: string,
|
8
|
+
grow?: boolean,
|
9
|
+
shrink?: boolean,
|
10
|
+
className?: string,
|
11
|
+
overflow?: "auto" | "hidden" | "initial" | "inherit" | "scroll" | "visible",
|
12
|
+
order?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'first' | 'none',
|
13
|
+
alignSelf?: "start" | "end" | "center" | "stretch" | null,
|
14
|
+
displayFlex?: boolean
|
15
|
+
} & GlobalProps
|
16
|
+
|
17
|
+
const FlexItem = (props: FlexItemPropTypes): React.ReactElement => {
|
18
|
+
const {
|
19
|
+
children,
|
20
|
+
className,
|
21
|
+
fixedSize,
|
22
|
+
grow,
|
23
|
+
overflow = null,
|
24
|
+
shrink,
|
25
|
+
flex = 'none',
|
26
|
+
order = 'none',
|
27
|
+
alignSelf,
|
28
|
+
displayFlex
|
29
|
+
} = props
|
30
|
+
const growClass = grow === true ? 'grow' : ''
|
31
|
+
const displayFlexClass = displayFlex === true ? `display_flex_${displayFlex}` : ''
|
32
|
+
const flexClass = flex !== 'none' ? `flex_${flex}` : ''
|
33
|
+
const overflowClass = overflow ? `overflow_${overflow}` : ''
|
34
|
+
const shrinkClass = shrink === true ? 'shrink' : ''
|
35
|
+
const alignSelfClass = alignSelf ? `align_self_${alignSelf}` : ''
|
36
|
+
const fixedStyle =
|
37
|
+
fixedSize !== undefined ? { flexBasis: `${fixedSize}` } : null
|
38
|
+
const orderClass = order !== 'none' ? `order_${order}` : null
|
39
|
+
|
40
|
+
return (
|
41
|
+
<div
|
42
|
+
className={classnames(buildCss('pb_flex_item_kit', growClass, shrinkClass, flexClass, displayFlexClass), overflowClass, orderClass, alignSelfClass, globalProps(props), className)}
|
43
|
+
style={fixedStyle}
|
44
|
+
>
|
45
|
+
{children}
|
46
|
+
</div>
|
47
|
+
)
|
48
|
+
}
|
49
|
+
|
50
|
+
export default FlexItem
|
@@ -3,6 +3,20 @@ import classnames from 'classnames'
|
|
3
3
|
import { buildAriaProps, buildDataProps } from '../utilities/props'
|
4
4
|
import { GlobalProps, globalProps } from '../utilities/globalProps'
|
5
5
|
|
6
|
+
export type IconSizes = "lg"
|
7
|
+
| "xs"
|
8
|
+
| "sm"
|
9
|
+
| "1x"
|
10
|
+
| "2x"
|
11
|
+
| "3x"
|
12
|
+
| "4x"
|
13
|
+
| "5x"
|
14
|
+
| "6x"
|
15
|
+
| "7x"
|
16
|
+
| "8x"
|
17
|
+
| "9x"
|
18
|
+
| "10x"
|
19
|
+
|
6
20
|
type IconProps = {
|
7
21
|
aria?: {[key: string]: string},
|
8
22
|
border?: string,
|
@@ -18,19 +32,7 @@ type IconProps = {
|
|
18
32
|
pull?: "left" | "right" | "none",
|
19
33
|
pulse?: boolean,
|
20
34
|
rotation?: 90 | 180 | 270,
|
21
|
-
size?:
|
22
|
-
| "xs"
|
23
|
-
| "sm"
|
24
|
-
| "1x"
|
25
|
-
| "2x"
|
26
|
-
| "3x"
|
27
|
-
| "4x"
|
28
|
-
| "5x"
|
29
|
-
| "6x"
|
30
|
-
| "7x"
|
31
|
-
| "8x"
|
32
|
-
| "9x"
|
33
|
-
| "10x",
|
35
|
+
size?: IconSizes,
|
34
36
|
spin?: boolean,
|
35
37
|
} & GlobalProps
|
36
38
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/* @flow */
|
2
|
+
|
3
|
+
import React from 'react'
|
4
|
+
import { noop } from 'lodash'
|
5
|
+
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'
|
6
|
+
import Image from '../../pb_image/_image'
|
7
|
+
|
8
|
+
type SlideType = {
|
9
|
+
alt?: string,
|
10
|
+
current?: number,
|
11
|
+
onClick: () => void,
|
12
|
+
onChange?: (index: number) => void,
|
13
|
+
onZoom: (zoom: boolean) => void,
|
14
|
+
zooming: boolean,
|
15
|
+
url: string,
|
16
|
+
}
|
17
|
+
|
18
|
+
export default function Slide({
|
19
|
+
alt,
|
20
|
+
onClick = noop,
|
21
|
+
onZoom = noop,
|
22
|
+
url,
|
23
|
+
zooming = false,
|
24
|
+
}: SlideType): React.ReactElement {
|
25
|
+
const handlePinchingStop = ({state}: {state: any}) => {
|
26
|
+
const isZooming = state.scale > 1
|
27
|
+
onZoom(isZooming)
|
28
|
+
}
|
29
|
+
|
30
|
+
return (
|
31
|
+
<TransformWrapper
|
32
|
+
doubleClick={{ mode: 'reset' }}
|
33
|
+
initialScale={1}
|
34
|
+
onPinchingStop={handlePinchingStop}
|
35
|
+
panning={{ disabled: !zooming }}
|
36
|
+
>
|
37
|
+
<button
|
38
|
+
className="Slide"
|
39
|
+
onClick={onClick}
|
40
|
+
onDoubleClick={() => onZoom(false)}
|
41
|
+
tabIndex={-1}
|
42
|
+
>
|
43
|
+
<TransformComponent wrapperClass="TransformComponent">
|
44
|
+
<Image
|
45
|
+
alt={alt}
|
46
|
+
url={url}
|
47
|
+
zIndex={3}
|
48
|
+
/>
|
49
|
+
</TransformComponent>
|
50
|
+
</button>
|
51
|
+
</TransformWrapper>
|
52
|
+
)
|
53
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
/* @flow */
|
2
|
+
|
3
|
+
import { noop } from 'lodash'
|
4
|
+
import React, { useState } from 'react'
|
5
|
+
|
6
|
+
import Slide from './Slide'
|
7
|
+
|
8
|
+
type SlidesType = {
|
9
|
+
urls: Array<string>,
|
10
|
+
current: number,
|
11
|
+
onChange: (index: number) => void,
|
12
|
+
onClick: (index: number) => void,
|
13
|
+
}
|
14
|
+
|
15
|
+
export default function Slides({
|
16
|
+
urls = [],
|
17
|
+
current = 0,
|
18
|
+
onChange = noop,
|
19
|
+
}: SlidesType): React.ReactElement {
|
20
|
+
const [zooming, setZooming] = useState(false)
|
21
|
+
|
22
|
+
const handleZoom = (isZooming: boolean) => setZooming(isZooming)
|
23
|
+
return (
|
24
|
+
<div
|
25
|
+
className="Slides"
|
26
|
+
>
|
27
|
+
<Slide
|
28
|
+
onClick={() => onChange(current)}
|
29
|
+
onZoom={handleZoom}
|
30
|
+
url={urls[current]}
|
31
|
+
zooming={zooming}
|
32
|
+
/>
|
33
|
+
</div>
|
34
|
+
)
|
35
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { noop } from 'lodash'
|
3
|
+
import classnames from 'classnames'
|
4
|
+
import Image from '../../pb_image/_image'
|
5
|
+
|
6
|
+
type ThumbnailType = {
|
7
|
+
active?: boolean,
|
8
|
+
alt?: string,
|
9
|
+
onClick: () => void,
|
10
|
+
buttonRef?: React.RefObject<HTMLButtonElement>,
|
11
|
+
url: string,
|
12
|
+
width?: string,
|
13
|
+
}
|
14
|
+
|
15
|
+
export default function Thumbnail({
|
16
|
+
active = false,
|
17
|
+
alt,
|
18
|
+
width,
|
19
|
+
url,
|
20
|
+
onClick = noop,
|
21
|
+
buttonRef,
|
22
|
+
}: ThumbnailType): React.ReactElement {
|
23
|
+
const activeClasses = classnames('Thumbnail', { active })
|
24
|
+
return (
|
25
|
+
<button
|
26
|
+
className={classnames(activeClasses)}
|
27
|
+
onClick={onClick}
|
28
|
+
ref={buttonRef}
|
29
|
+
style={{ width }}
|
30
|
+
type="button"
|
31
|
+
>
|
32
|
+
<Image
|
33
|
+
alt={alt}
|
34
|
+
size="sm"
|
35
|
+
url={url}
|
36
|
+
/>
|
37
|
+
</button>
|
38
|
+
)
|
39
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { noop } from 'lodash'
|
2
|
+
import classnames from 'classnames'
|
3
|
+
import React, { useLayoutEffect } from 'react'
|
4
|
+
import { useWindowSize } from '../hooks/useWindowSize'
|
5
|
+
|
6
|
+
import Thumbnail from './Thumbnail'
|
7
|
+
|
8
|
+
export const indexWithinBounds = (
|
9
|
+
current: number,
|
10
|
+
min: number,
|
11
|
+
max: number
|
12
|
+
): number => {
|
13
|
+
if (current < min) return 0
|
14
|
+
if (current > max) return max
|
15
|
+
return current
|
16
|
+
}
|
17
|
+
|
18
|
+
type ThumbnailsType = {
|
19
|
+
current: number,
|
20
|
+
onChange: (index: number) => void,
|
21
|
+
urls: string[],
|
22
|
+
}
|
23
|
+
|
24
|
+
export default function Thumbnails({
|
25
|
+
current = 0,
|
26
|
+
onChange = noop,
|
27
|
+
urls = [],
|
28
|
+
}: ThumbnailsType): React.ReactElement {
|
29
|
+
const viewportSize = useWindowSize()
|
30
|
+
const thumbnailWidth = viewportSize.width / 8
|
31
|
+
const draggable = thumbnailWidth * urls.length > viewportSize.width
|
32
|
+
const shouldBeCentered = (thumbnailWidth * urls.length) < (viewportSize.width * 0.9)
|
33
|
+
const css = classnames('Thumbnails', { draggable }, { centered: shouldBeCentered })
|
34
|
+
|
35
|
+
const otherProps: { buttonRef?: React.RefObject<HTMLButtonElement> } = {}
|
36
|
+
|
37
|
+
const thumbnailsRef: React.RefObject<HTMLDivElement> = React.createRef()
|
38
|
+
|
39
|
+
useLayoutEffect(() => {
|
40
|
+
if (shouldBeCentered) return
|
41
|
+
|
42
|
+
const currentThumbnail = otherProps.buttonRef.current
|
43
|
+
|
44
|
+
const thumbWidth = currentThumbnail.clientWidth,
|
45
|
+
scrollLeft = thumbWidth * current,
|
46
|
+
firstThumb: HTMLButtonElement = thumbnailsRef.current.querySelector('.Thumbnail:first-child'),
|
47
|
+
lastThumb: HTMLButtonElement = thumbnailsRef.current.querySelector('.Thumbnail:last-child')
|
48
|
+
|
49
|
+
firstThumb.style.marginLeft = `${(thumbnailsRef.current?.clientWidth / 2) - (thumbWidth / 2)}px`
|
50
|
+
lastThumb.style.marginRight = `${(thumbnailsRef.current?.clientWidth / 2) - (thumbWidth / 2)}px`
|
51
|
+
thumbnailsRef.current?.scrollTo(scrollLeft, 0)
|
52
|
+
})
|
53
|
+
|
54
|
+
return (
|
55
|
+
<div
|
56
|
+
className={css}
|
57
|
+
ref={thumbnailsRef}
|
58
|
+
>
|
59
|
+
{urls.map((url, i) => {
|
60
|
+
const active = i === current
|
61
|
+
if (active) otherProps.buttonRef = React.createRef()
|
62
|
+
return (
|
63
|
+
<Thumbnail
|
64
|
+
active={active}
|
65
|
+
alt={i.toString()}
|
66
|
+
key={i}
|
67
|
+
onClick={() => onChange(i)}
|
68
|
+
url={url}
|
69
|
+
{...otherProps}
|
70
|
+
/>
|
71
|
+
)
|
72
|
+
})}
|
73
|
+
</div>
|
74
|
+
)
|
75
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
/* eslint-disable jsx-control-statements/jsx-use-if-tag */
|
2
|
+
import { noop } from 'lodash'
|
3
|
+
import React, { useEffect } from 'react'
|
4
|
+
|
5
|
+
import Slides from './Slides'
|
6
|
+
import Thumbnails from './Thumbnails'
|
7
|
+
|
8
|
+
type CarouselType = {
|
9
|
+
initialPhoto?: string,
|
10
|
+
onClose?: () => void,
|
11
|
+
icon?: string,
|
12
|
+
iconSize?: number,
|
13
|
+
currentIndex: number,
|
14
|
+
photos: {
|
15
|
+
url: string,
|
16
|
+
thumbnail: string,
|
17
|
+
}[],
|
18
|
+
onChange: (index: number) => void,
|
19
|
+
onClick?: (index: number) => void,
|
20
|
+
}
|
21
|
+
|
22
|
+
export default function Carousel({
|
23
|
+
currentIndex,
|
24
|
+
photos,
|
25
|
+
onClick = noop,
|
26
|
+
onChange = noop,
|
27
|
+
}: CarouselType): React.ReactElement {
|
28
|
+
useEffect(() => {
|
29
|
+
document.body.style.overflow = 'hidden'
|
30
|
+
|
31
|
+
return () => {
|
32
|
+
document.body.style.overflow = 'initial'
|
33
|
+
}
|
34
|
+
}, [])
|
35
|
+
|
36
|
+
const handleChange = (index: number) => {
|
37
|
+
onChange(index)
|
38
|
+
}
|
39
|
+
|
40
|
+
return (
|
41
|
+
<div className="Lightbox">
|
42
|
+
<Slides
|
43
|
+
current={currentIndex}
|
44
|
+
onChange={handleChange}
|
45
|
+
onClick={onClick}
|
46
|
+
urls={photos.map((photo) => photo.url)}
|
47
|
+
/>
|
48
|
+
{photos.length > 1 ? (
|
49
|
+
<Thumbnails
|
50
|
+
current={currentIndex}
|
51
|
+
onChange={handleChange}
|
52
|
+
urls={photos.map((photo) => photo.thumbnail)}
|
53
|
+
/>
|
54
|
+
) : null}
|
55
|
+
</div>
|
56
|
+
)
|
57
|
+
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
import React, { Fragment, useMemo, useRef, useState } from 'react'
|
2
|
+
import { useKbdControls } from './hooks/useKbdControls'
|
3
|
+
import classnames from 'classnames'
|
4
|
+
import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
|
5
|
+
import { globalProps } from '../utilities/globalProps'
|
6
|
+
import LightboxHeader from './_lightbox_header'
|
7
|
+
import { LightboxContext } from './_lightbox_context'
|
8
|
+
import { IconSizes } from '../pb_icon/_icon'
|
9
|
+
|
10
|
+
import Carousel from './Carousel/index'
|
11
|
+
|
12
|
+
type LightboxType = {
|
13
|
+
aria?: {[key: string]: string},
|
14
|
+
children: React.ReactNode[] | React.ReactNode | string,
|
15
|
+
className?: string,
|
16
|
+
data?: {[key: string]: string | number},
|
17
|
+
description?: string,
|
18
|
+
id?: string,
|
19
|
+
photos: [],
|
20
|
+
initialPhoto?: number,
|
21
|
+
onClose: () => void,
|
22
|
+
icon: string,
|
23
|
+
iconSize: IconSizes,
|
24
|
+
showCount?: boolean,
|
25
|
+
textRight?: string,
|
26
|
+
trigger?: string,
|
27
|
+
title?: string,
|
28
|
+
}
|
29
|
+
|
30
|
+
const Lightbox = (props: LightboxType): React.ReactNode => {
|
31
|
+
const {
|
32
|
+
aria = {},
|
33
|
+
children,
|
34
|
+
className,
|
35
|
+
data = {},
|
36
|
+
description,
|
37
|
+
id = '',
|
38
|
+
initialPhoto = 0,
|
39
|
+
photos,
|
40
|
+
onClose,
|
41
|
+
icon = 'times',
|
42
|
+
iconSize = '2x',
|
43
|
+
showCount = true,
|
44
|
+
textRight = 'All Photos',
|
45
|
+
title,
|
46
|
+
} = props
|
47
|
+
const [activePhoto, setActivePhoto] = useState(initialPhoto)
|
48
|
+
|
49
|
+
const ariaProps = buildAriaProps(aria)
|
50
|
+
const dataProps = buildDataProps(data)
|
51
|
+
const classes = classnames(
|
52
|
+
buildCss('pb_lightbox_kit'),
|
53
|
+
globalProps(props),
|
54
|
+
className
|
55
|
+
)
|
56
|
+
|
57
|
+
const handleOnSlide = (photoIndex: number) => {
|
58
|
+
setActivePhoto(photoIndex)
|
59
|
+
}
|
60
|
+
|
61
|
+
const photosMap = useMemo(() => photos.map((photo) => ({
|
62
|
+
url: photo,
|
63
|
+
thumbnail: photo,
|
64
|
+
})), [photos])
|
65
|
+
|
66
|
+
const api = {
|
67
|
+
onClose: onClose,
|
68
|
+
onArrowLeft: () => {
|
69
|
+
setActivePhoto(activePhoto > 0 ? activePhoto - 1 : 0)
|
70
|
+
},
|
71
|
+
onArrowRight: () => {
|
72
|
+
const nextPhoto = activePhoto < photos.length - 1 ? activePhoto + 1 : photos.length - 1
|
73
|
+
setActivePhoto(nextPhoto)
|
74
|
+
},
|
75
|
+
}
|
76
|
+
useKbdControls(api)
|
77
|
+
|
78
|
+
const lightboxRef: any = useRef()
|
79
|
+
|
80
|
+
const headerTextRight = showCount ? `${activePhoto + 1} / ${photos.length}` : textRight
|
81
|
+
|
82
|
+
return (
|
83
|
+
<Fragment>
|
84
|
+
<LightboxContext.Provider value={api}>
|
85
|
+
<div
|
86
|
+
{...ariaProps}
|
87
|
+
{...dataProps}
|
88
|
+
className={classes}
|
89
|
+
id={id}
|
90
|
+
ref={lightboxRef}
|
91
|
+
>
|
92
|
+
<div className="carousel">
|
93
|
+
<Lightbox.Header
|
94
|
+
icon={icon}
|
95
|
+
iconSize={iconSize}
|
96
|
+
text={description}
|
97
|
+
textRight={headerTextRight}
|
98
|
+
title={title}
|
99
|
+
/>
|
100
|
+
{children}
|
101
|
+
<Carousel
|
102
|
+
currentIndex={activePhoto}
|
103
|
+
onChange={handleOnSlide}
|
104
|
+
photos={photosMap}
|
105
|
+
/>
|
106
|
+
</div>
|
107
|
+
</div>
|
108
|
+
</LightboxContext.Provider>
|
109
|
+
</Fragment>
|
110
|
+
)
|
111
|
+
}
|
112
|
+
|
113
|
+
Lightbox.Header = LightboxHeader
|
114
|
+
|
115
|
+
export default Lightbox
|