playbook_ui 10.21.0.pre.alpha.jg1 → 10.21.0.pre.alpha.lightbox

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +1 -0
  3. data/app/pb_kits/playbook/data/menu.yml +1 -0
  4. data/app/pb_kits/playbook/index.js +2 -1
  5. data/app/pb_kits/playbook/pb_avatar/_avatar.jsx +1 -1
  6. data/app/pb_kits/playbook/pb_avatar/_avatar.scss +2 -2
  7. data/app/pb_kits/playbook/pb_avatar/avatar.rb +1 -1
  8. data/app/pb_kits/playbook/pb_avatar/avatar.test.js +1 -1
  9. data/app/pb_kits/playbook/pb_background/_background.jsx +2 -7
  10. data/app/pb_kits/playbook/pb_background/_background.scss +8 -24
  11. data/app/pb_kits/playbook/pb_background/background.rb +6 -6
  12. data/app/pb_kits/playbook/pb_background/docs/_background_image.html.erb +2 -50
  13. data/app/pb_kits/playbook/pb_background/docs/_background_image.jsx +28 -71
  14. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.jsx +4 -0
  15. data/app/pb_kits/playbook/pb_bar_graph/bar_graph.rb +1 -0
  16. data/app/pb_kits/playbook/pb_button/_button.jsx +3 -3
  17. data/app/pb_kits/playbook/pb_button/_button.scss +18 -1
  18. data/app/pb_kits/playbook/pb_button/button.rb +11 -3
  19. data/app/pb_kits/playbook/pb_button/button.test.js +13 -0
  20. data/app/pb_kits/playbook/pb_button/docs/_button_size.html.erb +3 -0
  21. data/app/pb_kits/playbook/pb_button/docs/_button_size.jsx +26 -0
  22. data/app/pb_kits/playbook/pb_button/docs/_button_size.md +1 -0
  23. data/app/pb_kits/playbook/pb_button/docs/example.yml +2 -0
  24. data/app/pb_kits/playbook/pb_button/docs/index.js +1 -0
  25. data/app/pb_kits/playbook/pb_circle_chart/_circle_chart.jsx +3 -0
  26. data/app/pb_kits/playbook/pb_circle_chart/circle_chart.rb +1 -0
  27. data/app/pb_kits/playbook/pb_dashboard/pbChartsDarkTheme.js +215 -0
  28. data/app/pb_kits/playbook/pb_file_upload/_file_upload.jsx +17 -10
  29. data/app/pb_kits/playbook/pb_file_upload/fileupload.test.js +40 -0
  30. data/app/pb_kits/playbook/pb_gauge/_gauge.jsx +3 -0
  31. data/app/pb_kits/playbook/pb_gauge/gauge.rb +1 -0
  32. data/app/pb_kits/playbook/pb_image/_image.jsx +1 -1
  33. data/app/pb_kits/playbook/pb_image/_image.scss +3 -3
  34. data/app/pb_kits/playbook/pb_image/image.rb +1 -1
  35. data/app/pb_kits/playbook/pb_image/image.test.js +1 -1
  36. data/app/pb_kits/playbook/pb_lightbox/Carousel/Slide.jsx +55 -0
  37. data/app/pb_kits/playbook/pb_lightbox/Carousel/Slides.jsx +54 -0
  38. data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnail.jsx +39 -0
  39. data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnails.jsx +82 -0
  40. data/app/pb_kits/playbook/pb_lightbox/Carousel/index.jsx +59 -0
  41. data/app/pb_kits/playbook/pb_lightbox/Carousel/styles.scss +110 -0
  42. data/app/pb_kits/playbook/pb_lightbox/Carousel/useSlides.js +66 -0
  43. data/app/pb_kits/playbook/pb_lightbox/_lightbox.jsx +112 -0
  44. data/app/pb_kits/playbook/pb_lightbox/_lightbox_context.jsx +3 -0
  45. data/app/pb_kits/playbook/pb_lightbox/_lightbox_header.jsx +71 -0
  46. data/app/pb_kits/playbook/pb_lightbox/_lightbox_header_icon.jsx +26 -0
  47. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_compound_component.jsx +95 -0
  48. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.jsx +64 -0
  49. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.md +1 -0
  50. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_multiple.jsx +64 -0
  51. data/app/pb_kits/playbook/pb_lightbox/docs/example.yml +7 -0
  52. data/app/pb_kits/playbook/pb_lightbox/docs/index.js +3 -0
  53. data/app/pb_kits/playbook/pb_lightbox/hooks/useVisibility.js +21 -0
  54. data/app/pb_kits/playbook/pb_lightbox/hooks/useWindowSize.js +25 -0
  55. data/app/pb_kits/playbook/pb_lightbox/lightbox.scss +90 -0
  56. data/app/pb_kits/playbook/pb_lightbox/lightbox.test.jsx +30 -0
  57. data/app/pb_kits/playbook/pb_line_graph/_line_graph.jsx +4 -0
  58. data/app/pb_kits/playbook/pb_line_graph/line_graph.rb +1 -0
  59. data/app/pb_kits/playbook/pb_popover/_popover.jsx +2 -4
  60. data/app/pb_kits/playbook/pb_popover/docs/_popover_close.html.erb +7 -7
  61. data/app/pb_kits/playbook/pb_popover/index.js +4 -9
  62. data/app/pb_kits/playbook/pb_popover/popover.html.erb +1 -1
  63. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.jsx +4 -0
  64. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +2 -2
  65. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +14 -0
  66. data/app/pb_kits/playbook/playbook-doc.js +2 -0
  67. data/app/pb_kits/playbook/plugins/pb_chart.js +8 -4
  68. data/lib/playbook/version.rb +2 -2
  69. metadata +28 -3
  70. data/app/pb_kits/playbook/pb_background/docs/_background_image.md +0 -1
