rails_claude_skills 0.1.0
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 +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- metadata +199 -0
data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# Custom Themes for Kaminari
|
|
2
|
+
|
|
3
|
+
Guide to creating and customizing pagination themes for different UI frameworks and design systems.
|
|
4
|
+
|
|
5
|
+
## Generating Theme Templates
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Generate default theme to customize
|
|
9
|
+
rails g kaminari:views default
|
|
10
|
+
|
|
11
|
+
# Generate with custom namespace
|
|
12
|
+
rails g kaminari:views default --views-prefix admin
|
|
13
|
+
|
|
14
|
+
# Generate Bootstrap 4 theme
|
|
15
|
+
rails g kaminari:views bootstrap4
|
|
16
|
+
|
|
17
|
+
# Generate Bootstrap 5 theme
|
|
18
|
+
rails g kaminari:views bootstrap5
|
|
19
|
+
|
|
20
|
+
# Generate Tailwind theme
|
|
21
|
+
rails g kaminari:views tailwind
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Template Files Structure
|
|
25
|
+
|
|
26
|
+
Generated templates appear in `app/views/kaminari/`:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
app/views/kaminari/
|
|
30
|
+
├── _paginator.html.erb # Main wrapper
|
|
31
|
+
├── _first_page.html.erb # First page link
|
|
32
|
+
├── _prev_page.html.erb # Previous page link
|
|
33
|
+
├── _page.html.erb # Regular page link
|
|
34
|
+
├── _next_page.html.erb # Next page link
|
|
35
|
+
├── _last_page.html.erb # Last page link
|
|
36
|
+
└── _gap.html.erb # Ellipsis/gap between pages
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Creating a Custom Theme
|
|
40
|
+
|
|
41
|
+
### Tailwind CSS Theme
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Create theme directory
|
|
45
|
+
mkdir -p app/views/kaminari/tailwind
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```erb
|
|
49
|
+
<!-- app/views/kaminari/tailwind/_paginator.html.erb -->
|
|
50
|
+
<nav aria-label="Page navigation" class="flex justify-center my-8">
|
|
51
|
+
<ul class="flex space-x-2">
|
|
52
|
+
<%= first_page_tag unless current_page.first? %>
|
|
53
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
54
|
+
|
|
55
|
+
<% each_page do |page| %>
|
|
56
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
|
57
|
+
<%= page_tag page %>
|
|
58
|
+
<% elsif !page.was_truncated? -%>
|
|
59
|
+
<%= gap_tag %>
|
|
60
|
+
<% end %>
|
|
61
|
+
<% end %>
|
|
62
|
+
|
|
63
|
+
<%= next_page_tag unless current_page.last? %>
|
|
64
|
+
<%= last_page_tag unless current_page.last? %>
|
|
65
|
+
</ul>
|
|
66
|
+
</nav>
|
|
67
|
+
|
|
68
|
+
<!-- app/views/kaminari/tailwind/_page.html.erb -->
|
|
69
|
+
<% if page.current? %>
|
|
70
|
+
<li>
|
|
71
|
+
<span class="px-3 py-2 text-sm font-medium text-white bg-blue-600 rounded-md">
|
|
72
|
+
<%= page %>
|
|
73
|
+
</span>
|
|
74
|
+
</li>
|
|
75
|
+
<% else %>
|
|
76
|
+
<li>
|
|
77
|
+
<%= link_to page, url, remote: remote, rel: page.rel, class: 'px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50' %>
|
|
78
|
+
</li>
|
|
79
|
+
<% end %>
|
|
80
|
+
|
|
81
|
+
<!-- app/views/kaminari/tailwind/_first_page.html.erb -->
|
|
82
|
+
<li>
|
|
83
|
+
<%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote, class: 'px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50' %>
|
|
84
|
+
</li>
|
|
85
|
+
|
|
86
|
+
<!-- app/views/kaminari/tailwind/_prev_page.html.erb -->
|
|
87
|
+
<li>
|
|
88
|
+
<%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote, class: 'px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50' %>
|
|
89
|
+
</li>
|
|
90
|
+
|
|
91
|
+
<!-- app/views/kaminari/tailwind/_next_page.html.erb -->
|
|
92
|
+
<li>
|
|
93
|
+
<%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote, class: 'px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50' %>
|
|
94
|
+
</li>
|
|
95
|
+
|
|
96
|
+
<!-- app/views/kaminari/tailwind/_last_page.html.erb -->
|
|
97
|
+
<li>
|
|
98
|
+
<%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote, class: 'px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50' %>
|
|
99
|
+
</li>
|
|
100
|
+
|
|
101
|
+
<!-- app/views/kaminari/tailwind/_gap.html.erb -->
|
|
102
|
+
<li>
|
|
103
|
+
<span class="px-3 py-2 text-sm font-medium text-gray-700">
|
|
104
|
+
<%= t('views.pagination.truncate').html_safe %>
|
|
105
|
+
</span>
|
|
106
|
+
</li>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Using the Theme
|
|
110
|
+
|
|
111
|
+
```erb
|
|
112
|
+
<%= paginate @posts, theme: 'tailwind' %>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Bootstrap 5 Theme
|
|
116
|
+
|
|
117
|
+
```erb
|
|
118
|
+
<!-- app/views/kaminari/bootstrap5/_paginator.html.erb -->
|
|
119
|
+
<nav aria-label="Page navigation">
|
|
120
|
+
<ul class="pagination justify-content-center">
|
|
121
|
+
<%= first_page_tag unless current_page.first? %>
|
|
122
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
123
|
+
|
|
124
|
+
<% each_page do |page| %>
|
|
125
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
|
126
|
+
<%= page_tag page %>
|
|
127
|
+
<% elsif !page.was_truncated? -%>
|
|
128
|
+
<%= gap_tag %>
|
|
129
|
+
<% end %>
|
|
130
|
+
<% end %>
|
|
131
|
+
|
|
132
|
+
<%= next_page_tag unless current_page.last? %>
|
|
133
|
+
<%= last_page_tag unless current_page.last? %>
|
|
134
|
+
</ul>
|
|
135
|
+
</nav>
|
|
136
|
+
|
|
137
|
+
<!-- app/views/kaminari/bootstrap5/_page.html.erb -->
|
|
138
|
+
<li class="page-item <%= 'active' if page.current? %>">
|
|
139
|
+
<%= link_to page, url, remote: remote, rel: page.rel, class: 'page-link' %>
|
|
140
|
+
</li>
|
|
141
|
+
|
|
142
|
+
<!-- app/views/kaminari/bootstrap5/_first_page.html.erb -->
|
|
143
|
+
<li class="page-item">
|
|
144
|
+
<%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' %>
|
|
145
|
+
</li>
|
|
146
|
+
|
|
147
|
+
<!-- app/views/kaminari/bootstrap5/_prev_page.html.erb -->
|
|
148
|
+
<li class="page-item">
|
|
149
|
+
<%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link' %>
|
|
150
|
+
</li>
|
|
151
|
+
|
|
152
|
+
<!-- app/views/kaminari/bootstrap5/_next_page.html.erb -->
|
|
153
|
+
<li class="page-item">
|
|
154
|
+
<%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link' %>
|
|
155
|
+
</li>
|
|
156
|
+
|
|
157
|
+
<!-- app/views/kaminari/bootstrap5/_last_page.html.erb -->
|
|
158
|
+
<li class="page-item">
|
|
159
|
+
<%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link' %>
|
|
160
|
+
</li>
|
|
161
|
+
|
|
162
|
+
<!-- app/views/kaminari/bootstrap5/_gap.html.erb -->
|
|
163
|
+
<li class="page-item disabled">
|
|
164
|
+
<span class="page-link"><%= raw(t 'views.pagination.truncate') %></span>
|
|
165
|
+
</li>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Bulma CSS Theme
|
|
169
|
+
|
|
170
|
+
```erb
|
|
171
|
+
<!-- app/views/kaminari/bulma/_paginator.html.erb -->
|
|
172
|
+
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
|
173
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
174
|
+
<%= next_page_tag unless current_page.last? %>
|
|
175
|
+
|
|
176
|
+
<ul class="pagination-list">
|
|
177
|
+
<%= first_page_tag unless current_page.first? %>
|
|
178
|
+
|
|
179
|
+
<% each_page do |page| %>
|
|
180
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
|
181
|
+
<%= page_tag page %>
|
|
182
|
+
<% elsif !page.was_truncated? -%>
|
|
183
|
+
<%= gap_tag %>
|
|
184
|
+
<% end %>
|
|
185
|
+
<% end %>
|
|
186
|
+
|
|
187
|
+
<%= last_page_tag unless current_page.last? %>
|
|
188
|
+
</ul>
|
|
189
|
+
</nav>
|
|
190
|
+
|
|
191
|
+
<!-- app/views/kaminari/bulma/_page.html.erb -->
|
|
192
|
+
<li>
|
|
193
|
+
<% if page.current? %>
|
|
194
|
+
<a class="pagination-link is-current" aria-current="page"><%= page %></a>
|
|
195
|
+
<% else %>
|
|
196
|
+
<%= link_to page, url, remote: remote, rel: page.rel, class: 'pagination-link' %>
|
|
197
|
+
<% end %>
|
|
198
|
+
</li>
|
|
199
|
+
|
|
200
|
+
<!-- app/views/kaminari/bulma/_prev_page.html.erb -->
|
|
201
|
+
<%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'pagination-previous' %>
|
|
202
|
+
|
|
203
|
+
<!-- app/views/kaminari/bulma/_next_page.html.erb -->
|
|
204
|
+
<%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'pagination-next' %>
|
|
205
|
+
|
|
206
|
+
<!-- app/views/kaminari/bulma/_gap.html.erb -->
|
|
207
|
+
<li>
|
|
208
|
+
<span class="pagination-ellipsis">…</span>
|
|
209
|
+
</li>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Foundation Theme
|
|
213
|
+
|
|
214
|
+
```erb
|
|
215
|
+
<!-- app/views/kaminari/foundation/_paginator.html.erb -->
|
|
216
|
+
<ul class="pagination text-center" role="navigation" aria-label="Pagination">
|
|
217
|
+
<%= first_page_tag unless current_page.first? %>
|
|
218
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
219
|
+
|
|
220
|
+
<% each_page do |page| %>
|
|
221
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
|
222
|
+
<%= page_tag page %>
|
|
223
|
+
<% elsif !page.was_truncated? -%>
|
|
224
|
+
<%= gap_tag %>
|
|
225
|
+
<% end %>
|
|
226
|
+
<% end %>
|
|
227
|
+
|
|
228
|
+
<%= next_page_tag unless current_page.last? %>
|
|
229
|
+
<%= last_page_tag unless current_page.last? %>
|
|
230
|
+
</ul>
|
|
231
|
+
|
|
232
|
+
<!-- app/views/kaminari/foundation/_page.html.erb -->
|
|
233
|
+
<li class="<%= 'current' if page.current? %>">
|
|
234
|
+
<%= link_to_unless page.current?, page, url, remote: remote, rel: page.rel %>
|
|
235
|
+
</li>
|
|
236
|
+
|
|
237
|
+
<!-- app/views/kaminari/foundation/_gap.html.erb -->
|
|
238
|
+
<li class="ellipsis" aria-hidden="true"></li>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Minimal/Simple Theme
|
|
242
|
+
|
|
243
|
+
For projects that want simple text-based pagination:
|
|
244
|
+
|
|
245
|
+
```erb
|
|
246
|
+
<!-- app/views/kaminari/simple/_paginator.html.erb -->
|
|
247
|
+
<div class="simple-pagination">
|
|
248
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
249
|
+
<span class="page-info">
|
|
250
|
+
Page <%= current_page %> of <%= total_pages %>
|
|
251
|
+
</span>
|
|
252
|
+
<%= next_page_tag unless current_page.last? %>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<!-- app/views/kaminari/simple/_prev_page.html.erb -->
|
|
256
|
+
<%= link_to_unless current_page.first?, '← Previous', url, rel: 'prev', remote: remote, class: 'prev-page' %>
|
|
257
|
+
|
|
258
|
+
<!-- app/views/kaminari/simple/_next_page.html.erb -->
|
|
259
|
+
<%= link_to_unless current_page.last?, 'Next →', url, rel: 'next', remote: remote, class: 'next-page' %>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```css
|
|
263
|
+
/* app/assets/stylesheets/pagination.css */
|
|
264
|
+
.simple-pagination {
|
|
265
|
+
display: flex;
|
|
266
|
+
justify-content: space-between;
|
|
267
|
+
align-items: center;
|
|
268
|
+
padding: 1rem 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.page-info {
|
|
272
|
+
color: #666;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.prev-page,
|
|
276
|
+
.next-page {
|
|
277
|
+
padding: 0.5rem 1rem;
|
|
278
|
+
text-decoration: none;
|
|
279
|
+
color: #007bff;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.prev-page:hover,
|
|
283
|
+
.next-page:hover {
|
|
284
|
+
text-decoration: underline;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Icon-Based Navigation
|
|
289
|
+
|
|
290
|
+
Using Font Awesome or other icon libraries:
|
|
291
|
+
|
|
292
|
+
```erb
|
|
293
|
+
<!-- app/views/kaminari/icons/_prev_page.html.erb -->
|
|
294
|
+
<li>
|
|
295
|
+
<%= link_to_unless current_page.first?, url, rel: 'prev', remote: remote, class: 'page-link' do %>
|
|
296
|
+
<i class="fas fa-chevron-left"></i>
|
|
297
|
+
<span class="sr-only">Previous</span>
|
|
298
|
+
<% end %>
|
|
299
|
+
</li>
|
|
300
|
+
|
|
301
|
+
<!-- app/views/kaminari/icons/_next_page.html.erb -->
|
|
302
|
+
<li>
|
|
303
|
+
<%= link_to_unless current_page.last?, url, rel: 'next', remote: remote, class: 'page-link' do %>
|
|
304
|
+
<span class="sr-only">Next</span>
|
|
305
|
+
<i class="fas fa-chevron-right"></i>
|
|
306
|
+
<% end %>
|
|
307
|
+
</li>
|
|
308
|
+
|
|
309
|
+
<!-- app/views/kaminari/icons/_first_page.html.erb -->
|
|
310
|
+
<li>
|
|
311
|
+
<%= link_to_unless current_page.first?, url, remote: remote, class: 'page-link' do %>
|
|
312
|
+
<i class="fas fa-angle-double-left"></i>
|
|
313
|
+
<span class="sr-only">First</span>
|
|
314
|
+
<% end %>
|
|
315
|
+
</li>
|
|
316
|
+
|
|
317
|
+
<!-- app/views/kaminari/icons/_last_page.html.erb -->
|
|
318
|
+
<li>
|
|
319
|
+
<%= link_to_unless current_page.last?, url, remote: remote, class: 'page-link' do %>
|
|
320
|
+
<span class="sr-only">Last</span>
|
|
321
|
+
<i class="fas fa-angle-double-right"></i>
|
|
322
|
+
<% end %>
|
|
323
|
+
</li>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Multiple Themes in One App
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
# app/controllers/posts_controller.rb
|
|
330
|
+
class PostsController < ApplicationController
|
|
331
|
+
def index
|
|
332
|
+
@posts = Post.page(params[:page])
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# app/controllers/admin/posts_controller.rb
|
|
337
|
+
module Admin
|
|
338
|
+
class PostsController < AdminController
|
|
339
|
+
def index
|
|
340
|
+
@posts = Post.page(params[:page])
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
```erb
|
|
347
|
+
<!-- app/views/posts/index.html.erb -->
|
|
348
|
+
<%= paginate @posts, theme: 'tailwind' %>
|
|
349
|
+
|
|
350
|
+
<!-- app/views/admin/posts/index.html.erb -->
|
|
351
|
+
<%= paginate @posts, theme: 'bootstrap5' %>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Helper Methods in Templates
|
|
355
|
+
|
|
356
|
+
Available helper methods in pagination templates:
|
|
357
|
+
|
|
358
|
+
```erb
|
|
359
|
+
<!-- Access pagination metadata -->
|
|
360
|
+
<%= total_pages %> <!-- Total number of pages -->
|
|
361
|
+
<%= current_page %> <!-- Current page number -->
|
|
362
|
+
<%= per_page %> <!-- Items per page -->
|
|
363
|
+
|
|
364
|
+
<!-- Available page variables in each_page block -->
|
|
365
|
+
<% each_page do |page| %>
|
|
366
|
+
<%= page.number %> <!-- Page number -->
|
|
367
|
+
<%= page.current? %> <!-- Is this the current page? -->
|
|
368
|
+
<%= page.first? %> <!-- Is this the first page? -->
|
|
369
|
+
<%= page.last? %> <!-- Is this the last page? -->
|
|
370
|
+
<%= page.next? %> <!-- Is this the next page? -->
|
|
371
|
+
<%= page.prev? %> <!-- Is this the previous page? -->
|
|
372
|
+
<%= page.left_outer? %> <!-- In left outer window? -->
|
|
373
|
+
<%= page.right_outer? %> <!-- In right outer window? -->
|
|
374
|
+
<%= page.inside_window? %> <!-- Inside main window? -->
|
|
375
|
+
<%= page.was_truncated? %> <!-- Was truncated? -->
|
|
376
|
+
<%= page.rel %> <!-- Rel attribute value -->
|
|
377
|
+
<% end %>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Responsive Pagination
|
|
381
|
+
|
|
382
|
+
```erb
|
|
383
|
+
<!-- app/views/kaminari/responsive/_paginator.html.erb -->
|
|
384
|
+
<nav aria-label="Page navigation">
|
|
385
|
+
<!-- Mobile: Simple prev/next only -->
|
|
386
|
+
<ul class="pagination d-md-none">
|
|
387
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
388
|
+
<li class="page-item disabled">
|
|
389
|
+
<span class="page-link">Page <%= current_page %> of <%= total_pages %></span>
|
|
390
|
+
</li>
|
|
391
|
+
<%= next_page_tag unless current_page.last? %>
|
|
392
|
+
</ul>
|
|
393
|
+
|
|
394
|
+
<!-- Desktop: Full pagination -->
|
|
395
|
+
<ul class="pagination d-none d-md-flex">
|
|
396
|
+
<%= first_page_tag unless current_page.first? %>
|
|
397
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
398
|
+
|
|
399
|
+
<% each_page do |page| %>
|
|
400
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
|
401
|
+
<%= page_tag page %>
|
|
402
|
+
<% elsif !page.was_truncated? -%>
|
|
403
|
+
<%= gap_tag %>
|
|
404
|
+
<% end %>
|
|
405
|
+
<% end %>
|
|
406
|
+
|
|
407
|
+
<%= next_page_tag unless current_page.last? %>
|
|
408
|
+
<%= last_page_tag unless current_page.last? %>
|
|
409
|
+
</ul>
|
|
410
|
+
</nav>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Accessibility Best Practices
|
|
414
|
+
|
|
415
|
+
```erb
|
|
416
|
+
<!-- app/views/kaminari/accessible/_paginator.html.erb -->
|
|
417
|
+
<nav role="navigation" aria-label="Pagination Navigation" class="pagination-wrapper">
|
|
418
|
+
<ul class="pagination">
|
|
419
|
+
<%= first_page_tag unless current_page.first? %>
|
|
420
|
+
<%= prev_page_tag unless current_page.first? %>
|
|
421
|
+
|
|
422
|
+
<% each_page do |page| %>
|
|
423
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
|
424
|
+
<%= page_tag page %>
|
|
425
|
+
<% elsif !page.was_truncated? -%>
|
|
426
|
+
<%= gap_tag %>
|
|
427
|
+
<% end %>
|
|
428
|
+
<% end %>
|
|
429
|
+
|
|
430
|
+
<%= next_page_tag unless current_page.last? %>
|
|
431
|
+
<%= last_page_tag unless current_page.last? %>
|
|
432
|
+
</ul>
|
|
433
|
+
</nav>
|
|
434
|
+
|
|
435
|
+
<!-- app/views/kaminari/accessible/_page.html.erb -->
|
|
436
|
+
<li>
|
|
437
|
+
<% if page.current? %>
|
|
438
|
+
<span class="page-link current" aria-current="page" aria-label="Page <%= page %>, current page">
|
|
439
|
+
<%= page %>
|
|
440
|
+
</span>
|
|
441
|
+
<% else %>
|
|
442
|
+
<%= link_to page, url, remote: remote, rel: page.rel, class: 'page-link', 'aria-label': "Go to page #{page}" %>
|
|
443
|
+
<% end %>
|
|
444
|
+
</li>
|
|
445
|
+
|
|
446
|
+
<!-- app/views/kaminari/accessible/_prev_page.html.erb -->
|
|
447
|
+
<li>
|
|
448
|
+
<% unless current_page.first? %>
|
|
449
|
+
<%= link_to url, rel: 'prev', remote: remote, class: 'page-link', 'aria-label': 'Go to previous page' do %>
|
|
450
|
+
<span aria-hidden="true">«</span>
|
|
451
|
+
<span class="sr-only">Previous</span>
|
|
452
|
+
<% end %>
|
|
453
|
+
<% end %>
|
|
454
|
+
</li>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Testing Custom Themes
|
|
458
|
+
|
|
459
|
+
```ruby
|
|
460
|
+
# spec/views/posts/index.html.erb_spec.rb
|
|
461
|
+
require 'rails_helper'
|
|
462
|
+
|
|
463
|
+
RSpec.describe 'posts/index', type: :view do
|
|
464
|
+
let(:posts) { Kaminari.paginate_array((1..100).to_a).page(2).per(10) }
|
|
465
|
+
|
|
466
|
+
before do
|
|
467
|
+
assign(:posts, posts)
|
|
468
|
+
render
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
it 'renders pagination with custom theme' do
|
|
472
|
+
expect(rendered).to have_css('nav.pagination')
|
|
473
|
+
expect(rendered).to have_link('Previous')
|
|
474
|
+
expect(rendered).to have_link('Next')
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
it 'shows current page as active' do
|
|
478
|
+
expect(rendered).to have_css('.page-item.active', text: '2')
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
it 'includes accessibility attributes' do
|
|
482
|
+
expect(rendered).to have_css('[aria-label="Pagination"]')
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Best Practices
|
|
488
|
+
|
|
489
|
+
1. **Use semantic HTML**: Wrap pagination in `<nav>` with proper ARIA labels
|
|
490
|
+
2. **Make links accessible**: Include screen-reader text for icon-only links
|
|
491
|
+
3. **Indicate current page**: Use `aria-current="page"` and visual styling
|
|
492
|
+
4. **Support keyboard navigation**: Ensure links are focusable and have clear focus states
|
|
493
|
+
5. **Be responsive**: Consider mobile-friendly pagination on small screens
|
|
494
|
+
6. **Use consistent styling**: Match your application's design system
|
|
495
|
+
7. **Test thoroughly**: Verify pagination works across browsers and devices
|
|
496
|
+
8. **Maintain theme consistency**: Keep all partials in same theme directory
|
|
497
|
+
9. **Document custom themes**: Comment complex logic in templates
|
|
498
|
+
10. **Version control themes**: Track theme changes alongside code
|