playbook_ui 11.19.0 → 11.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_body/_body.scss +10 -1
  3. data/app/pb_kits/playbook/pb_body/docs/_body_styled.html.erb +9 -0
  4. data/app/pb_kits/playbook/pb_body/docs/_body_styled.jsx +20 -0
  5. data/app/pb_kits/playbook/pb_body/docs/_body_styled.md +1 -0
  6. data/app/pb_kits/playbook/pb_body/docs/example.yml +2 -0
  7. data/app/pb_kits/playbook/pb_body/docs/index.js +1 -0
  8. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +7 -7
  9. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +69 -3
  10. data/app/pb_kits/playbook/pb_dialog/_dialog.tsx +1 -1
  11. data/app/pb_kits/playbook/pb_dialog/dialog.rb +1 -1
  12. data/app/pb_kits/playbook/pb_dialog/dialog.test.jsx +12 -0
  13. data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +5 -4
  14. data/app/pb_kits/playbook/pb_file_upload/docs/_description.md +2 -3
  15. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_message.jsx +40 -0
  16. data/app/pb_kits/playbook/pb_file_upload/docs/example.yml +2 -2
  17. data/app/pb_kits/playbook/pb_file_upload/docs/index.js +1 -0
  18. data/app/pb_kits/playbook/pb_file_upload/fileupload.test.js +12 -0
  19. data/app/pb_kits/playbook/pb_radio/_radio.tsx +4 -4
  20. data/app/pb_kits/playbook/pb_select/_select.scss +1 -1
  21. data/app/pb_kits/playbook/pb_selectable_card/{_selectable_card.jsx → _selectable_card.tsx} +47 -42
  22. data/app/pb_kits/playbook/pb_selectable_card/docs/_selectable_card_default.jsx +1 -2
  23. data/app/pb_kits/playbook/pb_selectable_card/docs/_selectable_card_single_select.jsx +1 -1
  24. data/app/pb_kits/playbook/pb_selectable_card/selectable_card.test.js +185 -0
  25. data/app/pb_kits/playbook/pb_selectable_icon/{_selectable_icon.jsx → _selectable_icon.tsx} +29 -32
  26. data/app/pb_kits/playbook/pb_selectable_icon/docs/_selectable_icon_default.jsx +1 -5
  27. data/app/pb_kits/playbook/pb_selectable_icon/docs/_selectable_icon_single_select.jsx +1 -4
  28. data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.test.js +148 -0
  29. data/lib/playbook/version.rb +2 -2
  30. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb389dc6dd09b729a058710f9dbaffccf1c70f4994dabad0e8c877fdfb15160a
4
- data.tar.gz: 5f8d2f82abeccdb16d5a8e0896db8c9c701b524f1fdfd86ac0cdb3e296dc5b16
3
+ metadata.gz: 44219343f1cdaffaa416fa4dd981884638d123658f40c607e52c920714e5a798
4
+ data.tar.gz: 899dd9d3fa76fd138d2657cf64ce6470c681ede3c0bb34b37481a9a6dac17b2c
5
5
  SHA512:
6
- metadata.gz: 4ef7df4f5c65d61d144d0b552b11c7205451cb77d50dff97c181af2175de03374ee98d18505c0765b219b217caef55f5714425585a27f7d0d2597efa50a1af21
7
- data.tar.gz: '0068b2d612e89e7932399e089b1478faac10bd3a710e29ef4f555b6e57060236ff0a4f346c15ae61bd08e2a925b136174901803b55a066d1c1d6397b1379f1ee'
6
+ metadata.gz: 16c710bf1423492bbadabea26f9ef6819622547cbbbba4ffb9add95404e270eff04639b9575b73e6a19001a94dbafa535a84aeb11e49548067626afc7b6e1bd3
7
+ data.tar.gz: a707281c6a517f9bd0df3e50aac97b5d83b6f92a12858a85b090c647543b1c11ac9f0675d2de44a3e6652314a39e6bd16d5a96907f758741df112c54bdb55fcf
@@ -1,4 +1,5 @@
1
1
  @import "./body_mixins";
2
+ @import "../tokens/titles";
2
3
 
3
4
  [class^=pb_body_kit]{
4
5
  @include pb_body($text_lt_default);
@@ -12,13 +13,21 @@
12
13
  }
13
14
  }
14
15
  }
15
-
16
16
  @each $dark_color_name, $dark_color_value in $pb_dark_body_colors{
17
17
  &[class*=_#{$dark_color_name}][class*=dark]{
18
18
  @include pb_body($dark_color_value);
19
19
  }
20
20
  }
21
+ b, strong {
22
+ @include pb_title_4
23
+ }
21
24
 
