ruby_ui_admin 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/MIT-LICENSE +20 -0
- data/README.md +165 -0
- data/app/assets/stylesheets/ruby_ui_admin/application.tailwind.css +111 -0
- data/app/components/ruby_ui_admin/ui/base.rb +26 -0
- data/app/components/ruby_ui_admin/ui/icon.rb +79 -0
- data/app/components/ruby_ui_admin/ui/native_select.rb +37 -0
- data/app/components/ruby_ui_admin/ui/native_select_group.rb +18 -0
- data/app/components/ruby_ui_admin/ui/native_select_icon.rb +42 -0
- data/app/components/ruby_ui_admin/ui/native_select_option.rb +18 -0
- data/app/components/ruby_ui_admin/ui/pagination.rb +62 -0
- data/app/components/ruby_ui_admin/ui/pagination_content.rb +18 -0
- data/app/components/ruby_ui_admin/ui/pagination_ellipsis.rb +45 -0
- data/app/components/ruby_ui_admin/ui/pagination_item.rb +32 -0
- data/app/components/ruby_ui_admin/ui/select.rb +29 -0
- data/app/components/ruby_ui_admin/ui/toast_action.rb +30 -0
- data/app/components/ruby_ui_admin/ui/toast_cancel.rb +30 -0
- data/app/components/ruby_ui_admin/ui/toast_close.rb +43 -0
- data/app/components/ruby_ui_admin/ui/toast_description.rb +21 -0
- data/app/components/ruby_ui_admin/ui/toast_icon.rb +69 -0
- data/app/components/ruby_ui_admin/ui/toast_item.rb +75 -0
- data/app/components/ruby_ui_admin/ui/toast_region.rb +147 -0
- data/app/components/ruby_ui_admin/ui/toast_title.rb +21 -0
- data/app/components/ruby_ui_admin/ui.rb +12 -0
- data/app/components/ruby_ui_admin/views/action.rb +37 -0
- data/app/components/ruby_ui_admin/views/action_form.rb +104 -0
- data/app/components/ruby_ui_admin/views/action_trigger.rb +148 -0
- data/app/components/ruby_ui_admin/views/actions_menu.rb +34 -0
- data/app/components/ruby_ui_admin/views/base.rb +301 -0
- data/app/components/ruby_ui_admin/views/card_component.rb +69 -0
- data/app/components/ruby_ui_admin/views/dashboard.rb +40 -0
- data/app/components/ruby_ui_admin/views/docs.rb +91 -0
- data/app/components/ruby_ui_admin/views/field_input.rb +156 -0
- data/app/components/ruby_ui_admin/views/field_value.rb +301 -0
- data/app/components/ruby_ui_admin/views/filter_bar.rb +111 -0
- data/app/components/ruby_ui_admin/views/form.rb +151 -0
- data/app/components/ruby_ui_admin/views/home.rb +35 -0
- data/app/components/ruby_ui_admin/views/index.rb +324 -0
- data/app/components/ruby_ui_admin/views/path_helpers.rb +36 -0
- data/app/components/ruby_ui_admin/views/rails_helpers.rb +15 -0
- data/app/components/ruby_ui_admin/views/show.rb +77 -0
- data/app/components/ruby_ui_admin/views/show_fields.rb +37 -0
- data/app/components/ruby_ui_admin/views/show_tab.rb +58 -0
- data/app/components/ruby_ui_admin/views/structure_renderer.rb +148 -0
- data/app/components/ruby_ui_admin/views/translation.rb +12 -0
- data/app/controllers/ruby_ui_admin/actions_controller.rb +162 -0
- data/app/controllers/ruby_ui_admin/application_controller.rb +81 -0
- data/app/controllers/ruby_ui_admin/dashboards_controller.rb +12 -0
- data/app/controllers/ruby_ui_admin/docs_controller.rb +166 -0
- data/app/controllers/ruby_ui_admin/home_controller.rb +18 -0
- data/app/controllers/ruby_ui_admin/resources_controller.rb +259 -0
- data/config/locales/ruby_ui_admin.en.yml +59 -0
- data/config/locales/ruby_ui_admin.pt-BR.yml +59 -0
- data/config/routes/dynamic_routes.rb +11 -0
- data/config/routes.rb +23 -0
- data/docs/README.md +53 -0
- data/docs/actions/overview.md +112 -0
- data/docs/authorization/action-policy.md +72 -0
- data/docs/authorization/adapters.md +101 -0
- data/docs/authorization/explicit-mode.md +45 -0
- data/docs/authorization/field-authorization.md +53 -0
- data/docs/authorization/policies.md +62 -0
- data/docs/authorization/scopes.md +48 -0
- data/docs/customization/controllers.md +62 -0
- data/docs/customization/ejecting.md +55 -0
- data/docs/customization/javascript.md +161 -0
- data/docs/customization/theming-rubyui.md +80 -0
- data/docs/dashboards/overview.md +65 -0
- data/docs/fields/overview.md +94 -0
- data/docs/filters/overview.md +73 -0
- data/docs/generators-and-tasks/overview.md +42 -0
- data/docs/getting-started/authentication.md +98 -0
- data/docs/getting-started/configuration.md +174 -0
- data/docs/getting-started/installation.md +201 -0
- data/docs/getting-started/internationalization.md +50 -0
- data/docs/getting-started/practical-guide.md +310 -0
- data/docs/resources/associations.md +66 -0
- data/docs/resources/index-customization.md +68 -0
- data/docs/resources/overview.md +50 -0
- data/docs/resources/scopes.md +54 -0
- data/docs/resources/tabs-panels.md +65 -0
- data/lib/generators/ruby_ui_admin/action/action_generator.rb +21 -0
- data/lib/generators/ruby_ui_admin/action/templates/action.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/assets/assets_generator.rb +71 -0
- data/lib/generators/ruby_ui_admin/card/card_generator.rb +28 -0
- data/lib/generators/ruby_ui_admin/card/templates/chart_card.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/card/templates/metric_card.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/card/templates/partial_card.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/components/components_generator.rb +43 -0
- data/lib/generators/ruby_ui_admin/controller/controller_generator.rb +30 -0
- data/lib/generators/ruby_ui_admin/controller/templates/controller.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/dashboard/dashboard_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/dashboard/templates/dashboard.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/eject/eject_generator.rb +55 -0
- data/lib/generators/ruby_ui_admin/filter/filter_generator.rb +28 -0
- data/lib/generators/ruby_ui_admin/filter/templates/boolean_filter.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/filter/templates/select_filter.rb.tt +19 -0
- data/lib/generators/ruby_ui_admin/filter/templates/text_filter.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/install/install_generator.rb +62 -0
- data/lib/generators/ruby_ui_admin/install/templates/initializer.rb.tt +41 -0
- data/lib/generators/ruby_ui_admin/locales/locales_generator.rb +22 -0
- data/lib/generators/ruby_ui_admin/policy/policy_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/policy/templates/policy.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/resource/resource_generator.rb +84 -0
- data/lib/generators/ruby_ui_admin/resource/templates/resource.rb.tt +15 -0
- data/lib/generators/ruby_ui_admin/scope/scope_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/scope/templates/scope.rb.tt +11 -0
- data/lib/ruby_ui_admin/authorization/action_policy_adapter.rb +69 -0
- data/lib/ruby_ui_admin/authorization/adapter.rb +80 -0
- data/lib/ruby_ui_admin/authorization/can_can_can_adapter.rb +70 -0
- data/lib/ruby_ui_admin/authorization/pundit_adapter.rb +86 -0
- data/lib/ruby_ui_admin/authorization.rb +40 -0
- data/lib/ruby_ui_admin/base_action.rb +211 -0
- data/lib/ruby_ui_admin/base_dashboard.rb +70 -0
- data/lib/ruby_ui_admin/base_policy.rb +38 -0
- data/lib/ruby_ui_admin/base_resource.rb +503 -0
- data/lib/ruby_ui_admin/cards/base_card.rb +59 -0
- data/lib/ruby_ui_admin/cards/chart_card.rb +16 -0
- data/lib/ruby_ui_admin/cards/metric_card.rb +22 -0
- data/lib/ruby_ui_admin/cards/partial_card.rb +14 -0
- data/lib/ruby_ui_admin/configuration.rb +177 -0
- data/lib/ruby_ui_admin/current.rb +20 -0
- data/lib/ruby_ui_admin/dashboard_manager.rb +52 -0
- data/lib/ruby_ui_admin/engine.rb +80 -0
- data/lib/ruby_ui_admin/execution_context.rb +36 -0
- data/lib/ruby_ui_admin/fields/association_field.rb +67 -0
- data/lib/ruby_ui_admin/fields/badge_field.rb +19 -0
- data/lib/ruby_ui_admin/fields/base_field.rb +208 -0
- data/lib/ruby_ui_admin/fields/belongs_to_field.rb +49 -0
- data/lib/ruby_ui_admin/fields/boolean_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/boolean_group_field.rb +54 -0
- data/lib/ruby_ui_admin/fields/code_field.rb +18 -0
- data/lib/ruby_ui_admin/fields/date_field.rb +34 -0
- data/lib/ruby_ui_admin/fields/date_time_field.rb +37 -0
- data/lib/ruby_ui_admin/fields/field_manager.rb +27 -0
- data/lib/ruby_ui_admin/fields/file_field.rb +67 -0
- data/lib/ruby_ui_admin/fields/files_field.rb +62 -0
- data/lib/ruby_ui_admin/fields/has_and_belongs_to_many_field.rb +9 -0
- data/lib/ruby_ui_admin/fields/has_many_field.rb +24 -0
- data/lib/ruby_ui_admin/fields/has_one_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/hidden_field.rb +14 -0
- data/lib/ruby_ui_admin/fields/id_field.rb +19 -0
- data/lib/ruby_ui_admin/fields/key_value_field.rb +41 -0
- data/lib/ruby_ui_admin/fields/number_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/password_field.rb +21 -0
- data/lib/ruby_ui_admin/fields/record_link_field.rb +45 -0
- data/lib/ruby_ui_admin/fields/select_field.rb +71 -0
- data/lib/ruby_ui_admin/fields/status_field.rb +21 -0
- data/lib/ruby_ui_admin/fields/text_field.rb +14 -0
- data/lib/ruby_ui_admin/fields/textarea_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/url_field.rb +21 -0
- data/lib/ruby_ui_admin/filters/base_filter.rb +81 -0
- data/lib/ruby_ui_admin/filters/boolean_filter.rb +9 -0
- data/lib/ruby_ui_admin/filters/multiple_select_filter.rb +11 -0
- data/lib/ruby_ui_admin/filters/select_filter.rb +9 -0
- data/lib/ruby_ui_admin/filters/text_filter.rb +9 -0
- data/lib/ruby_ui_admin/menu/builder.rb +94 -0
- data/lib/ruby_ui_admin/resource_manager.rb +82 -0
- data/lib/ruby_ui_admin/scopes/base_scope.rb +107 -0
- data/lib/ruby_ui_admin/services/authorization_service.rb +38 -0
- data/lib/ruby_ui_admin/structure.rb +40 -0
- data/lib/ruby_ui_admin/tailwind_source.rb +65 -0
- data/lib/ruby_ui_admin/version.rb +5 -0
- data/lib/ruby_ui_admin/view.rb +34 -0
- data/lib/ruby_ui_admin.rb +110 -0
- data/lib/tasks/ruby_ui_admin.rake +71 -0
- data/public/ruby-ui-admin-assets/application.css +2 -0
- data/public/ruby-ui-admin-assets/controllers/index.js +36 -0
- data/public/ruby-ui-admin-assets/controllers/rua--bulk-select_controller.js +19 -0
- data/public/ruby-ui-admin-assets/controllers/rua--confirm_controller.js +51 -0
- data/public/ruby-ui-admin-assets/controllers/rua--dialog_controller.js +55 -0
- data/public/ruby-ui-admin-assets/controllers/rua--row-link_controller.js +16 -0
- data/public/ruby-ui-admin-assets/controllers/rua--tabs_controller.js +36 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--combobox_controller.js +197 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_content_controller.js +7 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_controller.js +15 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sidebar_controller.js +67 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--toast_controller.js +151 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--toaster_controller.js +314 -0
- data/public/ruby-ui-admin-assets/vendor/stimulus.js +2567 -0
- data/public/ruby-ui-admin-assets/vendor/turbo.js +7178 -0
- metadata +379 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b9d5449657a96e06d7fdec1b35f2acc22e61b18a8ee4f9af0dccc271210e3a04
|
|
4
|
+
data.tar.gz: cb13e7ee317cb31f45b529827d45c47a88f004db7b2470417de5ce38cddf69da
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cb7446e9a905dbd81ae1e5eda43ea3a7887fde9abbd65a7741b66a9b1eaf3623e9919f87b24d81a865bbed78f2a6808d77a518f30d8c2e04f9aaed6f4a1e67d9
|
|
7
|
+
data.tar.gz: 406ab2ee12504cc014060f9416b4729253cb67343a3fa5ae728b297ce5110c6f5d466f7f169a0582f9249b756790f9bd2bee55def37b7b59f4b900e7dd3837f5
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright The ruby_ui_admin authors
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# RubyUI Admin
|
|
2
|
+
|
|
3
|
+
`ruby_ui_admin` is a Rails admin dashboard engine, rendered with
|
|
4
|
+
[RubyUI](https://rubyui.com) (Phlex) components, that even serves its own documentation as HTML
|
|
5
|
+
inside your app at `<mount>/docs` (e.g. `/admin/docs`).
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## What is RubyUI Admin?
|
|
10
|
+
|
|
11
|
+
RubyUI Admin turns your Active Record models into a complete, production-ready admin panel with
|
|
12
|
+
very little code. You point it at a model, describe its fields in a compact Ruby DSL, and you get
|
|
13
|
+
index / show / new / edit screens, pagination, filters, named scopes, bulk and custom actions,
|
|
14
|
+
dashboards, and authorization — all rendered server-side.
|
|
15
|
+
|
|
16
|
+
It grew out of three ideas:
|
|
17
|
+
|
|
18
|
+
- **A UI built on your app's [RubyUI](https://rubyui.com) (Phlex) components.** Every screen is
|
|
19
|
+
composed from the same `RubyUI::*` components you've installed in your app — cards, tables,
|
|
20
|
+
dialogs, toasts, a collapsible sidebar — so the admin matches your design system and tracks your
|
|
21
|
+
RubyUI version. It's progressively enhanced with native Hotwire (Stimulus + Turbo).
|
|
22
|
+
- **A resource DSL inspired by [Avo](https://github.com/avo-hq/avo).** You configure the admin in plain Ruby
|
|
23
|
+
classes — `fields`, `filters`, `scopes`, `actions`, policies — instead of generating and then
|
|
24
|
+
hand-editing views. If you've used Avo, the shape will feel familiar.
|
|
25
|
+
- **Documentation you read inside the app.** The gem serves its own docs, rendered as HTML, at
|
|
26
|
+
`<mount>/docs` (e.g. `/admin/docs`) — so you can look things up without leaving the admin. It's on
|
|
27
|
+
in development by default and can be exposed in production (behind your auth gate) with
|
|
28
|
+
`config.docs_enabled = true`.
|
|
29
|
+
|
|
30
|
+
### Requirements
|
|
31
|
+
|
|
32
|
+
The admin renders **your host app's RubyUI components**, so it expects:
|
|
33
|
+
|
|
34
|
+
- [RubyUI](https://rubyui.com) installed and its components generated into your app
|
|
35
|
+
(`app/components/ruby_ui/*` as `RubyUI::*`), at a version compatible with the admin — run
|
|
36
|
+
`rails g ruby_ui_admin:components` to generate exactly the ones the admin renders.
|
|
37
|
+
- A **JavaScript bundler/importmap** that registers RubyUI's Stimulus controllers (and the admin's
|
|
38
|
+
own `rua--*` controllers — use `rails g ruby_ui_admin:assets` to copy them in), plus a **Tailwind
|
|
39
|
+
build** that covers `app/components/ruby_ui/**` and the admin's views. You wire these into the
|
|
40
|
+
admin layout via `config.head_assets`.
|
|
41
|
+
|
|
42
|
+
This makes the admin part of your app's UI rather than a self-contained bundle. (See
|
|
43
|
+
[Theming with RubyUI](docs/customization/theming-rubyui.md) and
|
|
44
|
+
[JavaScript](docs/customization/javascript.md).)
|
|
45
|
+
|
|
46
|
+
### Design principles
|
|
47
|
+
|
|
48
|
+
- **Server-rendered, progressively enhanced.** Everything works without JavaScript; Stimulus and
|
|
49
|
+
Turbo only sharpen a few interactions (tabs, action modals, toasts, clickable rows).
|
|
50
|
+
- **Your design system, not a fork.** The admin uses your own `RubyUI::*` components; if one needs a
|
|
51
|
+
change, change it in your app (or upstream a PR to RubyUI) — there's no vendored copy to maintain.
|
|
52
|
+
- **Plain Ruby, and ejectable.** Resources, policies, and actions are just classes; any view can be
|
|
53
|
+
ejected and customized when you outgrow the defaults.
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
- Resources with a full field catalog, tabs & panels, and schema auto-discovery
|
|
58
|
+
- CRUD with pagination, named scopes, filters and index customization
|
|
59
|
+
- Custom actions (inline modals, with a no-JS page fallback) and bulk actions
|
|
60
|
+
- Dashboards with metric / chart / partial cards
|
|
61
|
+
- action_policy authorization: per-rule, record scopes, and field-level rules
|
|
62
|
+
- Generators (`install`, `components`, `assets`, `resource`, `controller`, `action`, `filter`,
|
|
63
|
+
`policy`, `scope`, `dashboard`, `card`, `eject`, `locales`) and rake tasks
|
|
64
|
+
- Renders your app's RubyUI/Phlex components, enhanced with native Hotwire (Stimulus + Turbo)
|
|
65
|
+
- i18n (English + Brazilian Portuguese bundled)
|
|
66
|
+
|
|
67
|
+
See [`docs/`](docs/README.md) for the full documentation.
|
|
68
|
+
|
|
69
|
+
> Deferred / not yet built: inline association management (attach/detach + nested tables),
|
|
70
|
+
> grid/map index views, and a few reserved config options.
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
New to the gem? The [**practical guide**](docs/getting-started/practical-guide.md) is a
|
|
75
|
+
copy‑paste walkthrough from `rails new` to a working admin (two associated scaffolds,
|
|
76
|
+
configuration, resources and filters).
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
Add to your `Gemfile`:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
gem "ruby_ui_admin"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Mount the engine in `config/routes.rb`:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
mount_ruby_ui_admin at: "/admin"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Configure it in `config/initializers/ruby_ui_admin.rb`:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
RubyUIAdmin.configure do |config|
|
|
96
|
+
config.app_name = "My Admin"
|
|
97
|
+
config.current_user_method { current_user } # resolve the signed-in admin
|
|
98
|
+
config.authenticate_with { redirect_to main_app.login_path unless current_user }
|
|
99
|
+
config.authorization_client = :action_policy
|
|
100
|
+
config.explicit_authorization = false # allow rules a policy doesn't define
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Defining a resource
|
|
105
|
+
|
|
106
|
+
Resources live in `app/ruby_ui_admin/resources` and are namespaced under `RubyUIAdmin::Resources`:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
module RubyUIAdmin
|
|
110
|
+
module Resources
|
|
111
|
+
class Post < RubyUIAdmin::BaseResource
|
|
112
|
+
self.title = :title
|
|
113
|
+
self.includes = [:user]
|
|
114
|
+
self.authorization_policy = RubyUIAdmin::Policies::PostPolicy
|
|
115
|
+
|
|
116
|
+
def fields
|
|
117
|
+
field :id, as: :id
|
|
118
|
+
field :title, as: :text, link_to_record: true, sortable: true
|
|
119
|
+
field :body, as: :text, only_on: %i[show new edit]
|
|
120
|
+
field :published, as: :boolean
|
|
121
|
+
field :user, as: :belongs_to
|
|
122
|
+
field :created_at, as: :date_time, only_on: %i[index show]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Authorization
|
|
130
|
+
|
|
131
|
+
Policies use action_policy and live in `app/ruby_ui_admin/policies`
|
|
132
|
+
(`RubyUIAdmin::Policies::*`), inheriting `RubyUIAdmin::BasePolicy`:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
module RubyUIAdmin
|
|
136
|
+
module Policies
|
|
137
|
+
class PostPolicy < RubyUIAdmin::BasePolicy
|
|
138
|
+
def index? = true
|
|
139
|
+
def show? = true
|
|
140
|
+
def update? = true
|
|
141
|
+
def destroy? = user.admin?
|
|
142
|
+
|
|
143
|
+
relation_scope do |relation|
|
|
144
|
+
next relation if user.admin?
|
|
145
|
+
relation.where(published: true)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Documentation
|
|
153
|
+
|
|
154
|
+
Full documentation lives in [`docs/`](docs/README.md).
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
bin/setup # bundle install
|
|
160
|
+
bundle exec rake test # run the minitest suite against test/dummy
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Scan the engine's Phlex components so their utility classes are generated. */
|
|
4
|
+
@source "../../../components/ruby_ui_admin";
|
|
5
|
+
|
|
6
|
+
/* Safelist dynamic classes built via string interpolation (grid/col-span). */
|
|
7
|
+
@source inline("md:grid-cols-{1,2,3,4}");
|
|
8
|
+
@source inline("md:col-span-{2,3,4}");
|
|
9
|
+
|
|
10
|
+
/* RubyUI design tokens (light). Host apps may override these. */
|
|
11
|
+
:root {
|
|
12
|
+
--background: #ffffff;
|
|
13
|
+
--foreground: #0a0a0a;
|
|
14
|
+
--muted: #f4f4f5;
|
|
15
|
+
--muted-foreground: #71717a;
|
|
16
|
+
--border: #ededf0;
|
|
17
|
+
--input: #e4e4e7;
|
|
18
|
+
--ring: #18181b;
|
|
19
|
+
--primary: #18181b;
|
|
20
|
+
--primary-foreground: #fafafa;
|
|
21
|
+
--secondary: #f4f4f5;
|
|
22
|
+
--secondary-foreground: #18181b;
|
|
23
|
+
--accent: #f4f4f5;
|
|
24
|
+
--accent-foreground: #18181b;
|
|
25
|
+
--destructive: #ef4444;
|
|
26
|
+
--destructive-foreground: #fafafa;
|
|
27
|
+
--popover: #ffffff;
|
|
28
|
+
--popover-foreground: #0a0a0a;
|
|
29
|
+
--sidebar: #fafafa;
|
|
30
|
+
--sidebar-foreground: #3f3f46;
|
|
31
|
+
--sidebar-accent: #f4f4f5;
|
|
32
|
+
--sidebar-accent-foreground: #18181b;
|
|
33
|
+
--sidebar-border: #e5e7eb;
|
|
34
|
+
--sidebar-ring: #3b82f6;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dark {
|
|
38
|
+
--background: #0a0a0a;
|
|
39
|
+
--foreground: #fafafa;
|
|
40
|
+
--muted: #262626;
|
|
41
|
+
--muted-foreground: #a1a1aa;
|
|
42
|
+
--border: #232327;
|
|
43
|
+
--input: #262626;
|
|
44
|
+
--ring: #d4d4d8;
|
|
45
|
+
--primary: #fafafa;
|
|
46
|
+
--primary-foreground: #18181b;
|
|
47
|
+
--secondary: #262626;
|
|
48
|
+
--secondary-foreground: #fafafa;
|
|
49
|
+
--accent: #262626;
|
|
50
|
+
--accent-foreground: #fafafa;
|
|
51
|
+
--destructive: #ef4444;
|
|
52
|
+
--destructive-foreground: #fafafa;
|
|
53
|
+
--popover: #0a0a0a;
|
|
54
|
+
--popover-foreground: #fafafa;
|
|
55
|
+
--sidebar: #18181b;
|
|
56
|
+
--sidebar-foreground: #f4f4f5;
|
|
57
|
+
--sidebar-accent: #27272a;
|
|
58
|
+
--sidebar-accent-foreground: #fafafa;
|
|
59
|
+
--sidebar-border: #27272a;
|
|
60
|
+
--sidebar-ring: #3b82f6;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Map the tokens to Tailwind theme colors (kept as var() refs so dark mode works). */
|
|
64
|
+
@theme inline {
|
|
65
|
+
--color-background: var(--background);
|
|
66
|
+
--color-foreground: var(--foreground);
|
|
67
|
+
--color-muted: var(--muted);
|
|
68
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
69
|
+
--color-border: var(--border);
|
|
70
|
+
--color-input: var(--input);
|
|
71
|
+
--color-ring: var(--ring);
|
|
72
|
+
--color-primary: var(--primary);
|
|
73
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
74
|
+
--color-secondary: var(--secondary);
|
|
75
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
76
|
+
--color-accent: var(--accent);
|
|
77
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
78
|
+
--color-destructive: var(--destructive);
|
|
79
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
80
|
+
--color-popover: var(--popover);
|
|
81
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
82
|
+
--color-sidebar: var(--sidebar);
|
|
83
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
84
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
85
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
86
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
87
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Tailwind v4 defaults a bare `border` to currentColor; restore the v3/shadcn behaviour
|
|
91
|
+
so `border` (cards, table separators, dividers) uses the soft `--border` token. Inputs
|
|
92
|
+
keep their own `border-input` color. */
|
|
93
|
+
@layer base {
|
|
94
|
+
*,
|
|
95
|
+
::after,
|
|
96
|
+
::before,
|
|
97
|
+
::backdrop {
|
|
98
|
+
border-color: var(--color-border, currentColor);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* User-rendered links (raw `link_to` inside field blocks) underline on hover, matching
|
|
103
|
+
the framework's own links. Scoped to `.rua-rich` so it never touches buttons or nav. */
|
|
104
|
+
@layer base {
|
|
105
|
+
.rua-rich a {
|
|
106
|
+
text-underline-offset: 4px;
|
|
107
|
+
}
|
|
108
|
+
.rua-rich a:hover {
|
|
109
|
+
text-decoration-line: underline;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tailwind_merge"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module UI
|
|
7
|
+
# Base for all vendored UI primitives. Mirrors RubyUI::Base: merges default and
|
|
8
|
+
# user attributes and dedupes Tailwind classes.
|
|
9
|
+
class Base < Phlex::HTML
|
|
10
|
+
TAILWIND_MERGER = ::TailwindMerge::Merger.new.freeze unless defined?(TAILWIND_MERGER)
|
|
11
|
+
|
|
12
|
+
attr_reader :attrs
|
|
13
|
+
|
|
14
|
+
def initialize(**user_attrs)
|
|
15
|
+
@attrs = mix(default_attrs, user_attrs)
|
|
16
|
+
@attrs[:class] = TAILWIND_MERGER.merge(@attrs[:class]) if @attrs[:class]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def default_attrs
|
|
22
|
+
{}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module UI
|
|
5
|
+
# Inline Lucide icons (lucide.dev, ISC license) — vendored as SVG paths so the gem
|
|
6
|
+
# needs no icon dependency. Add entries to `render_paths` as needed.
|
|
7
|
+
#
|
|
8
|
+
# render RubyUIAdmin::UI::Icon.new(:eye)
|
|
9
|
+
# render RubyUIAdmin::UI::Icon.new(:trash, class: "size-5")
|
|
10
|
+
class Icon < Base
|
|
11
|
+
def initialize(name, **attrs)
|
|
12
|
+
@name = name.to_sym
|
|
13
|
+
super(**attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view_template
|
|
17
|
+
svg(
|
|
18
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
19
|
+
viewbox: "0 0 24 24",
|
|
20
|
+
fill: "none",
|
|
21
|
+
stroke: "currentColor",
|
|
22
|
+
stroke_width: "2",
|
|
23
|
+
stroke_linecap: "round",
|
|
24
|
+
stroke_linejoin: "round",
|
|
25
|
+
**attrs
|
|
26
|
+
) { |s| render_paths(s) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def render_paths(s)
|
|
32
|
+
case @name
|
|
33
|
+
when :eye # lucide: eye
|
|
34
|
+
s.path(d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0")
|
|
35
|
+
s.circle(cx: "12", cy: "12", r: "3")
|
|
36
|
+
when :pencil # lucide: square-pen
|
|
37
|
+
s.path(d: "M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7")
|
|
38
|
+
s.path(d: "M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z")
|
|
39
|
+
when :trash # lucide: trash-2
|
|
40
|
+
s.path(d: "M3 6h18")
|
|
41
|
+
s.path(d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2")
|
|
42
|
+
s.line(x1: "10", x2: "10", y1: "11", y2: "17")
|
|
43
|
+
s.line(x1: "14", x2: "14", y1: "11", y2: "17")
|
|
44
|
+
when :chevron_left # lucide: chevron-left
|
|
45
|
+
s.path(d: "m15 18-6-6 6-6")
|
|
46
|
+
when :chevron_right # lucide: chevron-right
|
|
47
|
+
s.path(d: "m9 18 6-6-6-6")
|
|
48
|
+
when :chevron_down # lucide: chevron-down
|
|
49
|
+
s.path(d: "m6 9 6 6 6-6")
|
|
50
|
+
when :circle # lucide: circle (generic leaf marker)
|
|
51
|
+
s.circle(cx: "12", cy: "12", r: "10")
|
|
52
|
+
when :layout_dashboard # lucide: layout-dashboard
|
|
53
|
+
s.rect(width: "7", height: "9", x: "3", y: "3", rx: "1")
|
|
54
|
+
s.rect(width: "7", height: "5", x: "14", y: "3", rx: "1")
|
|
55
|
+
s.rect(width: "7", height: "9", x: "14", y: "12", rx: "1")
|
|
56
|
+
s.rect(width: "7", height: "5", x: "3", y: "16", rx: "1")
|
|
57
|
+
when :link # lucide: link
|
|
58
|
+
s.path(d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71")
|
|
59
|
+
s.path(d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71")
|
|
60
|
+
when :log_out # lucide: log-out
|
|
61
|
+
s.path(d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4")
|
|
62
|
+
s.polyline(points: "16 17 21 12 16 7")
|
|
63
|
+
s.line(x1: "21", x2: "9", y1: "12", y2: "12")
|
|
64
|
+
when :download # lucide: download
|
|
65
|
+
s.path(d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4")
|
|
66
|
+
s.polyline(points: "7 10 12 15 17 10")
|
|
67
|
+
s.line(x1: "12", x2: "12", y1: "15", y2: "3")
|
|
68
|
+
when :file # lucide: file
|
|
69
|
+
s.path(d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z")
|
|
70
|
+
s.path(d: "M14 2v4a2 2 0 0 0 2 2h4")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def default_attrs
|
|
75
|
+
{class: "size-4", aria_hidden: "true"}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
# Adapted for this engine: full-width (to match the other form inputs), using the
|
|
5
|
+
# engine's `input`/`background` tokens, and without RubyUI's FormField Stimulus hooks
|
|
6
|
+
# (not used here). A styled native <select> with a custom chevron — no JS needed.
|
|
7
|
+
module RubyUIAdmin
|
|
8
|
+
module UI
|
|
9
|
+
class NativeSelect < Base
|
|
10
|
+
def initialize(size: :default, **attrs)
|
|
11
|
+
@size = size
|
|
12
|
+
super(**attrs)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def view_template(&block)
|
|
16
|
+
div(class: "group/native-select relative w-full has-[select:disabled]:opacity-50") do
|
|
17
|
+
select(**attrs, &block)
|
|
18
|
+
render NativeSelectIcon.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_attrs
|
|
25
|
+
{
|
|
26
|
+
class: [
|
|
27
|
+
"border-input bg-background text-foreground text-sm w-full min-w-0 appearance-none rounded-md border py-1 pr-8 pl-3 shadow-sm transition-[color,box-shadow] outline-none",
|
|
28
|
+
"focus-visible:outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2",
|
|
29
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
30
|
+
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive aria-invalid:ring-2",
|
|
31
|
+
(@size == :sm) ? "h-8" : "h-9"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
module RubyUIAdmin
|
|
5
|
+
module UI
|
|
6
|
+
class NativeSelectGroup < Base
|
|
7
|
+
def view_template(&)
|
|
8
|
+
optgroup(**attrs, &)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def default_attrs
|
|
14
|
+
{}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
module RubyUIAdmin
|
|
5
|
+
module UI
|
|
6
|
+
class NativeSelectIcon < Base
|
|
7
|
+
def view_template(&block)
|
|
8
|
+
span(**attrs) do
|
|
9
|
+
if block
|
|
10
|
+
block.call
|
|
11
|
+
else
|
|
12
|
+
icon
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def icon
|
|
20
|
+
svg(
|
|
21
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
22
|
+
viewbox: "0 0 24 24",
|
|
23
|
+
fill: "none",
|
|
24
|
+
stroke: "currentColor",
|
|
25
|
+
stroke_width: "2",
|
|
26
|
+
stroke_linecap: "round",
|
|
27
|
+
stroke_linejoin: "round",
|
|
28
|
+
class: "size-4",
|
|
29
|
+
aria_hidden: "true"
|
|
30
|
+
) do |s|
|
|
31
|
+
s.path(d: "m6 9 6 6 6-6")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def default_attrs
|
|
36
|
+
{
|
|
37
|
+
class: "text-muted-foreground pointer-events-none absolute top-1/2 right-2.5 -translate-y-1/2 select-none"
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
module RubyUIAdmin
|
|
5
|
+
module UI
|
|
6
|
+
class NativeSelectOption < Base
|
|
7
|
+
def view_template(&)
|
|
8
|
+
option(**attrs, &)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def default_attrs
|
|
14
|
+
{}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module UI
|
|
5
|
+
# Orchestrates RubyUI's Pagination primitives (PaginationContent / PaginationItem /
|
|
6
|
+
# PaginationEllipsis) from a Pagy instance: numbered page links with gaps, plus
|
|
7
|
+
# Previous/Next. In countless mode (no total) it shows Previous/Next only.
|
|
8
|
+
class Pagination < Base
|
|
9
|
+
def initialize(pagy:, url_for_page:, countless: false, **attrs)
|
|
10
|
+
@pagy = pagy
|
|
11
|
+
@url_for_page = url_for_page
|
|
12
|
+
@countless = countless
|
|
13
|
+
super(**attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view_template
|
|
17
|
+
nav(**attrs) do
|
|
18
|
+
render PaginationContent.new do
|
|
19
|
+
render_edge(:prev)
|
|
20
|
+
render_series unless @countless
|
|
21
|
+
render_edge(:next)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def default_attrs
|
|
29
|
+
{aria: {label: "pagination"}, role: "navigation", class: "flex w-full justify-end py-4"}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Pagy#series yields page numbers (Integer), the current page (String) and `:gap`.
|
|
33
|
+
def render_series
|
|
34
|
+
@pagy.series.each do |item|
|
|
35
|
+
if item == :gap
|
|
36
|
+
render PaginationEllipsis.new
|
|
37
|
+
elsif item.is_a?(String)
|
|
38
|
+
render(PaginationItem.new(href: @url_for_page.call(item), active: true)) { item }
|
|
39
|
+
else
|
|
40
|
+
render(PaginationItem.new(href: @url_for_page.call(item))) { item.to_s }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render_edge(direction)
|
|
46
|
+
page = (direction == :prev) ? @pagy.prev : @pagy.next
|
|
47
|
+
item_attrs = page ? {href: @url_for_page.call(page)} : {href: "#", class: "pointer-events-none opacity-50"}
|
|
48
|
+
render(PaginationItem.new(**item_attrs)) { edge_content(direction) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def edge_content(direction)
|
|
52
|
+
if direction == :prev
|
|
53
|
+
render RubyUIAdmin::UI::Icon.new(:chevron_left)
|
|
54
|
+
plain I18n.t("ruby_ui_admin.pagination.previous")
|
|
55
|
+
else
|
|
56
|
+
plain I18n.t("ruby_ui_admin.pagination.next")
|
|
57
|
+
render RubyUIAdmin::UI::Icon.new(:chevron_right)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
module RubyUIAdmin
|
|
5
|
+
module UI
|
|
6
|
+
class PaginationContent < Base
|
|
7
|
+
def view_template(&)
|
|
8
|
+
ul(**attrs, &)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def default_attrs
|
|
14
|
+
{class: "flex flex-row items-center gap-1"}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
module RubyUIAdmin
|
|
5
|
+
module UI
|
|
6
|
+
class PaginationEllipsis < Base
|
|
7
|
+
def view_template(&block)
|
|
8
|
+
li do
|
|
9
|
+
span(**attrs) do
|
|
10
|
+
icon
|
|
11
|
+
span(class: "sr-only") { "More pages" }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def icon
|
|
19
|
+
svg(
|
|
20
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
21
|
+
width: "24",
|
|
22
|
+
height: "24",
|
|
23
|
+
viewbox: "0 0 24 24",
|
|
24
|
+
fill: "none",
|
|
25
|
+
stroke: "currentColor",
|
|
26
|
+
stroke_width: "2",
|
|
27
|
+
stroke_linecap: "round",
|
|
28
|
+
stroke_linejoin: "round",
|
|
29
|
+
class: "h-4 w-4"
|
|
30
|
+
) do |s|
|
|
31
|
+
s.circle(cx: "12", cy: "12", r: "1")
|
|
32
|
+
s.circle(cx: "19", cy: "12", r: "1")
|
|
33
|
+
s.circle(cx: "5", cy: "12", r: "1")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def default_attrs
|
|
38
|
+
{
|
|
39
|
+
aria: {hidden: true},
|
|
40
|
+
class: "flex h-9 w-9 items-center justify-center"
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|