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.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +165 -0
  4. data/app/assets/stylesheets/ruby_ui_admin/application.tailwind.css +111 -0
  5. data/app/components/ruby_ui_admin/ui/base.rb +26 -0
  6. data/app/components/ruby_ui_admin/ui/icon.rb +79 -0
  7. data/app/components/ruby_ui_admin/ui/native_select.rb +37 -0
  8. data/app/components/ruby_ui_admin/ui/native_select_group.rb +18 -0
  9. data/app/components/ruby_ui_admin/ui/native_select_icon.rb +42 -0
  10. data/app/components/ruby_ui_admin/ui/native_select_option.rb +18 -0
  11. data/app/components/ruby_ui_admin/ui/pagination.rb +62 -0
  12. data/app/components/ruby_ui_admin/ui/pagination_content.rb +18 -0
  13. data/app/components/ruby_ui_admin/ui/pagination_ellipsis.rb +45 -0
  14. data/app/components/ruby_ui_admin/ui/pagination_item.rb +32 -0
  15. data/app/components/ruby_ui_admin/ui/select.rb +29 -0
  16. data/app/components/ruby_ui_admin/ui/toast_action.rb +30 -0
  17. data/app/components/ruby_ui_admin/ui/toast_cancel.rb +30 -0
  18. data/app/components/ruby_ui_admin/ui/toast_close.rb +43 -0
  19. data/app/components/ruby_ui_admin/ui/toast_description.rb +21 -0
  20. data/app/components/ruby_ui_admin/ui/toast_icon.rb +69 -0
  21. data/app/components/ruby_ui_admin/ui/toast_item.rb +75 -0
  22. data/app/components/ruby_ui_admin/ui/toast_region.rb +147 -0
  23. data/app/components/ruby_ui_admin/ui/toast_title.rb +21 -0
  24. data/app/components/ruby_ui_admin/ui.rb +12 -0
  25. data/app/components/ruby_ui_admin/views/action.rb +37 -0
  26. data/app/components/ruby_ui_admin/views/action_form.rb +104 -0
  27. data/app/components/ruby_ui_admin/views/action_trigger.rb +148 -0
  28. data/app/components/ruby_ui_admin/views/actions_menu.rb +34 -0
  29. data/app/components/ruby_ui_admin/views/base.rb +301 -0
  30. data/app/components/ruby_ui_admin/views/card_component.rb +69 -0
  31. data/app/components/ruby_ui_admin/views/dashboard.rb +40 -0
  32. data/app/components/ruby_ui_admin/views/docs.rb +91 -0
  33. data/app/components/ruby_ui_admin/views/field_input.rb +156 -0
  34. data/app/components/ruby_ui_admin/views/field_value.rb +301 -0
  35. data/app/components/ruby_ui_admin/views/filter_bar.rb +111 -0
  36. data/app/components/ruby_ui_admin/views/form.rb +151 -0
  37. data/app/components/ruby_ui_admin/views/home.rb +35 -0
  38. data/app/components/ruby_ui_admin/views/index.rb +324 -0
  39. data/app/components/ruby_ui_admin/views/path_helpers.rb +36 -0
  40. data/app/components/ruby_ui_admin/views/rails_helpers.rb +15 -0
  41. data/app/components/ruby_ui_admin/views/show.rb +77 -0
  42. data/app/components/ruby_ui_admin/views/show_fields.rb +37 -0
  43. data/app/components/ruby_ui_admin/views/show_tab.rb +58 -0
  44. data/app/components/ruby_ui_admin/views/structure_renderer.rb +148 -0
  45. data/app/components/ruby_ui_admin/views/translation.rb +12 -0
  46. data/app/controllers/ruby_ui_admin/actions_controller.rb +162 -0
  47. data/app/controllers/ruby_ui_admin/application_controller.rb +81 -0
  48. data/app/controllers/ruby_ui_admin/dashboards_controller.rb +12 -0
  49. data/app/controllers/ruby_ui_admin/docs_controller.rb +166 -0
  50. data/app/controllers/ruby_ui_admin/home_controller.rb +18 -0
  51. data/app/controllers/ruby_ui_admin/resources_controller.rb +259 -0
  52. data/config/locales/ruby_ui_admin.en.yml +59 -0
  53. data/config/locales/ruby_ui_admin.pt-BR.yml +59 -0
  54. data/config/routes/dynamic_routes.rb +11 -0
  55. data/config/routes.rb +23 -0
  56. data/docs/README.md +53 -0
  57. data/docs/actions/overview.md +112 -0
  58. data/docs/authorization/action-policy.md +72 -0
  59. data/docs/authorization/adapters.md +101 -0
  60. data/docs/authorization/explicit-mode.md +45 -0
  61. data/docs/authorization/field-authorization.md +53 -0
  62. data/docs/authorization/policies.md +62 -0
  63. data/docs/authorization/scopes.md +48 -0
  64. data/docs/customization/controllers.md +62 -0
  65. data/docs/customization/ejecting.md +55 -0
  66. data/docs/customization/javascript.md +161 -0
  67. data/docs/customization/theming-rubyui.md +80 -0
  68. data/docs/dashboards/overview.md +65 -0
  69. data/docs/fields/overview.md +94 -0
  70. data/docs/filters/overview.md +73 -0
  71. data/docs/generators-and-tasks/overview.md +42 -0
  72. data/docs/getting-started/authentication.md +98 -0
  73. data/docs/getting-started/configuration.md +174 -0
  74. data/docs/getting-started/installation.md +201 -0
  75. data/docs/getting-started/internationalization.md +50 -0
  76. data/docs/getting-started/practical-guide.md +310 -0
  77. data/docs/resources/associations.md +66 -0
  78. data/docs/resources/index-customization.md +68 -0
  79. data/docs/resources/overview.md +50 -0
  80. data/docs/resources/scopes.md +54 -0
  81. data/docs/resources/tabs-panels.md +65 -0
  82. data/lib/generators/ruby_ui_admin/action/action_generator.rb +21 -0
  83. data/lib/generators/ruby_ui_admin/action/templates/action.rb.tt +24 -0
  84. data/lib/generators/ruby_ui_admin/assets/assets_generator.rb +71 -0
  85. data/lib/generators/ruby_ui_admin/card/card_generator.rb +28 -0
  86. data/lib/generators/ruby_ui_admin/card/templates/chart_card.rb.tt +14 -0
  87. data/lib/generators/ruby_ui_admin/card/templates/metric_card.rb.tt +14 -0
  88. data/lib/generators/ruby_ui_admin/card/templates/partial_card.rb.tt +13 -0
  89. data/lib/generators/ruby_ui_admin/components/components_generator.rb +43 -0
  90. data/lib/generators/ruby_ui_admin/controller/controller_generator.rb +30 -0
  91. data/lib/generators/ruby_ui_admin/controller/templates/controller.rb.tt +24 -0
  92. data/lib/generators/ruby_ui_admin/dashboard/dashboard_generator.rb +18 -0
  93. data/lib/generators/ruby_ui_admin/dashboard/templates/dashboard.rb.tt +14 -0
  94. data/lib/generators/ruby_ui_admin/eject/eject_generator.rb +55 -0
  95. data/lib/generators/ruby_ui_admin/filter/filter_generator.rb +28 -0
  96. data/lib/generators/ruby_ui_admin/filter/templates/boolean_filter.rb.tt +13 -0
  97. data/lib/generators/ruby_ui_admin/filter/templates/select_filter.rb.tt +19 -0
  98. data/lib/generators/ruby_ui_admin/filter/templates/text_filter.rb.tt +13 -0
  99. data/lib/generators/ruby_ui_admin/install/install_generator.rb +62 -0
  100. data/lib/generators/ruby_ui_admin/install/templates/initializer.rb.tt +41 -0
  101. data/lib/generators/ruby_ui_admin/locales/locales_generator.rb +22 -0
  102. data/lib/generators/ruby_ui_admin/policy/policy_generator.rb +18 -0
  103. data/lib/generators/ruby_ui_admin/policy/templates/policy.rb.tt +24 -0
  104. data/lib/generators/ruby_ui_admin/resource/resource_generator.rb +84 -0
  105. data/lib/generators/ruby_ui_admin/resource/templates/resource.rb.tt +15 -0
  106. data/lib/generators/ruby_ui_admin/scope/scope_generator.rb +18 -0
  107. data/lib/generators/ruby_ui_admin/scope/templates/scope.rb.tt +11 -0
  108. data/lib/ruby_ui_admin/authorization/action_policy_adapter.rb +69 -0
  109. data/lib/ruby_ui_admin/authorization/adapter.rb +80 -0
  110. data/lib/ruby_ui_admin/authorization/can_can_can_adapter.rb +70 -0
  111. data/lib/ruby_ui_admin/authorization/pundit_adapter.rb +86 -0
  112. data/lib/ruby_ui_admin/authorization.rb +40 -0
  113. data/lib/ruby_ui_admin/base_action.rb +211 -0
  114. data/lib/ruby_ui_admin/base_dashboard.rb +70 -0
  115. data/lib/ruby_ui_admin/base_policy.rb +38 -0
  116. data/lib/ruby_ui_admin/base_resource.rb +503 -0
  117. data/lib/ruby_ui_admin/cards/base_card.rb +59 -0
  118. data/lib/ruby_ui_admin/cards/chart_card.rb +16 -0
  119. data/lib/ruby_ui_admin/cards/metric_card.rb +22 -0
  120. data/lib/ruby_ui_admin/cards/partial_card.rb +14 -0
  121. data/lib/ruby_ui_admin/configuration.rb +177 -0
  122. data/lib/ruby_ui_admin/current.rb +20 -0
  123. data/lib/ruby_ui_admin/dashboard_manager.rb +52 -0
  124. data/lib/ruby_ui_admin/engine.rb +80 -0
  125. data/lib/ruby_ui_admin/execution_context.rb +36 -0
  126. data/lib/ruby_ui_admin/fields/association_field.rb +67 -0
  127. data/lib/ruby_ui_admin/fields/badge_field.rb +19 -0
  128. data/lib/ruby_ui_admin/fields/base_field.rb +208 -0
  129. data/lib/ruby_ui_admin/fields/belongs_to_field.rb +49 -0
  130. data/lib/ruby_ui_admin/fields/boolean_field.rb +13 -0
  131. data/lib/ruby_ui_admin/fields/boolean_group_field.rb +54 -0
  132. data/lib/ruby_ui_admin/fields/code_field.rb +18 -0
  133. data/lib/ruby_ui_admin/fields/date_field.rb +34 -0
  134. data/lib/ruby_ui_admin/fields/date_time_field.rb +37 -0
  135. data/lib/ruby_ui_admin/fields/field_manager.rb +27 -0
  136. data/lib/ruby_ui_admin/fields/file_field.rb +67 -0
  137. data/lib/ruby_ui_admin/fields/files_field.rb +62 -0
  138. data/lib/ruby_ui_admin/fields/has_and_belongs_to_many_field.rb +9 -0
  139. data/lib/ruby_ui_admin/fields/has_many_field.rb +24 -0
  140. data/lib/ruby_ui_admin/fields/has_one_field.rb +13 -0
  141. data/lib/ruby_ui_admin/fields/hidden_field.rb +14 -0
  142. data/lib/ruby_ui_admin/fields/id_field.rb +19 -0
  143. data/lib/ruby_ui_admin/fields/key_value_field.rb +41 -0
  144. data/lib/ruby_ui_admin/fields/number_field.rb +13 -0
  145. data/lib/ruby_ui_admin/fields/password_field.rb +21 -0
  146. data/lib/ruby_ui_admin/fields/record_link_field.rb +45 -0
  147. data/lib/ruby_ui_admin/fields/select_field.rb +71 -0
  148. data/lib/ruby_ui_admin/fields/status_field.rb +21 -0
  149. data/lib/ruby_ui_admin/fields/text_field.rb +14 -0
  150. data/lib/ruby_ui_admin/fields/textarea_field.rb +13 -0
  151. data/lib/ruby_ui_admin/fields/url_field.rb +21 -0
  152. data/lib/ruby_ui_admin/filters/base_filter.rb +81 -0
  153. data/lib/ruby_ui_admin/filters/boolean_filter.rb +9 -0
  154. data/lib/ruby_ui_admin/filters/multiple_select_filter.rb +11 -0
  155. data/lib/ruby_ui_admin/filters/select_filter.rb +9 -0
  156. data/lib/ruby_ui_admin/filters/text_filter.rb +9 -0
  157. data/lib/ruby_ui_admin/menu/builder.rb +94 -0
  158. data/lib/ruby_ui_admin/resource_manager.rb +82 -0
  159. data/lib/ruby_ui_admin/scopes/base_scope.rb +107 -0
  160. data/lib/ruby_ui_admin/services/authorization_service.rb +38 -0
  161. data/lib/ruby_ui_admin/structure.rb +40 -0
  162. data/lib/ruby_ui_admin/tailwind_source.rb +65 -0
  163. data/lib/ruby_ui_admin/version.rb +5 -0
  164. data/lib/ruby_ui_admin/view.rb +34 -0
  165. data/lib/ruby_ui_admin.rb +110 -0
  166. data/lib/tasks/ruby_ui_admin.rake +71 -0
  167. data/public/ruby-ui-admin-assets/application.css +2 -0
  168. data/public/ruby-ui-admin-assets/controllers/index.js +36 -0
  169. data/public/ruby-ui-admin-assets/controllers/rua--bulk-select_controller.js +19 -0
  170. data/public/ruby-ui-admin-assets/controllers/rua--confirm_controller.js +51 -0
  171. data/public/ruby-ui-admin-assets/controllers/rua--dialog_controller.js +55 -0
  172. data/public/ruby-ui-admin-assets/controllers/rua--row-link_controller.js +16 -0
  173. data/public/ruby-ui-admin-assets/controllers/rua--tabs_controller.js +36 -0
  174. data/public/ruby-ui-admin-assets/controllers/ruby_ui--combobox_controller.js +197 -0
  175. data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_content_controller.js +7 -0
  176. data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_controller.js +15 -0
  177. data/public/ruby-ui-admin-assets/controllers/ruby_ui--sidebar_controller.js +67 -0
  178. data/public/ruby-ui-admin-assets/controllers/ruby_ui--toast_controller.js +151 -0
  179. data/public/ruby-ui-admin-assets/controllers/ruby_ui--toaster_controller.js +314 -0
  180. data/public/ruby-ui-admin-assets/vendor/stimulus.js +2567 -0
  181. data/public/ruby-ui-admin-assets/vendor/turbo.js +7178 -0
  182. 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
+ ![RubyUI Admin](/docs/assets/ruby-ui-admin.gif)
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