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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI.
|
|
4
|
+
# A page link styled with the Button classes (outline when active, ghost otherwise).
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module UI
|
|
7
|
+
class PaginationItem < Base
|
|
8
|
+
def initialize(href: "#", active: false, **attrs)
|
|
9
|
+
@href = href
|
|
10
|
+
@active = active
|
|
11
|
+
super(**attrs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def view_template(&block)
|
|
15
|
+
li do
|
|
16
|
+
a(href: @href, **attrs, &block)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def default_attrs
|
|
23
|
+
{
|
|
24
|
+
aria: {current: @active ? "page" : nil},
|
|
25
|
+
class: [
|
|
26
|
+
RubyUI::Button.new(variant: @active ? :outline : :ghost).attrs[:class]
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module UI
|
|
5
|
+
# Convenience wrapper around RubyUI's NativeSelect: takes an array of [label, value]
|
|
6
|
+
# `options` (plus `selected:`/`include_blank:`) instead of explicit option components,
|
|
7
|
+
# so call sites stay terse. Renders the styled native select with a custom chevron.
|
|
8
|
+
class Select < Base
|
|
9
|
+
def initialize(options: [], selected: nil, include_blank: false, **attrs)
|
|
10
|
+
@options = options
|
|
11
|
+
@selected = selected
|
|
12
|
+
@include_blank = include_blank
|
|
13
|
+
super(**attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view_template
|
|
17
|
+
render NativeSelect.new(**attrs) do
|
|
18
|
+
render(NativeSelectOption.new(value: "")) { "—" } if @include_blank
|
|
19
|
+
|
|
20
|
+
@options.each do |(label, value)|
|
|
21
|
+
opts = {value: value}
|
|
22
|
+
opts[:selected] = true if value.to_s == @selected.to_s
|
|
23
|
+
render(NativeSelectOption.new(**opts)) { label.to_s }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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 ToastAction < Base
|
|
7
|
+
def initialize(label:, on: nil, **attrs)
|
|
8
|
+
@label = label
|
|
9
|
+
@on = on
|
|
10
|
+
super(**attrs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template
|
|
14
|
+
button(**attrs) { @label }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def default_attrs
|
|
20
|
+
data = {slot: "action"}
|
|
21
|
+
data[:action] = @on if @on
|
|
22
|
+
{
|
|
23
|
+
type: "button",
|
|
24
|
+
data: data,
|
|
25
|
+
class: "inline-flex h-6 shrink-0 cursor-pointer items-center justify-center rounded px-2 text-xs font-medium bg-foreground text-background border-0 ml-auto hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-ring transition-opacity disabled:pointer-events-none disabled:opacity-50"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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 ToastCancel < Base
|
|
7
|
+
def initialize(label:, **attrs)
|
|
8
|
+
@label = label
|
|
9
|
+
super(**attrs)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def view_template
|
|
13
|
+
button(**attrs) { @label }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def default_attrs
|
|
19
|
+
{
|
|
20
|
+
type: "button",
|
|
21
|
+
data: {
|
|
22
|
+
slot: "cancel",
|
|
23
|
+
action: "click->ruby-ui--toast#dismiss"
|
|
24
|
+
},
|
|
25
|
+
class: "inline-flex h-6 shrink-0 cursor-pointer items-center justify-center rounded px-2 text-xs font-medium bg-foreground/10 text-foreground border-0 ml-auto hover:bg-foreground/15 focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
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 ToastClose < Base
|
|
7
|
+
def view_template
|
|
8
|
+
button(**attrs) do
|
|
9
|
+
svg(
|
|
10
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
11
|
+
width: "14",
|
|
12
|
+
height: "14",
|
|
13
|
+
viewbox: "0 0 24 24",
|
|
14
|
+
fill: "none",
|
|
15
|
+
stroke: "currentColor",
|
|
16
|
+
stroke_width: "2",
|
|
17
|
+
stroke_linecap: "round",
|
|
18
|
+
stroke_linejoin: "round",
|
|
19
|
+
class: "size-3.5"
|
|
20
|
+
) do |s|
|
|
21
|
+
s.path(d: "M18 6 6 18")
|
|
22
|
+
s.path(d: "m6 6 12 12")
|
|
23
|
+
end
|
|
24
|
+
span(class: "sr-only") { "Close" }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def default_attrs
|
|
31
|
+
{
|
|
32
|
+
type: "button",
|
|
33
|
+
aria_label: "Close toast",
|
|
34
|
+
data: {
|
|
35
|
+
slot: "close",
|
|
36
|
+
action: "click->ruby-ui--toast#dismiss"
|
|
37
|
+
},
|
|
38
|
+
class: "absolute right-2 top-2 size-6 cursor-pointer rounded-md text-foreground/60 p-0 flex items-center justify-center transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
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 ToastDescription < Base
|
|
7
|
+
def view_template(&)
|
|
8
|
+
div(**attrs, &)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def default_attrs
|
|
14
|
+
{
|
|
15
|
+
data: {slot: "description"},
|
|
16
|
+
class: "font-normal leading-[1.4] text-muted-foreground"
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
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 ToastIcon < Base
|
|
7
|
+
def initialize(variant: nil, **attrs)
|
|
8
|
+
@variant = variant&.to_sym
|
|
9
|
+
super(**attrs)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def view_template
|
|
13
|
+
return unless renderable?
|
|
14
|
+
|
|
15
|
+
span(**attrs) do
|
|
16
|
+
svg(
|
|
17
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
18
|
+
width: "16",
|
|
19
|
+
height: "16",
|
|
20
|
+
viewbox: "0 0 24 24",
|
|
21
|
+
fill: "none",
|
|
22
|
+
stroke: "currentColor",
|
|
23
|
+
stroke_width: "2",
|
|
24
|
+
stroke_linecap: "round",
|
|
25
|
+
stroke_linejoin: "round",
|
|
26
|
+
class: "#{svg_classes} -ml-px"
|
|
27
|
+
) { |s| paths(s) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def renderable?
|
|
34
|
+
%i[success error warning info loading].include?(@variant)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def svg_classes
|
|
38
|
+
base = "size-4"
|
|
39
|
+
(@variant == :loading) ? "#{base} animate-spin" : base
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def paths(s)
|
|
43
|
+
case @variant
|
|
44
|
+
when :success
|
|
45
|
+
s.circle(cx: "12", cy: "12", r: "10")
|
|
46
|
+
s.path(d: "m9 12 2 2 4-4")
|
|
47
|
+
when :error
|
|
48
|
+
s.path(d: "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z")
|
|
49
|
+
s.path(d: "m15 9-6 6")
|
|
50
|
+
s.path(d: "m9 9 6 6")
|
|
51
|
+
when :warning
|
|
52
|
+
s.path(d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3")
|
|
53
|
+
s.path(d: "M12 9v4")
|
|
54
|
+
s.path(d: "M12 17h.01")
|
|
55
|
+
when :info
|
|
56
|
+
s.circle(cx: "12", cy: "12", r: "10")
|
|
57
|
+
s.path(d: "M12 16v-4")
|
|
58
|
+
s.path(d: "M12 8h.01")
|
|
59
|
+
when :loading
|
|
60
|
+
s.path(d: "M21 12a9 9 0 1 1-6.219-8.56")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def default_attrs
|
|
65
|
+
{data: {slot: "icon"}, class: "shrink-0 inline-flex items-center justify-start relative size-4 -ml-[3px] mr-1 text-foreground"}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
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 ToastItem < Base
|
|
7
|
+
ALERT_VARIANTS = %i[error].freeze
|
|
8
|
+
|
|
9
|
+
def initialize(
|
|
10
|
+
variant: :default,
|
|
11
|
+
id: nil,
|
|
12
|
+
duration: nil,
|
|
13
|
+
dismissible: true,
|
|
14
|
+
invert: false,
|
|
15
|
+
on_dismiss: nil,
|
|
16
|
+
on_auto_close: nil,
|
|
17
|
+
**attrs
|
|
18
|
+
)
|
|
19
|
+
@variant = variant.to_sym
|
|
20
|
+
@id = id
|
|
21
|
+
@duration = duration
|
|
22
|
+
@dismissible = dismissible
|
|
23
|
+
@invert = invert
|
|
24
|
+
@on_dismiss = on_dismiss
|
|
25
|
+
@on_auto_close = on_auto_close
|
|
26
|
+
super(**attrs)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def view_template(&)
|
|
30
|
+
li(**attrs, &)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def default_attrs
|
|
36
|
+
a = {
|
|
37
|
+
role: ALERT_VARIANTS.include?(@variant) ? "alert" : "status",
|
|
38
|
+
aria_atomic: "true",
|
|
39
|
+
tabindex: "0",
|
|
40
|
+
data: {
|
|
41
|
+
variant: @variant.to_s,
|
|
42
|
+
state: "pending",
|
|
43
|
+
swipe: "none",
|
|
44
|
+
controller: "ruby-ui--toast",
|
|
45
|
+
ruby_ui__toaster_target: "toast",
|
|
46
|
+
ruby_ui__toast_dismissible_value: @dismissible.to_s,
|
|
47
|
+
ruby_ui__toast_invert_value: @invert.to_s
|
|
48
|
+
},
|
|
49
|
+
class: item_classes
|
|
50
|
+
}
|
|
51
|
+
a[:id] = @id if @id
|
|
52
|
+
a[:data][:ruby_ui__toast_duration_value] = @duration.to_s if @duration
|
|
53
|
+
a[:data][:ruby_ui__toast_on_dismiss_value] = @on_dismiss if @on_dismiss
|
|
54
|
+
a[:data][:ruby_ui__toast_on_auto_close_value] = @on_auto_close if @on_auto_close
|
|
55
|
+
a
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def item_classes
|
|
59
|
+
<<~CLASSES.tr("\n", " ").squeeze(" ").strip
|
|
60
|
+
group/toast pointer-events-auto absolute left-0 right-0
|
|
61
|
+
flex w-[356px] max-w-full items-center gap-1.5
|
|
62
|
+
rounded-lg border bg-popover text-popover-foreground
|
|
63
|
+
border-border p-4 text-[13px] shadow-[0_4px_12px_rgba(0,0,0,0.1)]
|
|
64
|
+
group-data-[close-button=true]/toaster:pr-10
|
|
65
|
+
transition-[transform,opacity] duration-300 ease-out
|
|
66
|
+
will-change-transform
|
|
67
|
+
opacity-[var(--opacity,1)]
|
|
68
|
+
data-[state=pending]:opacity-0
|
|
69
|
+
data-[state=closing]:opacity-0
|
|
70
|
+
data-[swipe=move]:transition-none
|
|
71
|
+
CLASSES
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Vendored from RubyUI (github.com/ruby-ui), namespaced under RubyUIAdmin::UI. The behaviour
|
|
4
|
+
# is driven by RubyUI's Stimulus controllers (ruby-ui--toaster / ruby-ui--toast), loaded
|
|
5
|
+
# self-hosted via the importmap in the admin layout.
|
|
6
|
+
module RubyUIAdmin
|
|
7
|
+
module UI
|
|
8
|
+
class ToastRegion < Base
|
|
9
|
+
SKELETON_VARIANTS = %i[default success error warning info loading].freeze
|
|
10
|
+
|
|
11
|
+
# Maps Rails flash keys to RubyUI toast variants (admin semantics: notice = success,
|
|
12
|
+
# alert = error), with explicit variant keys passed through.
|
|
13
|
+
FLASH_VARIANTS = {
|
|
14
|
+
"notice" => :success,
|
|
15
|
+
"success" => :success,
|
|
16
|
+
"alert" => :error,
|
|
17
|
+
"error" => :error,
|
|
18
|
+
"warning" => :warning,
|
|
19
|
+
"info" => :info
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
def initialize(
|
|
23
|
+
position: :bottom_right,
|
|
24
|
+
expand: false,
|
|
25
|
+
max: 3,
|
|
26
|
+
duration: 4000,
|
|
27
|
+
gap: 14,
|
|
28
|
+
offset: 24,
|
|
29
|
+
theme: :system,
|
|
30
|
+
rich_colors: false,
|
|
31
|
+
close_button: false,
|
|
32
|
+
hotkey: %w[alt t],
|
|
33
|
+
dir: :ltr,
|
|
34
|
+
flash: nil,
|
|
35
|
+
**attrs
|
|
36
|
+
)
|
|
37
|
+
@position = position.to_sym
|
|
38
|
+
@expand = expand
|
|
39
|
+
@max = max
|
|
40
|
+
@duration = duration
|
|
41
|
+
@gap = gap
|
|
42
|
+
@offset = offset
|
|
43
|
+
@theme = theme.to_sym
|
|
44
|
+
@rich_colors = rich_colors
|
|
45
|
+
@close_button = close_button
|
|
46
|
+
@hotkey = hotkey
|
|
47
|
+
@dir = dir
|
|
48
|
+
@flash = flash
|
|
49
|
+
super(**attrs)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def view_template(&block)
|
|
53
|
+
div(**attrs) do
|
|
54
|
+
ol(id: "ruby-ui-toaster", class: "pointer-events-auto relative m-0 p-0 list-none w-[356px] max-w-full") do
|
|
55
|
+
render_flash if @flash
|
|
56
|
+
yield(self) if block
|
|
57
|
+
end
|
|
58
|
+
SKELETON_VARIANTS.each { |v| skeleton(v) }
|
|
59
|
+
slot_template("actionTpl") { render ToastAction.new(label: "") }
|
|
60
|
+
slot_template("cancelTpl") { render ToastCancel.new(label: "") }
|
|
61
|
+
slot_template("closeTpl") { render ToastClose.new }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def flash_variant(key)
|
|
68
|
+
FLASH_VARIANTS[key.to_s] || :info
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_flash
|
|
72
|
+
@flash.each do |key, message|
|
|
73
|
+
next if message.nil? || message.to_s.empty?
|
|
74
|
+
|
|
75
|
+
variant = flash_variant(key)
|
|
76
|
+
render ToastItem.new(variant: variant, id: "flash-#{key}", duration: @duration) do
|
|
77
|
+
render ToastIcon.new(variant: variant)
|
|
78
|
+
render ToastTitle.new { message.to_s }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def skeleton(variant)
|
|
84
|
+
template(
|
|
85
|
+
data: {
|
|
86
|
+
ruby_ui__toaster_target: "skeleton",
|
|
87
|
+
variant: variant.to_s
|
|
88
|
+
}
|
|
89
|
+
) do
|
|
90
|
+
render ToastItem.new(variant: variant) do
|
|
91
|
+
render ToastIcon.new(variant: variant)
|
|
92
|
+
div(class: "flex flex-col gap-0.5 flex-1 min-w-0") do
|
|
93
|
+
render ToastTitle.new
|
|
94
|
+
render ToastDescription.new
|
|
95
|
+
end
|
|
96
|
+
render ToastClose.new if @close_button
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def slot_template(target_name, &)
|
|
102
|
+
template(data: {ruby_ui__toaster_target: target_name}, &)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def default_attrs
|
|
106
|
+
{
|
|
107
|
+
id: "ruby-ui-toaster-region",
|
|
108
|
+
role: "region",
|
|
109
|
+
aria_label: "Notifications",
|
|
110
|
+
aria_live: "polite",
|
|
111
|
+
data: {
|
|
112
|
+
controller: "ruby-ui--toaster",
|
|
113
|
+
# NOTE: deliberately NOT `turbo_permanent` (RubyUI ships it permanent). Under Turbo
|
|
114
|
+
# Drive a permanent region is carried over between visits, which would discard the new
|
|
115
|
+
# page's server-rendered flash toast — letting it re-render each visit keeps flash working.
|
|
116
|
+
close_button: @close_button.to_s,
|
|
117
|
+
position: @position.to_s.tr("_", "-"),
|
|
118
|
+
ruby_ui__toaster_position_value: @position.to_s.tr("_", "-"),
|
|
119
|
+
ruby_ui__toaster_expand_value: @expand.to_s,
|
|
120
|
+
ruby_ui__toaster_max_value: @max.to_s,
|
|
121
|
+
ruby_ui__toaster_duration_value: @duration.to_s,
|
|
122
|
+
ruby_ui__toaster_gap_value: @gap.to_s,
|
|
123
|
+
ruby_ui__toaster_offset_value: @offset.to_s,
|
|
124
|
+
ruby_ui__toaster_theme_value: @theme.to_s,
|
|
125
|
+
ruby_ui__toaster_rich_colors_value: @rich_colors.to_s,
|
|
126
|
+
ruby_ui__toaster_close_button_value: @close_button.to_s,
|
|
127
|
+
ruby_ui__toaster_hotkey_value: Array(@hotkey).join("+"),
|
|
128
|
+
ruby_ui__toaster_dir_value: @dir.to_s
|
|
129
|
+
},
|
|
130
|
+
class: region_classes
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def region_classes
|
|
135
|
+
<<~CLASSES.tr("\n", " ").squeeze(" ").strip
|
|
136
|
+
group/toaster pointer-events-none fixed z-[100] p-4 sm:p-6
|
|
137
|
+
data-[position=top-left]:top-0 data-[position=top-left]:left-0
|
|
138
|
+
data-[position=top-center]:top-0 data-[position=top-center]:left-1/2 data-[position=top-center]:-translate-x-1/2
|
|
139
|
+
data-[position=top-right]:top-0 data-[position=top-right]:right-0
|
|
140
|
+
data-[position=bottom-left]:bottom-0 data-[position=bottom-left]:left-0
|
|
141
|
+
data-[position=bottom-center]:bottom-0 data-[position=bottom-center]:left-1/2 data-[position=bottom-center]:-translate-x-1/2
|
|
142
|
+
data-[position=bottom-right]:bottom-0 data-[position=bottom-right]:right-0
|
|
143
|
+
CLASSES
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
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 ToastTitle < Base
|
|
7
|
+
def view_template(&)
|
|
8
|
+
div(**attrs, &)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def default_attrs
|
|
14
|
+
{
|
|
15
|
+
data: {slot: "title"},
|
|
16
|
+
class: "font-medium leading-normal"
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "phlex"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
# Vendored RubyUI primitives, namespaced under the engine so they never collide
|
|
7
|
+
# with a host app's own `RubyUI` Phlex::Kit. Visual language and
|
|
8
|
+
# Tailwind classes are taken from RubyUI for consistency.
|
|
9
|
+
module UI
|
|
10
|
+
extend Phlex::Kit
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Views
|
|
5
|
+
# Full-page confirmation/form for a custom action (no-JS fallback for the modal).
|
|
6
|
+
class Action < Base
|
|
7
|
+
def initialize(resource:, action:, action_id:, record_ids:)
|
|
8
|
+
@resource = resource
|
|
9
|
+
@action = action
|
|
10
|
+
@action_id = action_id
|
|
11
|
+
@record_ids = record_ids
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def page_title
|
|
15
|
+
"#{@action.name} · #{RubyUIAdmin.configuration.app_name}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def content
|
|
19
|
+
div(class: "mb-6") do
|
|
20
|
+
a(href: resource_index_path(@resource.class), class: "text-sm text-muted-foreground hover:underline") { "← #{@resource.navigation_label}" }
|
|
21
|
+
h1(class: "text-2xl font-semibold tracking-tight mt-1") { @action.name }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
render RubyUI::Card.new(class: "max-w-2xl") do
|
|
25
|
+
render RubyUI::CardContent.new(class: "pt-6") do
|
|
26
|
+
render RubyUIAdmin::Views::ActionForm.new(
|
|
27
|
+
resource: @resource,
|
|
28
|
+
action: @action,
|
|
29
|
+
action_id: @action_id,
|
|
30
|
+
record_ids: @record_ids
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Views
|
|
5
|
+
# The inner form of a custom action (message + fields + buttons). Shared by the
|
|
6
|
+
# action page (Views::Action) and the inline action dialog (Views::ActionTrigger).
|
|
7
|
+
class ActionForm < Phlex::HTML
|
|
8
|
+
include RubyUIAdmin::UI
|
|
9
|
+
include RailsHelpers
|
|
10
|
+
include PathHelpers
|
|
11
|
+
|
|
12
|
+
def initialize(resource:, action:, action_id:, record_ids: [], frame_id: nil)
|
|
13
|
+
@resource = resource
|
|
14
|
+
@action = action
|
|
15
|
+
@action_id = action_id
|
|
16
|
+
@record_ids = record_ids
|
|
17
|
+
@frame_id = frame_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# When loaded into the modal's `<turbo-frame>`, wrap the form in the matching frame so
|
|
21
|
+
# Turbo swaps it in. `data-turbo-frame="_top"` makes the submit break out into a full
|
|
22
|
+
# visit (POST→303→redirect+flash) instead of just reloading the frame. On the standalone
|
|
23
|
+
# action page (no frame_id) the form renders bare and `_top` is a harmless no-op.
|
|
24
|
+
def view_template
|
|
25
|
+
if @frame_id
|
|
26
|
+
tag(:turbo_frame, id: @frame_id) { render_form }
|
|
27
|
+
else
|
|
28
|
+
render_form
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def render_form
|
|
33
|
+
form(action: action_path, method: "post", enctype: form_enctype, class: "space-y-5", data: {turbo_frame: "_top"}) do
|
|
34
|
+
input(type: "hidden", name: "authenticity_token", value: form_authenticity_token)
|
|
35
|
+
record_id_fields
|
|
36
|
+
render_message
|
|
37
|
+
@action.get_fields.each { |field| render_field(field) }
|
|
38
|
+
render_actions
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def action_path
|
|
45
|
+
ruby_ui_admin.resource_action_path(resource_name: @resource.route_key, action_id: @action_id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# File-upload fields require a multipart form.
|
|
49
|
+
def form_enctype
|
|
50
|
+
@action.get_fields.any? { |field| %i[file files].include?(field.type) } ? "multipart/form-data" : nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def record_id_fields
|
|
54
|
+
Array(@record_ids).each do |id|
|
|
55
|
+
input(type: "hidden", name: "record_ids[]", value: id)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def render_message
|
|
60
|
+
message = @action.message
|
|
61
|
+
return if message.blank?
|
|
62
|
+
|
|
63
|
+
div(class: "text-sm text-muted-foreground") { raw(safe(message.to_s)) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def render_field(field)
|
|
67
|
+
# A lone checkbox reads as "[ ] Label" — render it inline beside the label.
|
|
68
|
+
if field.type == :boolean
|
|
69
|
+
div(class: "space-y-1.5") do
|
|
70
|
+
label(class: "flex items-center gap-2 text-sm font-medium leading-none") do
|
|
71
|
+
render RubyUIAdmin::Views::FieldInput.new(field: field, record: nil, name_prefix: "fields")
|
|
72
|
+
plain field.name
|
|
73
|
+
span(class: "text-destructive ml-0.5") { "*" } if field.required?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
div(class: "space-y-1.5") do
|
|
80
|
+
label(class: "text-sm font-medium leading-none") do
|
|
81
|
+
plain field.name
|
|
82
|
+
span(class: "text-destructive ml-0.5") { "*" } if field.required?
|
|
83
|
+
end
|
|
84
|
+
render RubyUIAdmin::Views::FieldInput.new(field: field, record: nil, name_prefix: "fields")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def render_actions
|
|
89
|
+
div(class: "flex items-center gap-3 pt-2") do
|
|
90
|
+
render(RubyUI::Button.new(type: :submit, variant: :primary)) { @action.class.confirm_button_label }
|
|
91
|
+
|
|
92
|
+
# Closes the dialog when inside one (Stimulus preventDefaults); otherwise falls back to
|
|
93
|
+
# the index. `_top` ensures any non-prevented click escapes the frame (a full visit)
|
|
94
|
+
# rather than rendering the index inside the modal frame.
|
|
95
|
+
a(
|
|
96
|
+
href: resource_index_path(@resource.class),
|
|
97
|
+
data: {action: "click->rua--dialog#close", turbo_frame: "_top"},
|
|
98
|
+
class: "text-sm text-muted-foreground hover:underline"
|
|
99
|
+
) { @action.class.cancel_button_label }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|