@@ -0,0 +1,215 @@
1
+ import colors from '../tokens/exports/_colors.scss'
2
+ import typography from '../tokens/exports/_typography.scss'
3
+
4
+ import Highcharts from 'highcharts'
5
+
6
+ const highchartsDarkTheme = {
7
+ lang: {
8
+ thousandsSep: ',',
9
+ },
10
+ colors: [
11
+ colors.data_1,
12
+ colors.data_2,
13
+ colors.data_3,
14
+ colors.data_4,
15
+ colors.data_5,
16
+ colors.data_6,
17
+ colors.data_7,
18
+ ],
19
+ chart: {
20
+ borderWidth: 0,
21
+ borderRadius: 0,
22
+ plotBackgroundColor: null,
23
+ plotShadow: false,
24
+ plotBorderWidth: 0,
25
+ },
26
+ title: {
27
+ style: {
28
+ color: colors.text_dk_default,
29
+ fontFamily: typography.font_family_base,
30
+ fontWeight: typography.regular,
31
+ fontSize: typography.heading_3,
32
+ },
33
+ },
34
+ subtitle: {
35
+ style: {
36
+ fontFamily: typography.font_family_base,
37
+ color: colors.text_dk_light,
38
+ fontWeight: typography.regular,
39
+ fontSize: typography.text_base,
40
+ },
41
+ },
42
+ xAxis: {
43
+ gridLineWidth: 0,
44
+ lineColor: colors.border_dark,
45
+ tickColor: colors.border_dark,
46
+ labels: {
47
+ style: {
48
+ fontFamily: typography.font_family_base,
49
+ color: colors.text_dk_lighter,
50
+ fontWeight: typography.bold,
51
+ fontSize: typography.text_smaller,
52
+ },
53
+ },
54
+ title: {
55
+ style: {
56
+ color: colors.text_dk_default,
57
+ fontFamily: typography.font_family_base,
58
+ fontWeight: typography.regular,
59
+ fontSize: typography.heading_4,
60
+ },
61
+ },
62
+ },
63
+ yAxis: {
64
+ alternateGridColor: null,
65
+ minorTickInterval: null,
66
+ gridLineColor: colors.border_dark,
67
+ minorGridLineColor: colors.border_dark,
68
+ lineWidth: 0,
69
+ tickWidth: 0,
70
+ labels: {
71
+ style: {
72
+ fontFamily: typography.font_family_base,
73
+ color: colors.text_dk_lighter,
74
+ fontWeight: typography.bold,
75
+ fontSize: typography.text_smaller,
76
+ },
77
+ },
78
+ title: {
79
+ style: {
80
+ fontFamily: typography.font_family_base,
81
+ color: colors.text_dk_lighter,
82
+ fontWeight: typography.bold,
83
+ fontSize: typography.text_smaller,
84
+ },
85
+ },
86
+ },
87
+ legend: {
88
+ layout: 'horizontal',
89
+ align: 'center',
90
+ verticalAlign: 'bottom',
91
+ itemStyle: {
92
+ fontFamily: typography.font_family_base,
93
+ color: colors.text_dk_light,
94
+ fontWeight: typography.regular,
95
+ fontSize: typography.text_smaller,
96
+ },
97
+ itemHoverStyle: {
98
+ color: colors.text_dk_default,
99
+ },
100
+ itemHiddenStyle: {
101
+ color: colors.text_dk_lighter,
102
+ },
103
+ },
104
+ labels: {
105
+ style: {
106
+ color: colors.primary,
107
+ },
108
+ },
109
+ tooltip: {
110
+ backgroundColor: {
111
+ linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
112
+ stops: [
113
+ [0, colors.card_light],
114
+ [1, colors.card_light],
115
+ ],
116
+ },
117
+ shadow: false,
118
+ borderWidth: 0,
119
+ borderRadius: 10,
120
+ style: {
121
+ fontFamily: typography.font_family_base,
122
+ color: colors.text_lt_default,
123
+ fontWeight: typography.regular,
124
+ fontSize: typography.text_smaller,
125
+ },
126
+ },
127
+ // specific to gauge
128
+ // unfilled gauge color
129
+ pane: {
130
+ background: {
131
+ borderColor: colors.border_dark,
132
+ },
133
+ },
134
+
135
+ plotOptions: {
136
+ series: {
137
+ type: 'area',
138
+ nullColor: colors.text_dk_lighter,
139
+ fillColor: {
140
+ linearGradient: {
141
+ x1: 0,
142
+ y1: 0,
143
+ x2: 0,
144
+ y2: 1,
145
+ },
146
+ stops: [
147
+ [0, Highcharts.getOptions().colors[0]],
148
+ [1, 'white'],
149
+ ],
150
+ },
151
+ threshold: null,
152
+ },
153
+
154
+ // GAUGE STYLES
155
+ solidgauge: {
156
+ borderColor: colors.primary,
157
+ borderWidth: 20,
158
+ radius: 90,
159
+ innerRadius: '90%',
160
+ dataLabels: {
161
+ borderWidth: 0,
162
+ color: colors.text_dk_default,
163
+ enabled: true,
164
+ style: {
165
+ fontFamily: typography.font_family_base,
166
+ fontWeight: typography.regular,
167
+ fontSize: typography.heading_2,
168
+ },
169
+ y: -26,
170
+ },
171
+ },
172
+
173
+ // PIE STYLES
174
+ pie: {
175
+ colors: [
176
+ colors.data_1,
177
+ colors.data_2,
178
+ colors.data_3,
179
+ colors.data_4,
180
+ colors.data_5,
181
+ colors.data_6,
182
+ colors.data_7,
183
+ ],
184
+ dataLabels: {
185
+ style: {
186
+ fontFamily: typography.font_family_base,
187
+ fontSize: typography.text_smaller,
188
+ color: colors.text_dk_light,
189
+ fontWeight: typography.regular,
190
+ },
191
+ },
192
+ },
193
+
194
+ // LINE CHART STYLES
195
+ line: {
196
+ dataLabels: {
197
+ color: colors.text_dk_light,
198
+ },
199
+ marker: {
200
+ lineColor: colors.border_dark,
201
+ },
202
+ area: {
203
+ shadow: false,
204
+ states: {
205
+ hover: {
206
+ lineWidth: 1,
207
+ },
208
+ },
209
+ threshold: null,
210
+ },
211
+ },
212
+ },
213
+ }
214
+
215
+ export { highchartsDarkTheme }
@@ -4,7 +4,7 @@ import React, { useCallback } from 'react'
4
4
  import { useDropzone } from 'react-dropzone'
