playbook_ui 10.18.2 → 10.19.0.pre.lightbox

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +2 -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_body/_body.jsx +1 -1
  6. data/app/pb_kits/playbook/pb_body/_body_mixins.scss +2 -1
  7. data/app/pb_kits/playbook/pb_body/body.rb +1 -1
  8. data/app/pb_kits/playbook/pb_body/body.test.js +29 -0
  9. data/app/pb_kits/playbook/pb_body/docs/_body_light.html.erb +5 -0
  10. data/app/pb_kits/playbook/pb_body/docs/_body_light.jsx +5 -0
  11. data/app/pb_kits/playbook/pb_body/docs/_body_light.md +6 -0
  12. data/app/pb_kits/playbook/pb_caption/_caption.jsx +6 -6
  13. data/app/pb_kits/playbook/pb_caption/_caption.scss +6 -17
  14. data/app/pb_kits/playbook/pb_caption/_caption_mixin.scss +13 -3
  15. data/app/pb_kits/playbook/pb_caption/caption.rb +3 -5
  16. data/app/pb_kits/playbook/pb_caption/caption.test.js +29 -0
  17. data/app/pb_kits/playbook/pb_caption/docs/_caption_colors.html.erb +3 -0
  18. data/app/pb_kits/playbook/pb_caption/docs/_caption_colors.jsx +25 -0
  19. data/app/pb_kits/playbook/pb_caption/docs/_caption_colors.md +6 -0
  20. data/app/pb_kits/playbook/pb_caption/docs/example.yml +2 -2
  21. data/app/pb_kits/playbook/pb_caption/docs/index.js +1 -2
  22. data/app/pb_kits/playbook/pb_lightbox/Carousel/Slide.jsx +53 -0
  23. data/app/pb_kits/playbook/pb_lightbox/Carousel/Slides.jsx +54 -0
  24. data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnail.jsx +39 -0
  25. data/app/pb_kits/playbook/pb_lightbox/Carousel/Thumbnails.jsx +82 -0
  26. data/app/pb_kits/playbook/pb_lightbox/Carousel/index.jsx +54 -0
  27. data/app/pb_kits/playbook/pb_lightbox/Carousel/styles.scss +110 -0
  28. data/app/pb_kits/playbook/pb_lightbox/Carousel/useSlides.js +66 -0
  29. data/app/pb_kits/playbook/pb_lightbox/Carousel/useUnscrollableBody.js +11 -0
  30. data/app/pb_kits/playbook/pb_lightbox/_lightbox.jsx +81 -0
  31. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.jsx +65 -0
  32. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_default.md +1 -0
  33. data/app/pb_kits/playbook/pb_lightbox/docs/_lightbox_multiple.jsx +65 -0
  34. data/app/pb_kits/playbook/pb_lightbox/docs/example.yml +6 -0
  35. data/app/pb_kits/playbook/pb_lightbox/docs/index.js +2 -0
  36. data/app/pb_kits/playbook/pb_lightbox/hooks/useToggler.js +10 -0
  37. data/app/pb_kits/playbook/pb_lightbox/hooks/useVisibility.js +21 -0
  38. data/app/pb_kits/playbook/pb_lightbox/hooks/useWindowSize.js +25 -0
  39. data/app/pb_kits/playbook/pb_lightbox/lightbox.scss +92 -0
  40. data/app/pb_kits/playbook/pb_lightbox/lightbox.test.jsx +30 -0
  41. data/app/pb_kits/playbook/pb_nav/_bold_mixin.scss +22 -0
  42. data/app/pb_kits/playbook/pb_nav/_horizontal_nav.scss +17 -1
  43. data/app/pb_kits/playbook/pb_nav/_vertical_nav.scss +9 -2
  44. data/app/pb_kits/playbook/pb_nav/docs/_bold_horizontal_nav.html.erb +6 -0
  45. data/app/pb_kits/playbook/pb_nav/docs/_bold_horizontal_nav.jsx +39 -0
  46. data/app/pb_kits/playbook/pb_nav/docs/_bold_vertical_nav.html.erb +6 -0
  47. data/app/pb_kits/playbook/pb_nav/docs/_bold_vertical_nav.jsx +39 -0
  48. data/app/pb_kits/playbook/pb_nav/docs/_horizontal_nav.html.erb +1 -1
  49. data/app/pb_kits/playbook/pb_nav/docs/_subtle_nav.html.erb +1 -1
  50. data/app/pb_kits/playbook/pb_nav/docs/example.yml +4 -0
  51. data/app/pb_kits/playbook/pb_nav/docs/index.js +2 -0
  52. data/app/pb_kits/playbook/pb_nav/nav.rb +1 -1
  53. data/app/pb_kits/playbook/pb_popover/docs/_popover_scroll_height.jsx +3 -0
  54. data/app/pb_kits/playbook/pb_popover/docs/_popover_z_index.jsx +1 -0
  55. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +62 -13
  56. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.html.erb +21 -2
  57. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.jsx +12 -3
  58. data/app/pb_kits/playbook/pb_title/_title.jsx +6 -4
  59. data/app/pb_kits/playbook/pb_title/_title.scss +5 -5
  60. data/app/pb_kits/playbook/pb_title/_title_mixin.scss +17 -0
  61. data/app/pb_kits/playbook/pb_title/docs/_title_colors.html.erb +4 -0
  62. data/app/pb_kits/playbook/pb_title/docs/_title_colors.jsx +37 -0
  63. data/app/pb_kits/playbook/pb_title/docs/_title_colors.md +6 -0
  64. data/app/pb_kits/playbook/pb_title/docs/_title_light.md +3 -0
  65. data/app/pb_kits/playbook/pb_title/docs/example.yml +2 -2
  66. data/app/pb_kits/playbook/pb_title/docs/index.js +1 -1
  67. data/app/pb_kits/playbook/pb_title/title.html.erb +2 -3
  68. data/app/pb_kits/playbook/pb_title/title.rb +5 -4
  69. data/app/pb_kits/playbook/pb_title/title.test.js +29 -0
  70. data/app/pb_kits/playbook/playbook-doc.js +2 -0
  71. data/app/pb_kits/playbook/utilities/_line_height.scss +11 -0
  72. data/app/pb_kits/playbook/utilities/globalProps.js +7 -1
  73. data/lib/playbook/classnames.rb +1 -0
  74. data/lib/playbook/kit_base.rb +2 -0
  75. data/lib/playbook/line_height.rb +29 -0
  76. data/lib/playbook/version.rb +1 -1
  77. metadata +41 -13
  78. data/app/pb_kits/playbook/pb_caption/docs/_caption_example.html.erb +0 -3
  79. data/app/pb_kits/playbook/pb_caption/docs/_caption_example.jsx +0 -27
  80. data/app/pb_kits/playbook/pb_caption/docs/_caption_example.md +0 -1
  81. data/app/pb_kits/playbook/pb_caption/docs/_caption_variants.html.erb +0 -1
  82. data/app/pb_kits/playbook/pb_caption/docs/_caption_variants.jsx +0 -17
  83. data/app/pb_kits/playbook/pb_caption/docs/_caption_variants.md +0 -3
  84. data/app/pb_kits/playbook/pb_title/docs/_title_variants.html.erb +0 -1
  85. data/app/pb_kits/playbook/pb_title/docs/_title_variants.jsx +0 -19
  86. data/app/pb_kits/playbook/pb_title/docs/_title_variants.md +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e3cbc0cd92e9520cc384a83a63b0c7929db90e80ee3c14545f4cb59cea7e908
