playbook_ui 11.1.2 → 11.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|