playbook_ui 16.2.0.pre.alpha.advancedtablecascadingcollapsereact14676 → 16.2.0.pre.alpha.faiconbuttonfix14520
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/CustomCell.tsx +4 -17
- data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +1 -3
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +9 -21
- data/app/pb_kits/playbook/pb_advanced_table/Utilities/ExpansionControlHelpers.tsx +1 -25
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +1 -5
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +1 -74
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +0 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +1 -2
- data/app/pb_kits/playbook/pb_collapsible/index.js +4 -16
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +1 -4
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +2 -2
- data/app/pb_kits/playbook/pb_dialog/index.js +5 -45
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +0 -2
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +2 -2
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +1 -1
- data/app/pb_kits/playbook/pb_dropdown/index.js +13 -68
- data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +3 -19
- data/app/pb_kits/playbook/pb_enhanced_element/element_observer.ts +1 -1
- data/app/pb_kits/playbook/pb_enhanced_element/index.ts +1 -2
- data/app/pb_kits/playbook/pb_icon/icon.rb +19 -168
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +0 -2
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +0 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +0 -1
- data/app/pb_kits/playbook/pb_pagination/_pagination.scss +1 -101
- data/app/pb_kits/playbook/pb_pagination/_pagination.test.jsx +1 -172
- data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +15 -178
- data/app/pb_kits/playbook/pb_pagination/docs/_pagination_default.jsx +1 -1
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +0 -4
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +0 -2
- data/app/pb_kits/playbook/pb_select/_select.tsx +0 -2
- data/app/pb_kits/playbook/pb_select/select.html.erb +2 -2
- data/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb +1 -1
- data/app/pb_kits/playbook/pb_star_rating/subcomponents/_star_rating_interactive.tsx +0 -1
- data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +0 -2
- data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +2 -2
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +21 -43
- data/app/pb_kits/playbook/pb_textarea/docs/example.yml +0 -1
- data/app/pb_kits/playbook/pb_textarea/docs/index.js +8 -9
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +4 -4
- data/app/pb_kits/playbook/pb_textarea/textarea.rb +2 -6
- data/app/pb_kits/playbook/pb_textarea/textarea.test.js +1 -134
- data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +0 -6
- data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +2 -2
- data/app/pb_kits/playbook/pb_tooltip/index.js +15 -60
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +1 -1
- data/dist/chunks/{_pb_line_graph-BGY7jEks.js → _pb_line_graph-BSLb5VXP.js} +1 -1
- data/dist/chunks/{_typeahead-QhswHQnq.js → _typeahead-DXIBDeMj.js} +1 -1
- data/dist/chunks/{globalProps-CK2YuA9O.js → globalProps-DyTB8IdV.js} +1 -1
- data/dist/chunks/{lib-DspaUdlc.js → lib-9wz3x5jl.js} +1 -1
- data/dist/chunks/vendor.js +5 -5
- data/dist/menu.yml +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/forms/builder/checkbox_field.rb +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +6 -12
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.jsx +0 -50
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.md +0 -1
- data/app/pb_kits/playbook/pb_kit_registry/index.ts +0 -180
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_react_reset_key.jsx +0 -100
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_react_reset_key.md +0 -1
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_input_options.jsx +0 -68
|
@@ -50,7 +50,6 @@ export default class PbEnhancedElement {
|
|
|
50
50
|
const enhansedElement = this.elements.get(element)
|
|
51
51
|
enhansedElement.disconnect()
|
|
52
52
|
this.elements.delete(element)
|
|
53
|
-
delete element._pbEnhanced
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
static start(): void {
|
|
@@ -58,7 +57,7 @@ export default class PbEnhancedElement {
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
static stop(): void {
|
|
61
|
-
this.
|
|
60
|
+
this.mutationObserver.stop()
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
connect(): void {
|
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "open-uri"
|
|
4
4
|
require "json"
|
|
5
|
-
require "digest"
|
|
6
5
|
|
|
7
6
|
module Playbook
|
|
8
7
|
module PbIcon
|
|
9
8
|
class Icon < Playbook::KitBase
|
|
10
|
-
ICON_PATH_DEV_CACHE_TTL_SECONDS = 2
|
|
11
|
-
ICON_PATH_PROD_CACHE_TTL_SECONDS = 60
|
|
12
|
-
|
|
13
9
|
prop :border, type: Playbook::Props::Boolean,
|
|
14
10
|
default: false
|
|
15
11
|
prop :fixed_width, type: Playbook::Props::Boolean,
|
|
@@ -86,35 +82,30 @@ module Playbook
|
|
|
86
82
|
)
|
|
87
83
|
end
|
|
88
84
|
|
|
89
|
-
# Instance-level memoization of alias map lookup result
|
|
90
85
|
def icon_alias_map
|
|
91
|
-
return
|
|
86
|
+
return unless Rails.application.config.respond_to?(:icon_alias_path)
|
|
92
87
|
|
|
93
|
-
|
|
88
|
+
base_path = Rails.application.config.icon_alias_path
|
|
89
|
+
json = File.read(Rails.root.join(base_path))
|
|
90
|
+
JSON.parse(json)["aliases"].freeze
|
|
94
91
|
end
|
|
95
92
|
|
|
96
|
-
# Instance-level memoization of resolved asset path
|
|
97
93
|
def asset_path
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
@asset_path =
|
|
101
|
-
if Rails.application.config.respond_to?(:icon_path)
|
|
102
|
-
resolved_icon = resolve_alias(icon)
|
|
103
|
-
path = self.class.icon_path_index[resolved_icon]
|
|
104
|
-
path if path && File.exist?(path)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def is_svg?
|
|
109
|
-
return @is_svg if defined?(@is_svg)
|
|
94
|
+
return unless Rails.application.config.respond_to?(:icon_path)
|
|
110
95
|
|
|
111
|
-
|
|
96
|
+
base_path = Rails.application.config.icon_path
|
|
97
|
+
resolved_icon = resolve_alias(icon)
|
|
98
|
+
icon_path = Dir.glob(Rails.root.join(base_path, "**", "#{resolved_icon}.svg")).first
|
|
99
|
+
icon_path if icon_path && File.exist?(icon_path)
|
|
112
100
|
end
|
|
113
101
|
|
|
114
102
|
def render_svg
|
|
115
103
|
doc = Nokogiri::XML(URI.open(asset_path || icon || custom_icon)) # rubocop:disable Security/Open
|
|
116
|
-
svg = doc.at_css
|
|
117
|
-
|
|
104
|
+
svg = doc.at_css "svg"
|
|
105
|
+
|
|
106
|
+
unless svg
|
|
107
|
+
return "" # Return an empty string if SVG element is not found
|
|
108
|
+
end
|
|
118
109
|
|
|
119
110
|
svg["class"] = %w[pb_custom_icon svg-inline--fa].concat([object.custom_icon_classname]).join(" ")
|
|
120
111
|
svg["id"] = object.id
|
|
@@ -122,9 +113,7 @@ module Playbook
|
|
|
122
113
|
svg["width"] = "auto"
|
|
123
114
|
svg["tabindex"] = object.tabindex
|
|
124
115
|
fill_color = object.color || "currentColor"
|
|
125
|
-
|
|
126
|
-
# Safely apply fill to all paths (avoids nil errors + handles multi-path icons)
|
|
127
|
-
doc.css("path").each { |p| p["fill"] = fill_color }
|
|
116
|
+
doc.at_css("path")["fill"] = fill_color
|
|
128
117
|
|
|
129
118
|
if object.data.present?
|
|
130
119
|
object.data.each do |key, value|
|
|
@@ -146,152 +135,14 @@ module Playbook
|
|
|
146
135
|
""
|
|
147
136
|
end
|
|
148
137
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@cache_mutex = Mutex.new
|
|
152
|
-
|
|
153
|
-
# Cache aliases.json across the process, but invalidate when the file changes (dev-safe)
|
|
154
|
-
def icon_alias_map
|
|
155
|
-
return @icon_alias_map if alias_cache_fresh?
|
|
156
|
-
|
|
157
|
-
cache_mutex.synchronize do
|
|
158
|
-
return @icon_alias_map if alias_cache_fresh?
|
|
159
|
-
|
|
160
|
-
@icon_alias_map =
|
|
161
|
-
if Rails.application.config.respond_to?(:icon_alias_path)
|
|
162
|
-
base_path = Rails.application.config.icon_alias_path
|
|
163
|
-
full_path = Rails.root.join(base_path)
|
|
164
|
-
@icon_alias_map_mtime = safe_mtime(full_path)
|
|
165
|
-
|
|
166
|
-
json = File.read(full_path)
|
|
167
|
-
parsed = JSON.parse(json)
|
|
168
|
-
parsed.fetch("aliases", {}).freeze
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
@icon_alias_map
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Cache an index of icon_name to file path for all SVGs in the configured directory, with invalidation based on directory mtime
|
|
176
|
-
# Avoids recursive Dir.glob for every icon render
|
|
177
|
-
def icon_path_index
|
|
178
|
-
return @icon_path_index if index_cache_fresh?
|
|
179
|
-
|
|
180
|
-
cache_mutex.synchronize do
|
|
181
|
-
return @icon_path_index if index_cache_fresh?
|
|
182
|
-
|
|
183
|
-
@icon_path_index =
|
|
184
|
-
if Rails.application.config.respond_to?(:icon_path)
|
|
185
|
-
base_path = Rails.application.config.icon_path
|
|
186
|
-
root = Rails.root.join(base_path)
|
|
187
|
-
|
|
188
|
-
# If path doesn't exist, keep behavior aligned (no path resolution)
|
|
189
|
-
if Dir.exist?(root)
|
|
190
|
-
@icon_path_index_cache_key = icon_path_cache_key(root)
|
|
191
|
-
|
|
192
|
-
# One scan builds the map for O(1) lookups
|
|
193
|
-
# Key is the filename (without .svg) to match existing usage
|
|
194
|
-
index = {}
|
|
195
|
-
Dir.glob(File.join(root.to_s, "**", "*.svg")).sort.each do |p|
|
|
196
|
-
name = File.basename(p, ".svg")
|
|
197
|
-
index[name] ||= p
|
|
198
|
-
end
|
|
199
|
-
index.freeze
|
|
200
|
-
else
|
|
201
|
-
@icon_path_index_cache_key = nil
|
|
202
|
-
{}
|
|
203
|
-
end
|
|
204
|
-
else
|
|
205
|
-
{}
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
@icon_path_index_checked_at = monotonic_now
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
@icon_path_index
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
private
|
|
215
|
-
|
|
216
|
-
def cache_mutex
|
|
217
|
-
@cache_mutex ||= Mutex.new
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def alias_cache_fresh?
|
|
221
|
-
return false unless defined?(@icon_alias_map)
|
|
222
|
-
|
|
223
|
-
return true unless Rails.application.config.respond_to?(:icon_alias_path)
|
|
224
|
-
|
|
225
|
-
full_path = Rails.root.join(Rails.application.config.icon_alias_path)
|
|
226
|
-
safe_mtime(full_path) == @icon_alias_map_mtime
|
|
227
|
-
rescue
|
|
228
|
-
false
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def index_cache_fresh?
|
|
232
|
-
return false unless defined?(@icon_path_index)
|
|
233
|
-
|
|
234
|
-
return true unless Rails.application.config.respond_to?(:icon_path)
|
|
235
|
-
|
|
236
|
-
# In development and production, skip re-checks for a short TTL window
|
|
237
|
-
# to avoid repeated tree scans on hot paths.
|
|
238
|
-
return true if Rails.env.development? && within_icon_index_ttl?(ICON_PATH_DEV_CACHE_TTL_SECONDS)
|
|
239
|
-
return true if Rails.env.production? && within_icon_index_ttl?(ICON_PATH_PROD_CACHE_TTL_SECONDS)
|
|
240
|
-
|
|
241
|
-
root = Rails.root.join(Rails.application.config.icon_path)
|
|
242
|
-
fresh = icon_path_cache_key(root) == @icon_path_index_cache_key
|
|
243
|
-
@icon_path_index_checked_at = monotonic_now
|
|
244
|
-
fresh
|
|
245
|
-
rescue
|
|
246
|
-
false
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def within_icon_index_ttl?(ttl_seconds)
|
|
250
|
-
return false unless defined?(@icon_path_index_checked_at)
|
|
251
|
-
|
|
252
|
-
(monotonic_now - @icon_path_index_checked_at) < ttl_seconds
|
|
253
|
-
rescue
|
|
254
|
-
false
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def monotonic_now
|
|
258
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def icon_path_cache_key(root)
|
|
262
|
-
return safe_mtime(root) unless Rails.env.development? || Rails.env.production?
|
|
263
|
-
|
|
264
|
-
digest = Digest::SHA1.new
|
|
265
|
-
root_prefix = "#{root}/"
|
|
266
|
-
|
|
267
|
-
Dir.glob(File.join(root.to_s, "**", "*.svg")).sort.each do |path|
|
|
268
|
-
digest << path.delete_prefix(root_prefix)
|
|
269
|
-
next unless Rails.env.development?
|
|
270
|
-
|
|
271
|
-
# Development tracks file metadata for rapid local edits.
|
|
272
|
-
# Production only needs path-set change detection during periodic checks.
|
|
273
|
-
stat = File.stat(path)
|
|
274
|
-
digest << stat.mtime.to_f.to_s
|
|
275
|
-
digest << stat.size.to_s
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
digest.hexdigest
|
|
279
|
-
rescue
|
|
280
|
-
nil
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def safe_mtime(path)
|
|
284
|
-
File.exist?(path) ? File.mtime(path) : nil
|
|
285
|
-
rescue
|
|
286
|
-
nil
|
|
287
|
-
end
|
|
138
|
+
def is_svg?
|
|
139
|
+
(icon || custom_icon.to_s).include?(".svg") || asset_path.present?
|
|
288
140
|
end
|
|
289
141
|
|
|
290
142
|
private
|
|
291
143
|
|
|
292
144
|
def resolve_alias(icon)
|
|
293
145
|
return icon unless icon_alias_map
|
|
294
|
-
return icon if icon.nil?
|
|
295
146
|
|
|
296
147
|
aliases = icon_alias_map[icon]
|
|
297
148
|
return icon unless aliases
|
|
@@ -304,8 +155,8 @@ module Playbook
|
|
|
304
155
|
end
|
|
305
156
|
|
|
306
157
|
def file_exists?(alias_name)
|
|
307
|
-
|
|
308
|
-
|
|
158
|
+
base_path = Rails.application.config.icon_path
|
|
159
|
+
File.exist?(Dir.glob(Rails.root.join(base_path, "**", "#{alias_name}.svg")).first)
|
|
309
160
|
end
|
|
310
161
|
|
|
311
162
|
def svg_size
|
|
@@ -532,7 +532,6 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
|
|
|
532
532
|
{requiredIndicator ? (
|
|
533
533
|
<Caption
|
|
534
534
|
className="pb_multi_level_select_kit_label"
|
|
535
|
-
color="lighter"
|
|
536
535
|
marginBottom="xs"
|
|
537
536
|
>
|
|
538
537
|
{label} <span className="required_indicator">*</span>
|
|
@@ -540,7 +539,6 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
|
|
|
540
539
|
) : (
|
|
541
540
|
<Caption
|
|
542
541
|
className="pb_multi_level_select_kit_label"
|
|
543
|
-
color="lighter"
|
|
544
542
|
marginBottom="xs"
|
|
545
543
|
text={label}
|
|
546
544
|
/>
|
|
@@ -39,4 +39,3 @@ examples:
|
|
|
39
39
|
- multi_level_select_disabled_options_parent: Disabled Parent Option (Return All Selected)
|
|
40
40
|
- multi_level_select_single_disabled: Disabled Options (Single Select)
|
|
41
41
|
- multi_level_select_required_indicator: Required Indicator
|
|
42
|
-
- multi_level_select_react_reset_key: Reset with Key (React)
|
|
@@ -17,4 +17,3 @@ export { default as MultiLevelSelectDisabledOptionsDefault } from "./_multi_leve
|
|
|
17
17
|
export { default as MultiLevelSelectLabel } from "./_multi_level_select_label.jsx"
|
|
18
18
|
export { default as MultiLevelSelectSingleDisabled } from "./_multi_level_select_single_disabled.jsx"
|
|
19
19
|
export { default as MultiLevelSelectRequiredIndicator } from "./_multi_level_select_required_indicator.jsx"
|
|
20
|
-
export { default as MultiLevelSelectReactResetKey } from "./_multi_level_select_react_reset_key.jsx"
|
|
@@ -73,9 +73,8 @@ $top_bottom_radius: 0px;
|
|
|
73
73
|
}
|
|
74
74
|
.disabled {
|
|
75
75
|
pointer-events: none;
|
|
76
|
-
cursor: not-allowed;
|
|
77
76
|
opacity: 0.5;
|
|
78
|
-
color:
|
|
77
|
+
color: grey;
|
|
79
78
|
|
|
80
79
|
& > span {
|
|
81
80
|
padding: $pagination_padding;
|
|
@@ -105,103 +104,4 @@ $top_bottom_radius: 0px;
|
|
|
105
104
|
border-right: none;
|
|
106
105
|
margin-left: $space_xxs;
|
|
107
106
|
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.pb_pagination_mobile {
|
|
111
|
-
padding: $space_xxs 0px !important;
|
|
112
|
-
position: relative;
|
|
113
|
-
|
|
114
|
-
.pagination-right {
|
|
115
|
-
border-left: none !important;
|
|
116
|
-
padding: 6px 13px !important;
|
|
117
|
-
flex-shrink: 0;
|
|
118
|
-
|
|
119
|
-
@media (max-width: 435px) {
|
|
120
|
-
padding: 6px 10px !important;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
.pagination-left {
|
|
124
|
-
border-right: none !important;
|
|
125
|
-
padding: 6px 13px !important;
|
|
126
|
-
flex-shrink: 0;
|
|
127
|
-
|
|
128
|
-
@media (max-width: 435px) {
|
|
129
|
-
padding: 6px 10px !important;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
.pagination-dropdown {
|
|
133
|
-
position: static;
|
|
134
|
-
flex: 1;
|
|
135
|
-
margin: 0 $space_xxs;
|
|
136
|
-
min-width: 0;
|
|
137
|
-
overflow: hidden;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.pagination-dropdown-trigger {
|
|
141
|
-
padding: 6px 10px 6px 13px !important;
|
|
142
|
-
background-color: $bg_light;
|
|
143
|
-
border-radius: $border_rad_light;
|
|
144
|
-
cursor: pointer;
|
|
145
|
-
transition: all 0.2s ease;
|
|
146
|
-
white-space: nowrap;
|
|
147
|
-
overflow: hidden;
|
|
148
|
-
|
|
149
|
-
&:hover {
|
|
150
|
-
background-color: $active_light;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@media (max-width: 435px) {
|
|
154
|
-
padding: 6px 8px 6px 10px !important;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.pagination-dropdown-menu {
|
|
159
|
-
position: absolute;
|
|
160
|
-
left: 0;
|
|
161
|
-
right: 0;
|
|
162
|
-
background-color: $white;
|
|
163
|
-
border: $border_rad_lightest solid $border_light;
|
|
164
|
-
border-radius: $border_rad_light;
|
|
165
|
-
box-shadow: $shadow_deep;
|
|
166
|
-
max-height: 200px;
|
|
167
|
-
overflow-y: auto;
|
|
168
|
-
z-index: 1000;
|
|
169
|
-
|
|
170
|
-
&.below {
|
|
171
|
-
top: 100%;
|
|
172
|
-
margin-top: $space_xs;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
&.above {
|
|
176
|
-
bottom: 100%;
|
|
177
|
-
margin-bottom: $space_xs;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.pagination-dropdown-option {
|
|
182
|
-
padding: $pagination_padding;
|
|
183
|
-
cursor: pointer;
|
|
184
|
-
transition: background-color 0.2s ease;
|
|
185
|
-
|
|
186
|
-
&:hover {
|
|
187
|
-
background-color: $active_light;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
&.active {
|
|
191
|
-
background-color: $primary;
|
|
192
|
-
.pb_body_kit {
|
|
193
|
-
color: $white;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
&:first-child {
|
|
198
|
-
border-top-left-radius: $border_rad_light;
|
|
199
|
-
border-top-right-radius: $border_rad_light;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
&:last-child {
|
|
203
|
-
border-bottom-left-radius: $border_rad_light;
|
|
204
|
-
border-bottom-right-radius: $border_rad_light;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
107
|
}
|
|
@@ -209,175 +209,4 @@ describe('Pagination Component', () => {
|
|
|
209
209
|
|
|
210
210
|
expect(screen.getByText('19')).toBeInTheDocument()
|
|
211
211
|
})
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
describe('Pagination Mobile View', () => {
|
|
215
|
-
let originalInnerWidth
|
|
216
|
-
|
|
217
|
-
beforeEach(() => {
|
|
218
|
-
// Store original value
|
|
219
|
-
originalInnerWidth = window.innerWidth
|
|
220
|
-
|
|
221
|
-
// Mock window.innerWidth for mobile
|
|
222
|
-
Object.defineProperty(window, 'innerWidth', {
|
|
223
|
-
writable: true,
|
|
224
|
-
configurable: true,
|
|
225
|
-
value: 767
|
|
226
|
-
})
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
afterEach(() => {
|
|
230
|
-
// Restore original value
|
|
231
|
-
Object.defineProperty(window, 'innerWidth', {
|
|
232
|
-
writable: true,
|
|
233
|
-
configurable: true,
|
|
234
|
-
value: originalInnerWidth
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
test('renders mobile layout on small screens', () => {
|
|
239
|
-
render(<Pagination {...defaultProps} />)
|
|
240
|
-
|
|
241
|
-
const mobilePagination = document.querySelector('.pb_pagination_mobile')
|
|
242
|
-
expect(mobilePagination).toBeInTheDocument()
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
test('renders Prev and Next buttons in mobile view', () => {
|
|
246
|
-
render(<Pagination {...defaultProps} />)
|
|
247
|
-
|
|
248
|
-
expect(screen.getByText('Prev')).toBeInTheDocument()
|
|
249
|
-
expect(screen.getByText('Next')).toBeInTheDocument()
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
test('displays current page and total in dropdown trigger', () => {
|
|
253
|
-
render(<Pagination {...defaultProps}
|
|
254
|
-
current={3}
|
|
255
|
-
total={10}
|
|
256
|
-
/>)
|
|
257
|
-
|
|
258
|
-
expect(screen.getByText('3', { exact: false })).toBeInTheDocument()
|
|
259
|
-
expect(screen.getByText(/of 10/)).toBeInTheDocument()
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test('opens dropdown when trigger is clicked', () => {
|
|
263
|
-
render(<Pagination {...defaultProps} />)
|
|
264
|
-
|
|
265
|
-
const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
|
|
266
|
-
expect(dropdownTrigger).toBeInTheDocument()
|
|
267
|
-
|
|
268
|
-
let dropdownMenu = document.querySelector('.pagination-dropdown-menu')
|
|
269
|
-
expect(dropdownMenu).not.toBeInTheDocument()
|
|
270
|
-
|
|
271
|
-
fireEvent.click(dropdownTrigger)
|
|
272
|
-
|
|
273
|
-
dropdownMenu = document.querySelector('.pagination-dropdown-menu')
|
|
274
|
-
expect(dropdownMenu).toBeInTheDocument()
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
test('displays all page options in dropdown', () => {
|
|
278
|
-
render(<Pagination {...defaultProps}
|
|
279
|
-
total={5}
|
|
280
|
-
/>)
|
|
281
|
-
|
|
282
|
-
const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
|
|
283
|
-
fireEvent.click(dropdownTrigger)
|
|
284
|
-
|
|
285
|
-
expect(screen.getByText('Page 1')).toBeInTheDocument()
|
|
286
|
-
expect(screen.getByText('Page 2')).toBeInTheDocument()
|
|
287
|
-
expect(screen.getByText('Page 3')).toBeInTheDocument()
|
|
288
|
-
expect(screen.getByText('Page 4')).toBeInTheDocument()
|
|
289
|
-
expect(screen.getByText('Page 5')).toBeInTheDocument()
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
test('highlights current page in dropdown', () => {
|
|
293
|
-
render(<Pagination {...defaultProps}
|
|
294
|
-
current={3}
|
|
295
|
-
total={5}
|
|
296
|
-
/>)
|
|
297
|
-
|
|
298
|
-
const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
|
|
299
|
-
fireEvent.click(dropdownTrigger)
|
|
300
|
-
|
|
301
|
-
const activePage = screen.getByText('Page 3').parentElement
|
|
302
|
-
expect(activePage).toHaveClass('active')
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
test('changes page when dropdown option is clicked', () => {
|
|
306
|
-
const mockOnChange = jest.fn()
|
|
307
|
-
render(<Pagination {...defaultProps}
|
|
308
|
-
onChange={mockOnChange}
|
|
309
|
-
total={5}
|
|
310
|
-
/>)
|
|
311
|
-
|
|
312
|
-
const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
|
|
313
|
-
fireEvent.click(dropdownTrigger)
|
|
314
|
-
|
|
315
|
-
const page3Option = screen.getByText('Page 3')
|
|
316
|
-
fireEvent.click(page3Option)
|
|
317
|
-
|
|
318
|
-
expect(mockOnChange).toHaveBeenCalledWith(3)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
test('closes dropdown after selecting a page', () => {
|
|
322
|
-
render(<Pagination {...defaultProps}
|
|
323
|
-
total={5}
|
|
324
|
-
/>)
|
|
325
|
-
|
|
326
|
-
const dropdownTrigger = document.querySelector('.pagination-dropdown-trigger')
|
|
327
|
-
fireEvent.click(dropdownTrigger)
|
|
328
|
-
|
|
329
|
-
let dropdownMenu = document.querySelector('.pagination-dropdown-menu')
|
|
330
|
-
expect(dropdownMenu).toBeInTheDocument()
|
|
331
|
-
|
|
332
|
-
const page3Option = screen.getByText('Page 3')
|
|
333
|
-
fireEvent.click(page3Option)
|
|
334
|
-
|
|
335
|
-
dropdownMenu = document.querySelector('.pagination-dropdown-menu')
|
|
336
|
-
expect(dropdownMenu).not.toBeInTheDocument()
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
test('Prev button navigates to previous page', () => {
|
|
340
|
-
const mockOnChange = jest.fn()
|
|
341
|
-
render(<Pagination {...defaultProps}
|
|
342
|
-
current={3}
|
|
343
|
-
onChange={mockOnChange}
|
|
344
|
-
/>)
|
|
345
|
-
|
|
346
|
-
const prevButton = document.querySelector('.pagination-left')
|
|
347
|
-
fireEvent.click(prevButton)
|
|
348
|
-
|
|
349
|
-
expect(mockOnChange).toHaveBeenCalledWith(2)
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
test('Next button navigates to next page', () => {
|
|
353
|
-
const mockOnChange = jest.fn()
|
|
354
|
-
render(<Pagination {...defaultProps}
|
|
355
|
-
current={3}
|
|
356
|
-
onChange={mockOnChange}
|
|
357
|
-
/>)
|
|
358
|
-
|
|
359
|
-
const nextButton = document.querySelector('.pagination-right')
|
|
360
|
-
fireEvent.click(nextButton)
|
|
361
|
-
|
|
362
|
-
expect(mockOnChange).toHaveBeenCalledWith(4)
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
test('disables Prev button on first page', () => {
|
|
366
|
-
render(<Pagination {...defaultProps}
|
|
367
|
-
current={1}
|
|
368
|
-
/>)
|
|
369
|
-
|
|
370
|
-
const prevButton = document.querySelector('.pagination-left')
|
|
371
|
-
expect(prevButton).toHaveClass('disabled')
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
test('disables Next button on last page', () => {
|
|
375
|
-
render(<Pagination {...defaultProps}
|
|
376
|
-
current={10}
|
|
377
|
-
/>)
|
|
378
|
-
|
|
379
|
-
const nextButton = document.querySelector('.pagination-right')
|
|
380
|
-
expect(nextButton).toHaveClass('disabled')
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
})
|
|
212
|
+
})
|