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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<nav aria-label="Breadcrumb" id="panda-breadcrumbs" <% if @attrs.any? %><%= tag.attributes(@attrs) %><% end %>>
|
|
2
|
+
<ol role="list">
|
|
3
|
+
<li class="inline-block">
|
|
4
|
+
<a href="<%= Panda::Core.config.admin_path %>" class="text-black/60 hover:text-black/80">
|
|
5
|
+
<i class="fa fa-solid fa-house fa-fw inline-block w-4 text-center"></i>
|
|
6
|
+
<span class="sr-only">Home</span>
|
|
7
|
+
</a>
|
|
8
|
+
</li>
|
|
9
|
+
<% @breadcrumbs.each do |crumb| %>
|
|
10
|
+
<li class="inline-block">
|
|
11
|
+
<i class="fa-solid fa-chevron-right fa-fw inline-block w-4 text-center font-light text-black/60 hover:text-black/80 px-2"></i>
|
|
12
|
+
<a href="<%= crumb.path %>" class="text-sm font-normal text-black/60 hover:text-black/80"><%= crumb.name %></a>
|
|
13
|
+
</li>
|
|
14
|
+
<% end %>
|
|
15
|
+
</ol>
|
|
16
|
+
</nav>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Breadcrumbs navigation component for admin pages
|
|
7
|
+
# Displays hierarchical navigation starting from admin home
|
|
8
|
+
class BreadcrumbsComponent < Panda::Core::Base
|
|
9
|
+
def initialize(breadcrumbs: [], **attrs)
|
|
10
|
+
@breadcrumbs = breadcrumbs
|
|
11
|
+
super(**attrs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :breadcrumbs
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def default_attrs
|
|
19
|
+
{
|
|
20
|
+
class: "px-4 w-full sm:px-6 py-2.5"
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -4,26 +4,19 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class ButtonComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if @as_button
|
|
17
|
-
button(**@attrs) do
|
|
18
|
-
render_content
|
|
19
|
-
end
|
|
20
|
-
else
|
|
21
|
-
a(**@attrs) do
|
|
22
|
-
render_content
|
|
23
|
-
end
|
|
24
|
-
end
|
|
7
|
+
def initialize(text: "Button", action: nil, href: "#", icon: nil, size: :regular, id: nil, as_button: false, **attrs)
|
|
8
|
+
@text = text
|
|
9
|
+
@action = action
|
|
10
|
+
@href = href
|
|
11
|
+
@icon = icon
|
|
12
|
+
@size = size
|
|
13
|
+
@id = id
|
|
14
|
+
@as_button = as_button
|
|
15
|
+
super(**attrs)
|
|
25
16
|
end
|
|
26
17
|
|
|
18
|
+
attr_reader :text, :action, :href, :icon, :size, :id, :as_button
|
|
19
|
+
|
|
27
20
|
def default_attrs
|
|
28
21
|
base = {
|
|
29
22
|
class: button_classes,
|
|
@@ -40,10 +33,22 @@ module Panda
|
|
|
40
33
|
private
|
|
41
34
|
|
|
42
35
|
def render_content
|
|
43
|
-
if computed_icon
|
|
44
|
-
i
|
|
36
|
+
icon_html = if computed_icon
|
|
37
|
+
content_tag(:i, "", class: "fa-solid fa-#{computed_icon}")
|
|
38
|
+
else
|
|
39
|
+
"".html_safe
|
|
45
40
|
end
|
|
46
|
-
|
|
41
|
+
text_html = @text.titleize
|
|
42
|
+
|
|
43
|
+
if icon_right?
|
|
44
|
+
(text_html + icon_html).html_safe
|
|
45
|
+
else
|
|
46
|
+
(icon_html + text_html).html_safe
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def icon_right?
|
|
51
|
+
@action.in?(%i[save create add new])
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
def computed_icon
|
|
@@ -51,35 +56,35 @@ module Panda
|
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
def button_classes
|
|
54
|
-
base = "inline-flex items-center rounded-
|
|
59
|
+
base = "inline-flex items-center rounded-xl font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 cursor-pointer transition-colors "
|
|
55
60
|
base + size_classes + action_classes
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
def size_classes
|
|
59
64
|
case @size
|
|
60
65
|
when :small, :sm
|
|
61
|
-
"gap-x-1.5 px-
|
|
66
|
+
"gap-x-1.5 px-3 py-1.5 text-xs "
|
|
62
67
|
when :medium, :regular, :md
|
|
63
|
-
"gap-x-
|
|
68
|
+
"gap-x-2 px-4 py-2 text-sm "
|
|
64
69
|
when :large, :lg
|
|
65
|
-
"gap-x-2 px-
|
|
70
|
+
"gap-x-2 px-5 py-2.5 text-base "
|
|
66
71
|
else
|
|
67
|
-
"gap-x-
|
|
72
|
+
"gap-x-2 px-4 py-2 text-sm "
|
|
68
73
|
end
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
def action_classes
|
|
72
77
|
case @action
|
|
73
|
-
when :save, :create
|
|
74
|
-
"text-white bg-
|
|
78
|
+
when :save, :create, :add, :new
|
|
79
|
+
"text-white bg-primary-500 hover:bg-primary-600 focus-visible:outline-primary-600"
|
|
75
80
|
when :save_inactive
|
|
76
|
-
"text-white bg-gray-
|
|
81
|
+
"text-white bg-gray-300 cursor-not-allowed"
|
|
77
82
|
when :secondary
|
|
78
|
-
"text-gray-700 border
|
|
83
|
+
"text-gray-700 border border-gray-200 bg-white hover:bg-gray-50"
|
|
79
84
|
when :delete, :destroy, :danger
|
|
80
|
-
"text-
|
|
85
|
+
"text-error-600 border border-error-200 bg-error-50 hover:bg-error-100 focus-visible:outline-error-300"
|
|
81
86
|
else
|
|
82
|
-
"text-gray-700 border
|
|
87
|
+
"text-gray-700 border border-gray-200 bg-white hover:bg-gray-50"
|
|
83
88
|
end
|
|
84
89
|
end
|
|
85
90
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%= content_tag :div, **attrs do %>
|
|
2
|
+
<div class="flex items-start gap-3">
|
|
3
|
+
<i class="fa-solid <%= icon_class %> mt-0.5"></i>
|
|
4
|
+
<div>
|
|
5
|
+
<% if title.present? %>
|
|
6
|
+
<div class="text-sm font-medium"><%= title %></div>
|
|
7
|
+
<% end %>
|
|
8
|
+
<div class="text-sm">
|
|
9
|
+
<% if content? %>
|
|
10
|
+
<%= content %>
|
|
11
|
+
<% else %>
|
|
12
|
+
<%= text %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Lightweight callout for inline notices (info, warning, success, error).
|
|
7
|
+
# Intended for static, in-form guidance (not toast/flash).
|
|
8
|
+
class CalloutComponent < Panda::Core::Base
|
|
9
|
+
def initialize(text: nil, title: nil, kind: :info, icon: nil, **attrs)
|
|
10
|
+
@text = text
|
|
11
|
+
@title = title
|
|
12
|
+
@kind = kind
|
|
13
|
+
@icon = icon
|
|
14
|
+
super(**attrs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :text, :title, :kind, :icon
|
|
18
|
+
|
|
19
|
+
def default_attrs
|
|
20
|
+
{
|
|
21
|
+
class: "rounded-2xl border px-4 py-3 text-sm #{tone_classes}"
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def icon_class
|
|
26
|
+
icon || default_icon
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def default_icon
|
|
30
|
+
case kind
|
|
31
|
+
when :success
|
|
32
|
+
"fa-circle-check"
|
|
33
|
+
when :warning
|
|
34
|
+
"fa-triangle-exclamation"
|
|
35
|
+
when :error, :alert
|
|
36
|
+
"fa-circle-xmark"
|
|
37
|
+
else
|
|
38
|
+
"fa-circle-info"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def tone_classes
|
|
43
|
+
case kind
|
|
44
|
+
when :success
|
|
45
|
+
"bg-emerald-50 text-emerald-700 border-emerald-200"
|
|
46
|
+
when :warning
|
|
47
|
+
"bg-amber-50 text-amber-700 border-amber-200"
|
|
48
|
+
when :error, :alert
|
|
49
|
+
"bg-rose-50 text-rose-700 border-rose-200"
|
|
50
|
+
else
|
|
51
|
+
"bg-gray-50 text-gray-700 border-gray-200"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Code block component for snippets and examples.
|
|
7
|
+
class CodeBlockComponent < Panda::Core::Base
|
|
8
|
+
def initialize(code: nil, **attrs)
|
|
9
|
+
@code = code
|
|
10
|
+
super(**attrs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :code
|
|
14
|
+
|
|
15
|
+
def default_attrs
|
|
16
|
+
{
|
|
17
|
+
class: "rounded-xl bg-gray-900/95 p-3 text-xs text-emerald-200 font-mono overflow-x-auto"
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<div class="relative" data-controller="toggle">
|
|
2
|
+
<main class="overflow-auto flex-1 h-full min-h-full max-h-full">
|
|
3
|
+
<div class="overflow-auto px-2 pt-2 mx-auto sm:px-6 lg:px-6">
|
|
4
|
+
<% if heading_slot? %>
|
|
5
|
+
<%= heading_slot %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<% if tab_bar_slot? %>
|
|
9
|
+
<%= tab_bar_slot %>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<section class="<%= section_classes %>">
|
|
13
|
+
<div class="flex-1 mt-4 w-full h-full">
|
|
14
|
+
<% if body_slot? %>
|
|
15
|
+
<%= body_slot %>
|
|
16
|
+
<% elsif content.present? %>
|
|
17
|
+
<%= content %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
</div>
|
|
22
|
+
</main>
|
|
23
|
+
|
|
24
|
+
<% if slideover_slot? %>
|
|
25
|
+
<%= slideover_slot %>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
@@ -4,90 +4,24 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class ContainerComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Called from Phlex - execute block directly to set instance variables
|
|
17
|
-
yield(self)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Set content_for :sidebar if slideover is present (enables breadcrumb toggle button)
|
|
22
|
-
# This must happen before rendering so the layout can use it
|
|
23
|
-
if @slideover_block && @slideover_title && defined?(view_context) && view_context
|
|
24
|
-
view_context.content_for(:sidebar) do
|
|
25
|
-
# The block contains ERB content, capture it for the sidebar
|
|
26
|
-
view_context.capture(&@slideover_block)
|
|
27
|
-
end
|
|
28
|
-
view_context.content_for(:sidebar_title, @slideover_title)
|
|
29
|
-
|
|
30
|
-
# Set footer content if present
|
|
31
|
-
if @footer_block
|
|
32
|
-
view_context.content_for(:sidebar_footer) do
|
|
33
|
-
view_context.capture(&@footer_block)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
main(class: "overflow-auto flex-1 h-full min-h-full max-h-full") do
|
|
39
|
-
div(class: "overflow-auto px-2 pt-2 mx-auto sm:px-6 lg:px-6") do
|
|
40
|
-
@heading_content&.call
|
|
41
|
-
@tab_bar_content&.call
|
|
42
|
-
|
|
43
|
-
section(class: section_classes) do
|
|
44
|
-
div(class: "flex-1 mt-4 w-full h-full") do
|
|
45
|
-
if @main_content
|
|
46
|
-
@main_content.call
|
|
47
|
-
elsif @body_html
|
|
48
|
-
raw(@body_html)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def content(&block)
|
|
57
|
-
@main_content = if defined?(view_context) && view_context
|
|
58
|
-
# Capture ERB content
|
|
59
|
-
-> { raw(view_context.capture(&block)) }
|
|
60
|
-
else
|
|
61
|
-
block
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def heading(**props, &block)
|
|
66
|
-
@heading_content = -> { render(Panda::Core::Admin::HeadingComponent.new(**props), &block) }
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def tab_bar(**props, &block)
|
|
70
|
-
@tab_bar_content = -> { render(Panda::Core::Admin::TabBarComponent.new(**props), &block) } if defined?(Panda::Core::Admin::TabBarComponent)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def slideover(**props, &block)
|
|
74
|
-
@slideover_title = props[:title] || "Settings"
|
|
75
|
-
@slideover_block = block # Save the block for content_for
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def footer(&block)
|
|
79
|
-
@footer_block = block
|
|
7
|
+
renders_one :heading_slot, Panda::Core::Admin::HeadingComponent
|
|
8
|
+
renders_one :tab_bar_slot, Panda::Core::Admin::TabBarComponent
|
|
9
|
+
renders_one :body_slot
|
|
10
|
+
renders_one :slideover_slot, Panda::Core::Admin::SlideoverComponent
|
|
11
|
+
renders_one :footer_slot
|
|
12
|
+
|
|
13
|
+
def initialize(full_height: false, **attrs)
|
|
14
|
+
@full_height = full_height
|
|
15
|
+
super(**attrs)
|
|
80
16
|
end
|
|
81
17
|
|
|
82
|
-
|
|
83
|
-
alias_method :with_slideover, :slideover
|
|
84
|
-
alias_method :with_footer, :footer
|
|
18
|
+
attr_reader :full_height
|
|
85
19
|
|
|
86
20
|
private
|
|
87
21
|
|
|
88
22
|
def section_classes
|
|
89
23
|
base = "flex-auto"
|
|
90
|
-
height =
|
|
24
|
+
height = full_height ? "h-[calc(100vh-9rem)]" : nil
|
|
91
25
|
[base, height].compact.join(" ")
|
|
92
26
|
end
|
|
93
27
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%= render Panda::Core::Admin::PanelComponent.new do |panel| %>
|
|
2
|
+
<% panel.with_heading_slot { heading } %>
|
|
3
|
+
<% panel.with_body_slot do %>
|
|
4
|
+
<div class="flex flex-wrap gap-3">
|
|
5
|
+
<% contributors.each do |contributor| %>
|
|
6
|
+
<%= render Panda::Core::Admin::UserDisplayComponent.new(user: contributor) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
</div>
|
|
9
|
+
<p class="mt-4 text-sm text-gray-500">
|
|
10
|
+
<%= pluralize(contributors.size, "contributor") %> •
|
|
11
|
+
<%= pluralize(total_count, count_label) %> •
|
|
12
|
+
Last updated <%= time_ago_in_words(last_updated_at) %> ago
|
|
13
|
+
</p>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
class ContributorsSummaryComponent < Panda::Core::Base
|
|
7
|
+
def initialize(contributors:, total_count:, last_updated_at:, count_label: "version", heading: "Contributors", **attrs)
|
|
8
|
+
@contributors = contributors
|
|
9
|
+
@total_count = total_count
|
|
10
|
+
@count_label = count_label
|
|
11
|
+
@last_updated_at = last_updated_at
|
|
12
|
+
@heading = heading
|
|
13
|
+
super(**attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :contributors, :total_count, :count_label, :last_updated_at, :heading
|
|
17
|
+
|
|
18
|
+
include ActionView::Helpers::TextHelper
|
|
19
|
+
include ActionView::Helpers::DateHelper
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div <% if @attrs.any? %><%= tag.attributes(@attrs) %><% end %>>
|
|
2
|
+
<div class="p-6 bg-white shadow rounded-lg">
|
|
3
|
+
<h2 class="text-lg font-medium text-gray-900">Welcome to Panda Admin</h2>
|
|
4
|
+
<p class="mt-1 text-sm text-gray-500">Manage your application from this central dashboard.</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="mt-6 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
|
8
|
+
<% if defined?(Panda::CMS) %>
|
|
9
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
10
|
+
<div class="px-4 py-5 sm:p-6">
|
|
11
|
+
<div class="flex items-center">
|
|
12
|
+
<div class="flex-shrink-0">
|
|
13
|
+
<i class="fa-solid fa-file-lines text-3xl text-gray-400"></i>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="ml-5 w-0 flex-1">
|
|
16
|
+
<dt class="text-sm font-medium text-gray-500 truncate">Content Management</dt>
|
|
17
|
+
<dd class="mt-1 text-lg font-semibold text-gray-900">
|
|
18
|
+
<%= helpers.link_to "Manage CMS", helpers.panda_cms.admin_cms_dashboard_path, class: "text-primary-600 hover:text-primary-800" %>
|
|
19
|
+
</dd>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="mt-3">
|
|
23
|
+
<p class="text-sm text-gray-500">Pages, posts, menus, and content blocks</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
|
|
29
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
30
|
+
<div class="px-4 py-5 sm:p-6">
|
|
31
|
+
<div class="flex items-center">
|
|
32
|
+
<div class="flex-shrink-0">
|
|
33
|
+
<i class="fa-regular fa-user text-3xl text-gray-400"></i>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="ml-5 w-0 flex-1">
|
|
36
|
+
<dt class="text-sm font-medium text-gray-500 truncate">My Profile</dt>
|
|
37
|
+
<dd class="mt-1 text-lg font-semibold text-gray-900">
|
|
38
|
+
<%= helpers.link_to "Edit Profile", helpers.edit_admin_my_profile_path, class: "text-primary-600 hover:text-primary-800" %>
|
|
39
|
+
</dd>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="mt-3">
|
|
43
|
+
<p class="text-sm text-gray-500">Update your personal information</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<%# Hook for additional dashboard cards %>
|
|
49
|
+
<% dashboard_cards.each do |card| %>
|
|
50
|
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
51
|
+
<div class="px-4 py-5 sm:p-6">
|
|
52
|
+
<div class="flex items-center">
|
|
53
|
+
<div class="flex-shrink-0">
|
|
54
|
+
<i class="<%= card[:icon] %> text-3xl text-gray-400"></i>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="ml-5 w-0 flex-1">
|
|
57
|
+
<dt class="text-sm font-medium text-gray-500 truncate"><%= card[:title] %></dt>
|
|
58
|
+
<dd class="mt-1 text-lg font-semibold text-gray-900">
|
|
59
|
+
<%= helpers.link_to card[:link_text], card[:path], class: "text-primary-600 hover:text-primary-800" %>
|
|
60
|
+
</dd>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="mt-3">
|
|
64
|
+
<p class="text-sm text-gray-500"><%= card[:description] %></p>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Default dashboard content component
|
|
7
|
+
# Displays welcome message and quick action cards
|
|
8
|
+
class DashboardDefaultContentComponent < Panda::Core::Base
|
|
9
|
+
def initialize(user:, **attrs)
|
|
10
|
+
@user = user
|
|
11
|
+
super(**attrs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :user
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def default_attrs
|
|
19
|
+
{class: "mt-5"}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dashboard_cards
|
|
23
|
+
@dashboard_cards ||= if Panda::Core.config.respond_to?(:admin_dashboard_cards)
|
|
24
|
+
Panda::Core.config.admin_dashboard_cards&.call(@user) || []
|
|
25
|
+
else
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Renders a right-aligned delete button using `button_to` with DELETE method
|
|
7
|
+
# and a Turbo confirmation dialog.
|
|
8
|
+
#
|
|
9
|
+
# Lives in panda-core because every admin app needs destructive action buttons,
|
|
10
|
+
# and the existing ButtonComponent renders `<a>` tags rather than `<form>`-based
|
|
11
|
+
# `button_to` elements required for safe DELETE requests (CSRF + method override).
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# render Panda::Core::Admin::DeleteButtonComponent.new(
|
|
15
|
+
# text: "Delete Person",
|
|
16
|
+
# path: person_path(@person),
|
|
17
|
+
# confirm: "Are you sure you want to delete this person?"
|
|
18
|
+
# )
|
|
19
|
+
class DeleteButtonComponent < Panda::Core::Base
|
|
20
|
+
attr_reader :text, :path, :confirm
|
|
21
|
+
|
|
22
|
+
def initialize(text:, path:, confirm:, **attrs)
|
|
23
|
+
@text = text
|
|
24
|
+
@path = path
|
|
25
|
+
@confirm = confirm
|
|
26
|
+
super(**attrs)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def default_attrs
|
|
32
|
+
{
|
|
33
|
+
class: "inline-flex items-center gap-2 rounded-xl px-3 py-1.5 text-sm font-medium " \
|
|
34
|
+
"text-error-600 border border-error-200 bg-error-50 hover:bg-error-100 " \
|
|
35
|
+
"shadow-sm cursor-pointer transition-colors"
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div <%= tag.attributes(**attrs) %>
|
|
2
|
+
data-controller="dropdown"
|
|
3
|
+
data-action="click@window->dropdown#hide keydown.escape@window->dropdown#hide">
|
|
4
|
+
<button type="button"
|
|
5
|
+
data-action="click->dropdown#toggle"
|
|
6
|
+
data-dropdown-target="button"
|
|
7
|
+
class="<%= button_classes %>"
|
|
8
|
+
aria-haspopup="true">
|
|
9
|
+
<%= render_text %>
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
<div data-dropdown-target="menu"
|
|
13
|
+
class="hidden absolute right-0 z-50 mt-2 w-48 origin-top-right rounded-xl bg-white shadow-sm border border-gray-200 focus:outline-none"
|
|
14
|
+
data-transition-enter="transition ease-out duration-100"
|
|
15
|
+
data-transition-enter-from="transform opacity-0 scale-95"
|
|
16
|
+
data-transition-enter-to="transform opacity-100 scale-100"
|
|
17
|
+
data-transition-leave="transition ease-in duration-75"
|
|
18
|
+
data-transition-leave-from="transform opacity-100 scale-100"
|
|
19
|
+
data-transition-leave-to="transform opacity-0 scale-95"
|
|
20
|
+
role="menu"
|
|
21
|
+
aria-orientation="vertical">
|
|
22
|
+
<div class="py-1" role="none">
|
|
23
|
+
<% items.each do |item| %>
|
|
24
|
+
<%= item %>
|
|
25
|
+
<% end %>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|