playbook_ui 14.24.0 → 14.25.0.pre.alpha.PLAY2379datekitjesttestreact9838
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +8 -2
- data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +1 -2
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +34 -0
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +56 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.html.erb +43 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.jsx +64 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control.jsx +60 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control.md +3 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control_per_row.jsx +57 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_padding_control_per_row.md +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +4 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +1 -1
- data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.html.erb +10 -1
- data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.rb +2 -0
- data/app/pb_kits/playbook/pb_circle_icon_button/docs/_circle_icon_button_input_options.html.erb +24 -0
- data/app/pb_kits/playbook/pb_circle_icon_button/docs/_circle_icon_button_input_options.md +3 -0
- data/app/pb_kits/playbook/pb_circle_icon_button/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_date/_date.tsx +5 -3
- data/app/pb_kits/playbook/pb_date/date.html.erb +6 -6
- data/app/pb_kits/playbook/pb_date/date.rb +2 -0
- data/app/pb_kits/playbook/pb_date/date.test.js +506 -0
- data/app/pb_kits/playbook/pb_date/docs/_date_with_show_current_year.html.erb +4 -0
- data/app/pb_kits/playbook/pb_date/docs/_date_with_show_current_year.jsx +17 -0
- data/app/pb_kits/playbook/pb_date/docs/_date_with_show_current_year.md +1 -0
- data/app/pb_kits/playbook/pb_date/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_date/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +19 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +1 -0
- data/app/pb_kits/playbook/pb_dropdown/index.js +3 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +2 -2
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.html.erb +22 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.jsx +43 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.md +1 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.html.erb +2 -1
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +1 -1
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +6 -0
- data/app/pb_kits/playbook/pb_flex/_flex.tsx +9 -6
- data/app/pb_kits/playbook/pb_flex/docs/_flex_gap.html.erb +12 -1
- data/app/pb_kits/playbook/pb_flex/docs/_flex_gap.jsx +26 -1
- data/app/pb_kits/playbook/pb_flex/docs/_flex_gap_rails.md +11 -0
- data/app/pb_kits/playbook/pb_flex/docs/_flex_gap_react.md +11 -0
- data/app/pb_kits/playbook/pb_flex/flex.rb +6 -12
- data/app/pb_kits/playbook/pb_icon/_icon.scss +4 -0
- data/app/pb_kits/playbook/pb_pagination/_pagination.test.jsx +212 -0
- data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +8 -1
- data/app/pb_kits/playbook/pb_pagination/docs/_pagination_external_control.jsx +112 -0
- data/app/pb_kits/playbook/pb_pagination/docs/_pagination_external_control_react.md +3 -0
- data/app/pb_kits/playbook/pb_pagination/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_pagination/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +11 -1
- data/app/pb_kits/playbook/pb_table/styles/_vertical_border.scss +49 -1
- data/app/pb_kits/playbook/pb_text_input/text_input.rb +15 -0
- data/app/pb_kits/playbook/utilities/_gap.scss +12 -24
- data/app/pb_kits/playbook/utilities/globalPropNames.mjs +2 -0
- data/app/pb_kits/playbook/utilities/globalProps.ts +28 -4
- data/dist/chunks/{_line_graph-BLS62QjW.js → _line_graph-CiVc-Cod.js} +1 -1
- data/dist/chunks/_typeahead-BQnvz-Ks.js +6 -0
- data/dist/chunks/{_weekday_stacked-Cv8-Sf6X.js → _weekday_stacked-CX4YxAHz.js} +3 -3
- data/dist/chunks/{lib-DgtxnJqa.js → lib-CY5ZPzic.js} +2 -2
- data/dist/chunks/{pb_form_validation-_NsOWfBS.js → pb_form_validation-D3b0JKHH.js} +1 -1
- data/dist/chunks/vendor.js +1 -1
- data/dist/menu.yml +2 -2
- data/dist/playbook-doc.js +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/classnames.rb +2 -0
- data/lib/playbook/spacing.rb +53 -1
- data/lib/playbook/version.rb +2 -2
- metadata +27 -8
- data/app/pb_kits/playbook/pb_flex/docs/_flex_gap.md +0 -9
- data/dist/chunks/_typeahead-CZL6rvfn.js +0 -6
data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.jsx
ADDED
@@ -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
|
data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_no_icon.md
ADDED
@@ -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,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?:
|
22
|
-
rowGap?:
|
23
|
-
columnGap?:
|
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: "
|
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
|
28
|
-
values: %w[xxs xs sm md lg xl none],
|
29
|
-
default: "none"
|
27
|
+
prop :gap
|
30
28
|
|
31
|
-
prop :row_gap
|
32
|
-
values: %w[xxs xs sm md lg xl none],
|
33
|
-
default: "none"
|
29
|
+
prop :row_gap
|
34
30
|
|
35
|
-
prop :column_gap
|
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}"
|
@@ -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
|
+
})
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useState } from "react";
|
1
|
+
import React, { useState, useEffect } from "react";
|
2
2
|
import classnames from 'classnames'
|
3
3
|
import { GlobalProps, globalProps } from '../utilities/globalProps'
|
4
4
|
import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
|
@@ -123,6 +123,13 @@ const Pagination = ( props: PaginationProps) => {
|
|
123
123
|
|
124
124
|
return buttons;
|
125
125
|
};
|
126
|
+
|
127
|
+
// Sync internal state with external current prop
|
128
|
+
useEffect(() => {
|
129
|
+
if (current >= 1 && current <= total) {
|
130
|
+
setCurrentPage(current);
|
131
|
+
}
|
132
|
+
}, [current, total]);
|
126
133
|
|
127
134
|
|
128
135
|
const ariaProps = buildAriaProps(aria)
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import React, { useState } from "react";
|
2
|
+
import Flex from '../../pb_flex/_flex'
|
3
|
+
import Pagination from '../../pb_pagination/_pagination'
|
4
|
+
import Select from '../../pb_select/_select'
|
5
|
+
import Table from '../../pb_table/_table'
|
6
|
+
|
7
|
+
import { data } from "./data";
|
8
|
+
|
9
|
+
const PaginationExternalControl = (props) => {
|
10
|
+
const [totalItems, setTotalItems] = useState(20);
|
11
|
+
const [itemsPerPage, setItemsPerPage] = useState(5);
|
12
|
+
const [currentPage, setCurrentPage] = useState(1);
|
13
|
+
|
14
|
+
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
15
|
+
|
16
|
+
const handlePageChange = (page) => {
|
17
|
+
setCurrentPage(page);
|
18
|
+
};
|
19
|
+
|
20
|
+
const limitedData = data.slice(0, totalItems);
|
21
|
+
const startIndex = (currentPage - 1) * itemsPerPage;
|
22
|
+
const paginatedItems = limitedData.slice(startIndex, startIndex + itemsPerPage);
|
23
|
+
|
24
|
+
const handleTotalItemsChange = (event) => {
|
25
|
+
const value = Number(event.target.value);
|
26
|
+
setTotalItems(value);
|
27
|
+
setCurrentPage(1);
|
28
|
+
};
|
29
|
+
|
30
|
+
const handleItemsPerPageChange = (event) => {
|
31
|
+
const value = Number(event.target.value);
|
32
|
+
setItemsPerPage(value);
|
33
|
+
setCurrentPage(1);
|
34
|
+
};
|
35
|
+
|
36
|
+
return (
|
37
|
+
<>
|
38
|
+
<Flex gap="sm">
|
39
|
+
<Select
|
40
|
+
label="Total Items"
|
41
|
+
onChange={handleTotalItemsChange}
|
42
|
+
options={[
|
43
|
+
{ value: "5", text: "5" },
|
44
|
+
{ value: "10", text: "10" },
|
45
|
+
{ value: "20", text: "20" }
|
46
|
+
]}
|
47
|
+
size="sm"
|
48
|
+
value={String(totalItems)}
|
49
|
+
{...props}
|
50
|
+
/>
|
51
|
+
|
52
|
+
<Select
|
53
|
+
label="Items per Page"
|
54
|
+
onChange={handleItemsPerPageChange}
|
55
|
+
options={[
|
56
|
+
{ value: "3", text: "3" },
|
57
|
+
{ value: "5", text: "5" },
|
58
|
+
{ value: "10", text: "10" }
|
59
|
+
]}
|
60
|
+
size="sm"
|
61
|
+
value={String(itemsPerPage)}
|
62
|
+
{...props}
|
63
|
+
/>
|
64
|
+
</Flex>
|
65
|
+
|
66
|
+
<Pagination
|
67
|
+
current={currentPage}
|
68
|
+
key={`pagination-top-${currentPage}`}
|
69
|
+
marginBottom="xs"
|
70
|
+
onChange={handlePageChange}
|
71
|
+
range={5}
|
72
|
+
total={totalPages}
|
73
|
+
{...props}
|
74
|
+
/>
|
75
|
+
<Table
|
76
|
+
marginBottom="xs"
|
77
|
+
responsive="none"
|
78
|
+
size="sm"
|
79
|
+
{...props}
|
80
|
+
>
|
81
|
+
<Table.Head>
|
82
|
+
<Table.Row>
|
83
|
+
<Table.Header>{"Column 1"}</Table.Header>
|
84
|
+
<Table.Header>{"Column 2"}</Table.Header>
|
85
|
+
<Table.Header>{"Column 3"}</Table.Header>
|
86
|
+
<Table.Header>{"Column 4"}</Table.Header>
|
87
|
+
<Table.Header>{"Column 5"}</Table.Header>
|
88
|
+
</Table.Row>
|
89
|
+
</Table.Head>
|
90
|
+
<Table.Body>
|
91
|
+
{paginatedItems.map((row, index) => (
|
92
|
+
<Table.Row key={index}>
|
93
|
+
{row.map((cell, cellIndex) => (
|
94
|
+
<Table.Cell key={cellIndex}>{cell}</Table.Cell>
|
95
|
+
))}
|
96
|
+
</Table.Row>
|
97
|
+
))}
|
98
|
+
</Table.Body>
|
99
|
+
</Table>
|
100
|
+
<Pagination
|
101
|
+
current={currentPage}
|
102
|
+
key={`pagination-bottom-${currentPage}`}
|
103
|
+
onChange={handlePageChange}
|
104
|
+
range={5}
|
105
|
+
total={totalPages}
|
106
|
+
{...props}
|
107
|
+
/>
|
108
|
+
</>
|
109
|
+
)
|
110
|
+
}
|
111
|
+
|
112
|
+
export default PaginationExternalControl
|
@@ -0,0 +1,3 @@
|
|
1
|
+
The Pagination component supports external control of the current page. This allows for programmatically reseting or changing the current page when filters or other criteria change, without needing to unmount and remount the component.
|
2
|
+
|
3
|
+
In this example, changing the "Total Items" or "Items per Page" dropdowns will automatically reset the pagination to page 1, demonstrating how external control works. The pagination component will update its internal state to reflect the new `current` prop value.
|