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,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# A button-styled dropdown menu. Combines ButtonComponent styling with
|
|
7
|
+
# DropdownComponent behaviour (Stimulus `dropdown` controller).
|
|
8
|
+
#
|
|
9
|
+
# @example In a heading slot
|
|
10
|
+
# heading.with_dropdown_button(text: "View Board") do |db|
|
|
11
|
+
# db.with_item(label: "Fundraising", href: "/boards/1")
|
|
12
|
+
# db.with_item(label: "Events", href: "/boards/2")
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
class DropdownButtonComponent < Panda::Core::Base
|
|
16
|
+
renders_many :items, "ItemComponent"
|
|
17
|
+
|
|
18
|
+
def initialize(text: "Options", action: :secondary, icon: nil, size: :regular, **attrs)
|
|
19
|
+
@text = text
|
|
20
|
+
@action = action
|
|
21
|
+
@icon = icon
|
|
22
|
+
@size = size
|
|
23
|
+
super(**attrs)
|
|
24
|
+
yield self if block_given?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_reader :text, :action, :icon, :size
|
|
28
|
+
|
|
29
|
+
class ItemComponent < Panda::Core::Base
|
|
30
|
+
def initialize(label:, href: "#", method: nil, **attrs)
|
|
31
|
+
@label = label
|
|
32
|
+
@href = href
|
|
33
|
+
@method = method
|
|
34
|
+
super(**attrs)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :label, :href, :method
|
|
38
|
+
|
|
39
|
+
def call
|
|
40
|
+
options = {
|
|
41
|
+
class: "block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 hover:text-gray-900",
|
|
42
|
+
role: "menuitem",
|
|
43
|
+
data: {dropdown_target: "menuItem"}
|
|
44
|
+
}
|
|
45
|
+
options[:data][:turbo_method] = method if method
|
|
46
|
+
|
|
47
|
+
content_tag(:a, label, **options.merge(href: href))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def default_attrs
|
|
54
|
+
{class: "relative inline-block text-left"}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def button_classes
|
|
58
|
+
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 "
|
|
59
|
+
base + size_classes + action_classes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def size_classes
|
|
63
|
+
case @size
|
|
64
|
+
when :small, :sm
|
|
65
|
+
"gap-x-1.5 px-3 py-1.5 text-xs "
|
|
66
|
+
when :medium, :regular, :md
|
|
67
|
+
"gap-x-2 px-4 py-2 text-sm "
|
|
68
|
+
when :large, :lg
|
|
69
|
+
"gap-x-2 px-5 py-2.5 text-base "
|
|
70
|
+
else
|
|
71
|
+
"gap-x-2 px-4 py-2 text-sm "
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def action_classes
|
|
76
|
+
case @action
|
|
77
|
+
when :save, :create, :add, :new
|
|
78
|
+
"text-white bg-primary-500 hover:bg-primary-600 focus-visible:outline-primary-600"
|
|
79
|
+
when :save_inactive
|
|
80
|
+
"text-white bg-gray-300 cursor-not-allowed"
|
|
81
|
+
when :secondary
|
|
82
|
+
"text-gray-700 border border-gray-200 bg-white hover:bg-gray-50"
|
|
83
|
+
when :delete, :destroy, :danger
|
|
84
|
+
"text-error-600 border border-error-200 bg-error-50 hover:bg-error-100 focus-visible:outline-error-300"
|
|
85
|
+
else
|
|
86
|
+
"text-gray-700 border border-gray-200 bg-white hover:bg-gray-50"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def render_text
|
|
91
|
+
parts = []
|
|
92
|
+
parts << content_tag(:i, "", class: "fa-solid fa-#{@icon}") if @icon
|
|
93
|
+
parts << @text.titleize
|
|
94
|
+
parts << content_tag(:i, "", class: "fa-solid fa-chevron-down text-xs", aria: {hidden: "true"})
|
|
95
|
+
safe_join(parts)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
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="inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 focus:outline-none"
|
|
8
|
+
aria-haspopup="true">
|
|
9
|
+
<% if trigger_slot? %>
|
|
10
|
+
<%= trigger_slot %>
|
|
11
|
+
<% else %>
|
|
12
|
+
<%= chevron_icon %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</button>
|
|
15
|
+
|
|
16
|
+
<div data-dropdown-target="menu"
|
|
17
|
+
class="hidden absolute right-0 z-50 mt-2 w-48 origin-top-right rounded-lg bg-white shadow-sm border border-gray-200 focus:outline-none"
|
|
18
|
+
data-transition-enter="transition ease-out duration-100"
|
|
19
|
+
data-transition-enter-from="transform opacity-0 scale-95"
|
|
20
|
+
data-transition-enter-to="transform opacity-100 scale-100"
|
|
21
|
+
data-transition-leave="transition ease-in duration-75"
|
|
22
|
+
data-transition-leave-from="transform opacity-100 scale-100"
|
|
23
|
+
data-transition-leave-to="transform opacity-0 scale-95"
|
|
24
|
+
role="menu"
|
|
25
|
+
aria-orientation="vertical">
|
|
26
|
+
<div class="py-1" role="none">
|
|
27
|
+
<% items.each do |item| %>
|
|
28
|
+
<%= item %>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Reusable dropdown menu component with a chevron trigger.
|
|
7
|
+
#
|
|
8
|
+
# Uses the pre-registered `dropdown` Stimulus controller from
|
|
9
|
+
# tailwindcss-stimulus-components for toggle, outside-click close,
|
|
10
|
+
# Escape key close, and CSS transitions.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic dropdown
|
|
13
|
+
# <%= render Panda::Core::Admin::DropdownComponent.new do |dropdown| %>
|
|
14
|
+
# <% dropdown.with_item(label: "Edit", href: edit_path) %>
|
|
15
|
+
# <% dropdown.with_item(label: "Delete", href: delete_path, method: :delete) %>
|
|
16
|
+
# <% end %>
|
|
17
|
+
#
|
|
18
|
+
# @example With custom trigger content
|
|
19
|
+
# <%= render Panda::Core::Admin::DropdownComponent.new do |dropdown| %>
|
|
20
|
+
# <% dropdown.with_trigger_slot do %>
|
|
21
|
+
# <span class="text-xs text-gray-500">Last 30 days</span>
|
|
22
|
+
# <i class="fa-solid fa-chevron-down text-xs"></i>
|
|
23
|
+
# <% end %>
|
|
24
|
+
# <% dropdown.with_item(label: "Last 7 days", href: "?period=7d") %>
|
|
25
|
+
# <% end %>
|
|
26
|
+
#
|
|
27
|
+
class DropdownComponent < Panda::Core::Base
|
|
28
|
+
renders_one :trigger_slot
|
|
29
|
+
renders_many :items, "ItemComponent"
|
|
30
|
+
|
|
31
|
+
class ItemComponent < Panda::Core::Base
|
|
32
|
+
def initialize(label:, href: "#", method: nil, **attrs)
|
|
33
|
+
@label = label
|
|
34
|
+
@href = href
|
|
35
|
+
@method = method
|
|
36
|
+
super(**attrs)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :label, :href, :method
|
|
40
|
+
|
|
41
|
+
def call
|
|
42
|
+
options = {
|
|
43
|
+
class: "block w-full text-left px-4 py-2 text-xs text-gray-500 hover:bg-gray-50 hover:text-gray-700",
|
|
44
|
+
role: "menuitem",
|
|
45
|
+
data: {dropdown_target: "menuItem"}
|
|
46
|
+
}
|
|
47
|
+
options[:data][:turbo_method] = method if method
|
|
48
|
+
|
|
49
|
+
content_tag(:a, label, **options.merge(href: href))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def default_attrs
|
|
56
|
+
{class: "relative inline-block text-left"}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def chevron_icon
|
|
60
|
+
content_tag(:i, nil, class: "fa-solid fa-chevron-down text-xs", aria: {hidden: "true"})
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= content_tag :div, **attrs do %>
|
|
2
|
+
<% if icon.present? %>
|
|
3
|
+
<div class="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-2xl bg-gray-100 text-gray-400">
|
|
4
|
+
<i class="<%= icon %>"></i>
|
|
5
|
+
</div>
|
|
6
|
+
<% end %>
|
|
7
|
+
<h3 class="text-lg font-semibold text-gray-900"><%= title %></h3>
|
|
8
|
+
<% if description.present? %>
|
|
9
|
+
<p class="mt-2 text-sm text-gray-500"><%= description %></p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if content? %>
|
|
12
|
+
<div class="mt-4 flex justify-center">
|
|
13
|
+
<%= content %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
# Empty state component for tables and lists.
|
|
7
|
+
class EmptyStateComponent < Panda::Core::Base
|
|
8
|
+
def initialize(title:, description: nil, icon: nil, **attrs)
|
|
9
|
+
@title = title
|
|
10
|
+
@description = description
|
|
11
|
+
@icon = icon
|
|
12
|
+
super(**attrs)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :title, :description, :icon
|
|
16
|
+
|
|
17
|
+
def default_attrs
|
|
18
|
+
{
|
|
19
|
+
class: "border-2 border-dashed border-gray-200 bg-white rounded-2xl px-6 py-10 text-center"
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<% if @files.any? %>
|
|
2
|
+
<%# Gallery %>
|
|
3
|
+
<section>
|
|
4
|
+
<h2 id="gallery-heading" class="sr-only">Files</h2>
|
|
5
|
+
<ul role="list" class="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8">
|
|
6
|
+
<% @files.each do |file| %>
|
|
7
|
+
<% is_selected = @selected_file && @selected_file.id == file.id %>
|
|
8
|
+
<li class="relative cursor-pointer">
|
|
9
|
+
<div class="<%= file_container_classes(is_selected) %> aspect-square">
|
|
10
|
+
<% if file.image? %>
|
|
11
|
+
<%= tag.img src: url_for(file),
|
|
12
|
+
alt: file.filename.to_s,
|
|
13
|
+
class: file_image_classes(is_selected),
|
|
14
|
+
onerror: "this.onerror=null;this.style.display='none';this.nextElementSibling.classList.remove('hidden')" %>
|
|
15
|
+
<div class="hidden flex items-center justify-center h-full bg-gray-100 dark:bg-gray-700" role="img" aria-label="Image preview not available">
|
|
16
|
+
<i class="fa-solid fa-file-image text-4xl text-gray-400"></i>
|
|
17
|
+
</div>
|
|
18
|
+
<% else %>
|
|
19
|
+
<%# File icon placeholder - implement based on file type %>
|
|
20
|
+
<div class="flex items-center justify-center h-full bg-gray-100 dark:bg-gray-700">
|
|
21
|
+
<i class="fa-solid fa-file text-4xl text-gray-400"></i>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<%= content_tag :button,
|
|
26
|
+
type: "button",
|
|
27
|
+
class: "absolute inset-0 focus:outline-hidden",
|
|
28
|
+
data: {
|
|
29
|
+
action: "click->file-gallery#selectFile",
|
|
30
|
+
file_id: file.id,
|
|
31
|
+
file_url: url_for(file),
|
|
32
|
+
file_name: file.filename.to_s,
|
|
33
|
+
file_size: file.byte_size,
|
|
34
|
+
file_type: file.content_type,
|
|
35
|
+
file_created: file.created_at.to_s
|
|
36
|
+
} do %>
|
|
37
|
+
<span class="sr-only">View details for <%= file.filename %></span>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<p class="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900 dark:text-white">
|
|
42
|
+
<%= file.filename.to_s %>
|
|
43
|
+
</p>
|
|
44
|
+
<p class="pointer-events-none block text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
45
|
+
<%= file_metadata(file) %>
|
|
46
|
+
</p>
|
|
47
|
+
</li>
|
|
48
|
+
<% end %>
|
|
49
|
+
</ul>
|
|
50
|
+
</section>
|
|
51
|
+
<% else %>
|
|
52
|
+
<%# Empty state %>
|
|
53
|
+
<div class="text-center py-12">
|
|
54
|
+
<i class="fa-solid fa-images text-6xl text-gray-400 mb-4"></i>
|
|
55
|
+
<h3 class="text-lg font-medium text-gray-900 dark:text-white">No files</h3>
|
|
56
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Upload files to see them here.</p>
|
|
57
|
+
</div>
|
|
58
|
+
<% end %>
|
|
@@ -4,75 +4,24 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class FileGalleryComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
render_gallery
|
|
13
|
-
else
|
|
14
|
-
render_empty_state
|
|
15
|
-
end
|
|
7
|
+
def initialize(selected_file: nil, files: [], blob_categories: {}, **attrs)
|
|
8
|
+
@files = files
|
|
9
|
+
@selected_file = selected_file
|
|
10
|
+
@blob_categories = blob_categories
|
|
11
|
+
super(**attrs)
|
|
16
12
|
end
|
|
17
13
|
|
|
14
|
+
attr_reader :files, :selected_file, :blob_categories
|
|
15
|
+
|
|
18
16
|
private
|
|
19
17
|
|
|
20
18
|
def render_gallery
|
|
21
|
-
|
|
22
|
-
h2(id: "gallery-heading", class: "sr-only") { "Files" }
|
|
23
|
-
ul(
|
|
24
|
-
role: "list",
|
|
25
|
-
class: "grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8"
|
|
26
|
-
) do
|
|
27
|
-
@files.each do |file|
|
|
28
|
-
render_file_item(file)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
19
|
+
# Implemented in ERB template
|
|
32
20
|
end
|
|
33
21
|
|
|
34
22
|
def render_file_item(file)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
li(class: "relative") do
|
|
38
|
-
div(
|
|
39
|
-
class: file_container_classes(is_selected),
|
|
40
|
-
style: "cursor: pointer;"
|
|
41
|
-
) do
|
|
42
|
-
if file.image?
|
|
43
|
-
img(
|
|
44
|
-
src: url_for(file),
|
|
45
|
-
alt: file.filename.to_s,
|
|
46
|
-
class: file_image_classes(is_selected)
|
|
47
|
-
)
|
|
48
|
-
else
|
|
49
|
-
render_file_icon(file)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
button(
|
|
53
|
-
type: "button",
|
|
54
|
-
class: "absolute inset-0 focus:outline-hidden",
|
|
55
|
-
data: {
|
|
56
|
-
action: "click->file-gallery#selectFile",
|
|
57
|
-
file_id: file.id,
|
|
58
|
-
file_url: url_for(file),
|
|
59
|
-
file_name: file.filename.to_s,
|
|
60
|
-
file_size: file.byte_size,
|
|
61
|
-
file_type: file.content_type,
|
|
62
|
-
file_created: file.created_at.to_s
|
|
63
|
-
}
|
|
64
|
-
) do
|
|
65
|
-
span(class: "sr-only") { "View details for #{file.filename}" }
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
p(class: "pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900 dark:text-white") do
|
|
70
|
-
plain file.filename.to_s
|
|
71
|
-
end
|
|
72
|
-
p(class: "pointer-events-none block text-sm font-medium text-gray-500 dark:text-gray-400") do
|
|
73
|
-
plain number_to_human_size(file.byte_size)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
23
|
+
@selected_file && @selected_file.id == file.id
|
|
24
|
+
# Implemented in ERB template
|
|
76
25
|
end
|
|
77
26
|
|
|
78
27
|
def file_container_classes(selected)
|
|
@@ -80,56 +29,33 @@ module Panda
|
|
|
80
29
|
focus = if selected
|
|
81
30
|
"outline-2 outline-offset-2 outline-panda-dark dark:outline-panda-light outline"
|
|
82
31
|
else
|
|
83
|
-
"focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-
|
|
32
|
+
"focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-primary-600 dark:focus-within:outline-primary-500"
|
|
84
33
|
end
|
|
85
34
|
"#{base} #{focus}"
|
|
86
35
|
end
|
|
87
36
|
|
|
88
37
|
def file_image_classes(selected)
|
|
89
|
-
base = "pointer-events-none aspect-
|
|
38
|
+
base = "pointer-events-none aspect-square object-contain p-1 outline -outline-offset-1 outline-black/5 dark:outline-white/10"
|
|
90
39
|
hover = selected ? "" : "group-hover:opacity-75"
|
|
91
40
|
"#{base} #{hover}"
|
|
92
41
|
end
|
|
93
42
|
|
|
94
43
|
def render_file_icon(file)
|
|
95
|
-
|
|
96
|
-
div(class: "text-center") do
|
|
97
|
-
svg(
|
|
98
|
-
class: "mx-auto h-12 w-12 text-gray-400",
|
|
99
|
-
fill: "none",
|
|
100
|
-
viewBox: "0 0 24 24",
|
|
101
|
-
stroke: "currentColor",
|
|
102
|
-
aria: {hidden: "true"}
|
|
103
|
-
) do
|
|
104
|
-
path(
|
|
105
|
-
stroke_linecap: "round",
|
|
106
|
-
stroke_linejoin: "round",
|
|
107
|
-
d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
108
|
-
)
|
|
109
|
-
end
|
|
110
|
-
p(class: "mt-1 text-xs text-gray-500 uppercase") { file.content_type&.split("/")&.last || "file" }
|
|
111
|
-
end
|
|
112
|
-
end
|
|
44
|
+
# Implemented in ERB template
|
|
113
45
|
end
|
|
114
46
|
|
|
115
47
|
def render_empty_state
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
|
|
128
|
-
)
|
|
129
|
-
end
|
|
130
|
-
h3(class: "mt-2 text-sm font-semibold text-gray-900") { "No files" }
|
|
131
|
-
p(class: "mt-1 text-sm text-gray-500") { "Get started by uploading a file." }
|
|
132
|
-
end
|
|
48
|
+
# Implemented in ERB template
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Build the metadata subtitle for a file in the gallery grid
|
|
52
|
+
def file_metadata(file)
|
|
53
|
+
parts = []
|
|
54
|
+
variant_count = file.variant_records.size
|
|
55
|
+
parts << "#{variant_count} #{"variant".pluralize(variant_count)}" if variant_count > 0
|
|
56
|
+
category = @blob_categories[file.id]
|
|
57
|
+
parts << category.name if category
|
|
58
|
+
parts.any? ? parts.join(" \u00B7 ") : number_to_human_size(file.byte_size)
|
|
133
59
|
end
|
|
134
60
|
|
|
135
61
|
# Helper method to generate URL for ActiveStorage attachment
|
|
@@ -139,6 +65,8 @@ module Panda
|
|
|
139
65
|
else
|
|
140
66
|
"#"
|
|
141
67
|
end
|
|
68
|
+
rescue
|
|
69
|
+
"#"
|
|
142
70
|
end
|
|
143
71
|
|
|
144
72
|
# Helper method for human-readable file sizes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div class="flex flex-wrap gap-2 mb-4">
|
|
2
|
+
<%= link_to @all_label, @url_helper.call(nil), class: pill_classes(@active_value.blank?) %>
|
|
3
|
+
<% @items.each do |item| %>
|
|
4
|
+
<%= link_to item[:label], @url_helper.call(item[:value]), class: pill_classes(@active_value == item[:value]) %>
|
|
5
|
+
<% end %>
|
|
6
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Admin
|
|
6
|
+
class FilterPillsComponent < Panda::Core::Base
|
|
7
|
+
def initialize(items:, url_helper:, param_name: :category, active_value: nil, all_label: "All", **attrs)
|
|
8
|
+
@items = items
|
|
9
|
+
@url_helper = url_helper
|
|
10
|
+
@param_name = param_name
|
|
11
|
+
@active_value = active_value
|
|
12
|
+
@all_label = all_label
|
|
13
|
+
super(**attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :items, :url_helper, :param_name, :active_value, :all_label
|
|
17
|
+
|
|
18
|
+
def pill_classes(active)
|
|
19
|
+
base = "inline-flex items-center rounded-full px-3 py-1.5 text-xs font-medium"
|
|
20
|
+
if active
|
|
21
|
+
"#{base} bg-primary-100 text-primary-700"
|
|
22
|
+
else
|
|
23
|
+
"#{base} bg-gray-100 text-gray-700 hover:bg-gray-200"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div aria-live="assertive" class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-50">
|
|
2
|
+
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
|
|
3
|
+
<%= content_tag :div, notification_attrs do %>
|
|
4
|
+
<div class="flex items-center">
|
|
5
|
+
<%= render_icon %>
|
|
6
|
+
<%= render_content %>
|
|
7
|
+
<%= render_close_button %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
@@ -4,37 +4,19 @@ module Panda
|
|
|
4
4
|
module Core
|
|
5
5
|
module Admin
|
|
6
6
|
class FlashMessageComponent < Panda::Core::Base
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Global notification container (fixed position)
|
|
14
|
-
div(
|
|
15
|
-
aria: {live: "assertive"},
|
|
16
|
-
class: "pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-50"
|
|
17
|
-
) do
|
|
18
|
-
div(class: "flex w-full flex-col items-center space-y-4 sm:items-end") do
|
|
19
|
-
# Notification panel with Tailwind Plus styling
|
|
20
|
-
div(**notification_attrs) do
|
|
21
|
-
div(class: "p-4") do
|
|
22
|
-
div(class: "flex items-start") do
|
|
23
|
-
render_icon
|
|
24
|
-
render_content
|
|
25
|
-
render_close_button
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
7
|
+
def initialize(message: "", kind: :default, temporary: true, subtitle: nil, **attrs)
|
|
8
|
+
@message = message
|
|
9
|
+
@kind = kind
|
|
10
|
+
@temporary = temporary
|
|
11
|
+
@subtitle = subtitle
|
|
12
|
+
super(**attrs)
|
|
31
13
|
end
|
|
32
14
|
|
|
33
|
-
|
|
15
|
+
attr_reader :message, :kind, :temporary, :subtitle
|
|
34
16
|
|
|
35
17
|
def notification_attrs
|
|
36
18
|
{
|
|
37
|
-
class: "pointer-events-auto w-full max-w-
|
|
19
|
+
class: "pointer-events-auto w-full max-w-md translate-y-0 transform rounded-2xl border px-4 py-3 shadow-lg transition duration-300 ease-out sm:translate-x-0 starting:translate-y-2 starting:opacity-0 starting:sm:translate-x-2 starting:sm:translate-y-0 #{tone_classes}",
|
|
38
20
|
data: {
|
|
39
21
|
controller: "alert",
|
|
40
22
|
alert_dismiss_after_value: (@temporary ? "5000" : nil)
|
|
@@ -43,39 +25,30 @@ module Panda
|
|
|
43
25
|
end
|
|
44
26
|
|
|
45
27
|
def render_icon
|
|
46
|
-
div
|
|
47
|
-
i
|
|
28
|
+
content_tag(:div, class: "shrink-0") do
|
|
29
|
+
content_tag(:i, "", class: "fa-solid size-5 #{icon_css} #{icon_colour_css}")
|
|
48
30
|
end
|
|
49
31
|
end
|
|
50
32
|
|
|
51
33
|
def render_content
|
|
52
|
-
div
|
|
53
|
-
p
|
|
54
|
-
if @subtitle
|
|
55
|
-
p
|
|
34
|
+
content_tag(:div, class: "ml-2 w-0 flex-1") do
|
|
35
|
+
message_html = content_tag(:p, @message, class: "text-sm font-medium flash-message-title")
|
|
36
|
+
subtitle_html = if @subtitle
|
|
37
|
+
content_tag(:p, @subtitle, class: "mt-1 text-xs opacity-80 flash-message-subtitle")
|
|
38
|
+
else
|
|
39
|
+
"".html_safe
|
|
56
40
|
end
|
|
41
|
+
(message_html + subtitle_html).html_safe
|
|
57
42
|
end
|
|
58
43
|
end
|
|
59
44
|
|
|
60
45
|
def render_close_button
|
|
61
|
-
div
|
|
62
|
-
button
|
|
46
|
+
content_tag(:div, class: "ml-4 flex shrink-0") do
|
|
47
|
+
content_tag(:button,
|
|
63
48
|
type: "button",
|
|
64
|
-
class: "inline-flex
|
|
65
|
-
data: {action: "alert#close"}
|
|
66
|
-
|
|
67
|
-
span(class: "sr-only") { "Close" }
|
|
68
|
-
svg(
|
|
69
|
-
viewBox: "0 0 20 20",
|
|
70
|
-
fill: "currentColor",
|
|
71
|
-
data: {slot: "icon"},
|
|
72
|
-
aria: {hidden: "true"},
|
|
73
|
-
class: "size-5"
|
|
74
|
-
) do |s|
|
|
75
|
-
s.path(
|
|
76
|
-
d: "M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z"
|
|
77
|
-
)
|
|
78
|
-
end
|
|
49
|
+
class: "inline-flex items-center cursor-pointer opacity-60 hover:opacity-100",
|
|
50
|
+
data: {action: "alert#close"}) do
|
|
51
|
+
content_tag(:i, "", class: "fa-solid fa-xmark")
|
|
79
52
|
end
|
|
80
53
|
end
|
|
81
54
|
end
|
|
@@ -83,30 +56,30 @@ module Panda
|
|
|
83
56
|
def icon_colour_css
|
|
84
57
|
case @kind
|
|
85
58
|
when :success
|
|
86
|
-
"text-
|
|
59
|
+
"text-emerald-600"
|
|
87
60
|
when :alert, :error
|
|
88
|
-
"text-
|
|
61
|
+
"text-rose-600"
|
|
89
62
|
when :warning
|
|
90
|
-
"text-
|
|
63
|
+
"text-amber-600"
|
|
91
64
|
when :info, :notice
|
|
92
|
-
"text-
|
|
65
|
+
"text-sky-600"
|
|
93
66
|
else
|
|
94
|
-
"text-gray-
|
|
67
|
+
"text-gray-500"
|
|
95
68
|
end
|
|
96
69
|
end
|
|
97
70
|
|
|
98
|
-
def
|
|
71
|
+
def tone_classes
|
|
99
72
|
case @kind
|
|
100
73
|
when :success
|
|
101
|
-
"
|
|
74
|
+
"bg-emerald-50 text-emerald-700 border-emerald-200"
|
|
102
75
|
when :alert, :error
|
|
103
|
-
"
|
|
76
|
+
"bg-rose-50 text-rose-700 border-rose-200"
|
|
104
77
|
when :warning
|
|
105
|
-
"
|
|
78
|
+
"bg-amber-50 text-amber-700 border-amber-200"
|
|
106
79
|
when :info, :notice
|
|
107
|
-
"
|
|
80
|
+
"bg-sky-50 text-sky-700 border-sky-200"
|
|
108
81
|
else
|
|
109
|
-
"
|
|
82
|
+
"bg-white text-gray-700 border-gray-200"
|
|
110
83
|
end
|
|
111
84
|
end
|
|
112
85
|
|