playbook_ui 14.24.0 → 14.25.0.pre.alpha.PLAY2413togglefocusstate9789

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +8 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +1 -2
  4. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +34 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +56 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.html.erb +43 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.jsx +64 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control.jsx +60 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control.md +3 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control_per_row.jsx +57 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control_per_row.md +1 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +4 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +4 -1
  14. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +1 -1
  15. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +1 -1
  16. data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.html.erb +10 -1
  17. data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.rb +2 -0
  18. data/app/pb_kits/playbook/pb_circle_icon_button/docs/_circle_icon_button_input_options.html.erb +24 -0
  19. data/app/pb_kits/playbook/pb_circle_icon_button/docs/_circle_icon_button_input_options.md +3 -0
  20. data/app/pb_kits/playbook/pb_circle_icon_button/docs/example.yml +1 -0
  21. data/app/pb_kits/playbook/pb_date/_date.tsx +5 -3
  22. data/app/pb_kits/playbook/pb_date/date.html.erb +6 -6
  23. data/app/pb_kits/playbook/pb_date/date.rb +2 -0
  24. data/app/pb_kits/playbook/pb_date/docs/_date_with_show_current_year.html.erb +4 -0
  25. data/app/pb_kits/playbook/pb_date/docs/_date_with_show_current_year.jsx +17 -0
  26. data/app/pb_kits/playbook/pb_date/docs/_date_with_show_current_year.md +1 -0
  27. data/app/pb_kits/playbook/pb_date/docs/example.yml +2 -0
  28. data/app/pb_kits/playbook/pb_date/docs/index.js +1 -0
  29. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +19 -0
  30. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +1 -0
  31. data/app/pb_kits/playbook/pb_dropdown/index.js +3 -0
  32. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +2 -2
  33. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.html.erb +22 -0
  34. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.jsx +43 -0
  35. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.md +1 -0
  36. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
  37. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +1 -0
  38. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.html.erb +2 -1
  39. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +1 -1
  40. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +6 -0
  41. data/app/pb_kits/playbook/pb_flex/_flex.tsx +9 -6
  42. data/app/pb_kits/playbook/pb_flex/docs/_flex_gap.html.erb +12 -1
  43. data/app/pb_kits/playbook/pb_flex/docs/_flex_gap.jsx +26 -1
  44. data/app/pb_kits/playbook/pb_flex/docs/_flex_gap_rails.md +11 -0
  45. data/app/pb_kits/playbook/pb_flex/docs/_flex_gap_react.md +11 -0
  46. data/app/pb_kits/playbook/pb_flex/flex.rb +6 -12
  47. data/app/pb_kits/playbook/pb_icon/_icon.scss +4 -0
  48. data/app/pb_kits/playbook/pb_pagination/_pagination.test.jsx +212 -0
  49. data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +8 -1
  50. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_external_control.jsx +112 -0
  51. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_external_control_react.md +3 -0
  52. data/app/pb_kits/playbook/pb_pagination/docs/example.yml +1 -0
  53. data/app/pb_kits/playbook/pb_pagination/docs/index.js +1 -0
  54. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +11 -1
  55. data/app/pb_kits/playbook/pb_table/styles/_vertical_border.scss +49 -1
  56. data/app/pb_kits/playbook/pb_text_input/text_input.rb +15 -0
  57. data/app/pb_kits/playbook/pb_toggle/_toggle.scss +20 -2
  58. data/app/pb_kits/playbook/pb_toggle/_toggle.tsx +3 -0
  59. data/app/pb_kits/playbook/pb_toggle/docs/_toggle_default.html.erb +4 -2
  60. data/app/pb_kits/playbook/pb_toggle/docs/_toggle_default.jsx +2 -1
  61. data/app/pb_kits/playbook/utilities/_gap.scss +12 -24
  62. data/app/pb_kits/playbook/utilities/globalPropNames.mjs +2 -0
  63. data/app/pb_kits/playbook/utilities/globalProps.ts +28 -4
  64. data/dist/chunks/{_line_graph-BLS62QjW.js → _line_graph-CiVc-Cod.js} +1 -1
  65. data/dist/chunks/_typeahead-BQnvz-Ks.js +6 -0
  66. data/dist/chunks/{_weekday_stacked-Cv8-Sf6X.js → _weekday_stacked-2gKd1RZJ.js} +3 -3
  67. data/dist/chunks/{lib-DgtxnJqa.js → lib-CY5ZPzic.js} +2 -2
  68. data/dist/chunks/{pb_form_validation-_NsOWfBS.js → pb_form_validation-D3b0JKHH.js} +1 -1
  69. data/dist/chunks/vendor.js +1 -1
  70. data/dist/menu.yml +2 -2
  71. data/dist/playbook-doc.js +2 -2
  72. data/dist/playbook-rails-react-bindings.js +1 -1
  73. data/dist/playbook-rails.js +1 -1
  74. data/dist/playbook.css +1 -1
  75. data/lib/playbook/classnames.rb +2 -0
  76. data/lib/playbook/spacing.rb +53 -1
  77. data/lib/playbook/version.rb +2 -2
  78. metadata +26 -8
  79. data/app/pb_kits/playbook/pb_flex/docs/_flex_gap.md +0 -9
  80. data/dist/chunks/_typeahead-CZL6rvfn.js +0 -6