25
+ a {
26
+ color: $primary;
27
+ &:hover {
28
+ color: $text_lt_default;
29
+ }
30
+ }
22
31
  @each $status_name, $status_value in $pb_body_status {
23
32
  &[class*=#{$status_name}] {
24
33
  @include pb_body($status_value);
@@ -0,0 +1,9 @@
1
+ <%= pb_rails("body") do %>
2
+ <b>This text is using the <%="<b>"%> tag</b>
3
+ <br />
4
+ <br />
5
+ <strong>This text is using the <%="<strong>"%> tag</strong>
6
+ <br />
7
+ <br />
8
+ <a href="#">This text is using the <%="<a>"%> tag</a>
9
+ <% end %>
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { Body } from '../..'
3
+
4
+ const BodyStyled = (props) => {
5
+ return (
6
+ <div>
7
+ <Body {...props}>
8
+ <b>{"This text is using the <b> tag"}</b>
9
+ <br />
10
+ <br />
11
+ <strong>{"This text is using the <strong> tag"}</strong>
12
+ <br />
13
+ <br />
14
+ <a href="#">{"This text is using the <a> tag"}</a>
15
+ </Body>
16
+ </div>
17
+ )
18
+ }
19
+
20
+ export default BodyStyled
@@ -0,0 +1 @@
1
+ Playbook styles the `b`, `strong` and `a` tags within the body kit to match Playbook's design system.
@@ -2,6 +2,8 @@ examples:
2
2
  rails:
3
3
  - body_light: Default
4
4
  - body_block: Block
5
+ - body_styled: Styled b/strong/a tags
5
6
  react:
6
7
  - body_light: Default
7
8
  - body_block: Block
9
+ - body_styled: Styled b/strong/a tags
@@ -1,2 +1,3 @@
1
1
  export { default as BodyLight } from './_body_light.jsx'
2
2
  export { default as BodyBlock } from './_body_block.jsx'
3
+ export { default as BodyStyled } from './_body_styled.jsx'
@@ -8,18 +8,18 @@ import { globalProps, GlobalProps } from '../utilities/globalProps'
8
8
  type CheckboxProps = {
9
9
  aria?: {[key: string]: string},
10
10
  checked?: boolean,
11
- children: Node,
11
+ children?: React.ReactChild[] | React.ReactChild,
12
12
  className?: string,
13
13
  dark?: boolean,
14
14
  data?: {[key: string]: string},
15
15
  error?: boolean,
16
16
  id?: string,
17
17
  indeterminate?: boolean,
18
- name: string,
19
- onChange: (event: React.FormEvent<HTMLInputElement>) => void,
20
- tabIndex: number,
21
- text: string,
22
- value: string,
18
+ name?: string,
19
+ onChange?: (event: React.FormEvent<HTMLInputElement>) => void,
20
+ tabIndex?: number,
21
+ text?: string,
22
+ value?: string,
23
23
  } & GlobalProps
24
24
 
25
25
  const Checkbox = (props: CheckboxProps): JSX.Element => {
@@ -34,7 +34,7 @@ const Checkbox = (props: CheckboxProps): JSX.Element => {
34
34
  id,
35
35
  indeterminate = false,
36
36
  name = '',
37
- onChange = () => {},
37
+ onChange = () => { void 0 },
38
38
  tabIndex,
39
39
  text = '',
40
40
  value = '',
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
  // Dialog Animations
12
-
12
+ // Dialog Animations for fading in and out from the center
13
13
  @keyframes modalFadeIn {
14
14
  from {
15
15
  transform: translate3d(0, -100%, 0);
@@ -32,6 +32,53 @@
32
32
  }
33
33
  }
34
34
 
35
+ // Dialog Animations for fading in and out from the left side
36
+ @keyframes modalFadeInLeft {
37
+ from {
38
+ transform: translate3d(-100%, 0, 0);
39
+ opacity: 0;
40
+ }
41
+ to {
42
+ transform: translate3d(0, 0, 0);
43
+ opacity: 1;
44
+ }
45
+ }
46
+
47
+ @keyframes modalFadeOutLeft {
48
+ from {
49
+ transform: translate3d(0, 0, 0);
50
+ opacity: 1;
51
+ }
52
+ to {
53
+ transform: translate3d(-50%, 0, 0);
54
+ opacity: 0;
55
+ }
56
+ }
57
+
58
+
59
+ // Dialog Animations for fading in and out from the right side
60
+ @keyframes modalFadeInRight {
61
+ from {
62
+ transform: translate3d(100%, 0, 0);
63
+ opacity: 0;
64
+ }
65
+ to {
66
+ transform: translate3d(0, 0, 0);
67
+ opacity: 1;
68
+ }
69
+ }
70
+
71
+ @keyframes modalFadeOutRight {
72
+ from {
73
+ transform: translate3d(0, 0, 0);
74
+ opacity: 1;
75
+ }
76
+ to {
77
+ transform: translate3d(50%, 0, 0);
78
+ opacity: 0;
79
+ }
80
+ }
81
+
35
82
  @keyframes overlayFade {
36
83
  from {
37
84
  opacity: 0;
@@ -63,7 +110,7 @@
63
110
  $medium: 500px;
64
111
  $large: 800px;
65
112
  $xlarge: 1150px;
66
- $animation-duration: 0.2s;
113
+ $animation-duration: .2s;
67
114
  $z-index: 100;
68
115
  $opacity_visible: 1;
69
116
  $opacity_hidden: 0;
@@ -87,6 +134,24 @@
87
134
  outline: none;
88
135
  animation-timing-function: $easeInOutQuint;
89
136
 
137
+ &[class*="_left"] {
138
+ animation-name: modalFadeInLeft;
139
+ &[class*="_before_close"] {
140
+ animation-name: modalFadeOutLeft;
141
+ animation-duration: $animation-duration;
142
+ opacity: $opacity_hidden;
143
+ }
144
+ }
145
+
146
+ &[class*="_right"] {
147
+ animation-name: modalFadeInRight;
148
+ &[class*="_before_close"] {
149
+ animation-name: modalFadeOutRight;
150
+ animation-duration: $animation-duration;
151
+ opacity: $opacity_hidden;
152
+ }
153
+ }
154
+
90
155
  &[class*="_status_size"] {
91
156
  width: $status_size;
92
157
  }
@@ -98,6 +163,7 @@
98
163
  &[class*="_md"] {
99
164
  width: $medium;
100
165
  }
166
+
101
167
 
102
168
  &[class*="_lg"] {
103
169
  width: $large;
@@ -111,7 +177,7 @@
111
177
  opacity: $opacity_visible;
112
178
  }
113
179
 
114
- &_before_close {
180
+ &[class*="_before_close"] {
115
181
  animation-name: modalFadeOut;
116
182
  animation-duration: $animation-duration;
117
183
  opacity: $opacity_hidden;
@@ -71,7 +71,7 @@ const Dialog = (props: DialogProps) => {
71
71
  const ariaProps = buildAriaProps(aria);
72
72
  const dataProps = buildDataProps(data);
73
73
  const dialogClassNames = {
74
- base: classnames("pb_dialog", buildCss("pb_dialog", size)),
74
+ base: classnames("pb_dialog", buildCss("pb_dialog", size, placement)),
75
75
  afterOpen: "pb_dialog_after_open",
76
76
  beforeClose: "pb_dialog_before_close",
77
77
  };
@@ -20,7 +20,7 @@ module Playbook
20
20
  default: ""
21
21
 
22
22
  def classname
23
- generate_classname("pb_dialog pb_dialog_rails pb_dialog_#{size}")
23
+ generate_classname("pb_dialog pb_dialog_rails pb_dialog_#{size}_#{placement}")
24
24
  end
25
25
 
26
26
  def full_height_style
@@ -25,6 +25,7 @@ function DialogTest({ props }) {
25
25
  onClose={close}
26
26
  onConfirm={() => setIsLoading(!isLoading)}
27
27
  opened={isOpen}
28
+ placement="right"
28
29
  portalClassName="portal"
29
30
  size={size}
30
31
  text={text}
@@ -98,3 +99,14 @@ test("renders the buttons", async () => {
98
99
  cleanup()
99
100
  });
100
101
 
102
+ test("renders the right placement dialog", async () => {
103
+
104
+ const { queryByText } = render(<DialogTest />);
105
+
106
+ fireEvent.click(queryByText('Open Dialog'));
107
+
108
+ await waitFor(() => expect(queryByText("Header Title is the Title Prop")));
109
+
110
+ cleanup()
111
+ });
112
+
@@ -12,6 +12,7 @@ import Card from '../pb_card/_card'
12
12
  type FileUploadProps = {
13
13
  accept?: string[],
14
14
  className?: string,
15
+ customMessage?: string,
15
16
  data?: {[key: string]: string | number},
16
17
  acceptedFilesDescription?: string,
17
18
  maxSize?: number,
@@ -28,6 +29,7 @@ const FileUpload = (props: FileUploadProps): React.ReactElement => {
28
29
  accept = null,
29
30
  acceptedFilesDescription = '',
30
31
  className,
32
+ customMessage,
31
33
  data = {},
32
34
  maxSize,
33
35
  onFilesAccepted = noop,
@@ -77,10 +79,9 @@ const FileUpload = (props: FileUploadProps): React.ReactElement => {
77
79
  const dataProps = buildDataProps(data)
78
80
 
79
81
  const getDescription = () => {
80
- let msg = ""
81
- accept === null ? msg += 'Choose a file or drag it here.' : msg += `Choose a file or drag it here. The accepted file types are: ${acceptedFilesDescription || acceptedFileTypes()}.`
82
- if (maxSize) msg += ` ${maxFileSizeText}`
83
- return msg
82
+ return customMessage
83
+ ? customMessage
84
+ : `Choose a file or drag it here.${accept === null ? '' : ` The accepted file types are: ${acceptedFilesDescription || acceptedFileTypes()}.`}${maxSize ? ` ${maxFileSizeText}` : ''}`;
84
85
  }
85
86
 
86
87
  return (
@@ -1,8 +1,7 @@
1
- This kit provides a drag and drop interface for file uploads. Currently, the kit leverages [react-dropzone](https://github.com/react-dropzone/react-dropzone).
1
+ This kit provides a drag and drop interface for file uploads. Currently, the kit leverages [react-dropzone](https://github.com/react-dropzone/react-dropzone).
2
2
 
3
3
  ### Props
4
4
 
5
5
  `accept: [String]` Use this prop to set the list of valid file types
6
+ `customMessage: [String]` Use this prop to set a custom message, replacing the default text
6
7
  `onFilesAccepted: Function` The callback function, providing the list of dropped files
7
-
8
-
@@ -0,0 +1,40 @@
1
+ /* @flow */
2
+
3
+ import React, { useState } from 'react'
4
+ import {
5
+ FileUpload,
6
+ List,
7
+ ListItem,
8
+ } from '../..'
9
+
10
+ const AcceptedFilesList = ({ files }: FileList) => (
11
+ <List>
12
+ {files.map((file) => (
13
+ <ListItem key={file.name}>{file.name}</ListItem>
14
+ ))}
15
+ </List>
16
+ )
17
+
18
+ const FileUploadCustomMessage = (props) => {
19
+ const [filesToUpload, setFilesToUpload] = useState([])
20
+
21
+ const handleOnFilesAccepted = (files) => {
22
+ setFilesToUpload([...filesToUpload, ...files])
23
+ }
24
+
25
+ return (
26
+ <div>
27
+ <AcceptedFilesList
28
+ files={filesToUpload}
29
+ {...props}
30
+ />
31
+ <FileUpload
32
+ customMessage="Playbook is awesome!"
33
+ onFilesAccepted={handleOnFilesAccepted}
34
+ {...props}
35
+ />
36
+ </div>
37
+ )
38
+ }
39
+
40
+ export default FileUploadCustomMessage
@@ -1,10 +1,10 @@
1
1
  examples:
2
2
 
3
3
  rails:
4
-
4
+
5
5
  react:
6
6
  - file_upload_default: Default List of files to upload
7
7
  - file_upload_accept: Accept only certain types of files
8
+ - file_upload_custom_message: Add a custom message
8
9
  - file_upload_custom_description: Add your one accepted files description
9
10
  - file_upload_max_size: Set a file size limit
10
-
@@ -1,4 +1,5 @@
1
1
  export { default as FileUploadDefault } from './_file_upload_default.jsx'
2
2
  export { default as FileUploadAccept } from './_file_upload_accept.jsx'
3
+ export { default as FileUploadCustomMessage } from './_file_upload_custom_message.jsx'
3
4
  export { default as FileUploadCustomDescription } from './_file_upload_custom_description.jsx'
4
5
  export { default as FileUploadMaxSize } from './_file_upload_max_size.jsx'
@@ -38,3 +38,15 @@ test('displays max file size text', () => {
38
38
  const kit = screen.getByTestId(testid)
39
39
  expect(kit).toHaveTextContent('Choose a file or drag it here. Max file size is 1 MB.')
40
40
  })
41
+
42
+ test('displays custom message', () => {
43
+ render(
44
+ <FileUpload
45
+ customMessage={'Hello world!'}
46
+ data={{ testid: testid }}
47
+ />
48
+ )
49
+
50
+ const kit = screen.getByTestId(testid)
51
+ expect(kit).toHaveTextContent('Hello world!')
52
+ })
@@ -10,16 +10,16 @@ type RadioProps = {
10
10
  aria?: {[key: string]: string},
11
11
  alignment?: string,
12
12
  checked?: boolean,
13
- children?: Node,
13
+ children?: React.ReactChild[] | React.ReactChild,
14
14
  className?: string,
15
15
  dark?: boolean,
16
16
  data?: {[key: string]: string},
17
17
  error?: boolean,
18
18
  id?: string,
19
19
  label: string,
20
- name: string,
21
- value: string,
22
- text: string,
20
+ name?: string,
21
+ value?: string,
22
+ text?: string,
23
23
  onChange: (event: React.FormEvent<HTMLInputElement> | null)=>void,
24
24
  } & GlobalProps
25
25
 
@@ -50,7 +50,7 @@
50
50
  border-color: $error;
51
51
  }
52
52
  .pb_select_kit_caret {
53
- top: 35%;
53
+ top: 25px;
54
54
  }
55
55
  }
56
56
  }
@@ -1,10 +1,9 @@
1
1
  /* @flow */
2
2
 
3
- import React from 'react'
3
+ import React, {useRef} from 'react'
4
4
  import classnames from 'classnames'
5
5
 
6
- import type { InputCallback } from '../types'
7
- import { globalProps } from '../utilities/globalProps'
6
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
8
7
  import {
9
8
  buildAriaProps,
10
9
  buildCss,
@@ -19,13 +18,13 @@ import Flex from '../pb_flex/_flex'
19
18
  import Radio from '../pb_radio/_radio'
20
19
 
21
20
  type SelectableCardProps = {
22
- aria?: object,
23
- checked: boolean,
24
- children?: array<React.ReactChild>,
21
+ aria?: { [key: string]: string },
22
+ checked?: boolean,
23
+ children?: React.ReactChild[] | React.ReactChild,
25
24
  className?: string,
26
- customIcon?: SVGElement,
25
+ customIcon?: {[key: string] :SVGElement},
27
26
  dark?: boolean,
28
- data: object,
27
+ data?: { [key: string]: string },
29
28
  disabled?: boolean,
30
29
  error?: boolean,
31
30
  icon?: boolean,
@@ -33,32 +32,31 @@ type SelectableCardProps = {
33
32
  inputId?: string,
34
33
  multi?: boolean,
35
34
  name?: string,
36
- onChange: InputCallback<HTMLInputElement>,
35
+ onChange: (event: React.FormEvent<HTMLInputElement>) => void,
37
36
  text?: string,
38
37
  value?: string,
39
38
  variant?: string,
40
- }
39
+ } & GlobalProps
41
40
 
42
- const SelectableCard = ({
43
- aria = {},
44
- checked = false,
45
- children,
46
- className,
47
- customIcon,
48
- dark = false,
49
- data = {},
50
- disabled = false,
51
- error = false,
52
- icon = false,
53
- inputId = null,
54
- multi = true,
55
- name,
56
- onChange = noop,
57
- text,
58
- value,
59
- variant = 'default',
60
- ...props
61
- }: SelectableCardProps) => {
41
+ const SelectableCard = (props: SelectableCardProps) => {
42
+ const {
43
+ aria = {},
44
+ checked = false,
45
+ className,
46
+ customIcon,
47
+ dark = false,
48
+ data = {},
49
+ disabled = false,
50
+ error = false,
51
+ icon = false,
52
+ inputId = null,
53
+ multi = true,
54
+ name,
55
+ onChange = noop,
56
+ text,
57
+ value,
58
+ variant = 'default',
59
+ } = props
62
60
  const ariaProps = buildAriaProps(aria)
63
61
  const dataProps = buildDataProps(data)
64
62
 
@@ -87,7 +85,7 @@ const SelectableCard = ({
87
85
  }
88
86
  }
89
87
 
90
- const inputRef = React.createRef()
88
+ const inputRef = useRef(null)
91
89
  // Delegate clicks to hidden input from visible one
92
90
  const handleClick = () => {
93
91
  inputRef.current.click()
@@ -96,7 +94,15 @@ const SelectableCard = ({
96
94
  const inputType = multi ? 'checkbox' : 'radio'
97
95
  const inputIdPresent = inputId !== null ? inputId : name
98
96
  const Input = multi ? Checkbox : Radio
99
- const labelProps = variant === 'displayInput' ? Object.assign(props, { padding: 'none' }) : props
97
+
98
+ const filteredProps = {...props}
99
+ delete filteredProps?.inputId
100
+ delete filteredProps?.children
101
+ delete filteredProps?.icon
102
+ delete filteredProps?.error
103
+ delete filteredProps?.dark
104
+ delete filteredProps?.multi
105
+ const labelProps: GlobalProps = variant === 'displayInput' ? { ...filteredProps, padding: 'none' } : { ...filteredProps }
100
106
 
101
107
  return (
102
108
  <div
@@ -105,7 +111,6 @@ const SelectableCard = ({
105
111
  className={classes}
106
112
  >
107
113
  <input
108
- {...props}
109
114
  checked={checked}
110
115
  disabled={disabled}
111
116
  id={inputIdPresent}
@@ -114,6 +119,7 @@ const SelectableCard = ({
114
119
  ref={inputRef}
115
120
  type={inputType}
116
121
  value={value}
122
+ {...filteredProps}
117
123
  />
118
124
 
119
125
  <label
@@ -121,8 +127,7 @@ const SelectableCard = ({
121
127
  htmlFor={inputIdPresent}
122
128
  >
123
129
  <div className="buffer">
124
- <Choose>
125
- <When condition={variant === 'displayInput'}>
130
+ {variant === 'displayInput' ?
126
131
  <Flex vertical="center">
127
132
  <Flex
128
133
  orientation="column"
@@ -130,7 +135,9 @@ const SelectableCard = ({
130
135
  paddingRight="xs"
131
136
  vertical="center"
132
137
  >
133
- <Input dark={dark}>
138
+ <Input
139
+ dark={dark}
140
+ >
134
141
  <input
135
142
  checked={checked}
136
143
  disabled={disabled}
@@ -146,14 +153,12 @@ const SelectableCard = ({
146
153
  padding="sm"
147
154
  status={error ? 'negative' : null}
148
155
  >
149
- {text || children}
156
+ {text ||props.children}
150
157
  </Card.Body>
151
158
  </Flex>
152
- </When>
153
- <Otherwise>
154
- {text || children}
155
- </Otherwise>
156
- </Choose>
159
+ :
160
+ text || props.children
161
+ }
157
162
  {displayIcon()}
158
163
  </div>
159
164
  </label>
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react'
2
- import SelectableCard from '../_selectable_card.jsx'
2
+ import SelectableCard from '../_selectable_card.tsx'
3
3
 
4
4
  const SelectableCardDefault = (props) => {
5
5
  const [selectedWithIcon, setSelectedWithIcon] = useState(true)
@@ -24,7 +24,6 @@ const SelectableCardDefault = (props) => {
24
24
 
25
25
  <SelectableCard
26
26
  checked={selectedNoIcon}
27
- icon={false}
28
27
  inputId="selectedWithoutIcon"
29
28
  name="selectedWithoutIcon"
30
29
  onChange={() => setSelectedNoIcon(!selectedNoIcon)}
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react'
2
- import SelectableCard from '../_selectable_card.jsx'
2
+ import SelectableCard from '../_selectable_card.tsx'
3
3
 
4
4
  const SelectableCardSingleSelect = (props) => {
5
5
  const [selected, setSelected] = useState(null)
@@ -0,0 +1,185 @@
1
+ import React, { useState } from 'react'
2
+ import { render, screen } from '../utilities/test-utils'
3
+ import SelectableCard from './_selectable_card'
4
+ import { Body, Title, Image } from '../'
5
+
6
+ const SelectableCardMultiSelect = () => {
7
+ const [selected, setSelected] = useState(true)
8
+ const [unselected, setUnselected] = useState(false)
9
+ const [disabled, setDisabled] = useState(false)
10
+
11
+ return (
12
+ <>
13
+ <SelectableCard
14
+ checked={selected}
15
+ inputId="selected"
16
+ name="selected"
17
+ onChange={() => setSelected(!selected)}
18
+ value="selected"
19
+ >
20
+ {'Selected'}
21
+ </SelectableCard>
22
+
23
+ <SelectableCard
24
+ checked={unselected}
25
+ inputId="unselected"
26
+ name="unselected"
27
+ onChange={() => setUnselected(!unselected)}
28
+ value="unselected"
29
+ >
30
+ {'Unselected'}
31
+ </SelectableCard>
32
+
33
+ <SelectableCard
34
+ checked={disabled}
35
+ disabled
36
+ inputId="disabled"
37
+ name="disabled"
38
+ onChange={() => setDisabled(!disabled)}
39
+ value="disabled"
40
+ >
41
+ {'Disabled'}
42
+ </SelectableCard>
43
+
44
+ </>
45
+ )
46
+ }
47
+
48
+ const SelectableCardSingleSelect = () => {
49
+ const [selected, setSelected] = useState(null)
50
+ const handleSelect = (event) => {
51
+ setSelected(event.target.value)
52
+ }
53
+
54
+ return (
55
+ <>
56
+ <SelectableCard
57
+ checked={selected === 'male'}
58
+ inputId="male1"
59
+ multi={false}
60
+ name="gender"
61
+ onChange={handleSelect}
62
+ value="male"
63
+ >
64
+ {'Male'}
65
+ </SelectableCard>
66
+
67
+ <SelectableCard
68
+ checked={selected === 'female'}
69
+ inputId="female1"
70
+ multi={false}
71
+ name="gender"
72
+ onChange={handleSelect}
73
+ value="female"
74
+ >
75
+ {'Female'}
76
+ </SelectableCard>
77
+
78
+ <SelectableCard
79
+ checked={selected === 'other'}
80
+ inputId="other1"
81
+ multi={false}
82
+ name="gender"
83
+ onChange={handleSelect}
84
+ value="other"
85
+ >
86
+ {'Other'}
87
+ </SelectableCard>
88
+ </>
89
+ )
90
+ }
91
+
92
+
93
+ test('should start with a checked item', () => {
94
+ render(<SelectableCardMultiSelect />)
95
+
96
+ const kit = screen.getByLabelText('Selected')
97
+ expect(kit).toBeChecked()
98
+ })
99
+
100
+ test('should start with a disabled item', () => {
101
+ render(<SelectableCardMultiSelect />)
102
+
103
+ const kit = screen.getByLabelText('Disabled')
104
+ expect(kit).toBeDisabled()
105
+ })
106
+
107
+ test('should click and check an item', () => {
108
+ render(<SelectableCardMultiSelect />)
109
+
110
+ const kit = screen.getByLabelText('Unselected')
111
+ expect(kit).not.toBeChecked()
112
+ kit.click()
113
+ expect(kit).toBeChecked()
114
+ })
115
+
116
+ test('should check multiple items', () => {
117
+ render(<SelectableCardMultiSelect />)
118
+
119
+ const checkedItem = screen.getByLabelText('Selected')
120
+ expect(checkedItem).toBeChecked()
121
+
122
+ const uncheckedItem = screen.getByLabelText('Unselected')
123
+ expect(uncheckedItem).not.toBeChecked()
124
+
125
+ uncheckedItem.click()
126
+
127
+ expect(checkedItem).toBeChecked
128
+ expect(uncheckedItem).toBeChecked
129
+ })
130
+
131
+ test('should check only single item', () => {
132
+ render(<SelectableCardSingleSelect/>)
133
+
134
+ const male = screen.getByLabelText('Male')
135
+ expect(male).not.toBeChecked
136
+
137
+ const female = screen.getByLabelText('Female')
138
+ expect(female).not.toBeChecked
139
+
140
+ const other = screen.getByLabelText('Other')
141
+ expect(other).not.toBeChecked
142
+
143
+ male.click()
144
+ other.click()
145
+
146
+ expect(male).not.toBeChecked
147
+ expect(female).not.toBeChecked
148
+ expect(other).toBeChecked
149
+ })
150
+
151
+ test('should have text passed through the text prop', () => {
152
+ render (<SelectableCard text="This passes text through the tag"/>)
153
+
154
+ expect(screen.getByText("This passes text through the tag")).toBeInTheDocument()
155
+ })
156
+
157
+ test('should pass block content', () => {
158
+ render (<SelectableCard>
159
+ <Title
160
+ size={4}
161
+ text="Block"
162
+ />
163
+ <Body
164
+ tag="span"
165
+ >
166
+ {'This uses block'}
167
+ </Body>
168
+ </SelectableCard>
169
+ )
170
+ expect(screen.getByText("This uses block")).toBeInTheDocument()
171
+ })
172
+
173
+ test('should pass image inside card content', () => {
174
+ render (<SelectableCard>
175
+ <Image
176
+ rounded
177
+ size="xl"
178
+ url="https://unsplash.it/500/400/?image=634"
179
+ />
180
+ </SelectableCard>
181
+ )
182
+ const dispalyedImg = document.querySelector("img")
183
+
184
+ expect(dispalyedImg.src).toContain("image=634")
185
+ })
@@ -1,23 +1,20 @@
1
1
  /* @flow */
2
-
3
2
  import React from 'react'
4
3
  import classnames from 'classnames'
5
-
6
4
  import { globalProps } from '../utilities/globalProps'
7
5
  import {
8
6
  buildAriaProps,
9
7
  buildCss,
10
8
  buildDataProps,
11
9
  } from '../utilities/props'
12
-
13
10
  import Icon from '../pb_icon/_icon'
14
11
  import Title from '../pb_title/_title'
15
12
 
16
13
  type SelectableIconProps = {
17
- aria?: Object,
14
+ aria?: {[key: string]: string},
18
15
  checked?: boolean,
19
16
  className?: string,
20
- customIcon?: SVGElement,
17
+ customIcon?: {[key: string] :SVGElement},
21
18
  disabled?: boolean,
22
19
  data?: Object,
23
20
  icon: string,
@@ -59,54 +56,54 @@ const SelectableIcon = ({
59
56
  )
60
57
 
61
58
  const inputType = multi === false ? 'radio' : 'checkbox'
62
-
63
59
  const inputIdPresent = inputId !== null ? inputId : name
64
60
 
65
61
  return (
66
62
  <div
67
- {...ariaProps}
68
- {...dataProps}
69
- className={classes}
63
+ {...ariaProps}
64
+ {...dataProps}
65
+ className={classes}
70
66
  >
71
- <If condition={inputs === 'disabled'}>
67
+ {inputs === 'disabled' && (
72
68
  <>
73
69
  <Icon
74
- customIcon={customIcon}
75
- icon={icon}
76
- size="2x"
70
+ customIcon={customIcon}
71
+ icon={icon}
72
+ size="2x"
77
73
  />
78
74
  <Title
79
- size={4}
80
- tag="h4"
81
- text={text}
75
+ size={4}
76
+ tag="h4"
77
+ text={text}
82
78
  />
83
79
  </>
84
- </If>
85
- <If condition={inputs === 'enabled'}>
80
+ )}
81
+
82
+ {inputs === 'enabled' && (
86
83
  <>
87
84
  <input
88
- {...props}
89
- checked={checked}
90
- disabled={disabled}
91
- id={inputIdPresent}
92
- name={name}
93
- type={inputType}
94
- value={value}
85
+ {...props}
86
+ checked={checked}
87
+ disabled={disabled}
88
+ id={inputIdPresent}
89
+ name={name}
90
+ type={inputType}
91
+ value={value}
95
92
  />
96
93
  <label htmlFor={inputIdPresent}>
97
94
  <Icon
98
- customIcon={customIcon}
99
- icon={icon}
100
- size="2x"
95
+ customIcon={customIcon}
96
+ icon={icon}
97
+ size="2x"
101
98
  />
102
99
  <Title
103
- size={4}
104
- tag="h4"
105
- text={text}
100
+ size={4}
101
+ tag="h4"
102
+ text={text}
106
103
  />
107
104
  </label>
108
105
  </>
109
- </If>
106
+ )}
110
107
  </div>
111
108
  )
112
109
  }
@@ -1,8 +1,7 @@
1
1
  import React, { useState } from 'react'
2
-
3
2
  import SelectableIcon from '../_selectable_icon'
4
3
 
5
- const SelectableIconDefault = (props) => {
4
+ const SelectableIconDefault = () => {
6
5
  const [ checkSelected, toggleSelected ] = useState(true)
7
6
  const [ checkUnselected, toggleUnselected ] = useState(false)
8
7
  const [ checkDisabled, toggleDisabled ] = useState(false)
@@ -16,7 +15,6 @@ const SelectableIconDefault = (props) => {
16
15
  inputId={10}
17
16
  onChange={() => toggleSelected(!checkSelected)}
18
17
  text="US Dollar"
19
- {...props}
20
18
  />
21
19
 
22
20
  <SelectableIcon
@@ -25,7 +23,6 @@ const SelectableIconDefault = (props) => {
25
23
  inputId={11}
26
24
  onChange={() => toggleUnselected(!checkUnselected)}
27
25
  text="Euro"
28
- {...props}
29
26
  />
30
27
 
31
28
  <SelectableIcon
@@ -35,7 +32,6 @@ const SelectableIconDefault = (props) => {
35
32
  inputId={12}
36
33
  onChange={() => toggleDisabled(!checkDisabled)}
37
34
  text="Yen"
38
- {...props}
39
35
  />
40
36
  </div>
41
37
  )
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
2
2
 
3
3
  import SelectableIcon from '../_selectable_icon'
4
4
 
5
- const SelectableIconSingleSelect = (props) => {
5
+ const SelectableIconSingleSelect = () => {
6
6
  const [ selectedFormat, toggleFormat ] = useState(null)
7
7
 
8
8
  return (
@@ -17,7 +17,6 @@ const SelectableIconSingleSelect = (props) => {
17
17
  onChange={() => toggleFormat('Cassette')}
18
18
  text="Cassette"
19
19
  value="Cassette"
20
- {...props}
21
20
  />
22
21
 
23
22
  <SelectableIcon
@@ -29,7 +28,6 @@ const SelectableIconSingleSelect = (props) => {
29
28
  onChange={() => toggleFormat('CD')}
30
29
  text="CD"
31
30
  value="CD"
32
- {...props}
33
31
  />
34
32
 
35
33
  <SelectableIcon
@@ -41,7 +39,6 @@ const SelectableIconSingleSelect = (props) => {
41
39
  onChange={() => toggleFormat('Vinyl')}
42
40
  text="Vinyl"
43
41
  value="Vinyl"
44
- {...props}
45
42
  />
46
43
  </div>
47
44
  )
@@ -0,0 +1,148 @@
1
+ import React, { useState } from 'react'
2
+ import { render, screen } from '../utilities/test-utils'
3
+ import SelectableIcon from './_selectable_icon'
4
+
5
+ const testId = "selectableIcon"
6
+
7
+ const SelectableIconMultiSelect = () => {
8
+ const [checkSelected, toggleSelected] = useState(true)
9
+ const [checkUnselected, toggleUnselected] = useState(false)
10
+ const [checkDisabled, toggleDisabled] = useState(false)
11
+
12
+ return (
13
+ <>
14
+ <SelectableIcon
15
+ checked={checkSelected}
16
+ className="custom-class"
17
+ data={{ testid: testId }}
18
+ icon="dollar-sign"
19
+ inputId={10}
20
+ onChange={() => toggleSelected(!checkSelected)}
21
+ text="US Dollar"
22
+ />
23
+
24
+ <SelectableIcon
25
+ checked={checkUnselected}
26
+ icon="euro-sign"
27
+ inputId={11}
28
+ onChange={() => toggleUnselected(!checkUnselected)}
29
+ text="Euro"
30
+ />
31
+
32
+ <SelectableIcon
33
+ checked={checkDisabled}
34
+ disabled
35
+ icon="yen-sign"
36
+ inputId={12}
37
+ onChange={() => toggleDisabled(!checkDisabled)}
38
+ text="Yen"
39
+ />
40
+ </>
41
+ )
42
+ }
43
+
44
+ const SelectableIconSingleSelect = () => {
45
+ const [selectedFormat, toggleFormat] = useState('')
46
+
47
+ return (
48
+ <>
49
+ <SelectableIcon
50
+ checked={selectedFormat === 'Cassette'}
51
+ icon="cassette-tape"
52
+ inputId={13}
53
+ multi={false}
54
+ name="music-format"
55
+ onChange={() => toggleFormat('Cassette')}
56
+ text="Cassette"
57
+ value="Cassette"
58
+ />
59
+
60
+ <SelectableIcon
61
+ checked={selectedFormat === 'CD'}
62
+ icon="compact-disc"
63
+ inputId={14}
64
+ multi={false}
65
+ name="music-format"
66
+ onChange={() => toggleFormat('CD')}
67
+ text="CD"
68
+ value="CD"
69
+ />
70
+
71
+ <SelectableIcon
72
+ checked={selectedFormat === 'Vinyl'}
73
+ icon="album-collection"
74
+ inputId={15}
75
+ multi={false}
76
+ name="music-format"
77
+ onChange={() => toggleFormat('Vinyl')}
78
+ text="Vinyl"
79
+ value="Vinyl"
80
+ />
81
+ </>
82
+ )
83
+ }
84
+
85
+ test('should start with a checked item', () => {
86
+ render(<SelectableIconMultiSelect />)
87
+
88
+ const kit = screen.getByLabelText('US Dollar')
89
+ expect(kit).toBeChecked()
90
+ })
91
+
92
+ test('should start with a disabled item', () => {
93
+ render(<SelectableIconMultiSelect />)
94
+
95
+ const kit = screen.getByLabelText('Yen')
96
+ expect(kit).toBeDisabled()
97
+ })
98
+
99
+ test('should click and check an item', () => {
100
+ render(<SelectableIconMultiSelect />)
101
+
102
+ const kit = screen.getByLabelText('Euro')
103
+ expect(kit).not.toBeChecked()
104
+ kit.click()
105
+ expect(kit).toBeChecked()
106
+ })
107
+
108
+ test('should check multiple items', () => {
109
+ render(<SelectableIconMultiSelect />)
110
+
111
+ const usDollarItem = screen.getByLabelText('US Dollar')
112
+ expect(usDollarItem).toBeChecked()
113
+
114
+ const euroItem = screen.getByLabelText('Euro')
115
+ expect(euroItem).not.toBeChecked()
116
+
117
+ euroItem.click()
118
+
119
+ expect(usDollarItem).toBeChecked()
120
+ expect(euroItem).toBeChecked()
121
+ })
122
+
123
+ test('should check single item', () => {
124
+ render(<SelectableIconSingleSelect />)
125
+
126
+ const cassetteItem = screen.getByLabelText('Cassette')
127
+ expect(cassetteItem).not.toBeChecked()
128
+
129
+ const cdItem = screen.getByLabelText('CD')
130
+ expect(cdItem).not.toBeChecked()
131
+
132
+ const vinylItem = screen.getByLabelText('Vinyl')
133
+ expect(vinylItem).not.toBeChecked()
134
+
135
+ cassetteItem.click()
136
+ cdItem.click()
137
+
138
+ expect(cassetteItem).not.toBeChecked()
139
+ expect(cdItem).toBeChecked()
140
+ expect(vinylItem).not.toBeChecked()
141
+ })
142
+
143
+ test('should render custom class and data', () => {
144
+ render(<SelectableIconMultiSelect />)
145
+
146
+ const kit = screen.getByTestId(testId)
147
+ expect(kit).toHaveClass('custom-class')
148
+ })
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playbook
4
- PREVIOUS_VERSION = "11.18.0"
5
- VERSION = "11.19.0"
4
+ PREVIOUS_VERSION = "11.19.0"
5
+ VERSION = "11.20.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: playbook_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.19.0
4
+ version: 11.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Power UX
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-01-16 00:00:00.000000000 Z
12
+ date: 2023-01-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -397,6 +397,9 @@ files:
397
397
  - app/pb_kits/playbook/pb_body/docs/_body_light.html.erb
398
398
  - app/pb_kits/playbook/pb_body/docs/_body_light.jsx
399
399
  - app/pb_kits/playbook/pb_body/docs/_body_light.md
400
+ - app/pb_kits/playbook/pb_body/docs/_body_styled.html.erb
401
+ - app/pb_kits/playbook/pb_body/docs/_body_styled.jsx
402
+ - app/pb_kits/playbook/pb_body/docs/_body_styled.md
400
403
  - app/pb_kits/playbook/pb_body/docs/_description.md
401
404
  - app/pb_kits/playbook/pb_body/docs/_footer.md
402
405
  - app/pb_kits/playbook/pb_body/docs/example.yml
@@ -887,6 +890,7 @@ files:
887
890
  - app/pb_kits/playbook/pb_file_upload/docs/_file_upload_accept.jsx
888
891
  - app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_description.jsx
889
892
  - app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_description.md
893
+ - app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_message.jsx
890
894
  - app/pb_kits/playbook/pb_file_upload/docs/_file_upload_default.jsx
891
895
  - app/pb_kits/playbook/pb_file_upload/docs/_file_upload_max_size.jsx
892
896
  - app/pb_kits/playbook/pb_file_upload/docs/example.yml
@@ -1699,8 +1703,8 @@ files:
1699
1703
  - app/pb_kits/playbook/pb_select/select.html.erb
1700
1704
  - app/pb_kits/playbook/pb_select/select.rb
1701
1705
  - app/pb_kits/playbook/pb_select/select.test.js
1702
- - app/pb_kits/playbook/pb_selectable_card/_selectable_card.jsx
1703
1706
  - app/pb_kits/playbook/pb_selectable_card/_selectable_card.scss
1707
+ - app/pb_kits/playbook/pb_selectable_card/_selectable_card.tsx
1704
1708
  - app/pb_kits/playbook/pb_selectable_card/docs/_description.md
1705
1709
  - app/pb_kits/playbook/pb_selectable_card/docs/_footer.md
1706
1710
  - app/pb_kits/playbook/pb_selectable_card/docs/_selectable_card_block.html.erb
@@ -1725,6 +1729,7 @@ files:
1725
1729
  - app/pb_kits/playbook/pb_selectable_card/docs/index.js
1726
1730
  - app/pb_kits/playbook/pb_selectable_card/selectable_card.html.erb
1727
1731
  - app/pb_kits/playbook/pb_selectable_card/selectable_card.rb
1732
+ - app/pb_kits/playbook/pb_selectable_card/selectable_card.test.js
1728
1733
  - app/pb_kits/playbook/pb_selectable_card_icon/_selectable_card_icon.jsx
1729
1734
  - app/pb_kits/playbook/pb_selectable_card_icon/_selectable_card_icon.scss
1730
1735
  - app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_checkmark.html.erb
@@ -1738,8 +1743,8 @@ files:
1738
1743
  - app/pb_kits/playbook/pb_selectable_card_icon/docs/index.js
1739
1744
  - app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb
1740
1745
  - app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.rb
1741
- - app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.jsx
1742
1746
  - app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.scss
1747
+ - app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.tsx
1743
1748
  - app/pb_kits/playbook/pb_selectable_icon/docs/_selectable_icon_default.html.erb
1744
1749
  - app/pb_kits/playbook/pb_selectable_icon/docs/_selectable_icon_default.jsx
1745
1750
  - app/pb_kits/playbook/pb_selectable_icon/docs/_selectable_icon_options.html.erb
@@ -1749,6 +1754,7 @@ files:
1749
1754
  - app/pb_kits/playbook/pb_selectable_icon/docs/index.js
1750
1755
  - app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb
1751
1756
  - app/pb_kits/playbook/pb_selectable_icon/selectable_icon.rb
1757
+ - app/pb_kits/playbook/pb_selectable_icon/selectable_icon.test.js
1752
1758
  - app/pb_kits/playbook/pb_selectable_list/_item.jsx
1753
1759
  - app/pb_kits/playbook/pb_selectable_list/_selectable_list.jsx
1754
1760
  - app/pb_kits/playbook/pb_selectable_list/_selectable_list.scss