5
5
  import classnames from 'classnames'
6
6
 
7
- import { buildCss, noop } from '../utilities/props'
7
+ import { buildCss, buildDataProps, noop } from '../utilities/props'
8
8
  import { globalProps } from '../utilities/globalProps'
9
9
  import type { Callback } from '../types'
10
10
 
@@ -14,15 +14,17 @@ import Card from '../pb_card/_card'
14
14
  type FileUploadProps = {
15
15
  accept?: array<string>,
16
16
  className?: string,
17
+ data?: object,
17
18
  acceptedFilesDescription?: string,
18
19
  onFilesAccepted: Callback,
19
20
  }
20
21
 
21
22
  const FileUpload = (props: FileUploadProps) => {
22
23
  const {
23
- accept = ['*'],
24
+ accept = null,
24
25
  acceptedFilesDescription = '',
25
26
  className,
27
+ data = {},
26
28
  onFilesAccepted = noop,
27
29
  } = props
28
30
  const onDrop = useCallback((files) => {
@@ -34,17 +36,22 @@ const FileUpload = (props: FileUploadProps) => {
34
36
  onDrop,
35
37
  })
36
38
 
37
- const acceptedFileTypes = accept.map((fileType) => {
38
- if (fileType.startsWith('image/')) {
39
- return fileType.replace('image/', ' ')
40
- } else {
41
- return fileType
42
- }
43
- })
39
+ const acceptedFileTypes = () => {
40
+ return accept.map((fileType) => {
41
+ if (fileType.startsWith('image/')) {
42
+ return fileType.replace('image/', ' ')
43
+ } else {
44
+ return fileType
45
+ }
46
+ })
47
+ }
48
+
49
+ const dataProps = buildDataProps(data)
44
50
 
45
51
  return (
46
52
  <div
47
53
  className={classnames(buildCss('pb_file_upload_kit'), globalProps(props), className)}
54
+ {...dataProps}
48
55
  {...getRootProps()}
49
56
  >
50
57
  <Card>
@@ -53,7 +60,7 @@ const FileUpload = (props: FileUploadProps) => {
53
60
  <If condition={isDragActive}>
54
61
  <p>{'Drop the files here ...'}</p>
55
62
  <Else />
56
- <p>{`Choose a file or drag it here. The accepted file types are: ${acceptedFilesDescription || acceptedFileTypes}`}</p>
63
+ <p>{accept === null ? 'Choose a file or drag it here' : `Choose a file or drag it here. The accepted file types are: ${acceptedFilesDescription || acceptedFileTypes()}`}</p>
57
64
  </If>
58
65
  </Body>
59
66
  </Card>
@@ -0,0 +1,40 @@
1
+ import React from 'react'
2
+ import { render, screen } from '../utilities/test-utils'
3
+
4
+ import FileUpload from './_file_upload'
5
+
6
+ const testid = 'fileupload-test'
7
+
8
+ test('returns namespaced class name', () => {
9
+ render(
10
+ <FileUpload
11
+ data={{ testid: testid }}
12
+ />
13
+ )
14
+
15
+ const kit = screen.getByTestId(testid)
16
+ expect(kit).toHaveClass('pb_file_upload_kit')
17
+ })
18
+
19
+ test('shows default drag text', () => {
20
+ render(
21
+ <FileUpload
22
+ data={{ testid: testid }}
23
+ />
24
+ )
25
+
26
+ const kit = screen.getByTestId(testid)
27
+ expect(kit).toHaveTextContent('Choose a file or drag it here')
28
+ })
29
+
30
+ test('shows type-specific drag text', () => {
31
+ render(
32
+ <FileUpload
33
+ accept={['image/svg+xml']}
34
+ data={{ testid: testid }}
35
+ />
36
+ )
37
+
38
+ const kit = screen.getByTestId(testid)
39
+ expect(kit).toHaveTextContent('Choose a file or drag it here. The accepted file types are: svg+xml')
40
+ })
@@ -12,6 +12,7 @@ type GaugeProps = {
12
12
  aria: Object,
13
13
  className?: string,
14
14
  chartData?: array,
15
+ dark?: Boolean,
15
16
  data?: Object,
16
17
  disableAnimation: boolean,
17
18
  fullCircle: boolean,
@@ -33,6 +34,7 @@ const Gauge = (props: GaugeProps) => {
33
34
  aria = {},
34
35
  className,
35
36
  chartData = [{ name: 'Name', value: 0 }],
37
+ dark = false,
36
38
  data = {},
37
39
  disableAnimation = false,
38
40
  fullCircle = false,
@@ -67,6 +69,7 @@ const Gauge = (props: GaugeProps) => {
67
69
  id: id,
68
70
  chartData: formattedChartData,
69
71
  circumference: fullCircle ? [0, 360] : [-100, 100],
72
+ dark,
70
73
  disableAnimation: disableAnimation,
71
74
  height: height,
72
75
  min: min,
@@ -32,6 +32,7 @@ module Playbook
32
32
  id: id,
33
33
  chartData: chart_data_formatted,
34
34
  circumference: full_circle ? [0, 360] : [-100, 100],
35
+ dark: dark ? "dark" : "",
35
36
  disableAnimation: disable_animation,
36
37
  height: height,
37
38
  min: min,
@@ -34,7 +34,7 @@ const Image = (props: ImageProps) => {
34
34
 
35
35
  const ariaProps = buildAriaProps(aria)
36
36
  const classes = classnames(
37
- buildCss('pb_image_kit', size),
37
+ buildCss('pb_image_kit', size ? `size_${size}` : null),
38
38
  'lazyload',
39
39
  transition,
40
40
  { rounded },
@@ -14,7 +14,7 @@ $image-sizes: (
14
14
  object-fit: cover;
15
15
 
16
16
  @each $name, $size in $image-sizes {
17
- &[class*=_#{$name}] {
17
+ &[class*=size_#{$name}] {
18
18
  width: $size;
19
19
  height: $size;
20
20
  object-fit: cover;
@@ -35,7 +35,7 @@ $image-sizes: (
35
35
  transition: opacity 300ms ease-in;
36
36
  }
37
37
  }
38
-
38
+
39
39
  &.blur {
40
40
  filter: blur(5px);
41
41
  &.lazyloaded {
@@ -44,7 +44,7 @@ $image-sizes: (
44
44
  transition: filter 300ms ease-in;
45
45
  }
46
46
  }
47
-
47
+
48
48
  &.scale {
49
49
  opacity: 0;
50
50
  transform: scale(0.9);
@@ -27,7 +27,7 @@ module Playbook
27
27
  end
28
28
 
29
29
  def size_class
30
- size == "none" ? nil : "_#{size}"
30
+ size == "none" ? nil : "_size_#{size}"
31
31
  end
32
32
 
33
33
  def transition_class
@@ -26,7 +26,7 @@ test('default classname', () => {
26
26
 
27
27
  test('size = xs', () => {
28
28
  const kit = renderKit(Image, props, { size: 'xs' })
29
- expect(kit).toHaveClass('pb_image_kit_xs lazyload')
29
+ expect(kit).toHaveClass('pb_image_kit_size_xs lazyload')
30
30
  })
31
31
 
32
32
  test('transition = blur', () => {
@@ -0,0 +1,55 @@
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
+ import styles from './styles.scss'
9
+
10
+ type SlideType = {
11
+ alt: string,
12
+ current: number,
13
+ onClick: () => void,
14
+ onChange: (index: number) => void,
15
+ onZoom: (zoom: number) => void,
16
+ zooming: boolean,
17
+ url: string,
18
+ }
19
+
20
+ export default function Slide({
21
+ alt,
22
+ onClick = noop,
23
+ onZoom = noop,
24
+ url,
25
+ zooming = false,
26
+ }: SlideType) {
27
+ const handlePinchingStop = (e) => {
28
+ const isZooming = e.state.scale > 1
29
+ onZoom(isZooming)
30
+ }
31
+
32
+ return (
33
+ <TransformWrapper
34
+ doubleClick={{ mode: 'reset' }}
35
+ initialScale={1}
36
+ onPinchingStop={handlePinchingStop}
37
+ panning={{ disabled: !zooming }}
38
+ >
39
+ <button
40
+ className={styles.Slide}
41
+ onClick={onClick}
42
+ onDoubleClick={() => onZoom(false)}
43
+ tabIndex={-1}
44
+ >
45
+ <TransformComponent className={styles.TransformComponent}>
46
+ <Image
47
+ alt={alt}
48
+ url={url}
49
+ zIndex="3"
50
+ />
51
+ </TransformComponent>
52
+ </button>
53
+ </TransformWrapper>
54
+ )
55
+ }
@@ -0,0 +1,54 @@
1
+ /* @flow */
2
+
3
+ import { noop } from 'lodash'
4
+ import { motion } from 'framer-motion'
5
+ import React, { useState } from 'react'
6
+
7
+ import Slide from './Slide'
8
+ import styles from './styles.scss'
9
+ import useSlides from './useSlides'
10
+
11
+ type SlidesType = {
12
+ urls: Array<string>,
13
+ current: number,
14
+ onChange: (index: number) => void,
15
+ onClick: (index: number) => void,
16
+ }
17
+
18
+ export default function Slides({
19
+ urls = [],
20
+ current = 0,
21
+ onClick = noop,
22
+ onChange = noop,
23
+ }: SlidesType) {
24
+ const [zooming, setZooming] = useState(false)
25
+ const { controls, dragConstraints, handleDragEnd } = useSlides({
26
+ current,
27
+ pagesCount: urls.length,
28
+ onChange,
29
+ })
30
+
31
+ const handleZoom = (isZooming) => setZooming(isZooming)
32
+
33
+ return (
34
+ <motion.div
35
+ animate={controls}
36
+ className={styles.Slides}
37
+ drag={!zooming && 'x'}
38
+ dragConstraints={dragConstraints}
39
+ dragElastic={0.05}
40
+ onDragEnd={handleDragEnd}
41
+ transition={{ type: 'spring', bounce: 0 }}
42
+ >
43
+ {urls.map((url, i) => (
44
+ <Slide
45
+ key={i}
46
+ onClick={() => onClick(i)}
47
+ onZoom={handleZoom}
48
+ url={url}
49
+ zooming={zooming}
50
+ />
51
+ ))}
52
+ </motion.div>
53
+ )
54
+ }
@@ -0,0 +1,39 @@
1
+ /* @flow */
2
+
3
+ import React from 'react'
4
+ import { noop } from 'lodash'
5
+ import classnames from 'classnames'
6
+ import Image from '../../pb_image/_image'
7
+
8
+ import styles from './styles.scss'
9
+
10
+ type ThumbnailType = {
11
+ active?: boolean,
12
+ alt?: string,
13
+ onClick: () => void,
14
+ url: string,
15
+ width?: string,
16
+ }
17
+
18
+ export default function Thumbnail({
19
+ active = false,
20
+ alt,
21
+ width,
22
+ url,
23
+ onClick = noop,
24
+ }: ThumbnailType) {
25
+ return (
26
+ <button
27
+ className={classnames(styles.Thumbnail, { [styles.active]: active })}
28
+ onClick={onClick}
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,82 @@
1
+ /* @flow */
2
+
3
+ import { noop } from 'lodash'
4
+ import classnames from 'classnames'
5
+ import React, { useEffect } from 'react'
6
+ import { motion, useAnimation } from 'framer-motion'
7
+ import { useWindowSize } from '../hooks/useWindowSize'
8
+
9
+ import Thumbnail from './Thumbnail'
10
+
11
+ import styles from './styles.scss'
12
+
13
+ export const indexWithinBounds = (
14
+ current: number,
15
+ min: number,
16
+ max: number
17
+ ): number => {
18
+ if (current < min) return 0
19
+ if (current > max) return max
20
+ return current
21
+ }
22
+
23
+ type ThumbnailsType = {
24
+ current: number,
25
+ onChange: () => null,
26
+ urls: [],
27
+ }
28
+
29
+ export default function Thumbnails({
30
+ current = 0,
31
+ onChange = noop,
32
+ urls = [],
33
+ }: ThumbnailsType) {
34
+ const controls = useAnimation()
35
+ const viewportSize = useWindowSize()
36
+ const thumbnailWidth = viewportSize.width / 8
37
+ const draggable = thumbnailWidth * urls.length > viewportSize.width
38
+ const css = classnames(styles.Thumbnails, {
39
+ [styles.draggable]: draggable,
40
+ })
41
+ const dragConstraints = {
42
+ left: -1 * (thumbnailWidth * urls.length - viewportSize.width),
43
+ right: 0,
44
+ }
45
+
46
+ const modifyTarget = (target) => {
47
+ const nextIndex = Math.round(Math.abs(target) / thumbnailWidth)
48
+ const snapTargetIndex = indexWithinBounds(nextIndex, 0, urls.length)
49
+ const snapTarget = snapTargetIndex * thumbnailWidth
50
+ const direction = Math.sign(target)
51
+ return direction * snapTarget
52
+ }
53
+
54
+ useEffect(() => {
55
+ if (draggable) {
56
+ const x = Math.max(-current * thumbnailWidth, dragConstraints.left)
57
+ controls.start({ x })
58
+ }
59
+ }, [controls, current, draggable, dragConstraints.left, thumbnailWidth])
60
+
61
+ return (
62
+ <motion.div
63
+ animate={controls}
64
+ className={css}
65
+ drag={draggable && 'x'}
66
+ dragConstraints={dragConstraints}
67
+ dragElastic={0.05}
68
+ dragTransition={{ modifyTarget }}
69
+ transition={{ type: 'spring', bounce: 0 }}
70
+ >
71
+ {urls.map((url, i) => (
72
+ <Thumbnail
73
+ active={i === current}
74
+ alt={i}
75
+ key={i}
76
+ onClick={() => onChange(i)}
77
+ url={url}
78
+ />
79
+ ))}
80
+ </motion.div>
81
+ )
82
+ }
@@ -0,0 +1,59 @@
1
+ /* eslint-disable jsx-control-statements/jsx-use-if-tag */
2
+ /* @flow */
3
+
4
+ import { noop } from 'lodash'
5
+ import React, { useEffect, useState } from 'react'
6
+
7
+ import Slides from './Slides'
8
+ import Thumbnails from './Thumbnails'
9
+ import styles from './styles.scss'
10
+
11
+ type CarouselType = {
12
+ initialPhoto: string,
13
+ onClose: Function,
14
+ icon: string,
15
+ iconSize: number,
16
+ current: number,
17
+ photos: Array<string>,
18
+ onChange: (index: number) => void,
19
+ onClick: (index: number) => void,
20
+ }
21
+
22
+ export default function Carousel({
23
+ current = 0,
24
+ photos,
25
+ onClick = noop,
26
+ onChange = noop,
27
+ }: CarouselType) {
28
+ useEffect(() => {
29
+ document.body.style.overflow = 'hidden'
30
+
31
+ return () => {
32
+ document.body.style.overflow = 'initial'
33
+ }
34
+ }, [])
35
+
36
+ const [currentIndex, setCurrentIndex] = useState(current)
37
+ const handleChange = (index) => {
38
+ setCurrentIndex(index)
39
+ onChange(index)
40
+ }
41
+
42
+ return (
43
+ <div className={styles.Lightbox}>
44
+ <Slides
45
+ current={currentIndex}
46
+ onChange={handleChange}
47
+ onClick={onClick}
48
+ urls={photos.map((photo) => photo.url)}
49
+ />
50
+ {photos.length > 1 ? (
51
+ <Thumbnails
52
+ current={currentIndex}
53
+ onChange={handleChange}
54
+ urls={photos.map((photo) => photo.thumbnail)}
55
+ />
56
+ ) : null}
57
+ </div>
58
+ )
59
+ }