@@ -5,12 +5,14 @@ examples:
5
5
  - date_variants: Variants
6
6
  - date_alignment: Alignment
7
7
  - date_timezone: Timezones
8
+ - date_with_show_current_year: Show Current Year
8
9
  - date_unstyled: Unstyled
9
10
 
10
11
  react:
11
12
  - date_default: Default
12
13
  - date_variants: Variants
13
14
  - date_alignment: Alignment
15
+ - date_with_show_current_year: Show Current Year
14
16
  - date_unstyled: Unstyled
15
17
 
16
18
  swift:
@@ -2,3 +2,4 @@ export { default as DateDefault } from './_date_default.jsx'
2
2
  export { default as DateVariants } from './_date_variants.jsx'
3
3
  export { default as DateAlignment } from './_date_alignment.jsx'
4
4
  export { default as DateUnstyled } from './_date_unstyled.jsx'
5
+ export { default as DateWithShowCurrentYear } from './_date_with_show_current_year.jsx'
@@ -4,6 +4,7 @@ import classnames from 'classnames'
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import { deprecatedProps, globalProps, GlobalProps } from '../utilities/globalProps'
6
6
  import { getAllIcons } from "../utilities/icons/allicons"
7
+ import { camelToSnakeCase } from '../utilities/text'
7
8
 
8
9
  import datePickerHelper from './date_picker_helper'
9
10
  import Icon from '../pb_icon/_icon'
@@ -114,6 +115,20 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
114
115
  const inputAriaProps = buildAriaProps(inputAria)
115
116
  const inputDataProps = buildDataProps(inputData)
116
117
 
118
+ // Convert cursor prop to CSS-style format to apply to input tag below
119
+ const getCursorStyle = (cursor?: string): string => {
120
+ // If input is disabled, always use 'not-allowed'
121
+ if (disableInput) return 'not-allowed'
122
+
123
+ // If cursor prop is provided, convert it to styling format
124
+ if (cursor) {
125
+ return camelToSnakeCase(cursor).replace(/_/g, '-')
126
+ }
127
+
128
+ // Default to 'pointer'
129
+ return 'pointer'
130
+ }
131
+
117
132
  useEffect(() => {
118
133
  datePickerHelper({
119
134
  allowInput,
@@ -149,6 +164,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
149
164
  required: false,
150
165
  }, scrollContainer)
151
166
  }, initializeOnce ? [] : undefined)
167
+
152
168
  const filteredProps = {...props}
153
169
  if (filteredProps.marginBottom === undefined) {
154
170
  filteredProps.marginBottom = "sm"
@@ -163,6 +179,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
163
179
  error ? 'error' : null,
164
180
  className
165
181
  )
