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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +11 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
  5. data/.github/ISSUE_TEMPLATE/question.yml +90 -0
  6. data/.github/dependabot.yml +19 -0
  7. data/.github/workflows/ci.yml +77 -0
  8. data/.github/workflows/release.yml +66 -0
  9. data/.rubocop.yml +52 -0
  10. data/CHANGELOG.md +94 -0
  11. data/CLAUDE.md +332 -0
  12. data/CODE_OF_CONDUCT.md +134 -0
  13. data/CONTRIBUTING.md +580 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +544 -0
  16. data/Rakefile +8 -0
  17. data/lib/generators/claude/agent/agent_generator.rb +71 -0
  18. data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
  19. data/lib/generators/claude/command/command_generator.rb +50 -0
  20. data/lib/generators/claude/command/templates/command.md.tt +28 -0
  21. data/lib/generators/claude/commands_library/create-pr.md +27 -0
  22. data/lib/generators/claude/commands_library/dbchange.md +19 -0
  23. data/lib/generators/claude/commands_library/quality.md +20 -0
  24. data/lib/generators/claude/commands_library/stimulus.md +19 -0
  25. data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
  26. data/lib/generators/claude/install/install_generator.rb +211 -0
  27. data/lib/generators/claude/install/templates/README.md.tt +59 -0
  28. data/lib/generators/claude/install/templates/USAGE +28 -0
  29. data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
  30. data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
  31. data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
  32. data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
  33. data/lib/generators/claude/rule/rule_generator.rb +175 -0
  34. data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
  35. data/lib/generators/claude/rules_library/code-style.md +37 -0
  36. data/lib/generators/claude/rules_library/database.md +47 -0
  37. data/lib/generators/claude/rules_library/hotwire.md +56 -0
  38. data/lib/generators/claude/rules_library/security.md +54 -0
  39. data/lib/generators/claude/rules_library/testing.md +47 -0
  40. data/lib/generators/claude/skill/skill_generator.rb +196 -0
  41. data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
  42. data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
  43. data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
  44. data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
  45. data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
  46. data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
  47. data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
  48. data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
  49. data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
  50. data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
  51. data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
  52. data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
  53. data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
  54. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
  55. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
  56. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
  57. data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
  58. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
  59. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
  60. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
  61. data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
  62. data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
  63. data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
  64. data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
  65. data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
  66. data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
  67. data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
  68. data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
  69. data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
  70. data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
  71. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
  72. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
  73. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
  74. data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
  75. data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
  76. data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
  77. data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
  78. data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
  79. data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
  80. data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
  81. data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
  82. data/lib/generators/claude/views/views_generator.rb +113 -0
  83. data/lib/rails_claude_skills/railtie.rb +16 -0
  84. data/lib/rails_claude_skills/version.rb +5 -0
  85. data/lib/rails_claude_skills.rb +27 -0
  86. data/sig/rails_claude_skills.rbs +4 -0
  87. metadata +199 -0
@@ -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">&hellip;</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">&laquo;</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