4
- data.tar.gz: 3dbd612141159027a9da789d6a371cc8e8f2dccb9eaabff33f4c4ddea0c1e473
3
+ metadata.gz: baa1f6f416384a560ac9d4e03d4aed20b4d01a980f4384c24a29a6a48ee19a60
4
+ data.tar.gz: 79ba5f8ef47057ac2e187c46676d559c59149afda9ecfb934debaab366104728
5
5
  SHA512:
6
- metadata.gz: e5a15bb11978d3ac1e8bc98c067cfc39ca9012e8e995e758d5debe9073db0dad4b63b2222c6bf687bc739c5dfaca5e249e12b2b89345c711284000d177f00ea7
7
- data.tar.gz: f5f8ad98e52dde53cd3c634757aa6ffeb9c9e6d53a4081c3861e839d1ab6b210d26dd63691be86d9c946cc7c6b4760f006b772e5bb3924be97de70d19f320ac1
6
+ metadata.gz: edaa286cf749d9fdaa8b3ae854f59bc62681f0baffb90718b335a6774eec4133357f4eb7190b9f4545ec55b6cf41856052a0bc58ccedbd1d7c707b580654095a
7
+ data.tar.gz: a37e1263c2648e1bb032d49dc971871e7d5df0bd64091d4e61cdaf8e97f5df5915b2c6eb96f9b22508e3b3dc7c609206be70a979ccafd86616fa83b3cb005d38
@@ -47,6 +47,7 @@
47
47
  @import 'pb_label_value/label_value';
