ariadne_view_components 0.0.94.3 → 0.0.94.5
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/CHANGELOG.md +12 -0
- data/app/assets/javascripts/ariadne_view_components.js +9 -9
- data/app/assets/javascripts/ariadne_view_components.js.br +0 -0
- data/app/assets/javascripts/ariadne_view_components.js.gz +0 -0
- data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
- data/app/assets/stylesheets/ariadne_view_components.css +1 -1
- data/app/assets/stylesheets/ariadne_view_components.css.br +0 -0
- data/app/assets/stylesheets/ariadne_view_components.css.gz +0 -0
- data/app/components/ariadne/behaviors/tooltipable.rb +11 -6
- data/app/components/ariadne/ui/pagination/component.html.erb +46 -26
- data/app/components/ariadne/ui/pagination/component.rb +104 -11
- data/app/frontend/controllers/pagination_controller.ts +42 -0
- data/app/frontend/controllers/tooltip_controller.ts +24 -40
- data/app/lib/ariadne/pagination_calculator.rb +78 -0
- data/config/initializers/pagy.rb +15 -0
- data/lib/ariadne/view_components/version.rb +1 -1
- metadata +5 -2
@@ -6,23 +6,116 @@ module Ariadne
|
|
6
6
|
module Pagination
|
7
7
|
# Pagination is a horizontal set of links to navigate paginated content.
|
8
8
|
class Component < Ariadne::BaseComponent
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
# Required props:
|
10
|
+
# - total_count: Total number of items to paginate.
|
11
|
+
# - current_page: Current page number.
|
12
|
+
# - items_per_page: Number of items per page.
|
13
|
+
# - label: Main label for screen readers (e.g., "Pagination").
|
14
|
+
# - goto_label: Label prefix for page links (e.g., "Go to page").
|
15
|
+
# - callback_page_url: Lambda that receives a page number and returns the URL.
|
16
|
+
# - callback_per_page_change: Lambda that receives a per_page value and returns the URL.
|
17
|
+
# - per_page_options: Array of allowed page sizes (e.g., [10, 25, 50, 100]).
|
18
|
+
# - frame_id: Turbo frame identifier (if applicable).
|
19
|
+
# - show_results_counter: Boolean indicating whether to display the results counter.
|
14
20
|
|
21
|
+
# Use dry-initializer for props
|
22
|
+
option :total_count
|
23
|
+
option :current_page, default: proc { 1 }
|
24
|
+
option :items_per_page, default: proc { 10 }
|
25
|
+
option :label, default: proc { I18n.t("pagy.aria_label.nav", default: "Pagination") }
|
26
|
+
option :goto_label, default: proc { I18n.t("pagy.goto_label", default: "Go to page") }
|
15
27
|
option :callback_page_url
|
28
|
+
option :callback_per_page_change
|
29
|
+
option :per_page_options
|
30
|
+
option :frame_id
|
31
|
+
option :show_results_counter, default: proc { true }
|
32
|
+
|
33
|
+
def initialize(**options)
|
34
|
+
super
|
35
|
+
@calculator = Ariadne::PaginationCalculator.new(
|
36
|
+
total_count: total_count,
|
37
|
+
page: current_page,
|
38
|
+
items_per_page: items_per_page,
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :calculator
|
16
45
|
|
17
46
|
def page_links
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
47
|
+
calculator.page_series
|
48
|
+
end
|
49
|
+
|
50
|
+
def component_classes
|
51
|
+
merge_tailwind_classes(
|
52
|
+
"ariadne-flex ariadne-items-center ariadne-justify-between ariadne-w-full",
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def link_classes(is_active = false)
|
57
|
+
base_classes = "ariadne-inline-flex ariadne-items-center ariadne-justify-center ariadne-min-w-[2rem] ariadne-h-8 ariadne-px-2 ariadne-text-sm ariadne-font-medium ariadne-rounded-md"
|
58
|
+
|
59
|
+
active_classes = if is_active
|
60
|
+
"ariadne-bg-gray-100 dark:ariadne-bg-gray-700 ariadne-text-gray-900 dark:ariadne-text-white ariadne-cursor-default"
|
61
|
+
else
|
62
|
+
"ariadne-transition-colors ariadne-text-gray-600 hover:ariadne-bg-gray-50 dark:ariadne-text-gray-300 dark:hover:ariadne-bg-gray-700 ariadne-cursor-pointer"
|
63
|
+
end
|
64
|
+
|
65
|
+
merge_tailwind_classes([base_classes, active_classes])
|
66
|
+
end
|
67
|
+
|
68
|
+
def disabled_link_classes
|
69
|
+
merge_tailwind_classes(
|
70
|
+
"ariadne-inline-flex ariadne-items-center ariadne-justify-center ariadne-min-w-[2rem] ariadne-h-8 ariadne-px-2 ariadne-text-sm ariadne-font-medium ariadne-rounded-md ariadne-text-gray-300 dark:ariadne-text-gray-600 ariadne-cursor-not-allowed",
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def ellipsis_classes
|
75
|
+
merge_tailwind_classes(
|
76
|
+
"ariadne-inline-flex ariadne-items-center ariadne-justify-center ariadne-min-w-[2rem] ariadne-h-8 ariadne-px-1 ariadne-text-sm ariadne-font-medium ariadne-text-gray-500 dark:ariadne-text-gray-400",
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def select_classes
|
81
|
+
merge_tailwind_classes(
|
82
|
+
"ariadne-h-8 ariadne-pl-2 ariadne-pr-8 ariadne-text-sm ariadne-font-medium ariadne-rounded-md ariadne-border ariadne-border-gray-300 dark:ariadne-border-gray-600 ariadne-bg-white dark:ariadne-bg-gray-800 ariadne-text-gray-700 dark:ariadne-text-gray-200 hover:ariadne-border-primary-500 focus:ariadne-outline-none focus:ariadne-ring-1 focus:ariadne-ring-primary-500 focus:ariadne-border-primary-500",
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def counter_classes
|
87
|
+
merge_tailwind_classes(
|
88
|
+
"ariadne-text-sm ariadne-text-gray-700 dark:ariadne-text-gray-300 ariadne-font-medium",
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def formatted_info
|
93
|
+
{
|
94
|
+
text: "#{calculator.from}-#{calculator.to} of #{total_count}",
|
95
|
+
role: "status",
|
96
|
+
aria: { live: "polite" },
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def select_options
|
101
|
+
per_page_options.map do |size|
|
102
|
+
{
|
103
|
+
value: callback_per_page_change.call(size),
|
104
|
+
label: "#{size} per page",
|
105
|
+
selected: size == items_per_page,
|
106
|
+
}
|
24
107
|
end
|
25
108
|
end
|
109
|
+
|
110
|
+
def current_per_page_option
|
111
|
+
select_options.find { |option| option[:selected] } || select_options.first
|
112
|
+
end
|
113
|
+
|
114
|
+
def nav_section_classes
|
115
|
+
merge_tailwind_classes(
|
116
|
+
"ariadne-flex ariadne-items-center ariadne-gap-1 ariadne-justify-center",
|
117
|
+
)
|
118
|
+
end
|
26
119
|
end
|
27
120
|
end
|
28
121
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import {controllerFactory} from '@utils/createController'
|
2
|
+
|
3
|
+
export default class PaginationController extends controllerFactory<HTMLElement>()({
|
4
|
+
targets: {
|
5
|
+
perPage: HTMLSelectElement,
|
6
|
+
},
|
7
|
+
}) {
|
8
|
+
connect(): void {
|
9
|
+
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Handles changes to the per-page selector
|
14
|
+
*/
|
15
|
+
perPageChange(event: Event): void {
|
16
|
+
event.preventDefault()
|
17
|
+
const select = event.target as HTMLSelectElement
|
18
|
+
const value = select.value
|
19
|
+
|
20
|
+
// Get the current URL and update it
|
21
|
+
const url = new URL(window.location.href)
|
22
|
+
const newParams = new URLSearchParams(value.split('?')[1])
|
23
|
+
|
24
|
+
// Update the URL with new parameters
|
25
|
+
newParams.forEach((value, key) => {
|
26
|
+
url.searchParams.set(key, value)
|
27
|
+
})
|
28
|
+
|
29
|
+
window.location.href = url.toString()
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Handles clicks on pagination links
|
34
|
+
*/
|
35
|
+
navigate(event: Event): void {
|
36
|
+
const link = (event.target as HTMLElement).closest('a')
|
37
|
+
if (!link) return
|
38
|
+
|
39
|
+
event.preventDefault()
|
40
|
+
window.location.href = link.href
|
41
|
+
}
|
42
|
+
}
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import {controllerFactory} from '@utils/createController'
|
2
|
-
import {computePosition, arrow, offset, flip} from '@floating-ui/dom'
|
3
|
-
|
2
|
+
import {computePosition, arrow, offset, flip, Placement} from '@floating-ui/dom'
|
4
3
|
|
5
4
|
const TRIANGLE_HEIGHT = 6
|
6
5
|
const DEFAULT_OFFSET = 4;
|
@@ -15,53 +14,38 @@ export default class TooltipController extends controllerFactory<HTMLElement>()(
|
|
15
14
|
},
|
16
15
|
}) {
|
17
16
|
async update() {
|
17
|
+
const placement = this.element.getAttribute('data-placement') as Placement || 'top'
|
18
|
+
|
18
19
|
computePosition(this.activatorTarget, this.tooltipTarget, {
|
19
|
-
placement
|
20
|
-
middleware: [
|
21
|
-
|
20
|
+
placement,
|
21
|
+
middleware: [
|
22
|
+
flip({ padding: 12 }),
|
23
|
+
offset(TRIANGLE_HEIGHT + DEFAULT_OFFSET),
|
24
|
+
arrow({ element: this.arrowTarget })
|
25
|
+
],
|
22
26
|
}).then(({x, y, placement, middlewareData}) => {
|
23
27
|
Object.assign(this.tooltipTarget.style, {
|
24
|
-
left:
|
28
|
+
left: `${x}px`,
|
25
29
|
top: `${y}px`,
|
26
30
|
})
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
const {x: arrowX, y: arrowY} = middlewareData.arrow || {}
|
33
|
+
const staticSide = {
|
34
|
+
top: 'bottom',
|
35
|
+
right: 'left',
|
36
|
+
bottom: 'top',
|
37
|
+
left: 'right',
|
38
|
+
}[placement.split('-')[0]] as string
|
39
|
+
|
40
|
+
const arrowStyles: Partial<CSSStyleDeclaration> = {
|
41
|
+
left: arrowX != null ? `${arrowX}px` : '',
|
42
|
+
top: arrowY != null ? `${arrowY}px` : '',
|
32
43
|
right: '',
|
33
44
|
bottom: '',
|
34
|
-
})
|
35
|
-
|
36
|
-
const {x: arrowX, y: arrowY} = middlewareData.arrow || {}
|
37
|
-
const primaryPlacement = placement.split('-')[0]
|
38
|
-
|
39
|
-
switch (primaryPlacement) {
|
40
|
-
case 'top':
|
41
|
-
Object.assign(this.arrowTarget.style, {
|
42
|
-
left: arrowX ? `${arrowX}px` : '',
|
43
|
-
bottom: '-4px', // Aligns arrow to the bottom edge of the tooltip
|
44
|
-
})
|
45
|
-
break
|
46
|
-
case 'bottom':
|
47
|
-
Object.assign(this.arrowTarget.style, {
|
48
|
-
left: arrowX ? `${arrowX}px` : '',
|
49
|
-
top: '-4px', // Aligns arrow to the top edge of the tooltip
|
50
|
-
})
|
51
|
-
break
|
52
|
-
case 'left':
|
53
|
-
Object.assign(this.arrowTarget.style, {
|
54
|
-
top: arrowY ? `${arrowY}px` : '',
|
55
|
-
right: '-4px', // Aligns arrow to the right edge of the tooltip
|
56
|
-
})
|
57
|
-
break
|
58
|
-
case 'right':
|
59
|
-
Object.assign(this.arrowTarget.style, {
|
60
|
-
top: arrowY ? `${arrowY}px` : '',
|
61
|
-
left: '-4px', // Aligns arrow to the left edge of the tooltip
|
62
|
-
})
|
63
|
-
break
|
64
45
|
}
|
46
|
+
arrowStyles[staticSide as 'top' | 'right' | 'bottom' | 'left'] = '-4px'
|
47
|
+
|
48
|
+
Object.assign(this.arrowTarget.style, arrowStyles)
|
65
49
|
})
|
66
50
|
}
|
67
51
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Ariadne
|
5
|
+
# Handles pagination calculations and data structure
|
6
|
+
class PaginationCalculator
|
7
|
+
attr_reader :total_count, :page, :items_per_page, :pages
|
8
|
+
|
9
|
+
def initialize(total_count:, page: 1, items_per_page: 10)
|
10
|
+
@total_count = total_count
|
11
|
+
@items_per_page = items_per_page
|
12
|
+
@pages = calculate_total_pages
|
13
|
+
@page = handle_page_overflow(page)
|
14
|
+
end
|
15
|
+
|
16
|
+
def from
|
17
|
+
return 0 if total_count.zero?
|
18
|
+
|
19
|
+
(page - 1) * items_per_page + 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def to
|
23
|
+
return 0 if total_count.zero?
|
24
|
+
|
25
|
+
[page * items_per_page, total_count].min
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_page
|
29
|
+
page < pages ? page + 1 : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def prev_page
|
33
|
+
page > 1 ? page - 1 : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def page_series
|
37
|
+
return [] if pages <= 1
|
38
|
+
return (1..pages).to_a if pages <= 5
|
39
|
+
|
40
|
+
links = []
|
41
|
+
links << 1
|
42
|
+
|
43
|
+
if page <= 3
|
44
|
+
# Near the beginning: 1 2 3 4 5 6 7 8 ... 20
|
45
|
+
(2..8).each { |n| links << n if n <= pages }
|
46
|
+
links << nil if pages > 8
|
47
|
+
links << pages if pages > 8
|
48
|
+
elsif page >= pages - 2
|
49
|
+
# Near the end: 1 ... 13 14 15 16 17 18 19 20
|
50
|
+
links << nil
|
51
|
+
((pages - 7)..pages).each { |n| links << n if n > 1 }
|
52
|
+
else
|
53
|
+
# Middle range: 1 ... 9 10 11 12 13 14 ... 20
|
54
|
+
links << nil
|
55
|
+
((page - 2)..(page + 2)).each { |n| links << n }
|
56
|
+
links << nil
|
57
|
+
links << pages
|
58
|
+
end
|
59
|
+
|
60
|
+
links
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def calculate_total_pages
|
66
|
+
return 1 if total_count.zero?
|
67
|
+
|
68
|
+
(total_count.to_f / items_per_page).ceil
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_page_overflow(requested_page)
|
72
|
+
return 1 if requested_page < 1
|
73
|
+
return pages if requested_page > pages
|
74
|
+
|
75
|
+
requested_page
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pagy"
|
4
|
+
|
5
|
+
# Only require the necessary extras:
|
6
|
+
require "pagy/extras/overflow" # Handles overflow scenarios gracefully.
|
7
|
+
require "pagy/extras/metadata" # Provides metadata for JSON/client-side rendering.
|
8
|
+
require "pagy/extras/headers" # Useful for API responses (if needed).
|
9
|
+
require "pagy/extras/keyset" # For efficient keyset pagination with large data sets.
|
10
|
+
require "pagy/extras/array" # Required for series method
|
11
|
+
|
12
|
+
# Customize default settings:
|
13
|
+
Pagy::DEFAULT[:limit] = 10 # Default items per page.
|
14
|
+
Pagy::DEFAULT[:size] = 9 # Number of links in the nav bar.
|
15
|
+
Pagy::DEFAULT[:overflow] = :last_page
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ariadne_view_components
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.94.
|
4
|
+
version: 0.0.94.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garen J. Torikian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tailwind_merge
|
@@ -265,6 +265,7 @@ files:
|
|
265
265
|
- app/frontend/ariadne/theme.ts
|
266
266
|
- app/frontend/controllers/form_autosubmit_controller.ts
|
267
267
|
- app/frontend/controllers/form_validity_controller.ts
|
268
|
+
- app/frontend/controllers/pagination_controller.ts
|
268
269
|
- app/frontend/controllers/tooltip_controller.ts
|
269
270
|
- app/frontend/entrypoints/application.ts
|
270
271
|
- app/frontend/stylesheets/ariadne_view_components.css
|
@@ -281,9 +282,11 @@ files:
|
|
281
282
|
- app/lib/ariadne/form.rb
|
282
283
|
- app/lib/ariadne/icon_helper.rb
|
283
284
|
- app/lib/ariadne/logger_helper.rb
|
285
|
+
- app/lib/ariadne/pagination_calculator.rb
|
284
286
|
- app/lib/ariadne/view_component/html_attrs.rb
|
285
287
|
- app/lib/ariadne/view_component/style_variants.rb
|
286
288
|
- app/lib/ariadne/view_helper.rb
|
289
|
+
- config/initializers/pagy.rb
|
287
290
|
- lib/ariadne/accessibility.rb
|
288
291
|
- lib/ariadne/forms/acts_as_component.rb
|
289
292
|
- lib/ariadne/forms/base.html.erb
|