panda-core 0.12.2 → 1.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 +4 -4
- data/LICENSE +1 -1
- data/README.md +53 -0
- data/app/assets/builds/panda_core_admin.css +2 -0
- data/app/assets/tailwind/application.css +413 -131
- data/app/builders/panda/core/form_builder.rb +129 -27
- data/app/components/panda/core/UI/badge.html.erb +10 -0
- data/app/components/panda/core/UI/badge.rb +14 -30
- data/app/components/panda/core/UI/button.html.erb +5 -0
- data/app/components/panda/core/UI/button.rb +15 -19
- data/app/components/panda/core/UI/card.html.erb +3 -0
- data/app/components/panda/core/UI/card.rb +7 -6
- data/app/components/panda/core/admin/attribute_diff_component.html.erb +21 -0
- data/app/components/panda/core/admin/attribute_diff_component.rb +29 -0
- data/app/components/panda/core/admin/breadcrumb_component.html.erb +36 -0
- data/app/components/panda/core/admin/breadcrumb_component.rb +12 -64
- data/app/components/panda/core/admin/breadcrumbs_component.html.erb +16 -0
- data/app/components/panda/core/admin/breadcrumbs_component.rb +26 -0
- data/app/components/panda/core/admin/button_component.html.erb +9 -0
- data/app/components/panda/core/admin/button_component.rb +37 -32
- data/app/components/panda/core/admin/callout_component.html.erb +17 -0
- data/app/components/panda/core/admin/callout_component.rb +57 -0
- data/app/components/panda/core/admin/code_block_component.html.erb +9 -0
- data/app/components/panda/core/admin/code_block_component.rb +23 -0
- data/app/components/panda/core/admin/container_component.html.erb +27 -0
- data/app/components/panda/core/admin/container_component.rb +11 -77
- data/app/components/panda/core/admin/contributors_summary_component.html.erb +15 -0
- data/app/components/panda/core/admin/contributors_summary_component.rb +23 -0
- data/app/components/panda/core/admin/dashboard_default_content_component.html.erb +70 -0
- data/app/components/panda/core/admin/dashboard_default_content_component.rb +32 -0
- data/app/components/panda/core/admin/delete_button_component.html.erb +4 -0
- data/app/components/panda/core/admin/delete_button_component.rb +41 -0
- data/app/components/panda/core/admin/dropdown_button_component.html.erb +28 -0
- data/app/components/panda/core/admin/dropdown_button_component.rb +100 -0
- data/app/components/panda/core/admin/dropdown_component.html.erb +32 -0
- data/app/components/panda/core/admin/dropdown_component.rb +65 -0
- data/app/components/panda/core/admin/empty_state_component.html.erb +16 -0
- data/app/components/panda/core/admin/empty_state_component.rb +25 -0
- data/app/components/panda/core/admin/file_gallery_component.html.erb +58 -0
- data/app/components/panda/core/admin/file_gallery_component.rb +26 -98
- data/app/components/panda/core/admin/filter_pills_component.html.erb +6 -0
- data/app/components/panda/core/admin/filter_pills_component.rb +29 -0
- data/app/components/panda/core/admin/flash_message_component.html.erb +11 -0
- data/app/components/panda/core/admin/flash_message_component.rb +33 -60
- data/app/components/panda/core/admin/form_error_component.html.erb +7 -0
- data/app/components/panda/core/admin/form_error_component.rb +8 -16
- data/app/components/panda/core/admin/form_footer_component.html.erb +16 -0
- data/app/components/panda/core/admin/form_footer_component.rb +65 -0
- data/app/components/panda/core/admin/form_input_component.html.erb +1 -0
- data/app/components/panda/core/admin/form_input_component.rb +21 -13
- data/app/components/panda/core/admin/form_section_component.html.erb +16 -0
- data/app/components/panda/core/admin/form_section_component.rb +64 -0
- data/app/components/panda/core/admin/form_select_component.html.erb +5 -0
- data/app/components/panda/core/admin/form_select_component.rb +25 -26
- data/app/components/panda/core/admin/heading_component.html.erb +26 -0
- data/app/components/panda/core/admin/heading_component.rb +23 -60
- data/app/components/panda/core/admin/login_form_component.html.erb +38 -0
- data/app/components/panda/core/admin/login_form_component.rb +28 -0
- data/app/components/panda/core/admin/main_layout_component.html.erb +88 -0
- data/app/components/panda/core/admin/main_layout_component.rb +19 -0
- data/app/components/panda/core/admin/my_profile/connected_account_component.html.erb +41 -0
- data/app/components/panda/core/admin/my_profile/connected_account_component.rb +27 -0
- data/app/components/panda/core/admin/navigation/item_component.html.erb +34 -0
- data/app/components/panda/core/admin/navigation/item_component.rb +69 -0
- data/app/components/panda/core/admin/navigation/sub_item_component.html.erb +9 -0
- data/app/components/panda/core/admin/navigation/sub_item_component.rb +44 -0
- data/app/components/panda/core/admin/navigation/user_menu_component.html.erb +30 -0
- data/app/components/panda/core/admin/navigation/user_menu_component.rb +36 -0
- data/app/components/panda/core/admin/page_header_component.html.erb +28 -0
- data/app/components/panda/core/admin/page_header_component.rb +19 -35
- data/app/components/panda/core/admin/pagination_component.html.erb +33 -0
- data/app/components/panda/core/admin/pagination_component.rb +56 -0
- data/app/components/panda/core/admin/panel_component.html.erb +19 -0
- data/app/components/panda/core/admin/panel_component.rb +4 -29
- data/app/components/panda/core/admin/prose_panel_component.html.erb +6 -0
- data/app/components/panda/core/admin/prose_panel_component.rb +43 -0
- data/app/components/panda/core/admin/search_bar_component.html.erb +19 -0
- data/app/components/panda/core/admin/search_bar_component.rb +31 -0
- data/app/components/panda/core/admin/search_filter_bar_component.html.erb +22 -0
- data/app/components/panda/core/admin/search_filter_bar_component.rb +27 -0
- data/app/components/panda/core/admin/secret_field_component.html.erb +24 -0
- data/app/components/panda/core/admin/secret_field_component.rb +31 -0
- data/app/components/panda/core/admin/sidebar_component.html.erb +160 -0
- data/app/components/panda/core/admin/sidebar_component.rb +55 -0
- data/app/components/panda/core/admin/slideover_component.html.erb +67 -0
- data/app/components/panda/core/admin/slideover_component.rb +31 -70
- data/app/components/panda/core/admin/statistics_component.html.erb +4 -0
- data/app/components/panda/core/admin/statistics_component.rb +6 -8
- data/app/components/panda/core/admin/tab_bar_component.html.erb +48 -0
- data/app/components/panda/core/admin/tab_bar_component.rb +34 -69
- data/app/components/panda/core/admin/table_component.html.erb +82 -0
- data/app/components/panda/core/admin/table_component.rb +54 -87
- data/app/components/panda/core/admin/tag_badge_component.html.erb +7 -0
- data/app/components/panda/core/admin/tag_badge_component.rb +21 -0
- data/app/components/panda/core/admin/tag_component.html.erb +1 -0
- data/app/components/panda/core/admin/tag_component.rb +51 -20
- data/app/components/panda/core/admin/tag_input_component.html.erb +37 -0
- data/app/components/panda/core/admin/tag_input_component.rb +22 -0
- data/app/components/panda/core/admin/tags_panel_component.html.erb +8 -0
- data/app/components/panda/core/admin/tags_panel_component.rb +33 -0
- data/app/components/panda/core/admin/user_activity_component.html.erb +15 -0
- data/app/components/panda/core/admin/user_activity_component.rb +9 -22
- data/app/components/panda/core/admin/user_display_component.html.erb +8 -0
- data/app/components/panda/core/admin/user_display_component.rb +19 -22
- data/app/components/panda/core/base.rb +35 -42
- data/app/components/panda/core/shared/footer_component.html.erb +2 -0
- data/app/components/panda/core/shared/footer_component.rb +11 -0
- data/app/components/panda/core/shared/header_component.html.erb +32 -0
- data/app/components/panda/core/shared/header_component.rb +19 -0
- data/app/constraints/panda/core/admin_constraint.rb +2 -2
- data/app/controllers/concerns/panda/core/admin_authorization.rb +58 -0
- data/app/controllers/concerns/panda/core/authorizable.rb +107 -0
- data/app/controllers/panda/core/admin/base_controller.rb +62 -3
- data/app/controllers/panda/core/admin/editor/search_controller.rb +17 -0
- data/app/controllers/panda/core/admin/feature_flags_controller.rb +35 -0
- data/app/controllers/panda/core/admin/file_categories_controller.rb +84 -0
- data/app/controllers/panda/core/admin/files_controller.rb +230 -0
- data/app/controllers/panda/core/admin/import_sessions_controller.rb +120 -0
- data/app/controllers/panda/core/admin/my_profile/logins_controller.rb +24 -0
- data/app/controllers/panda/core/admin/my_profile_controller.rb +31 -0
- data/app/controllers/panda/core/admin/search_controller.rb +15 -0
- data/app/controllers/panda/core/admin/sessions_controller.rb +85 -8
- data/app/controllers/panda/core/admin/tags_controller.rb +77 -0
- data/app/controllers/panda/core/admin/test_sessions_controller.rb +5 -6
- data/app/controllers/panda/core/admin/users_controller.rb +184 -0
- data/app/controllers/panda/core/application_controller.rb +13 -1
- data/app/helpers/panda/core/active_storage_helper.rb +24 -0
- data/app/helpers/panda/core/feature_flag_helper.rb +11 -0
- data/app/helpers/panda/core/form_helper.rb +15 -0
- data/app/javascript/panda/core/controllers/avatar_upload_controller.js +73 -0
- data/app/javascript/panda/core/controllers/clipboard_controller.js +119 -0
- data/app/javascript/panda/core/controllers/collapsible_item_controller.js +45 -0
- data/app/javascript/panda/core/controllers/custom_select_controller.js +256 -0
- data/app/javascript/panda/core/controllers/datepicker_controller.js +121 -0
- data/app/javascript/panda/core/controllers/daterange_controller.js +193 -0
- data/app/javascript/panda/core/controllers/file_gallery_controller.js +134 -0
- data/app/javascript/panda/core/controllers/file_upload_controller.js +165 -0
- data/app/javascript/panda/core/controllers/global_search_controller.js +184 -0
- data/app/javascript/panda/core/controllers/index.js +36 -0
- data/app/javascript/panda/core/controllers/mobile_sidebar_controller.js +58 -0
- data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +49 -24
- data/app/javascript/panda/core/controllers/row_link_controller.js +27 -0
- data/app/javascript/panda/core/controllers/tag_input_controller.js +183 -0
- data/app/javascript/panda/core/tailwindcss-stimulus-components.js +2 -3
- data/app/javascript/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js +8129 -0
- data/app/javascript/panda/core/vendor/@hotwired--turbo.js +7197 -100
- data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +511 -2
- data/app/javascript/panda/core/vendor/@tailwindplus--elements@1.0.22.js +65 -0
- data/app/javascript/panda/core/vendor/cropperjs@2.1.0.js +3 -0
- data/app/javascript/panda/core/vendor/vanilla-calendar-pro.js +2 -0
- data/app/models/concerns/panda/core/has_metadata.rb +164 -0
- data/app/models/concerns/panda/core/importable.rb +79 -0
- data/app/models/concerns/panda/core/searchable.rb +66 -0
- data/app/models/concerns/panda/core/taggable.rb +37 -0
- data/app/models/panda/core/feature_flag.rb +56 -0
- data/app/models/panda/core/file_categorization.rb +14 -0
- data/app/models/panda/core/file_category.rb +45 -0
- data/app/models/panda/core/import_session.rb +79 -0
- data/app/models/panda/core/presence.rb +24 -0
- data/app/models/panda/core/tag.rb +33 -0
- data/app/models/panda/core/tagging.rb +14 -0
- data/app/models/panda/core/user.rb +96 -18
- data/app/models/panda/core/user_activity.rb +42 -0
- data/app/models/panda/core/user_session.rb +35 -0
- data/app/services/panda/core/attach_avatar_service.rb +15 -3
- data/app/services/panda/core/csv_import_service.rb +65 -0
- data/app/services/panda/core/file_categorizer.rb +52 -0
- data/app/services/panda/core/file_parser.rb +76 -0
- data/app/services/panda/core/invite_user_service.rb +44 -0
- data/app/services/panda/core/presence_service.rb +49 -0
- data/app/views/layouts/panda/core/admin.html.erb +6 -67
- data/app/views/layouts/panda/core/admin_simple.html.erb +2 -2
- data/app/views/panda/core/admin/dashboard/_default_content.html.erb +1 -73
- data/app/views/panda/core/admin/dashboard/show.html.erb +12 -15
- data/app/views/panda/core/admin/feature_flags/index.html.erb +56 -0
- data/app/views/panda/core/admin/file_categories/_form.html.erb +20 -0
- data/app/views/panda/core/admin/file_categories/edit.html.erb +5 -0
- data/app/views/panda/core/admin/file_categories/index.html.erb +55 -0
- data/app/views/panda/core/admin/file_categories/new.html.erb +5 -0
- data/app/views/panda/core/admin/files/_file_details.html.erb +156 -0
- data/app/views/panda/core/admin/files/index.html.erb +61 -0
- data/app/views/panda/core/admin/import_sessions/column_map.html.erb +43 -0
- data/app/views/panda/core/admin/import_sessions/index.html.erb +48 -0
- data/app/views/panda/core/admin/import_sessions/new.html.erb +37 -0
- data/app/views/panda/core/admin/import_sessions/preview.html.erb +51 -0
- data/app/views/panda/core/admin/import_sessions/show.html.erb +87 -0
- data/app/views/panda/core/admin/my_profile/edit.html.erb +51 -39
- data/app/views/panda/core/admin/my_profile/logins/show.html.erb +72 -0
- data/app/views/panda/core/admin/sessions/new.html.erb +1 -38
- data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +1 -18
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -99
- data/app/views/panda/core/admin/shared/_slideover.html.erb +3 -3
- data/app/views/panda/core/admin/tags/_form.html.erb +21 -0
- data/app/views/panda/core/admin/tags/edit.html.erb +4 -0
- data/app/views/panda/core/admin/tags/index.html.erb +41 -0
- data/app/views/panda/core/admin/tags/new.html.erb +4 -0
- data/app/views/panda/core/admin/users/activity.html.erb +90 -0
- data/app/views/panda/core/admin/users/edit.html.erb +48 -0
- data/app/views/panda/core/admin/users/index.html.erb +161 -0
- data/app/views/panda/core/admin/users/sessions.html.erb +103 -0
- data/app/views/panda/core/admin/users/show.html.erb +224 -0
- data/app/views/panda/core/shared/_footer.html.erb +1 -2
- data/app/views/panda/core/shared/_header.html.erb +4 -22
- data/config/brakeman.ignore +68 -31
- data/config/importmap.rb +13 -11
- data/config/routes.rb +49 -2
- data/db/migrate/20260202171614_create_panda_core_file_categories.rb +30 -0
- data/db/migrate/20260203000001_add_user_management_fields_to_panda_core_users.rb +21 -0
- data/db/migrate/20260203000002_create_panda_core_user_activities.rb +24 -0
- data/db/migrate/20260203000003_create_panda_core_user_sessions.rb +25 -0
- data/db/migrate/20260207100000_create_panda_core_presences.rb +21 -0
- data/db/migrate/20260213000001_fix_active_storage_attachments_record_id_type.rb +44 -0
- data/db/migrate/20260217000001_create_panda_core_feature_flags.rb +14 -0
- data/db/migrate/20260303000001_add_metadata_to_panda_core_users.rb +11 -0
- data/db/migrate/20260310000001_create_panda_core_tags.rb +27 -0
- data/db/migrate/20260310000002_create_panda_core_import_sessions.rb +29 -0
- data/lib/generators/panda/core/install_generator.rb +32 -0
- data/lib/generators/panda/core/templates/panda.rb +63 -0
- data/lib/panda/core/asset_loader.rb +6 -3
- data/lib/panda/core/authentication.rb +3 -3
- data/lib/panda/core/configuration.rb +195 -1
- data/lib/panda/core/engine/admin_controller_config.rb +4 -3
- data/lib/panda/core/engine/autoload_config.rb +21 -9
- data/lib/panda/core/engine/omniauth_config.rb +114 -32
- data/lib/panda/core/engine/pending_migrations_check.rb +46 -0
- data/lib/panda/core/engine/route_config.rb +1 -1
- data/lib/panda/core/engine/sqlite_schema_compatibility.rb +36 -0
- data/lib/panda/core/engine/{phlex_config.rb → view_component_config.rb} +5 -7
- data/lib/panda/core/engine.rb +90 -25
- data/lib/panda/core/middleware.rb +35 -0
- data/lib/panda/core/module_registry.rb +21 -13
- data/lib/panda/core/navigation_registry.rb +455 -0
- data/lib/panda/core/oauth_providers.rb +27 -0
- data/lib/panda/core/permission_registry.rb +89 -0
- data/lib/panda/core/search_registry.rb +69 -0
- data/lib/panda/core/seeds/file_categories.rb +30 -0
- data/lib/panda/core/shared/inflections_config.rb +1 -5
- data/lib/panda/core/testing/rails_helper.rb +87 -0
- data/lib/panda/core/testing/support/authentication_test_helpers.rb +22 -18
- data/lib/panda/core/testing/support/system/cuprite_helpers.rb +4 -4
- data/lib/panda/core/testing/view_component_test_controller.rb +19 -0
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core/widget_registry.rb +50 -0
- data/lib/panda/core.rb +20 -0
- data/lib/tasks/panda/core/file_categories.rake +41 -0
- data/public/panda-core-assets/panda-core-0.12.2.css +2 -0
- data/public/panda-core-assets/panda-core-0.12.3.css +2 -0
- data/public/panda-core-assets/panda-core-0.12.5.css +2 -0
- data/public/panda-core-assets/panda-core-0.13.0.css +2 -0
- data/public/panda-core-assets/panda-core-0.14.0.css +2 -0
- data/public/panda-core-assets/panda-core-0.14.4.css +2 -0
- data/public/panda-core-assets/panda-core.css +2 -2
- data/public/panda-core-assets/vanilla-calendar.css +47 -0
- metadata +205 -26
- data/lib/panda/core/engine/inflections_config.rb +0 -23
|
@@ -4,33 +4,25 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class FormErrorComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def view_template
|
|
11
|
-
return unless should_render?
|
|
12
|
-
|
|
13
|
-
div(**@attrs) do
|
|
14
|
-
div(class: "text-sm text-red-600") do
|
|
15
|
-
error_messages.each do |message|
|
|
16
|
-
p { message }
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
7
|
+
def initialize(model:, **attrs)
|
|
8
|
+
@model = model
|
|
9
|
+
super(**attrs)
|
|
20
10
|
end
|
|
21
11
|
|
|
12
|
+
attr_reader :model
|
|
13
|
+
|
|
22
14
|
def default_attrs
|
|
23
15
|
{
|
|
24
16
|
class: "mb-4 p-4 bg-red-50 border border-red-200 rounded-md"
|
|
25
17
|
}
|
|
26
18
|
end
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def should_render?
|
|
20
|
+
def render?
|
|
31
21
|
error_messages.any?
|
|
32
22
|
end
|
|
33
23
|
|
|
24
|
+
private
|
|
25
|
+
|
|
34
26
|
def error_messages
|
|
35
27
|
@error_messages ||= if @model&.respond_to?(:errors)
|
|
36
28
|
@model.errors.full_messages
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div <%= tag.attributes(**attrs) %>>
|
|
2
|
+
<% if content? %>
|
|
3
|
+
<%= content %>
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<% if cancel_path %>
|
|
7
|
+
<%= link_to "Cancel", cancel_path, class: cancel_link_classes %>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<%= tag.button type: "submit", class: submit_button_classes, data: submit_data_attrs do %>
|
|
11
|
+
<% if computed_icon %>
|
|
12
|
+
<i class="fa-solid <%= computed_icon %>"></i>
|
|
13
|
+
<% end %>
|
|
14
|
+
<%= submit_text %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Standardized form footer component with submit button(s) and optional secondary actions.
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# <%= render Panda::Core::Admin::FormFooterComponent.new(submit_text: "Save") %>
|
|
10
|
+
#
|
|
11
|
+
# @example With icon
|
|
12
|
+
# <%= render Panda::Core::Admin::FormFooterComponent.new(
|
|
13
|
+
# submit_text: "Create Page",
|
|
14
|
+
# icon: "fa-plus"
|
|
15
|
+
# ) %>
|
|
16
|
+
#
|
|
17
|
+
# @example With cancel link
|
|
18
|
+
# <%= render Panda::Core::Admin::FormFooterComponent.new(
|
|
19
|
+
# submit_text: "Update",
|
|
20
|
+
# cancel_path: admin_cms_pages_path
|
|
21
|
+
# ) %>
|
|
22
|
+
#
|
|
23
|
+
# @example With block for custom secondary actions
|
|
24
|
+
# <%= render Panda::Core::Admin::FormFooterComponent.new(submit_text: "Save") do %>
|
|
25
|
+
# <%= link_to "Preview", preview_path, class: "text-sm text-gray-600" %>
|
|
26
|
+
# <% end %>
|
|
27
|
+
#
|
|
28
|
+
class FormFooterComponent < Panda::Core::Base
|
|
29
|
+
def initialize(submit_text: "Save", icon: nil, cancel_path: nil, submit_action: nil, **attrs)
|
|
30
|
+
@submit_text = submit_text
|
|
31
|
+
@icon = icon
|
|
32
|
+
@cancel_path = cancel_path
|
|
33
|
+
@submit_action = submit_action
|
|
34
|
+
super(**attrs)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :submit_text, :icon, :cancel_path, :submit_action
|
|
38
|
+
|
|
39
|
+
def default_attrs
|
|
40
|
+
{
|
|
41
|
+
class: "flex justify-end gap-x-3 mt-2"
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def submit_button_classes
|
|
46
|
+
"inline-flex items-center gap-x-2 justify-center rounded-xl bg-primary-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-primary-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def cancel_link_classes
|
|
50
|
+
"inline-flex items-center justify-center rounded-xl px-3 py-2 text-sm font-medium text-gray-600 hover:text-gray-900"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def computed_icon
|
|
54
|
+
@icon
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def submit_data_attrs
|
|
58
|
+
attrs = {disable_with: "Saving..."}
|
|
59
|
+
attrs[:action] = submit_action if submit_action
|
|
60
|
+
attrs
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.input(**attrs) %>
|
|
@@ -4,18 +4,19 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class FormInputComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
input(**@attrs)
|
|
7
|
+
def initialize(name: "", value: nil, placeholder: nil, autocomplete: nil, type: :text, required: false, disabled: false, **attrs)
|
|
8
|
+
@name = name
|
|
9
|
+
@value = value
|
|
10
|
+
@type = type
|
|
11
|
+
@placeholder = placeholder
|
|
12
|
+
@required = required
|
|
13
|
+
@disabled = disabled
|
|
14
|
+
@autocomplete = autocomplete
|
|
15
|
+
super(**attrs)
|
|
17
16
|
end
|
|
18
17
|
|
|
18
|
+
attr_reader :name, :value, :type, :placeholder, :required, :disabled, :autocomplete
|
|
19
|
+
|
|
19
20
|
def default_attrs
|
|
20
21
|
base_attrs = {
|
|
21
22
|
type: @type.to_s,
|
|
@@ -36,12 +37,19 @@ module Panda
|
|
|
36
37
|
private
|
|
37
38
|
|
|
38
39
|
def input_classes
|
|
39
|
-
|
|
40
|
+
# Border/radius/focus-ring are themeable via the --panda-input-*
|
|
41
|
+
# tokens (see app/assets/tailwind/application.css). The var()
|
|
42
|
+
# fallbacks resolve to today's rounded-xl/border-gray-300/
|
|
43
|
+
# outline-primary-500 exactly, so this is a no-op unless a host
|
|
44
|
+
# app sets data-theme.
|
|
45
|
+
classes = "block w-full h-11 rounded-[var(--panda-input-radius,var(--radius-xl))] border border-[var(--panda-input-border,var(--color-gray-300))] bg-white px-3 py-2 text-gray-900 " \
|
|
46
|
+
"placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-[var(--panda-input-focus-ring,var(--color-primary-500))] " \
|
|
47
|
+
"dark:bg-white/5 dark:text-white dark:border-white/10 dark:placeholder:text-gray-500"
|
|
40
48
|
|
|
41
49
|
if @disabled
|
|
42
|
-
classes + "
|
|
50
|
+
classes + " border-gray-200 bg-gray-100 text-gray-400 cursor-not-allowed"
|
|
43
51
|
else
|
|
44
|
-
classes
|
|
52
|
+
classes
|
|
45
53
|
end
|
|
46
54
|
end
|
|
47
55
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div <%= tag.attributes(**attrs) %>>
|
|
2
|
+
<h3 class="<%= heading_classes %>">
|
|
3
|
+
<% if icon %>
|
|
4
|
+
<i class="fa-solid <%= icon %>"></i>
|
|
5
|
+
<% end %>
|
|
6
|
+
<%= title %>
|
|
7
|
+
</h3>
|
|
8
|
+
|
|
9
|
+
<% if description %>
|
|
10
|
+
<p class="<%= description_classes %>"><%= description %></p>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div class="<%= content_classes %>">
|
|
14
|
+
<%= content %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Standardized form section component for grouping related form fields.
|
|
7
|
+
# Provides a consistent heading style for form sections without the heavy
|
|
8
|
+
# visual weight of PanelComponent.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# <%= render Panda::Core::Admin::FormSectionComponent.new(title: "SEO Settings") do %>
|
|
12
|
+
# <%= f.text_field :seo_title %>
|
|
13
|
+
# <%= f.text_area :seo_description %>
|
|
14
|
+
# <% end %>
|
|
15
|
+
#
|
|
16
|
+
# @example With description
|
|
17
|
+
# <%= render Panda::Core::Admin::FormSectionComponent.new(
|
|
18
|
+
# title: "Notification Settings",
|
|
19
|
+
# description: "Configure how you receive notifications"
|
|
20
|
+
# ) do %>
|
|
21
|
+
# ...
|
|
22
|
+
# <% end %>
|
|
23
|
+
#
|
|
24
|
+
# @example With icon
|
|
25
|
+
# <%= render Panda::Core::Admin::FormSectionComponent.new(
|
|
26
|
+
# title: "Menu Items",
|
|
27
|
+
# icon: "fa-bars"
|
|
28
|
+
# ) do %>
|
|
29
|
+
# ...
|
|
30
|
+
# <% end %>
|
|
31
|
+
#
|
|
32
|
+
class FormSectionComponent < Panda::Core::Base
|
|
33
|
+
def initialize(title:, description: nil, icon: nil, border_top: true, **attrs)
|
|
34
|
+
@title = title
|
|
35
|
+
@description = description
|
|
36
|
+
@icon = icon
|
|
37
|
+
@border_top = border_top
|
|
38
|
+
super(**attrs)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attr_reader :title, :description, :icon, :border_top
|
|
42
|
+
|
|
43
|
+
def default_attrs
|
|
44
|
+
base_classes = "mt-6 pt-4"
|
|
45
|
+
base_classes += " border-t border-gray-200" if border_top
|
|
46
|
+
|
|
47
|
+
{class: base_classes}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def heading_classes
|
|
51
|
+
"text-sm font-semibold text-gray-700 flex items-center gap-2"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def description_classes
|
|
55
|
+
"mt-1 text-xs text-gray-500"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def content_classes
|
|
59
|
+
"mt-4 space-y-4"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -4,22 +4,18 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class FormSelectComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def view_template
|
|
16
|
-
select(**@attrs) do
|
|
17
|
-
render_prompt if @prompt
|
|
18
|
-
render_blank if @include_blank && !@prompt
|
|
19
|
-
render_options
|
|
20
|
-
end
|
|
7
|
+
def initialize(prompt:, name: "", options: [], required: false, disabled: false, include_blank: false, **attrs)
|
|
8
|
+
@name = name
|
|
9
|
+
@options = options
|
|
10
|
+
@prompt = prompt
|
|
11
|
+
@required = required
|
|
12
|
+
@disabled = disabled
|
|
13
|
+
@include_blank = include_blank
|
|
14
|
+
super(**attrs)
|
|
21
15
|
end
|
|
22
16
|
|
|
17
|
+
attr_reader :name, :options, :prompt, :required, :disabled, :include_blank
|
|
18
|
+
|
|
23
19
|
def default_attrs
|
|
24
20
|
base_attrs = {
|
|
25
21
|
name: @name,
|
|
@@ -36,31 +32,34 @@ module Panda
|
|
|
36
32
|
private
|
|
37
33
|
|
|
38
34
|
def select_classes
|
|
39
|
-
classes = "block w-full rounded-
|
|
35
|
+
classes = "block w-full h-11 rounded-xl border border-gray-300 bg-white px-3 py-2 text-gray-900 " \
|
|
36
|
+
"focus:outline-2 focus:-outline-offset-2 focus:outline-primary-500 " \
|
|
37
|
+
"dark:bg-white/5 dark:text-white dark:border-white/10"
|
|
40
38
|
|
|
41
39
|
if @disabled
|
|
42
|
-
classes + "
|
|
40
|
+
classes + " border-gray-200 bg-gray-100 text-gray-400 cursor-not-allowed"
|
|
43
41
|
else
|
|
44
|
-
classes
|
|
42
|
+
classes
|
|
45
43
|
end
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
def render_prompt
|
|
49
|
-
option
|
|
47
|
+
content_tag(:option, @prompt, value: "", disabled: true, selected: @selected.nil?)
|
|
50
48
|
end
|
|
51
49
|
|
|
52
50
|
def render_blank
|
|
53
|
-
|
|
51
|
+
content_tag(:option, "", value: "")
|
|
54
52
|
end
|
|
55
53
|
|
|
56
54
|
def render_options
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
safe_join(
|
|
56
|
+
@options.map do |option_data|
|
|
57
|
+
label, value = option_data
|
|
58
|
+
option_attrs = {value: value.to_s}
|
|
59
|
+
option_attrs[:selected] = true if value.to_s == @selected.to_s
|
|
60
|
+
content_tag(:option, label.to_s, **option_attrs)
|
|
61
|
+
end
|
|
62
|
+
)
|
|
64
63
|
end
|
|
65
64
|
end
|
|
66
65
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div class="heading-wrapper">
|
|
2
|
+
<% heading_tag = case level when 1 then :h1 when 2 then :h2 when 3 then :h3 when :panel then :h3 else :h2 end %>
|
|
3
|
+
|
|
4
|
+
<%= tag.send(heading_tag, class: heading_classes) do %>
|
|
5
|
+
<div class="grow flex items-center gap-x-2">
|
|
6
|
+
<% if icon.present? %>
|
|
7
|
+
<%= tag.i(class: icon) %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<span><%= text %></span>
|
|
10
|
+
</div>
|
|
11
|
+
<% if buttons.any? || dropdown_buttons.any? %>
|
|
12
|
+
<span class="actions flex gap-x-2 mt-1 min-h-[2.5rem]">
|
|
13
|
+
<% dropdown_buttons.each do |dropdown_button| %>
|
|
14
|
+
<%= dropdown_button %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% buttons.each do |button| %>
|
|
17
|
+
<%= button %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</span>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% end %>
|
|
22
|
+
|
|
23
|
+
<% if meta %>
|
|
24
|
+
<%= tag.p(raw(meta), class: "text-sm text-black/60 -mt-1 mb-5") %>
|
|
25
|
+
<% end %>
|
|
26
|
+
</div>
|
|
@@ -4,78 +4,41 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class HeadingComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
when 2
|
|
22
|
-
h2(class: heading_classes(@meta.present?)) { render_content }
|
|
23
|
-
when 3
|
|
24
|
-
h3(class: heading_classes(@meta.present?)) { render_content }
|
|
25
|
-
when :panel
|
|
26
|
-
h3(class: panel_heading_classes) { @text }
|
|
27
|
-
else
|
|
28
|
-
h2(class: heading_classes(@meta.present?)) { render_content }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
if @meta
|
|
32
|
-
p(class: "text-sm text-black/60 -mt-1 mb-5") { raw(@meta) }
|
|
33
|
-
end
|
|
34
|
-
end
|
|
7
|
+
renders_many :buttons, Panda::Core::Admin::ButtonComponent
|
|
8
|
+
renders_many :dropdown_buttons, Panda::Core::Admin::DropdownButtonComponent
|
|
9
|
+
|
|
10
|
+
def initialize(text: "", icon: "", meta: nil, level: 2, additional_styles: nil, **attrs, &block)
|
|
11
|
+
@text = text
|
|
12
|
+
@icon = icon
|
|
13
|
+
@meta = meta
|
|
14
|
+
@level = level
|
|
15
|
+
@additional_styles = additional_styles
|
|
16
|
+
super(**attrs)
|
|
17
|
+
# Execute block if passed to new() directly (for tests/direct instantiation)
|
|
18
|
+
# This allows: HeadingComponent.new(text: "Pages") { |h| h.with_button(...) }
|
|
19
|
+
# Note: When used via render() with a block, ViewComponent handles the block separately
|
|
20
|
+
yield self if block_given?
|
|
35
21
|
end
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
@buttons ||= []
|
|
39
|
-
@buttons << Panda::Core::Admin::ButtonComponent.new(**props)
|
|
40
|
-
end
|
|
23
|
+
attr_reader :text, :icon, :meta, :level
|
|
41
24
|
|
|
42
25
|
private
|
|
43
26
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
span { @text }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
span(class: "actions flex gap-x-2 mt-1 min-h-[2.5rem]") do
|
|
51
|
-
@buttons&.each { |btn| render(btn) }
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def heading_classes(has_meta = false)
|
|
56
|
-
margin_bottom = has_meta ? "mb-0.5" : "mb-5"
|
|
57
|
-
base = "flex text-black #{margin_bottom} -mt-2"
|
|
27
|
+
def heading_classes
|
|
28
|
+
margin_bottom = @meta.present? ? "mb-0.5" : "mb-5"
|
|
29
|
+
base = "flex items-center gap-2 text-gray-900 #{margin_bottom}"
|
|
58
30
|
styles = case @level
|
|
59
31
|
when 1
|
|
60
|
-
"text-2xl font-
|
|
32
|
+
"text-2xl font-semibold"
|
|
61
33
|
when 2
|
|
62
|
-
"text-xl font-
|
|
34
|
+
"text-xl font-semibold"
|
|
63
35
|
when 3
|
|
64
|
-
"text-
|
|
36
|
+
"text-lg font-medium"
|
|
65
37
|
else
|
|
66
|
-
"text-xl font-
|
|
38
|
+
"text-xl font-semibold"
|
|
67
39
|
end
|
|
68
40
|
|
|
69
|
-
[base, styles,
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def panel_heading_classes
|
|
73
|
-
"text-base font-medium px-4 py-3 text-white"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def additional_styles_array
|
|
77
|
-
return [] if @additional_styles.blank?
|
|
78
|
-
@additional_styles.is_a?(String) ? @additional_styles.split(" ") : @additional_styles
|
|
41
|
+
[base, styles, @additional_styles].compact.join(" ")
|
|
79
42
|
end
|
|
80
43
|
end
|
|
81
44
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div <%= tag.attributes(**attrs) %>>
|
|
2
|
+
<div class="text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
3
|
+
<% if Panda::Core.config.login_logo_path %>
|
|
4
|
+
<img src="<%= Panda::Core.config.login_logo_path %>" class="py-2 mx-auto w-auto h-32">
|
|
5
|
+
<% end %>
|
|
6
|
+
<h2 class="mt-10 mb-6 text-2xl font-bold text-center text-white">
|
|
7
|
+
<%= Panda::Core.config.login_page_title || "Sign in to your account" %>
|
|
8
|
+
</h2>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<% if @providers&.any? %>
|
|
12
|
+
<% @providers.each do |provider| %>
|
|
13
|
+
<% provider_config = Panda::Core.config.authentication_providers[provider] %>
|
|
14
|
+
<% provider_path = provider_config&.dig(:path_name) || provider %>
|
|
15
|
+
<div class="mt-4 text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
16
|
+
<%= helpers.form_tag "#{Panda::Core.config.admin_path}/auth/#{provider_path}", method: "post", data: {turbo: false} do %>
|
|
17
|
+
<button type="submit" id="button-sign-in-<%= provider_path %>" class="inline-flex gap-x-2 items-center py-2.5 px-3.5 mx-auto mb-4 bg-white text-gray-900 rounded-md border min-w-56 border-neutral-400 hover:bg-gray-50">
|
|
18
|
+
<i class="<%= oauth_provider_non_brand?(provider) ? 'fa-solid' : 'fa-brands' %> fa-<%= oauth_provider_icon(provider) %> text-xl mr-1"></i>
|
|
19
|
+
Sign in with <%= oauth_provider_name(provider, provider_config) %>
|
|
20
|
+
</button>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% else %>
|
|
25
|
+
<div class="rounded-md bg-yellow-50 p-4 sm:mx-auto sm:w-full sm:max-w-sm">
|
|
26
|
+
<div class="flex">
|
|
27
|
+
<div class="ml-3">
|
|
28
|
+
<h3 class="text-sm font-medium text-yellow-800">
|
|
29
|
+
No authentication providers configured
|
|
30
|
+
</h3>
|
|
31
|
+
<div class="mt-2 text-sm text-yellow-700">
|
|
32
|
+
<p>Please configure at least one authentication provider in your Panda Core configuration.</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Login form component for OAuth provider authentication
|
|
7
|
+
# Displays configured authentication providers for user login
|
|
8
|
+
class LoginFormComponent < Panda::Core::Base
|
|
9
|
+
include Panda::Core::SessionsHelper
|
|
10
|
+
|
|
11
|
+
def initialize(providers: [], **attrs)
|
|
12
|
+
@providers = providers
|
|
13
|
+
super(**attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :providers
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def default_attrs
|
|
21
|
+
{
|
|
22
|
+
class: "flex flex-col justify-center py-12 px-6 min-h-full text-center lg:px-8"
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<%= render Panda::Core::Shared::HeaderComponent.new(html_class: "h-full", body_class: "") %>
|
|
2
|
+
|
|
3
|
+
<div class="flex h-full" id="panda-container">
|
|
4
|
+
<div data-controller="mobile-sidebar" data-action="keydown.esc@window->mobile-sidebar#close">
|
|
5
|
+
<div data-mobile-sidebar-target="backdrop" class="hidden fixed inset-0 bg-gray-900/50 z-40 lg:hidden" data-action="click->mobile-sidebar#close"></div>
|
|
6
|
+
<div class="absolute top-0 w-full lg:flex lg:fixed lg:inset-y-0 z-50 lg:flex-col lg:w-72">
|
|
7
|
+
<div data-mobile-sidebar-target="sidebar" class="flex overflow-y-auto flex-col gap-y-5 px-4 pb-4 max-h-16 bg-gradient-admin lg:max-h-full grow">
|
|
8
|
+
<div class="flex items-center justify-between pt-4 -mb-5 lg:hidden">
|
|
9
|
+
<button data-mobile-sidebar-target="hamburger" data-action="click->mobile-sidebar#toggle" type="button" class="text-white/80 hover:text-white p-1" aria-expanded="false" aria-label="Toggle navigation">
|
|
10
|
+
<i class="fa-solid fa-bars text-xl"></i>
|
|
11
|
+
</button>
|
|
12
|
+
<span class="text-white font-medium text-lg"><%= Panda::Core.config.admin_title || "Panda Admin" %></span>
|
|
13
|
+
<span class="w-7"></span>
|
|
14
|
+
</div>
|
|
15
|
+
<%= render Panda::Core::Admin::SidebarComponent.new(user: @user) %>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="flex flex-col flex-1 mt-16 ml-0 lg:mt-0 lg:ml-72" id="panda-inner-container" <% if content_for :sidebar %> data-controller="toggle" data-action="keydown.esc@window->toggle#hide keydown.escape@window->toggle#hide" tabindex="-1"<% end %>>
|
|
20
|
+
<section id="panda-main" class="flex flex-row h-full">
|
|
21
|
+
<div class="flex-1 h-full overflow-y-auto pb-8" id="panda-primary-content">
|
|
22
|
+
<%= render Panda::Core::Admin::BreadcrumbsComponent.new(breadcrumbs: @breadcrumbs) %>
|
|
23
|
+
<%= render "panda/core/admin/shared/flash" %>
|
|
24
|
+
<%= content %>
|
|
25
|
+
</div>
|
|
26
|
+
<% if content_for :sidebar %>
|
|
27
|
+
<!-- Backdrop overlay -->
|
|
28
|
+
<div data-toggle-target="toggleable" class="hidden fixed inset-0 bg-gray-500/75 transition-opacity duration-500 ease-in-out dark:bg-gray-900/50 z-40"
|
|
29
|
+
data-transition-enter="transition-opacity duration-500 ease-in-out"
|
|
30
|
+
data-transition-enter-from="opacity-0"
|
|
31
|
+
data-transition-enter-to="opacity-100"
|
|
32
|
+
data-transition-leave="transition-opacity duration-500 ease-in-out"
|
|
33
|
+
data-transition-leave-from="opacity-100"
|
|
34
|
+
data-transition-leave-to="opacity-0"></div>
|
|
35
|
+
|
|
36
|
+
<!-- Slideover panel -->
|
|
37
|
+
<div data-toggle-target="toggleable" class="hidden ml-auto block size-full max-w-md transform fixed right-0 top-0 h-full z-50"
|
|
38
|
+
data-transition-enter="transform transition ease-in-out duration-500 sm:duration-700"
|
|
39
|
+
data-transition-enter-from="translate-x-full"
|
|
40
|
+
data-transition-enter-to="translate-x-0"
|
|
41
|
+
data-transition-leave="transform transition ease-in-out duration-500 sm:duration-700"
|
|
42
|
+
data-transition-leave-from="translate-x-0"
|
|
43
|
+
data-transition-leave-to="translate-x-full"
|
|
44
|
+
id="slideover">
|
|
45
|
+
<div class="relative flex h-full flex-col bg-white shadow-xl dark:bg-gray-800 dark:after:absolute dark:after:inset-y-0 dark:after:left-0 dark:after:w-px dark:after:bg-white/10">
|
|
46
|
+
<!-- Header -->
|
|
47
|
+
<div class="bg-gradient-admin px-4 py-3 sm:px-6">
|
|
48
|
+
<div class="flex items-center justify-between">
|
|
49
|
+
<h2 class="text-base font-semibold text-white" id="slideover-title"><%= content_for?(:sidebar_title) ? yield(:sidebar_title) : "Sidebar" %></h2>
|
|
50
|
+
<div class="ml-3 flex items-center">
|
|
51
|
+
<button type="button" data-action="click->toggle#toggle touch->toggle#toggle" class="flex items-center gap-x-1 rounded-full px-2 py-1 text-xs text-white/80 bg-white/10 hover:text-white hover:bg-white/20 transition-colors">
|
|
52
|
+
<span>Close</span>
|
|
53
|
+
<i class="fa-solid fa-xmark"></i>
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Content -->
|
|
60
|
+
<% if content_for?(:sidebar) %>
|
|
61
|
+
<div class="flex-1 overflow-y-auto">
|
|
62
|
+
<%= yield :sidebar %>
|
|
63
|
+
</div>
|
|
64
|
+
<% end %>
|
|
65
|
+
|
|
66
|
+
<!-- Footer (if present) -->
|
|
67
|
+
<% if content_for?(:sidebar_footer) %>
|
|
68
|
+
<div class="flex shrink-0 justify-end gap-x-3 border-t border-gray-200 px-4 py-4 dark:border-white/10">
|
|
69
|
+
<%= yield :sidebar_footer %>
|
|
70
|
+
</div>
|
|
71
|
+
<% end %>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<script>
|
|
75
|
+
window.addEventListener('keydown', function(e) {
|
|
76
|
+
if (e.key === 'Escape') {
|
|
77
|
+
document.querySelectorAll('[data-toggle-target="toggleable"]').forEach(function(el) {
|
|
78
|
+
el.classList.add('hidden');
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
<% end %>
|
|
84
|
+
</section>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<%= render Panda::Core::Shared::FooterComponent.new %>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Main admin layout component
|
|
7
|
+
# Handles the full admin page layout with sidebar, header, and main content
|
|
8
|
+
class MainLayoutComponent < ViewComponent::Base
|
|
9
|
+
def initialize(user:, breadcrumbs: [], **attrs)
|
|
10
|
+
super()
|
|
11
|
+
@user = user
|
|
12
|
+
@breadcrumbs = breadcrumbs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :user, :breadcrumbs
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|