48
48
  @import 'pb_layout/layout';
49
49
  @import 'pb_legend/legend';
50
+ @import 'pb_lightbox/lightbox';
50
51
  @import 'pb_line_graph/line_graph';
51
52
  @import 'pb_list/list';
52
53
  @import 'pb_loading_inline/loading_inline';
@@ -98,4 +99,5 @@
98
99
  @import './utilities/positioning';
99
100
  @import './utilities/number_spacing';
100
101
  @import './utilities/shadow';
102
+ @import './utilities/line_height';
101
103
  @import './utilities/display';
@@ -102,3 +102,4 @@ kits:
102
102
  - title_detail
103
103
  - user_badge
104
104
  - walkthrough
105
+ - lightbox
@@ -4,7 +4,6 @@ import 'lazysizes/plugins/attrchange/ls.attrchange'
4
4
  import 'lazysizes'
5
5
 
6
6
  // vvv React Component JSX Imports from the React Kits vvv
7
- export { default as Walkthrough } from './pb_walkthrough/_walkthrough'
8
7
  export { default as Avatar } from './pb_avatar/_avatar'
9
8
  export { default as AvatarActionButton } from './pb_avatar_action_button/_avatar_action_button'
10
9
  export { default as Background } from './pb_background/_background'
@@ -55,6 +54,7 @@ export { default as Layout } from './pb_layout/_layout'
55
54
  export { default as Legend } from './pb_legend/_legend'
56
55
  export { default as LineGraph } from './pb_line_graph/_line_graph'
57
56
  export { default as List } from './pb_list/_list'
57
+ export { default as Lightbox } from './pb_lightbox/_lightbox'
58
58
  export { default as ListItem } from './pb_list/_list_item'
59
59
  export { default as LoadingInline } from './pb_loading_inline/_loading_inline'
60
60
  export { default as Message } from './pb_message/_message'
@@ -101,6 +101,7 @@ export { default as Toggle } from './pb_toggle/_toggle'
101
101
  export { default as Typeahead } from './pb_typeahead/_typeahead'
102
102
  export { default as User } from './pb_user/_user'
103
103
  export { default as UserBadge } from './pb_user_badge/_user_badge'
104
+ export { default as Walkthrough } from './pb_walkthrough/_walkthrough'
104
105
  export { default as WeekdayStacked } from './pb_weekday_stacked/_weekday_stacked'
105
106
  // ^^^ React Component JSX Imports from the React Kits ^^^
106
107
 
@@ -12,7 +12,7 @@ type BodyProps = {
12
12
  aria?: object,
13
13
  className?: string,
14
14
  children?: array<React.ReactChild>,
15
- color?: 'default' | 'light' | 'lighter',
15
+ color?: 'default' | 'light' | 'lighter' | 'link',
16
16
  dark?: boolean,
17
17
  data?: object,
18
18
  highlightedText?: array<string>,
@@ -6,6 +6,7 @@ $pb_body_colors: (
6
6
  default: $text_lt_default,
7
7
  light: $text_lt_light,
8
8
  lighter: $text_lt_lighter,
9
+ link: $primary,
9
10
  );
10
11
 
11
12
  $pb_dark_body_colors: (
@@ -63,4 +64,4 @@ $pb_body_status: (
63
64
 
64
65
  @mixin pb_body_positive {
65
66
  @include pb_body($success);
66
- }
67
+ }
@@ -4,7 +4,7 @@ module Playbook
4
4
  module PbBody
5
5
  class Body < Playbook::KitBase
6
6
  prop :color, type: Playbook::Props::Enum,
7
- values: %w[default light lighter],
7
+ values: %w[default light lighter link],
8
8
  default: "default"
9
9
  prop :status, type: Playbook::Props::Enum,
10
10
  values: %w[neutral negative positive],
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+ import { render, screen } from '../utilities/test-utils'
3
+
4
+ import Body from './_body'
5
+
6
+ test('returns namespaced class name', () => {
7
+ render(
8
+ <Body
9
+ data={{ testid: 'primary-test' }}
10
+ text="Test colors"
11
+ />
12
+ )
13
+
14
+ const kit = screen.getByTestId('primary-test')
15
+ expect(kit).toHaveClass('pb_body_kit')
16
+ })
17
+
18
+ test('with colors', () => {
19
+ render(
20
+ <Body
21
+ color="success"
22
+ data={{ testid: 'primary-test' }}
23
+ text="Test colors"
24
+ />
25
+ )
26
+
27
+ const kit = screen.getByTestId('primary-test')
28
+ expect(kit).toHaveClass('pb_body_kit_success')
29
+ })
@@ -12,6 +12,11 @@
12
12
  color: "lighter"
13
13
  }) %>
