playbook_ui 10.17.0 → 10.19.0.pre.lightbox

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +4 -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_bar_graph/docs/_bar_graph_colors.md +1 -1
  6. data/app/pb_kits/playbook/pb_body/_body.jsx +1 -1
  7. data/app/pb_kits/playbook/pb_body/_body_mixins.scss +2 -1
  8. data/app/pb_kits/playbook/pb_body/body.rb +1 -1
  9. data/app/pb_kits/playbook/pb_body/body.test.js +29 -0
  10. data/app/pb_kits/playbook/pb_body/docs/_body_light.html.erb +5 -0
  11. data/app/pb_kits/playbook/pb_body/docs/_body_light.jsx +5 -0
  12. data/app/pb_kits/playbook/pb_body/docs/_body_light.md +6 -0
  13. data/app/pb_kits/playbook/pb_caption/_caption.jsx +6 -6
  14. data/app/pb_kits/playbook/pb_caption/_caption.scss +6 -17
  15. data/app/pb_kits/playbook/pb_caption/_caption_mixin.scss +13 -3
  16. data/app/pb_kits/playbook/pb_caption/caption.rb +3 -5
  17. data/app/pb_kits/playbook/pb_caption/caption.test.js +29 -0
  18. data/app/pb_kits/playbook/pb_caption/docs/_caption_colors.html.erb +3 -0
  19. data/app/pb_kits/playbook/pb_caption/docs/_caption_colors.jsx +25 -0
  20. data/app/pb_kits/playbook/pb_caption/docs/_caption_colors.md +6 -0
  21. data/app/pb_kits/playbook/pb_caption/docs/example.yml +2 -2
  22. data/app/pb_kits/playbook/pb_caption/docs/index.js +1 -2
  23. data/app/pb_kits/playbook/pb_card/docs/_card_background.html.erb +1 -1
  24. data/app/pb_kits/playbook/pb_card/docs/_card_content.html.erb +3 -1
  25. data/app/pb_kits/playbook/pb_card/docs/_card_content.jsx +9 -2
  26. data/app/pb_kits/playbook/pb_circle_chart/docs/_circle_chart_colors.md +2 -0
  27. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_default_date.md +3 -1
  28. data/app/pb_kits/playbook/pb_date_time_stacked/date_time_stacked.test.js +13 -4
  29. data/app/pb_kits/playbook/pb_gauge/_gauge.jsx +3 -0
  30. data/app/pb_kits/playbook/pb_gauge/docs/_gauge_colors.html.erb +12 -0
  31. data/app/pb_kits/playbook/pb_gauge/docs/_gauge_colors.jsx +19 -0
  32. data/app/pb_kits/playbook/pb_gauge/docs/_gauge_colors.md +2 -0
  33. data/app/pb_kits/playbook/pb_gauge/docs/example.yml +2 -0
  34. data/app/pb_kits/playbook/pb_gauge/docs/index.js +1 -0
  35. data/app/pb_kits/playbook/pb_gauge/gauge.rb +2 -0
  36. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.scss +2 -2
  37. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_color.html.erb +1 -1
  38. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_color.jsx +1 -1
  39. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_color.md +1 -0
  40. data/app/pb_kits/playbook/pb_icon_circle/icon_circle.rb +1 -1
  41. data/app/pb_kits/playbook/pb_image/_image.jsx +4 -1
  42. data/app/pb_kits/playbook/pb_image/_image.scss +24 -8
  43. data/app/pb_kits/playbook/pb_image/docs/_transition_image.html.erb +54 -0
  44. data/app/pb_kits/playbook/pb_image/docs/_transition_image.jsx +77 -0
  45. data/app/pb_kits/playbook/pb_image/docs/_transition_image.md +1 -0
  46. data/app/pb_kits/playbook/pb_image/docs/example.yml +2 -0
  47. data/app/pb_kits/playbook/pb_image/docs/index.js +1 -0
  48. data/app/pb_kits/playbook/pb_image/image.html.erb +1 -1
  49. data/app/pb_kits/playbook/pb_image/image.rb +8 -1
  50. data/app/pb_kits/playbook/pb_image/image.test.js +9 -4
  51. data/app/pb_kits/playbook/pb_lightbox/Carousel/Slide.jsx +53 -0
  52. data/app/pb_kits/playbook/pb_lightbox/Carousel/Slides.jsx +54 -0
  53. data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnail.jsx +39 -0
  54. data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnails.jsx +82 -0
  55. data/app/pb_kits/playbook/pb_lightbox/Carousel/index.jsx +54 -0
  56. data/app/pb_kits/playbook/pb_lightbox/Carousel/styles.scss +110 -0
  57. data/app/pb_kits/playbook/pb_lightbox/Carousel/useSlides.js +66 -0
  58. data/app/pb_kits/playbook/pb_lightbox/Carousel/useUnscrollableBody.js +11 -0
  59. data/app/pb_kits/playbook/pb_lightbox/_lightbox.jsx +81 -0
  60. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.jsx +65 -0
  61. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.md +1 -0
  62. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_multiple.jsx +65 -0
  63. data/app/pb_kits/playbook/pb_lightbox/docs/example.yml +6 -0
  64. data/app/pb_kits/playbook/pb_lightbox/docs/index.js +2 -0
  65. data/app/pb_kits/playbook/pb_lightbox/hooks/useToggler.js +10 -0
  66. data/app/pb_kits/playbook/pb_lightbox/hooks/useVisibility.js +21 -0
  67. data/app/pb_kits/playbook/pb_lightbox/hooks/useWindowSize.js +25 -0
  68. data/app/pb_kits/playbook/pb_lightbox/lightbox.scss +92 -0
  69. data/app/pb_kits/playbook/pb_lightbox/lightbox.test.jsx +30 -0
  70. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_colors.md +1 -1
  71. data/app/pb_kits/playbook/pb_nav/_bold_mixin.scss +22 -0
  72. data/app/pb_kits/playbook/pb_nav/_horizontal_nav.scss +17 -1
  73. data/app/pb_kits/playbook/pb_nav/_vertical_nav.scss +9 -2
  74. data/app/pb_kits/playbook/pb_nav/docs/_bold_horizontal_nav.html.erb +6 -0
  75. data/app/pb_kits/playbook/pb_nav/docs/_bold_horizontal_nav.jsx +39 -0
  76. data/app/pb_kits/playbook/pb_nav/docs/_bold_vertical_nav.html.erb +6 -0
  77. data/app/pb_kits/playbook/pb_nav/docs/_bold_vertical_nav.jsx +39 -0
  78. data/app/pb_kits/playbook/pb_nav/docs/_horizontal_nav.html.erb +1 -1
  79. data/app/pb_kits/playbook/pb_nav/docs/_subtle_nav.html.erb +1 -1
  80. data/app/pb_kits/playbook/pb_nav/docs/example.yml +4 -0
  81. data/app/pb_kits/playbook/pb_nav/docs/index.js +2 -0
  82. data/app/pb_kits/playbook/pb_nav/nav.rb +1 -1
  83. data/app/pb_kits/playbook/pb_popover/docs/_popover_scroll_height.jsx +3 -0
  84. data/app/pb_kits/playbook/pb_popover/docs/_popover_z_index.jsx +1 -0
  85. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +62 -13
  86. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.html.erb +21 -2
  87. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.jsx +12 -3
  88. data/app/pb_kits/playbook/pb_title/_title.jsx +6 -4
  89. data/app/pb_kits/playbook/pb_title/_title.scss +5 -5
  90. data/app/pb_kits/playbook/pb_title/_title_mixin.scss +17 -0
  91. data/app/pb_kits/playbook/pb_title/docs/_title_colors.html.erb +4 -0
  92. data/app/pb_kits/playbook/pb_title/docs/_title_colors.jsx +37 -0
  93. data/app/pb_kits/playbook/pb_title/docs/_title_colors.md +6 -0
  94. data/app/pb_kits/playbook/pb_title/docs/_title_light.html.erb +4 -1
  95. data/app/pb_kits/playbook/pb_title/docs/_title_light.md +3 -0
  96. data/app/pb_kits/playbook/pb_title/docs/example.yml +2 -2
  97. data/app/pb_kits/playbook/pb_title/docs/index.js +1 -1
  98. data/app/pb_kits/playbook/pb_title/title.html.erb +3 -2
  99. data/app/pb_kits/playbook/pb_title/title.rb +5 -4
  100. data/app/pb_kits/playbook/pb_title/title.test.js +29 -0
  101. data/app/pb_kits/playbook/playbook-doc.js +2 -0
  102. data/app/pb_kits/playbook/plugins/pb_chart.js +13 -22
  103. data/app/pb_kits/playbook/tokens/_colors.scss +3 -1
  104. data/app/pb_kits/playbook/utilities/_cursor.scss +3 -0
  105. data/app/pb_kits/playbook/utilities/_display.scss +23 -0
  106. data/app/pb_kits/playbook/utilities/_line_height.scss +11 -0
  107. data/app/pb_kits/playbook/utilities/globalProps.js +19 -1
  108. data/lib/playbook/classnames.rb +3 -0
  109. data/lib/playbook/cursor.rb +29 -0
  110. data/lib/playbook/display.rb +29 -0
  111. data/lib/playbook/engine.rb +0 -1
  112. data/lib/playbook/kit_base.rb +6 -0
  113. data/lib/playbook/line_height.rb +29 -0
  114. data/lib/playbook/version.rb +2 -2
  115. data/lib/playbook.rb +2 -0
  116. metadata +55 -15
  117. data/app/pb_kits/playbook/pb_caption/docs/_caption_example.html.erb +0 -3
  118. data/app/pb_kits/playbook/pb_caption/docs/_caption_example.jsx +0 -27
  119. data/app/pb_kits/playbook/pb_caption/docs/_caption_example.md +0 -1
  120. data/app/pb_kits/playbook/pb_caption/docs/_caption_variants.html.erb +0 -1
  121. data/app/pb_kits/playbook/pb_caption/docs/_caption_variants.jsx +0 -17
  122. data/app/pb_kits/playbook/pb_caption/docs/_caption_variants.md +0 -3
  123. data/app/pb_kits/playbook/pb_title/docs/_title_variants.html.erb +0 -1
  124. data/app/pb_kits/playbook/pb_title/docs/_title_variants.jsx +0 -19
  125. data/app/pb_kits/playbook/pb_title/docs/_title_variants.md +0 -3