182
+
166
183
  const iconWrapperClass = () => {
167
184
  let base = 'cal_icon_wrapper'
168
185
  if (dark) {
@@ -176,6 +193,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
176
193
  }
177
194
  return base
178
195
  }
196
+
179
197
  const angleDown = getAllIcons()["angleDown"].icon as unknown as { [key: string]: SVGElement }
180
198
 
181
199
  return (
@@ -206,6 +224,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
206
224
  name={name}
207
225
  onChange={inputOnChange}
208
226
  placeholder={placeholder}
227
+ style={{ cursor: getCursorStyle(filteredProps.cursor) }}
209
228
  value={inputValue}
210
229
  />
211
230
 
@@ -10,6 +10,7 @@
10
10
  <%= pb_rails("text_input", props: {
11
11
  aria: object.input_aria,
12
12
  autocomplete: false,
13
+ cursor: object.cursor,
13
14
  dark: object.dark,
14
15
  data: object.input_data,
15
16
  disabled: object.disable_input,
@@ -284,6 +284,9 @@ export default class PbDropdown extends PbEnhancedElement {
284
284
  this.adjustDropdownHeight();
285
285
  }
286
286
  });
287
+ this.element.querySelector(DROPDOWN_INPUT).value = Array.from(this.selectedOptions)
288
+ .map((opt) => JSON.parse(opt).id)
289
+ .join(",");
287
290
  } else {
288
291
  options.forEach((option) => {
289
292
  option.classList.remove("pb_dropdown_option_selected");
@@ -52,7 +52,7 @@ const FixedConfirmationToast = (props: FixedConfirmationToastProps): React.React
52
52
  } = props;
53
53
 
54
54
  const returnedIcon = icon || iconMap[status]
55
- const iconClass = icon ? "custom_icon" : ""
55
+ const iconClass = icon && icon !== "none" ? "custom_icon" : ""
56
56
 
57
57
  const css = classnames(
58
58
  `pb_fixed_confirmation_toast_kit_${status}`,
@@ -92,7 +92,7 @@ const FixedConfirmationToast = (props: FixedConfirmationToastProps): React.React
92
92
  onClick={handleClick}
93
93
  {...htmlProps}
94
94
  >
95
- {returnedIcon && (
95
+ {returnedIcon && icon !== "none" && (
96
96
  <Icon
97
97
  className="pb_icon"
98
98
  fixedWidth
@@ -0,0 +1,22 @@
1
+ <%= pb_rails("fixed_confirmation_toast", props: {
2
+ text: "Error Message",
3
+ status: "error",
4
+ icon: "none",
5
+ closeable: true
6
+ })%>
7
+
8
+ <br><br>
9
+
10
+ <%= pb_rails("fixed_confirmation_toast", props: {
11
+ text: "Items Successfully Moved",
12
+ status: "success",
13
+ icon: "none"
14
+ })%>
15
+
16
+ <br><br>
17
+
18
+ <%= pb_rails("fixed_confirmation_toast", props: {
19
+ text: "Scan to Assign Selected Items",
20
+ status: "neutral",
21
+ icon: "none"
22
+ })%>
@@ -0,0 +1,43 @@
1
+ import React from 'react'
2
+
3
+ import FixedConfirmationToast from '../_fixed_confirmation_toast'
4
+
5
+ const FixedConfirmationToastNoIcon = (props) => {
6
+ return (
7
+ <div>
8
+ <div>
9
+ <FixedConfirmationToast
10
+ closeable
11
+ icon="none"
12
+ status="error"
13
+ text="Error Message"
14
+ {...props}
15
+ />
16
+ </div>
17
+
18
+ <br />
19
+
20
+ <div>
21
+ <FixedConfirmationToast
22
+ icon="none"
23
+ status="success"
24
+ text="Items Successfully Moved"
25
+ {...props}
26
+ />
27
+ </div>
28
+
29
+ <br />
30
+
31
+ <div>
32
+ <FixedConfirmationToast
33
+ icon="none"
34
+ status="neutral"
35
+ text="Scan to Assign Selected Items"
36
+ {...props}
37
+ />
38
+ </div>
39
+ </div>
40
+ )
41
+ }
42
+
43
+ export default FixedConfirmationToastNoIcon
@@ -0,0 +1 @@
1
+ Setting `icon` prop to "none" will render the fixed confirmation toast without the left side icon.
@@ -8,6 +8,7 @@ examples:
8
8
  - fixed_confirmation_toast_auto_close: Click to Show Auto Close
9
9
  - fixed_confirmation_toast_children: Children
10
10
  - fixed_confirmation_toast_custom_icon: Custom Icon
11
+ - fixed_confirmation_toast_no_icon: No Icon
11
12
 
12
13
  react:
13
14
  - fixed_confirmation_toast_default: Default
@@ -17,6 +18,7 @@ examples:
17
18
  - fixed_confirmation_toast_auto_close: Click to Show Auto Close
18
19
  - fixed_confirmation_toast_children: Children
19
20
  - fixed_confirmation_toast_custom_icon: Custom Icon
21
+ - fixed_confirmation_toast_no_icon: No Icon
20
22
 
21
23
  swift:
22
24
  - fixed_confirmation_toast_default_swift: Default
@@ -5,3 +5,4 @@ export { default as FixedConfirmationToastPositions } from './_fixed_confirmatio
5
5
  export { default as FixedConfirmationToastAutoClose } from './_fixed_confirmation_toast_auto_close.jsx'
6
6
  export { default as FixedConfirmationToastChildren } from './_fixed_confirmation_toast_children.jsx'
7
7
  export { default as FixedConfirmationToastCustomIcon } from './_fixed_confirmation_toast_custom_icon.jsx'
8
+ export { default as FixedConfirmationToastNoIcon } from './_fixed_confirmation_toast_no_icon.jsx'
@@ -1,6 +1,7 @@
1
1
  <%= pb_content_tag do %>
2
+ <% if object.icon_value && object.icon_value != "none" %>
2
3
  <%= pb_rails("icon", props: { icon: object.icon_value, classname: "pb_icon", fixed_width: true }) %>
3
-
4
+ <% end %>
4
5
  <% if content %>
5
6
  <%= content %>
6
7
  <% elsif object.show_text? %>
@@ -56,7 +56,7 @@ module Playbook
56
56
  end
57
57
 
58
58
  def icon_class
59
- icon.present? ? " custom_icon" : ""
59
+ icon.present? && icon != "none" ? " custom_icon" : ""
60
60
  end
61
61
 
62
62
  def classname
@@ -56,6 +56,12 @@ test('renders custom icon when provided', () => {
56
56
  expect(container.querySelector('.custom_icon')).toBeInTheDocument();
57
57
  });
58
58
 
59
+ test("renders no icon when icon prop is 'none'", () => {
60
+ const { container } = render(<FixedConfirmationToast icon="none" />);
61
+ expect(container.querySelector('.pb_icon')).not.toBeInTheDocument();
62
+ expect(container.querySelector('.custom_icon')).not.toBeInTheDocument();
63
+ });
64
+
59
65
  test('renders correctly with multiLine prop', () => {
60
66
  const { container } = render(<FixedConfirmationToast multiLine />);
61
67
  expect(container.querySelector('._multi_line')).toBeInTheDocument();
@@ -4,6 +4,9 @@ import { buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
4
4
  import { GlobalProps, globalProps, globalInlineProps } from '../utilities/globalProps'
5
5
  import { GenericObject, Sizes } from '../types'
6
6
 
7
+ type SizeType = Sizes | "none"
8
+ type SizeResponsiveType = { [key: string]: SizeType }
9
+
7
10
  type FlexProps = {
8
11
  children: React.ReactChild[] | React.ReactNode,
9
12
  className?: string,
@@ -18,9 +21,9 @@ type FlexProps = {
18
21
  reverse?: boolean,
19
22
  vertical?: "top" | "center" | "bottom" | "stretch" | "baseline" | "none",
20
23
  align?: "start" | "center" | "end" | "stretch" | "baseline" | "none",
21
- gap?: Sizes | "none",
22
- rowGap?: Sizes| "none",
23
- columnGap?: Sizes| "none",
24
+ gap?: SizeType | SizeResponsiveType,
25
+ rowGap?: SizeType | SizeResponsiveType,
26
+ columnGap?: SizeType | SizeResponsiveType,
24
27
  wrap?: boolean,
25
28
  alignSelf?: "start" | "end" | "center" | "stretch" | "none"
26
29
  } & GlobalProps
@@ -53,9 +56,9 @@ const Flex = (props: FlexProps): React.ReactElement => {
53
56
  const alignClass = align !== 'none' ? `align_items_${align}` : `align_items_${vertical}`
54
57
  const inlineClass = inline === true ? 'inline' : ''
55
58
  const spacingClass = spacing !== undefined ? `spacing_${spacing}` : ''
56
- const gapClass = gap !== 'none' ? `gap_${gap}` : ''
57
- const rowGapClass = rowGap !== 'none' ? `rowGap_${rowGap}` : ''
58
- const columnGapClass = columnGap !== 'none' ? `columnGap_${columnGap}` : ''
59
+ const gapClass = (gap !== 'none' && typeof gap === 'object') ? `gap_${gap}` : ''
60
+ const rowGapClass = (rowGap !== 'none' && typeof rowGap === 'object') ? `rowGap_${rowGap}` : ''
61
+ const columnGapClass = (columnGap !== 'none' && typeof columnGap === 'object') ? `columnGap_${columnGap}` : ''
59
62
  const wrapClass = wrap === true ? 'wrap' : ''
60
63
  const reverseClass = reverse === true ? 'reverse' : ''
61
64
  const alignSelfClass = alignSelf !== 'none' ? `align_self_${alignSelf}` : ''
@@ -1,4 +1,4 @@
1
- <%= pb_rails("title", props: {size: 4, text: "Row"}) %>
1
+ <%= pb_rails("title", props: {size: 4, text: "Gap"}) %>
2
2
  <br/>
3
3
  <div class="flex-doc-example">
4
4
  <%= pb_rails("flex", props:{ gap: "xxs", wrap:true}) do %>
@@ -31,3 +31,14 @@
31
31
  <%= pb_rails("flex/flex_item") do %>4<% end %>
32
32
  <% end %>
33
33
  </div>
34
+
35
+ <br/><br/>
36
+ <%= pb_rails("title", props: {size: 4, text: "Responsive"}) %>
37
+ <br/>
38
+ <div class="flex-doc-example">
39
+ <%= pb_rails("flex", props: { gap: { xs: "none", sm: "sm", md: "md", lg: "lg", xl: "xl" }, wrap: true }) do %>
40
+ <% 40.times do |i| %>
41
+ <%= pb_rails("flex/flex_item") do %> <%=i%> <% end %>
42
+ <% end %>
43
+ <% end %>
44
+ </div>
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import Flex from '../../pb_flex/_flex'
3
3
  import FlexItem from '../../pb_flex/_flex_item'
4
+ import Title from '../../pb_title/_title'
4
5
 
5
6
  const FlexGap = (props) => {
6
7
  const count = () => {
@@ -13,6 +14,8 @@ const FlexGap = (props) => {
13
14
 
14
15
  return (
15
16
  <>
17
+ <Title size={4}>Gap</Title>
18
+ <br />
16
19
  <div className="flex-doc-example">
17
20
  <Flex
18
21
  gap="xxs"
@@ -27,8 +30,10 @@ const FlexGap = (props) => {
27
30
  </Flex>
28
31
  </div>
29
32
 
30
- <br />
33
+ <br /><br />
31
34
 
35
+ <Title size={4}>Column Gap</Title>
36
+ <br />
32
37
  <div className="flex-doc-example">
33
38
  <Flex
34
39
  columnGap="lg"
@@ -48,6 +53,9 @@ const FlexGap = (props) => {
48
53
  </FlexItem>
49
54
  </Flex>
50
55
  </div>
56
+ <br /><br />
57
+
58
+ <Title size={4}>Row Gap</Title>
51
59
  <br />
52
60
  <div className="flex-doc-example">
53
61
  <Flex
@@ -69,6 +77,23 @@ const FlexGap = (props) => {
69
77
  </FlexItem>
70
78
  </Flex>
71
79
  </div>
80
+
81
+ <br /><br />
82
+ <Title size={4}>Responsive</Title>
83
+ <br />
84
+ <div className="flex-doc-example">
85
+ <Flex
86
+ gap={{ xs: "none", sm: "sm", md: "md", lg: "lg", xl: "xl" }}
87
+ wrap
88
+ {...props}
89
+ >
90
+ {count().map((v, key) => (
91
+ <FlexItem key={key}>
92
+ {v}
93
+ </FlexItem>
94
+ ))}
95
+ </Flex>
96
+ </div>
72
97
  </>
73
98
  )
74
99
  }
@@ -0,0 +1,11 @@
1
+ ##### Prop
2
+
3
+ `gap` | `row_gap` | `column_gap` | **Type**: String | Hash | **Values**: xxs | xs | sm | md | lg | xl | none
4
+
5
+ Setting the gap prop sets the `row_gap` and the `column_gap` props to the same size and creates equal space within a flex container.
6
+
7
+ Setting the `row_gap` prop creates space between rows in a flex container.
8
+
9
+ Setting the `column_gap` prop creates space between columns in a flex container.
10
+
11
+ You can also set responsive values by passing a hash with device sizes and values.
@@ -0,0 +1,11 @@
1
+ ##### Prop
2
+
3
+ `gap` | `rowGap` | `columnGap` | **Type**: String | Object | **Values**: xxs | xs | sm | md | lg | xl | none
4
+
5
+ Setting the gap prop sets the `rowGap` and the `columnGap` props to the same size and creates equal space within a flex container.
6
+
7
+ Setting the `rowGap` prop creates space between rows in a flex container.
8
+
9
+ Setting the `columnGap` prop creates space between columns in a flex container.
10
+
11
+ You can also set responsive values by passing an object with device sizes and values.
@@ -24,17 +24,11 @@ module Playbook
24
24
  default: "none",
25
25
  deprecated: true
26
26
 
27
- prop :gap, type: Playbook::Props::Enum,
28
- values: %w[xxs xs sm md lg xl none],
29
- default: "none"
27
+ prop :gap
30
28
 
31
- prop :row_gap, type: Playbook::Props::Enum,
32
- values: %w[xxs xs sm md lg xl none],
33
- default: "none"
29
+ prop :row_gap
34
30
 
35
- prop :column_gap, type: Playbook::Props::Enum,
36
- values: %w[xxs xs sm md lg xl none],
37
- default: "none"
31
+ prop :column_gap
38
32
 
39
33
  prop :reverse, type: Playbook::Props::Boolean,
40
34
  default: false
@@ -133,7 +127,7 @@ module Playbook
133
127
  end
134
128
 
135
129
  def gap_class
136
- if gap == "none"
130
+ if gap == "none" || gap.nil? || gap.is_a?(Hash)
137
131
  nil
138
132
  else
139
133
  "gap_#{gap}"
@@ -141,7 +135,7 @@ module Playbook
141
135
  end
142
136
 
143
137
  def row_gap_class
144
- if row_gap == "none"
138
+ if row_gap == "none" || row_gap.nil? || row_gap.is_a?(Hash)
145
139
  nil
146
140
  else
147
141
  "rowGap_#{row_gap}"
@@ -149,7 +143,7 @@ module Playbook
149
143
  end
150
144
 
151
145
  def column_gap_class
152
- if column_gap == "none"
146
+ if column_gap == "none" || column_gap.nil? || column_gap.is_a?(Hash)
153
147
  nil
154
148
  else
155
149
  "columnGap_#{column_gap}"
@@ -242,4 +242,8 @@ svg {
242
242
  animation: fa-spin 1s infinite linear;
243
243
  }
244
244
  }
245
+
246
+ &.dark {
247
+ color: white;
248
+ }
245
249
  }
@@ -0,0 +1,212 @@
1
+ import React from 'react'
2
+ import { ensureAccessible, renderKit, render, fireEvent, screen } from '../utilities/test-utils'
3
+ import Pagination from './_pagination'
4
+
5
+ const defaultProps = {
6
+ data: { testid: 'pagination-test' },
7
+ total: 10,
8
+ current: 1,
9
+ range: 5,
10
+ }
11
+
12
+ describe('Pagination Component', () => {
13
+ test('returns namespaced class name', () => {
14
+ const kit = renderKit(Pagination, defaultProps)
15
+ expect(kit).toBeInTheDocument()
16
+ expect(kit).toHaveClass('pb_paginate')
17
+ })
18
+
19
+ it("should be accessible", async () => {
20
+ ensureAccessible(Pagination, defaultProps)
21
+ })
22
+
23
+ test('renders with default props', () => {
24
+ render(<Pagination {...defaultProps} />)
25
+
26
+ const pagination = screen.getByTestId('pagination-test')
27
+ expect(pagination).toBeInTheDocument()
28
+ expect(pagination).toHaveClass('pb_paginate')
29
+ })
30
+
31
+ test('renders pagination buttons correctly', () => {
32
+ render(<Pagination {...defaultProps} />)
33
+
34
+ expect(screen.getByText('1')).toBeInTheDocument()
35
+ expect(screen.getByText('2')).toBeInTheDocument()
36
+ expect(screen.getByText('3')).toBeInTheDocument()
37
+ expect(screen.getByText('4')).toBeInTheDocument()
38
+ expect(screen.getByText('5')).toBeInTheDocument()
39
+
40
+ // Check for navigation arrows by looking for the li elements with specific classes
41
+ const leftArrow = document.querySelector('.pagination-left')
42
+ const rightArrow = document.querySelector('.pagination-right')
43
+ expect(leftArrow).toBeInTheDocument()
44
+ expect(rightArrow).toBeInTheDocument()
45
+ })
46
+
47
+ test('highlights current page as active', () => {
48
+ render(<Pagination {...defaultProps}
49
+ current={3}
50
+ />)
51
+
52
+ const activePage = screen.getByText('3')
53
+ expect(activePage).toHaveClass('active')
54
+ })
55
+
56
+ test('calls onChange when page is clicked', () => {
57
+ const mockOnChange = jest.fn()
58
+ render(<Pagination {...defaultProps}
59
+ onChange={mockOnChange}
60
+ />)
61
+
62
+ const pageButton = screen.getByText('3')
63
+ fireEvent.click(pageButton)
64
+
65
+ expect(mockOnChange).toHaveBeenCalledWith(3)
66
+ })
67
+
68
+ test('disables left arrow on first page', () => {
69
+ render(<Pagination {...defaultProps}
70
+ current={1}
71
+ />)
72
+
73
+ const leftArrow = document.querySelector('.pagination-left')
74
+ expect(leftArrow).toHaveClass('disabled')
75
+ })
76
+
77
+ test('disables right arrow on last page', () => {
78
+ render(<Pagination {...defaultProps}
79
+ current={10}
80
+ />)
81
+
82
+ const rightArrow = document.querySelector('.pagination-right')
83
+ expect(rightArrow).toHaveClass('disabled')
84
+ })
85
+
86
+ test('does not render when total is 1 or less', () => {
87
+ const { container } = render(<Pagination {...defaultProps}
88
+ total={1}
89
+ />)
90
+
91
+ expect(container.firstChild).toBeNull()
92
+ })
93
+
94
+ test('renders with custom className', () => {
95
+ render(<Pagination {...defaultProps}
96
+ className="custom-class"
97
+ />)
98
+
99
+ const pagination = screen.getByTestId('pagination-test')
100
+ expect(pagination).toHaveClass('custom-class')
101
+ })
102
+
103
+ test('renders with custom id', () => {
104
+ render(<Pagination {...defaultProps}
105
+ id="custom-id"
106
+ />)
107
+
108
+ const pagination = screen.getByTestId('pagination-test')
109
+ expect(pagination).toHaveAttribute('id', 'custom-id')
110
+ })
111
+
112
+ test('renders with custom range', () => {
113
+ render(<Pagination {...defaultProps}
114
+ range={3}
115
+ />)
116
+
117
+ expect(screen.getByText('1')).toBeInTheDocument()
118
+ expect(screen.getByText('2')).toBeInTheDocument()
119
+ expect(screen.getByText('3')).toBeInTheDocument()
120
+ expect(screen.getByText('9')).toBeInTheDocument()
121
+ expect(screen.getByText('10')).toBeInTheDocument()
122
+ })
123
+
124
+ test('handles large number of pages correctly', () => {
125
+ render(<Pagination {...defaultProps}
126
+ current={50}
127
+ range={5}
128
+ total={100}
129
+ />)
130
+
131
+ const pagination = screen.getByTestId('pagination-test')
132
+ expect(pagination).toBeInTheDocument()
133
+ expect(pagination).toHaveClass('pb_paginate')
134
+
135
+
136
+ expect(screen.getByText('48')).toBeInTheDocument()
137
+ expect(screen.getByText('49')).toBeInTheDocument()
138
+ expect(screen.getByText('50')).toBeInTheDocument()
139
+ expect(screen.getByText('51')).toBeInTheDocument()
140
+ expect(screen.getByText('52')).toBeInTheDocument()
141
+ })
142
+
143
+ test('syncs with external current prop changes', () => {
144
+ const { rerender } = render(<Pagination {...defaultProps}
145
+ current={1}
146
+ />)
147
+
148
+ expect(screen.getByText('1')).toHaveClass('active')
149
+
150
+ rerender(<Pagination {...defaultProps}
151
+ current={3}
152
+ />)
153
+
154
+ expect(screen.getByText('3')).toHaveClass('active')
155
+ expect(screen.getByText('1')).not.toHaveClass('active')
156
+ })
157
+
158
+ test('validates current prop is within valid range', () => {
159
+ const { rerender } = render(<Pagination {...defaultProps}
160
+ current={1}
161
+ />)
162
+
163
+ rerender(<Pagination {...defaultProps}
164
+ current={0}
165
+ />)
166
+
167
+ expect(screen.getByText('1')).toHaveClass('active')
168
+
169
+ rerender(<Pagination {...defaultProps}
170
+ current={15}
171
+ />)
172
+
173
+ expect(screen.getByText('1')).toHaveClass('active')
174
+ })
175
+
176
+ test('handles htmlOptions props', () => {
177
+ const htmlOptions = { 'data-test': 'test-value' }
178
+ render(<Pagination {...defaultProps}
179
+ htmlOptions={htmlOptions}
180
+ />)
181
+
182
+ const pagination = screen.getByTestId('pagination-test')
183
+ expect(pagination).toHaveAttribute('data-test', 'test-value')
184
+ })
185
+
186
+ test('renders first and last page buttons when range is small', () => {
187
+ render(<Pagination {...defaultProps}
188
+ current={10}
189
+ range={3}
190
+ total={20}
191
+ />)
192
+
193
+ expect(screen.getByText('1')).toBeInTheDocument()
194
+ expect(screen.getByText('20')).toBeInTheDocument()
195
+
196
+ expect(screen.getByText('9')).toBeInTheDocument()
197
+ expect(screen.getByText('10')).toBeInTheDocument()
198
+ expect(screen.getByText('11')).toBeInTheDocument()
199
+ })
200
+
201
+ test('renders second and second-to-last page buttons when needed', () => {
202
+ render(<Pagination {...defaultProps}
203
+ current={10}
204
+ range={3}
205
+ total={20}
206
+ />)
207
+
208
+ expect(screen.getByText('2')).toBeInTheDocument()
209
+
210
+ expect(screen.getByText('19')).toBeInTheDocument()
211
+ })
212
+ })