14
14
 
15
+ <%= pb_rails("body", props: {
16
+ text: "I am a body kit (Link)",
17
+ color: "link"
18
+ }) %>
19
+
15
20
  <%= pb_rails("body", props: {
16
21
  text: "I am a body kit (Status: negative)",
17
22
  status: "negative"
@@ -18,6 +18,11 @@ const BodyLight = (props) => {
18
18
  text="I am a body kit (Lighter)"
19
19
  {...props}
20
20
  />
21
+ <Body
22
+ color="link"
23
+ text="I am a body kit (Link)"
24
+ {...props}
25
+ />
21
26
  <Body
22
27
  status="negative"
23
28
  text="I am a body kit (Status: negative)"
@@ -0,0 +1,6 @@
1
+ ##### Prop
2
+ This kit uses `default` color by default, and can be replaced with colors below:
3
+
4
+ * `light` `lighter` `success` `error` `link`
5
+
6
+ - These colors are not for standard usage. You can use the color prop to make fixes if colors are not appearing properly, but consult your UX team members if you are deciding to implement it
@@ -3,33 +3,33 @@
3
3
  import React from 'react'
4
4
  import classnames from 'classnames'
5
5
  import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
6
- import { globalProps } from '../utilities/globalProps.js'
6
+ import { deprecatedProps, globalProps } from '../utilities/globalProps.js'
7
7
 
8
8
  type CaptionProps = {
9
9
  aria?: object,
10
- className?: string,
11
10
  children: array<React.ReactNode> | React.ReactNode,
11
+ className?: string,
12
+ color?: "default" | "light" | "lighter" | "success" | "error" | "link",
12
13
  data?: object,
13
14
  id?: string,
14
15
  size?: "xs" | "sm" | "md" | "lg" | "xl",
15
16
  tag?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div" | "caption",
16
17
  text?: string,
17
18
  variant?: null | "link",
18
- color?: "default" | "link" | "light",
19
19
  };
20
20
 
21
21
  const Caption = (props: CaptionProps) => {
22
+ if (props.variant) deprecatedProps('Title', ['variant']) //variant prop is deprecated, use color instead
22
23
  const {
23
24
  aria = {},
24
- className,
25
25
  children,
26
+ className,
26
27
  color,
27
28
  data = {},
28
29
  id,
29
30
  size = 'md',
30
31
  tag = 'div',
31
32
  text,
32
- variant = null,
33
33
  } = props
34
34
  const tagOptions = [
35
35
  'h1',
@@ -48,7 +48,7 @@ const Caption = (props: CaptionProps) => {
48
48
  const ariaProps = buildAriaProps(aria)
49
49
  const dataProps = buildDataProps(data)
50
50
  const css = classnames(
51
- buildCss('pb_caption_kit', size, variant, color),
51
+ buildCss('pb_caption_kit', size, color),
52
52
  globalProps(props),
53
53
  className,
54
54
  )
@@ -10,26 +10,15 @@
10
10
 
11
11
  &[class^="pb_caption_kit_xs"] {
12
12
  @include caption_xs;
13
- &[class*="_link"] {
14
- color: $primary;}
15
13
  }
16
14
 
17
- &[class*="light"] {
18
- color: $text_lt_light;
19
- }
20
-
21
- &[class*="default"] {
22
- color: $text_lt_default;
23
- }
24
-
25
- &[class*="link"] {
26
- color: $primary;
27
- }
15
+ @include pb_caption_kit_colors;
28
16
 
29
- &[class*="dark"] {
30
- @each $dark_color_name, $dark_color_value in $pb_dark_caption_colors {
31
- &[class*="_#{$dark_color_name}"][class*="dark"] {
32
- @include caption_color($dark_color_value);
17
+ &.dark {
18
+ @include caption_dark;
19
+ @each $name, $color in $pb_dark_caption_colors {
20
+ &[class*="_#{$name}"] {
21
+ color: $color;
33
22
  }
34
23
  }
35
24
  }
@@ -4,13 +4,16 @@
4
4
  $pb_caption_colors: (
5
5
  default: $text_lt_default,
6
6
  light: $text_lt_light,
7
- link: $primary-action,
7
+ link: $primary,
8
+ lighter: $text_lt_lighter,
9
+ success: $success,
10
+ error: $error,
8
11
  );
9
12
 
10
13
  $pb_dark_caption_colors: (
11
- default: #fff,
14
+ default: $text_dk_default,
12
15
  light: $text_dk_light,
13
- link: $primary-action,
16
+ link: $primary,
14
17
  );
15
18
 
16
19
 
@@ -39,3 +42,10 @@ $pb_dark_caption_colors: (
39
42
  @mixin caption_dark {
40
43
  color: $text_dk_light;
41
44
  }
45
+ @mixin pb_caption_kit_colors {
46
+ @each $name, $color in $pb_caption_colors {
47
+ &[class*="#{$name}"] {
48
+ color: $color
49
+ }
50
+ }
51
+ }
@@ -10,16 +10,14 @@ module Playbook
10
10
  values: %w[h1 h2 h3 h4 h5 h6 p span div caption],
11
11
  default: "div"
12
12
  prop :text
13
- prop :variant, type: Playbook::Props::Enum,
14
- values: [nil, "link"],
15
- default: nil
13
+ prop :variant, deprecated: true
16
14
 
17
15
  prop :color, type: Playbook::Props::Enum,
18
- values: [nil, "light", "default", "link"],
16
+ values: [nil, "default", "light", "lighter", "success", "error", "link"],
19
17
  default: nil
20
18
 
21
19
  def classname
22
- generate_classname("pb_caption_kit", size, variant, color)
20
+ generate_classname("pb_caption_kit", size, color)
23
21
  end
24
22
  end
25
23
  end
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+ import { render, screen } from '../utilities/test-utils'
3
+
4
+ import Caption from './_caption'
5
+
6
+ test('returns namespaced class name', () => {
7
+ render(
8
+ <Caption
9
+ data={{ testid: 'primary-test' }}
10
+ text="Test colors"
11
+ />
12
+ )
13
+
14
+ const kit = screen.getByTestId('primary-test')
15
+ expect(kit).toHaveClass('pb_caption_kit_md')
16
+ })
17
+
18
+ test('with colors', () => {
19
+ render(
20
+ <Caption
21
+ color="success"
22
+ data={{ testid: 'primary-test' }}
23
+ text="Test colors"
24
+ />
25
+ )
26
+
27
+ const kit = screen.getByTestId('primary-test')
28
+ expect(kit).toHaveClass('pb_caption_kit_md_success')
29
+ })
@@ -0,0 +1,3 @@
1
+ <%= pb_rails("caption", props: { text: "Test colors" }) %>
2
+ <%= pb_rails("caption", props: { text: "Test colors", color: "success" }) %>
3
+ <%= pb_rails("caption", props: { text: "Test colors", color: "link" }) %>
@@ -0,0 +1,25 @@
1
+ import React from 'react'
2
+ import { Caption } from '../../'
3
+
4
+ const CaptionColors = (props) => {
5
+ return (
6
+ <div>
7
+ <Caption
8
+ text="Test colors"
9
+ {...props}
10
+ />
11
+ <Caption
12
+ color="success"
13
+ text="Test colors"
14
+ {...props}
15
+ />
16
+ <Caption
17
+ color="link"
18
+ text="Test colors"
19
+ {...props}
20
+ />
21
+ </div>
22
+ )
23
+ }
24
+
25
+ export default CaptionColors
@@ -0,0 +1,6 @@
1
+ ##### Prop
2
+ Caption kit will use `light` color by default. Other available colors are:
3
+
4
+ * `default` `lighter` `success` `error` `link`
5
+
6
+ - These colors are not for standard usage. You can use the color prop to make fixes if colors are not appearing properly, but consult your UX team members if you are deciding to implement it
@@ -2,9 +2,9 @@ examples:
2
2
  rails:
3
3
  - caption_light: Default
4
4
  - caption_block: Block
5
- - caption_example: Color Variations
5
+ - caption_colors: Colors
6
6
 
7
7
  react:
8
8
  - caption_light: Default
9
9
  - caption_block: Block
10
- - caption_example: Color Variations
10
+ - caption_colors: Colors
@@ -1,4 +1,3 @@
1
1
  export { default as CaptionLight } from './_caption_light.jsx'
2
- export { default as CaptionVariants } from './_caption_variants.jsx'
2
+ export { default as CaptionColors } from './_caption_colors.jsx'
3
3
  export { default as CaptionBlock } from './_caption_block.jsx'
4
- export { default as CaptionExample } from './_caption_example.jsx'
@@ -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
+ }