@@ -14,6 +14,7 @@ type ImageProps = {
14
14
  onError?: () => {},
15
15
  size: "xs" | "sm" | "md" | "lg" | "xl",
16
16
  rounded?: boolean,
17
+ transition: "blur" | "fade" | "scale",
17
18
  url: string,
18
19
  }
19
20
 
@@ -27,6 +28,7 @@ const Image = (props: ImageProps) => {
27
28
  onError = null,
28
29
  rounded = false,
29
30
  size = '',
31
+ transition = 'fade',
30
32
  url = '',
31
33
  } = props
32
34
 
@@ -34,7 +36,7 @@ const Image = (props: ImageProps) => {
34
36
  const classes = classnames(
35
37
  buildCss('pb_image_kit', size),
36
38
  'lazyload',
37
- 'blur_up',
39
+ transition,
38
40
  { rounded },
39
41
  globalProps(props),
40
42
  className
@@ -53,6 +55,7 @@ const Image = (props: ImageProps) => {
53
55
  onError={onError}
54
56
  rounded={+rounded}
55
57
  src={url}
58
+ transition={transition}
56
59
  />
57
60
  </div>
58
61
  )
@@ -28,15 +28,31 @@ $image-sizes: (
28
28
  border-radius: $border-rad-heaviest;
29
29
  }
30
30
 
31
- .blur_up {
32
- -webkit-filter: blur(6px);
33
- filter: blur(6px);
34
- transition: filter 550ms, -webkit-filter 550ms;
31
+ &.fade {
32
+ opacity: 0;
33
+ &.lazyloaded {
34
+ opacity: 1;
35
+ transition: opacity 300ms ease-in;
36
+ }
35
37
  }
36
-
37
- .blur_up.lazyloaded {
38
- -webkit-filter: blur(0);
39
- filter: blur(0);
38
+
39
+ &.blur {
40
+ filter: blur(5px);
41
+ &.lazyloaded {
42
+ -webkit-filter: blur(0);
43
+ filter: blur(0);
44
+ transition: filter 300ms ease-in;
45
+ }
46
+ }
47
+
48
+ &.scale {
49
+ opacity: 0;
50
+ transform: scale(0.9);
51
+ &.lazyloaded {
52
+ opacity: 1;
53
+ transform: scale(1);
54
+ transition: 700ms ease-in;
55
+ }
40
56
  }
41
57
  }
42
58
  }
@@ -0,0 +1,54 @@
1
+ <%= pb_rails("flex") do %>
2
+ <%= pb_rails("flex/flex_item", props: {fixed_size: "250px"}) do %>
3
+ <%= pb_rails("select", props: {
4
+ id: "transition-dropdown",
5
+ label: "",
6
+ blank_selection: "Select a Transition...",
7
+ options: [
8
+ {
9
+ value: 'fade',
10
+ },
11
+ {
12
+ value: 'blur',
13
+ },
14
+ {
15
+ value: 'scale',
16
+ },
17
+ ]
18
+ }) %>
19
+ <% end %>
20
+ <%= pb_rails("flex/flex_item") do %>
21
+ <%= pb_rails("button", props: {classname: "button", text: "Load Image", margin_left: "sm", margin_top: "xs", disabled: true }) %>
22
+ <% end %>
23
+ <% end %>
24
+ <%= pb_rails("image", props: { classname: "transition-image", alt: "picture of a misty forest", url: "", on_error: "this.style.display = 'none'" }) %>
25
+
26
+ <script>
27
+ window.addEventListener('DOMContentLoaded', () => {
28
+ const button = document.querySelector(".button")
29
+ const transitionDropdown = document.querySelector("#transition-dropdown")
30
+ var option = ''
31
+
32
+ transitionDropdown.addEventListener('change', function() {
33
+ if (this.value === '') {
34
+ button.className = button.className + ' _disabled'
35
+ } else {
36
+ button.removeAttribute("disabled");
37
+ button.className = button.className.replace(/\_disabled/gi, '')
38
+ option = this.value
39
+ }
40
+ });
41
+
42
+ const updateTransition = (transition) => {
43
+ const imageTransition = document.querySelector('.transition-image')
44
+ imageTransition.removeAttribute("style")
45
+ imageTransition.classList.remove("fade", "blur", "scale", "lazyloaded")
46
+ imageTransition.classList.add(transition, "lazyload")
47
+ imageTransition.src = "https://unsplash.it/500/400/?image=634"
48
+ }
49
+
50
+ button.addEventListener('click', function() {
51
+ updateTransition(option)
52
+ })
53
+ });
54
+ </script>
@@ -0,0 +1,77 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import Image from '../_image'
4
+ import Select from '../../pb_select/_select'
5
+ import FlexItem from '../../pb_flex/_flex_item'
6
+ import Flex from '../../pb_flex/_flex'
7
+ import Button from '../../pb_button/_button'
8
+
9
+ const TransitionImage = (props) => {
10
+ const [transition, setTransition] = useState('')
11
+ const [apply, setApply] = useState({
12
+ url: '',
13
+ transition: '',
14
+ })
15
+
16
+ const loadImage = () => {
17
+ document.querySelector('.image').classList.remove(transition, 'lazyloaded')
18
+ setApply({
19
+ url: 'https://unsplash.it/500/400/?image=634',
20
+ transition: transition,
21
+ },
22
+ document.querySelector('.image').classList.add(transition, 'lazyload')
23
+ )
24
+ }
25
+
26
+ const handleTransition = ({ target }) => {
27
+ setTransition(target.value)
28
+ }
29
+
30
+ const options = [
31
+ {
32
+ value: 'fade',
33
+ },
34
+ {
35
+ value: 'blur',
36
+ },
37
+ {
38
+ value: 'scale',
39
+ },
40
+ ]
41
+
42
+ return (
43
+ <>
44
+ <Flex>
45
+ <FlexItem fixedSize="250px">
46
+ <Select
47
+ blankSelection="Select a Transition..."
48
+ label=""
49
+ onChange={handleTransition}
50
+ options={options}
51
+ {...props}
52
+ />
53
+ </FlexItem>
54
+ <FlexItem>
55
+ <Button
56
+ disabled={transition === '' ? true : false}
57
+ marginLeft="sm"
58
+ onClick={loadImage}
59
+ text="Load Image"
60
+ {...props}
61
+ />
62
+ </FlexItem>
63
+ </Flex>
64
+ <div style={{ display: apply.url === '' ? 'none' : 'block' }}>
65
+ <Image
66
+ alt="picture of a misty forest"
67
+ className="image"
68
+ transition={apply.transition}
69
+ url={apply.url}
70
+ {...props}
71
+ />
72
+ </div>
73
+ </>
74
+ )
75
+ }
76
+
77
+ export default TransitionImage
@@ -0,0 +1 @@
1
+ To add a transition, simply use the `transition` prop and one of the three string options `"fade"`, `"blur"`, or `"scale"`.
@@ -3,7 +3,9 @@ examples:
3
3
  - default_image: Default
4
4
  - rounded_image: Rounded
5
5
  - custom_error_image: Error Handling
6
+ - transition_image: Transition
6
7
  react:
7
8
  - default_image: Default
8
9
  - rounded_image: Rounded
9
10
  - custom_error_image: Error Handling
11
+ - transition_image: Transition
@@ -1,3 +1,4 @@
1
1
  export { default as DefaultImage } from './_default_image.jsx'
2
2
  export { default as RoundedImage } from './_rounded_image.jsx'
3
3
  export { default as CustomErrorImage } from './_custom_error_image.jsx'
4
+ export { default as TransitionImage } from './_transition_image.jsx'
@@ -7,4 +7,4 @@
7
7
  alt: object.alt,
8
8
  onerror: object.on_error,
9
9
  )
10
- %>
10
+ %>
@@ -11,10 +11,13 @@ module Playbook
11
11
  prop :size, type: Playbook::Props::Enum,
12
12
  values: %w[xs sm md lg xl none],
13
13
  default: "none"
14
+ prop :transition, type: Playbook::Props::Enum,
15
+ values: %w[blur fade scale none],
16
+ default: "fade"
14
17
  prop :url
15
18
 
16
19
  def classname
17
- generate_classname("pb_image_kit#{size_class} lazyload blur_up") + rounded_class
20
+ generate_classname("pb_image_kit#{size_class} #{transition_class} lazyload") + rounded_class
18
21
  end
19
22
 
20
23
  private
@@ -26,6 +29,10 @@ module Playbook
26
29
  def size_class
27
30
  size == "none" ? nil : "_#{size}"
28
31
  end
32
+
33
+ def transition_class
34
+ transition == "none" ? nil : transition
35
+ end
29
36
  end
30
37
  end
31
38
  end
@@ -7,6 +7,7 @@ const props = {
7
7
  data: { testid: 'avatar' },
8
8
  size: null,
9
9
  url: 'https://unsplash.it/500/400/?image=634',
10
+ transition: 'blur',
10
11
  }
11
12
 
12
13
  it('Should be accessible', async () => {
@@ -20,16 +21,20 @@ test('alt attribute', () => {
20
21
 
21
22
  test('default classname', () => {
22
23
  const kit = renderKit(Image, props)
23
- expect(kit).toHaveClass('pb_image_kit lazyload blur_up')
24
+ expect(kit).toHaveClass('pb_image_kit lazyload')
24
25
  })
25
26
 
26
27
  test('size = xs', () => {
27
28
  const kit = renderKit(Image, props, { size: 'xs' })
28
- expect(kit).toHaveClass('pb_image_kit_xs lazyload blur_up')
29
+ expect(kit).toHaveClass('pb_image_kit_xs lazyload')
30
+ })
31
+
32
+ test('transition = blur', () => {
33
+ const kit = renderKit(Image, props, { transition: 'blur' })
34
+ expect(kit).toHaveClass('pb_image_kit lazyload blur')
29
35
  })
30
36
 
31
37
  test('rounded = true', () => {
32
38
  const kit = renderKit(Image, props, { rounded: true })
33
- expect(kit).toHaveClass('pb_image_kit lazyload blur_up rounded')
39
+ expect(kit).toHaveClass('pb_image_kit lazyload rounded')
34
40
  })
35
-
@@ -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
+
7
+ import styles from './styles.scss'
8
+
9
+ type SlideType = {
10
+ alt: string,
11
+ current: number,
12
+ onClick: () => void,
13
+ onChange: (index: number) => void,
14
+ onZoom: (zoom: number) => void,
15
+ zooming: boolean,
16
+ url: string,
17
+ }
18
+
19
+ export default function Slide({
20
+ alt,
21
+ onClick = noop,
22
+ onZoom = noop,
23
+ url,
24
+ zooming = false,
25
+ }: SlideType) {
26
+ const handlePinchingStop = (e) => {
27
+ const isZooming = e.state.scale > 1
28
+ onZoom(isZooming)
29
+ }
30
+
31
+ return (
32
+ <TransformWrapper
33
+ doubleClick={{ mode: 'reset' }}
34
+ initialScale={1}
35
+ onPinchingStop={handlePinchingStop}
36
+ panning={{ disabled: !zooming }}
37
+ >
38
+ <button
39
+ className={styles.Slide}
40
+ onClick={onClick}
41
+ onDoubleClick={() => onZoom(false)}
42
+ tabIndex={-1}
43
+ >
44
+ <TransformComponent className={styles.TransformComponent}>
45
+ <img
46
+ alt={alt}
47
+ src={url}
48
+ />
49
+ </TransformComponent>
50
+ </button>
51
+ </TransformWrapper>
52
+ )
53
+ }
@@ -0,0 +1,54 @@
1
+ /* @flow */
2
+
3
+ import { noop } from 'lodash'
4
+ import { motion } from 'framer-motion/dist/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/dist/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,54 @@
1
+ /* eslint-disable jsx-control-statements/jsx-use-if-tag */
2
+ /* @flow */
3
+
4
+ import { noop } from 'lodash'
5
+ import React, { useState } from 'react'
6
+
7
+ import Slides from './Slides'
8
+ import Thumbnails from './Thumbnails'
9
+ import useUnscrollableBody from './useUnscrollableBody'
10
+
11
+ import styles from './styles.scss'
12
+
13
+ type CarouselType = {
14
+ initialPhoto: string,
15
+ onClose: Function,
16
+ icon: string,
17
+ iconSize: number,
18
+ current: number,
19
+ photos: Array<string>,
20
+ onChange: (index: number) => void,
21
+ onClick: (index: number) => void,
22
+ }
23
+
24
+ export default function Carousel({
25
+ current = 0,
26
+ photos,
27
+ onClick = noop,
28
+ onChange = noop,
29
+ }: CarouselType) {
30
+ useUnscrollableBody()
31
+ const [currentIndex, setCurrentIndex] = useState(current)
32
+ const handleChange = (index) => {
33
+ setCurrentIndex(index)
34
+ onChange(index)
35
+ }
36
+
37
+ return (
38
+ <div className={styles.Lightbox}>
39
+ <Slides
40
+ current={currentIndex}
41
+ onChange={handleChange}
42
+ onClick={onClick}
43
+ urls={photos.map((photo) => photo.url)}
44
+ />
45
+ {photos.length > 1 ? (
46
+ <Thumbnails
47
+ current={currentIndex}
48
+ onChange={handleChange}
49
+ urls={photos.map((photo) => photo.thumbnail)}
50
+ />
51
+ ) : null}
52
+ </div>
53
+ )
54
+ }
@@ -0,0 +1,110 @@
1
+ .Lightbox {
2
+ width: 100vw;
3
+ height: 100vh;
4
+ position: fixed;
5
+ left: 0;
6
+ top: 0;
7
+ display: flex;
8
+ align-items: center;
9
+ flex-direction: column;
10
+ background-color: black;
11
+ z-index: 1;
12
+ overflow: hidden;
13
+ }
14
+
15
+ .Slides {
16
+ display: flex;
17
+ flex-grow: 1;
18
+ height: calc(100% - 100px);
19
+ width: 100%;
20
+ z-index: 1;
21
+
22
+ [class^="react-transform-wrapper"] {
23
+ flex-shrink: 0;
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+
28
+ [class^="react-transform-content"] {
29
+ width: 100%;
30
+ height: 100%;
31
+ }
32
+ }
33
+
34
+ .Slide,
35
+ .Thumbnail {
36
+ flex-shrink: 0;
37
+ border: none;
38
+ margin: 0;
39
+ padding: 0;
40
+ cursor: pointer;
41
+ background-color: transparent;
42
+ }
43
+
44
+ .Slide {
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ width: 100%;
49
+ height: 100%;
50
+ overflow: hidden;
51
+
52
+ img {
53
+ width: 100vw;
54
+ height: 100vh;
55
+ object-fit: contain;
56
+ }
57
+ }
58
+
59
+ .BackBtn,
60
+ .NextBtn {
61
+ z-index: 2;
62
+ color: black;
63
+ position: absolute;
64
+ width: 50px;
65
+ height: 50px;
66
+ top: calc(50vh - 5px);
67
+ border: none;
68
+ border-radius: 50%;
69
+ background-color: white;
70
+ }
71
+
72
+ .BackBtn::before,
73
+ .NextBtn::before {
74
+ content: "▸";
75
+ }
76
+
77
+ .BackBtn {
78
+ left: 30px;
79
+ transform: rotate(180deg);
80
+ }
81
+
82
+ .NextBtn {
83
+ right: 30px;
84
+ }
85
+
86
+ .Thumbnails {
87
+ display: flex;
88
+ padding: 3px;
89
+ }
90
+
91
+ .Thumbnails.draggable {
92
+ align-self: flex-start;
93
+ }
94
+
95
+ .Thumbnail {
96
+ padding: 3px;
97
+ height: 100%;
98
+
99
+ img {
100
+ width: 100%;
101
+ height: 100%;
102
+ }
103
+ }
104
+
105
+ .Thumbnail.active {
106
+ padding: 6px;
107
+ background-color: white;
108
+ box-shadow: 0 0 6px white;
109
+ }
110
+