solidus_admin 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +7 -0
  3. data/README.md +31 -0
  4. data/Rakefile +21 -0
  5. data/app/assets/config/solidus_admin_manifest.js +4 -0
  6. data/app/assets/images/solidus_admin/.keep +0 -0
  7. data/app/assets/images/solidus_admin/arrow_down_s_fill_gray_700.svg +3 -0
  8. data/app/assets/images/solidus_admin/arrow_down_s_fill_red_400.svg +3 -0
  9. data/app/assets/images/solidus_admin/arrow_right_up_line.svg +5 -0
  10. data/app/assets/images/solidus_admin/favicon.ico +0 -0
  11. data/app/assets/images/solidus_admin/remixicon.symbol.svg +11 -0
  12. data/app/assets/stylesheets/solidus_admin/application.css +3 -0
  13. data/app/assets/stylesheets/solidus_admin/application.tailwind.css.erb +35 -0
  14. data/app/components/solidus_admin/base_component.rb +42 -0
  15. data/app/components/solidus_admin/feedback/component.html.erb +11 -0
  16. data/app/components/solidus_admin/feedback/component.rb +4 -0
  17. data/app/components/solidus_admin/feedback/component.yml +5 -0
  18. data/app/components/solidus_admin/orders/index/component.html.erb +31 -0
  19. data/app/components/solidus_admin/orders/index/component.rb +118 -0
  20. data/app/components/solidus_admin/orders/index/component.yml +13 -0
  21. data/app/components/solidus_admin/products/index/component.html.erb +30 -0
  22. data/app/components/solidus_admin/products/index/component.rb +126 -0
  23. data/app/components/solidus_admin/products/index/component.yml +13 -0
  24. data/app/components/solidus_admin/products/show/component.html.erb +149 -0
  25. data/app/components/solidus_admin/products/show/component.js +9 -0
  26. data/app/components/solidus_admin/products/show/component.rb +26 -0
  27. data/app/components/solidus_admin/products/show/component.yml +17 -0
  28. data/app/components/solidus_admin/products/status/component.rb +31 -0
  29. data/app/components/solidus_admin/products/status/component.yml +3 -0
  30. data/app/components/solidus_admin/sidebar/account_nav/component.html.erb +67 -0
  31. data/app/components/solidus_admin/sidebar/account_nav/component.rb +15 -0
  32. data/app/components/solidus_admin/sidebar/account_nav/component.yml +3 -0
  33. data/app/components/solidus_admin/sidebar/component.html.erb +39 -0
  34. data/app/components/solidus_admin/sidebar/component.js +14 -0
  35. data/app/components/solidus_admin/sidebar/component.rb +21 -0
  36. data/app/components/solidus_admin/sidebar/component.yml +2 -0
  37. data/app/components/solidus_admin/sidebar/item/component.html.erb +26 -0
  38. data/app/components/solidus_admin/sidebar/item/component.rb +27 -0
  39. data/app/components/solidus_admin/skip_link/component.rb +24 -0
  40. data/app/components/solidus_admin/skip_link/component.yml +2 -0
  41. data/app/components/solidus_admin/ui/badge/component.rb +34 -0
  42. data/app/components/solidus_admin/ui/button/component.rb +101 -0
  43. data/app/components/solidus_admin/ui/forms/checkbox/component.rb +42 -0
  44. data/app/components/solidus_admin/ui/forms/field/component.html.erb +28 -0
  45. data/app/components/solidus_admin/ui/forms/field/component.rb +72 -0
  46. data/app/components/solidus_admin/ui/forms/input/component.js +16 -0
  47. data/app/components/solidus_admin/ui/forms/input/component.rb +99 -0
  48. data/app/components/solidus_admin/ui/forms/switch/component.rb +47 -0
  49. data/app/components/solidus_admin/ui/icon/component.rb +25 -0
  50. data/app/components/solidus_admin/ui/icon/names.txt +2494 -0
  51. data/app/components/solidus_admin/ui/panel/component.html.erb +36 -0
  52. data/app/components/solidus_admin/ui/panel/component.js +14 -0
  53. data/app/components/solidus_admin/ui/panel/component.rb +19 -0
  54. data/app/components/solidus_admin/ui/panel/component.yml +4 -0
  55. data/app/components/solidus_admin/ui/tab/component.rb +43 -0
  56. data/app/components/solidus_admin/ui/table/component.html.erb +170 -0
  57. data/app/components/solidus_admin/ui/table/component.js +118 -0
  58. data/app/components/solidus_admin/ui/table/component.rb +150 -0
  59. data/app/components/solidus_admin/ui/table/component.yml +11 -0
  60. data/app/components/solidus_admin/ui/table/pagination/component.html.erb +28 -0
  61. data/app/components/solidus_admin/ui/table/pagination/component.rb +14 -0
  62. data/app/components/solidus_admin/ui/table/pagination/component.yml +3 -0
  63. data/app/components/solidus_admin/ui/toast/component.html.erb +26 -0
  64. data/app/components/solidus_admin/ui/toast/component.js +17 -0
  65. data/app/components/solidus_admin/ui/toast/component.rb +18 -0
  66. data/app/components/solidus_admin/ui/toast/component.yml +4 -0
  67. data/app/components/solidus_admin/ui/toggletip/component.html.erb +53 -0
  68. data/app/components/solidus_admin/ui/toggletip/component.js +26 -0
  69. data/app/components/solidus_admin/ui/toggletip/component.rb +98 -0
  70. data/app/components/solidus_admin/ui/toggletip/component.yml +2 -0
  71. data/app/controllers/solidus_admin/accounts_controller.rb +11 -0
  72. data/app/controllers/solidus_admin/authentication_adapters/backend.rb +26 -0
  73. data/app/controllers/solidus_admin/base_controller.rb +21 -0
  74. data/app/controllers/solidus_admin/controller_helpers/authentication.rb +31 -0
  75. data/app/controllers/solidus_admin/controller_helpers/authorization.rb +29 -0
  76. data/app/controllers/solidus_admin/controller_helpers/locale.rb +32 -0
  77. data/app/controllers/solidus_admin/orders_controller.rb +21 -0
  78. data/app/controllers/solidus_admin/products_controller.rb +93 -0
  79. data/app/helpers/solidus_admin/components_helper.rb +9 -0
  80. data/app/helpers/solidus_admin/layout_helper.rb +18 -0
  81. data/app/javascript/solidus_admin/application.js +2 -0
  82. data/app/javascript/solidus_admin/controllers/application.js +9 -0
  83. data/app/javascript/solidus_admin/controllers/components.js +35 -0
  84. data/app/javascript/solidus_admin/controllers/hello_controller.js +7 -0
  85. data/app/javascript/solidus_admin/controllers/index.js +14 -0
  86. data/app/javascript/solidus_admin/utils.js +8 -0
  87. data/app/views/layouts/solidus_admin/application.html.erb +30 -0
  88. data/app/views/layouts/solidus_admin/preview.html.erb +10 -0
  89. data/app/views/solidus_admin/.keep +0 -0
  90. data/bin/rails +13 -0
  91. data/config/importmap.rb +13 -0
  92. data/config/locales/main_nav.en.yml +13 -0
  93. data/config/locales/orders.en.yml +4 -0
  94. data/config/locales/products.en.yml +10 -0
  95. data/config/routes.rb +13 -0
  96. data/config/solidus_admin/tailwind.config.js.erb +95 -0
  97. data/docs/customizing_main_navigation.md +42 -0
  98. data/docs/customizing_tailwind.md +78 -0
  99. data/docs/customizing_view_components.md +153 -0
  100. data/lib/generators/solidus_admin/component/USAGE +13 -0
  101. data/lib/generators/solidus_admin/component/component_generator.rb +130 -0
  102. data/lib/generators/solidus_admin/component/templates/component.html.erb.tt +3 -0
  103. data/lib/generators/solidus_admin/component/templates/component.js.tt +14 -0
  104. data/lib/generators/solidus_admin/component/templates/component.rb.tt +14 -0
  105. data/lib/generators/solidus_admin/component/templates/component.yml.tt +4 -0
  106. data/lib/generators/solidus_admin/component/templates/component_preview.rb.tt +15 -0
  107. data/lib/generators/solidus_admin/component/templates/component_preview_overview.html.erb +7 -0
  108. data/lib/generators/solidus_admin/component/templates/component_spec.rb.tt +16 -0
  109. data/lib/generators/solidus_admin/install/install_generator.rb +44 -0
  110. data/lib/generators/solidus_admin/install/templates/config/initializers/solidus_admin.rb +44 -0
  111. data/lib/solidus_admin/configuration.rb +217 -0
  112. data/lib/solidus_admin/engine.rb +67 -0
  113. data/lib/solidus_admin/importmap.rb +26 -0
  114. data/lib/solidus_admin/main_nav_item.rb +97 -0
  115. data/lib/solidus_admin/preview.rb +81 -0
  116. data/lib/solidus_admin/tailwindcss.rb +58 -0
  117. data/lib/solidus_admin/version.rb +5 -0
  118. data/lib/solidus_admin.rb +15 -0
  119. data/lib/tasks/importmap.rake +10 -0
  120. data/lib/tasks/tailwindcss.rake +55 -0
  121. data/solidus_admin.gemspec +35 -0
  122. metadata +255 -18
@@ -0,0 +1,67 @@
1
+ <details class="relative w-full" aria-label="<%= t('.account') %>">
2
+ <summary
3
+ class="
4
+ flex gap-1.5
5
+ p-3 mt-2 rounded
6
+ body-small-bold text-gray-500
7
+ hover:bg-gray-25 [[open]_>_&]:bg-gray-25
8
+ cursor-pointer
9
+ [&::marker]:hidden
10
+ [&::-webkit-details-marker]:hidden
11
+ ">
12
+ <%= icon_tag("user-smile-fill", class: "inline-block align-text-bottom shrink-0 w-6 h-6 rounded-[4.81rem] body-small fill-yellow bg-black") %>
13
+ <span class="overflow-hidden whitespace-nowrap text-ellipsis">
14
+ <%= @user_label %>
15
+ </span>
16
+ </summary>
17
+
18
+ <ul
19
+ class="
20
+ p-2 mb-1 absolute bottom-full left-0 w-full
21
+ body-small text-black bg-white
22
+ border border-gray-100 rounded-lg
23
+ shadow-base
24
+ ">
25
+
26
+ <% if (available_locales = Spree.i18n_available_locales).any? %>
27
+ <li class="h-8 flex items-center hover:bg-gray-25 rounded">
28
+ <%= form_tag request.fullpath, method: :get do %>
29
+ <label class="flex gap-2 items-center px-2">
30
+ <%= icon_tag("global-line", class: "w-full max-w-[20px] h-5 fill-current shrink") %>
31
+ <select class="w-full appearance-none grow bg-transparent outline-none" onchange="this.form.requestSubmit()" name="switch_to_locale">
32
+ <%= options_for_select(
33
+ available_locales
34
+ .map do |locale|
35
+ [
36
+ t(
37
+ "spree.i18n.this_file_language",
38
+ locale: locale,
39
+ default: locale.to_s,
40
+ fallback: false
41
+ ),
42
+ locale
43
+ ]
44
+ end
45
+ .sort,
46
+ selected: I18n.locale,
47
+ ) %>
48
+ </select>
49
+ <%= icon_tag("expand-up-down-line", class: "w-full max-w-[20px] h-5 fill-current shrink") %>
50
+ </label>
51
+ <% end %>
52
+ </li>
53
+ <% end %>
54
+ <li class="h-8 flex items-center hover:bg-gray-25 rounded">
55
+ <%= link_to @account_path, class: 'flex gap-2 items-center px-2' do %>
56
+ <%= icon_tag("user-3-line", class: "w-5 h-5 fill-current shrink") %>
57
+ <span><%= t('.account') %></span>
58
+ <% end %>
59
+ </li>
60
+ <li class="h-8 flex items-center hover:bg-gray-25 rounded">
61
+ <%= button_to @logout_path, method: @logout_method, class: 'flex gap-2 items-center px-2' do %>
62
+ <%= icon_tag("logout-box-line", class: "w-5 h-5 fill-current shrink") %>
63
+ <span><%= t('.logout') %></span>
64
+ <% end %>
65
+ </li>
66
+ </ul>
67
+ </details>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Account navigation
4
+ class SolidusAdmin::Sidebar::AccountNav::Component < SolidusAdmin::BaseComponent
5
+ # @param user_label [String]
6
+ # @param account_path [String]
7
+ # @param logout_path [String]
8
+ # @param logout_method [Symbol]
9
+ def initialize(user_label:, account_path:, logout_path:, logout_method:)
10
+ @user_label = user_label
11
+ @account_path = account_path
12
+ @logout_path = logout_path
13
+ @logout_method = logout_method
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ en:
2
+ account: "Account"
3
+ logout: "Logout"
@@ -0,0 +1,39 @@
1
+ <nav class="
2
+ flex flex-col
3
+ bg-gray-15
4
+ p-4
5
+ w-full
6
+ " data-controller="<%= stimulus_id %>" data-<%= stimulus_id %>-cookie-value="solidus_admin">
7
+ <%= link_to @store.url, class: "py-3 px-2 text-left flex mb-4" do %>
8
+ <%= image_tag @logo_path, alt: t('.visit_store'), class: "max-h-7" %>
9
+ <% end %>
10
+
11
+ <%= link_to @store.url, target: :_blank, class: "flex mb-4 px-2 py-1.5 border border-gray-100 rounded-sm shadow-sm" do %>
12
+ <div class="flex-grow">
13
+ <p class="body-small-bold text-black"><%= @store.name %></p>
14
+ <p class="body-tiny text-gray-500"><%= @store.url %></p>
15
+ </div>
16
+ <%= render component("ui/icon").new(name: 'arrow-right-up-line', class: 'w-4 h-4 fill-gray-400') %>
17
+ <% end %>
18
+
19
+ <ul>
20
+ <%= render component("sidebar/item").with_collection(items, fullpath: request.fullpath) %>
21
+ </ul>
22
+
23
+ <div class="mt-auto">
24
+ <div class="group mb-3">
25
+ <label class="flex gap-3 items-center py-0.5 px-3 pb-0.5 rounded hover:text-red-500 hover:bg-gray-50 body-small-bold text-black cursor-pointer">
26
+ <%= t('spree.navigation.switch_to_legacy') %>
27
+ <div class="flex items-center">
28
+ <%= render component("ui/forms/switch").new(size: :s, checked: false, 'data-action': "#{stimulus_id}#setCookie:prevent") %>
29
+ </div>
30
+ </label>
31
+ </div>
32
+ <%= render component("sidebar/account_nav").new(
33
+ user_label: helpers.current_solidus_admin_user.email,
34
+ account_path: solidus_admin.account_path,
35
+ logout_path: helpers.solidus_admin_logout_path,
36
+ logout_method: helpers.solidus_admin_logout_method,
37
+ ) %>
38
+ </div>
39
+ </nav>
@@ -0,0 +1,14 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ cookie: String
6
+ }
7
+
8
+ setCookie(event) {
9
+ let value = !event.currentTarget.checked
10
+
11
+ document.cookie = `${this.cookieValue}=${value}; Path=/`
12
+ location.reload()
13
+ }
14
+ }
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Renders the sidebar
4
+ class SolidusAdmin::Sidebar::Component < SolidusAdmin::BaseComponent
5
+ def initialize(
6
+ store:,
7
+ logo_path: SolidusAdmin::Config.logo_path,
8
+ items: SolidusAdmin::Config.menu_items
9
+ )
10
+ @logo_path = logo_path
11
+ @items = items.map do |attrs|
12
+ children = attrs[:children].to_a.map { SolidusAdmin::MainNavItem.new(**_1, top_level: false) }
13
+ SolidusAdmin::MainNavItem.new(**attrs, children: children, top_level: true)
14
+ end
15
+ @store = store
16
+ end
17
+
18
+ def items
19
+ @items.sort_by(&:position)
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ en:
2
+ visit_store: Visit your store
@@ -0,0 +1,26 @@
1
+ <li class="group <%= "active" if active? %>">
2
+ <a
3
+ href="<%= path %>"
4
+ aria-current="<%= @item.current?(@url_helpers, @fullpath) ? "page" : "false" %>"
5
+ class="
6
+ flex gap-3 items-center
7
+ py-0.5 px-3 mb-0.5 rounded
8
+ hover:text-red-500 hover:bg-gray-50
9
+ <%= "text-red-500 bg-gray-50" if active? %>
10
+ <%= @item.top_level ? "body-small-bold text-black" : "body-small text-gray-600" %>
11
+ "
12
+ >
13
+ <i class="w-[1.125rem] h-[1.125rem] body-small flex">
14
+ <% if @item.icon %>
15
+ <%= icon_tag(@item.icon, class: "inline-block w-full h-full fill-current") %>
16
+ <% end %>
17
+ </i>
18
+ <%= @item.name %>
19
+ </a>
20
+
21
+ <% if @item.children? %>
22
+ <ul class="<%= "hidden" unless active? %>">
23
+ <%= render self.class.with_collection(@item.children, url_helpers: @url_helpers, fullpath: @fullpath) %>
24
+ </ul>
25
+ <% end %>
26
+ </li>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Menu item within a {Sidebar}
4
+ class SolidusAdmin::Sidebar::Item::Component < SolidusAdmin::BaseComponent
5
+ with_collection_parameter :item
6
+
7
+ # @param item [SolidusAdmin::MainNavItem
8
+ # @param fullpath [String] the current path
9
+ # @param url_helpers [#solidus_admin, #spree] context for generating paths
10
+ def initialize(
11
+ item:,
12
+ fullpath: "#",
13
+ url_helpers: Struct.new(:spree, :solidus_admin).new(spree, solidus_admin)
14
+ )
15
+ @item = item
16
+ @url_helpers = url_helpers
17
+ @fullpath = fullpath
18
+ end
19
+
20
+ def path
21
+ @item.path(@url_helpers)
22
+ end
23
+
24
+ def active?
25
+ @item.active?(@url_helpers, @fullpath)
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Skip to content link
4
+ class SolidusAdmin::SkipLink::Component < SolidusAdmin::BaseComponent
5
+ # @param href [String] the href attribute for the skip link
6
+ def initialize(href:)
7
+ @href = href
8
+ end
9
+
10
+ def call
11
+ link_to t(".skip_link"),
12
+ @href,
13
+ class: %{
14
+ sr-only
15
+ focus:not-sr-only
16
+ inline-block
17
+ focus:p-2
18
+ focus:absolute
19
+ body-small
20
+ text-white
21
+ bg-black
22
+ }
23
+ end
24
+ end
@@ -0,0 +1,2 @@
1
+ en:
2
+ skip_link: Skip to content
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidusAdmin::UI::Badge::Component < SolidusAdmin::BaseComponent
4
+ include ViewComponent::InlineTemplate
5
+
6
+ COLORS = {
7
+ graphite_light: "text-black bg-graphiteLight",
8
+ red: 'text-red-500 bg-red-100',
9
+ green: 'text-forest bg-seafoam',
10
+ blue: 'text-blue bg-sky',
11
+ black: 'text-white bg-black',
12
+ yellow: 'text-orange bg-papayaWhip',
13
+ }.freeze
14
+
15
+ SIZES = {
16
+ s: 'leading-4 px-2 py-0.5 text-3 font-[500]',
17
+ m: 'leading-5 px-3 py-0.5 text-3.5 font-[500]',
18
+ l: 'leading-6 px-3 py-0.5 text-4 font-[500]',
19
+ }.freeze
20
+
21
+ def initialize(name:, color: :graphite_light, size: :m)
22
+ @name = name
23
+
24
+ @class_name = [
25
+ 'inline-flex items-center rounded-full whitespace-nowrap', # layout
26
+ SIZES.fetch(size.to_sym), # size
27
+ COLORS.fetch(color.to_sym), # color
28
+ ].join(' ')
29
+ end
30
+
31
+ erb_template <<~ERB
32
+ <div class="<%= @class_name %>"><%= @name %></div>
33
+ ERB
34
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidusAdmin::UI::Button::Component < SolidusAdmin::BaseComponent
4
+ SIZES = {
5
+ s: %w[
6
+ h-7 w-7 p-1
7
+ text-xs font-semibold leading-none
8
+ ],
9
+ m: %w[
10
+ h-9 w-9 p-1.5
11
+ text-sm font-semibold leading-none
12
+ ],
13
+ l: %w[
14
+ h-12 w-12 p-2
15
+ text-base font-semibold leading-none
16
+ ],
17
+ }
18
+
19
+ TEXT_PADDINGS = {
20
+ s: %w[px-1.5 w-auto],
21
+ m: %w[px-3 w-auto],
22
+ l: %w[px-4 w-auto],
23
+ }
24
+
25
+ ICON_SIZES = {
26
+ s: %w[w-[1.4em] h-[1.4em]],
27
+ m: %w[w-[1.35em] h-[1.35em]],
28
+ l: %w[w-[1.5em] h-[1.5em]],
29
+ }
30
+
31
+ SCHEMES = {
32
+ primary: %w[
33
+ text-white bg-black
34
+ hover:text-white hover:bg-gray-600
35
+ active:text-white active:bg-gray-800
36
+ focus:text-white focus:bg-gray-700
37
+ disabled:text-gray-400 disabled:bg-gray-100 disabled:cursor-not-allowed
38
+ aria-disabled:text-gray-400 aria-disabled:bg-gray-100 aria-disabled:aria-disabled:cursor-not-allowed
39
+ ],
40
+ secondary: %w[
41
+ text-gray-700 bg-white border border-1 border-gray-200
42
+ hover:bg-gray-50
43
+ active:bg-gray-100
44
+ focus:bg-gray-50
45
+ disabled:text-gray-300 disabled:bg-white disabled:cursor-not-allowed
46
+ aria-disabled:text-gray-300 aria-disabled:bg-white aria-disabled:cursor-not-allowed
47
+ ],
48
+ danger: %w[
49
+ text-red-500 bg-white border border-1 border-red-500
50
+ hover:bg-red-500 hover:border-red-600 hover:text-white
51
+ active:bg-red-600 active:border-red-700 active:text-white
52
+ focus:bg-red-50 focus:bg-red-500 focus:border-red-600 focus:text-white
53
+ disabled:text-red-300 disabled:bg-white disabled:border-red-200 disabled:cursor-not-allowed
54
+ aria-disabled:text-red-300 aria-disabled:bg-white aria-disabled:border-red-200 aria-disabled:cursor-not-allowed
55
+ ],
56
+ ghost: %w[
57
+ text-gray-700 bg-transparent
58
+ hover:bg-gray-50
59
+ active:bg-gray-100
60
+ focus:bg-gray-50 focus:ring-gray-300 focus:ring-2
61
+ disabled:text-gray-300 disabled:bg-transparent disabled:border-gray-300 disabled:cursor-not-allowed
62
+ aria-disabled:text-gray-300 aria-disabled:bg-transparent aria-disabled:border-gray-300 aria-disabled:cursor-not-allowed
63
+ ],
64
+ }
65
+
66
+ def initialize(
67
+ tag: :button,
68
+ text: nil,
69
+ icon: nil,
70
+ size: :m,
71
+ scheme: :primary,
72
+ **attributes
73
+ )
74
+ @tag = tag
75
+ @text = text
76
+ @icon = icon
77
+ @attributes = attributes
78
+
79
+ @attributes[:class] = [
80
+ 'justify-start items-center justify-center gap-1 inline-flex rounded',
81
+ 'focus:ring focus:ring-gray-300 focus:ring-0.5 focus:bg-white focus:ring-offset-0 [&:focus-visible]:outline-none',
82
+ SIZES.fetch(size.to_sym),
83
+ (TEXT_PADDINGS.fetch(size.to_sym) if @text),
84
+ SCHEMES.fetch(scheme.to_sym),
85
+ @attributes[:class],
86
+ ].join(' ')
87
+
88
+ @icon_classes = [
89
+ 'fill-current',
90
+ ICON_SIZES.fetch(size.to_sym),
91
+ ]
92
+ end
93
+
94
+ def call
95
+ content = []
96
+ content << render(component('ui/icon').new(name: @icon, class: @icon_classes)) if @icon
97
+ content << @text if @text
98
+
99
+ content_tag(@tag, safe_join(content), **@attributes)
100
+ end
101
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidusAdmin::UI::Forms::Checkbox::Component < SolidusAdmin::BaseComponent
4
+ SIZES = {
5
+ s: 'w-4 h-4',
6
+ m: 'w-5 h-5',
7
+ }.freeze
8
+
9
+ def initialize(size: :m, **attributes)
10
+ @size = size
11
+ @attributes = attributes
12
+ end
13
+
14
+ def call
15
+ tag.input(
16
+ type: 'checkbox',
17
+ class: "
18
+ #{SIZES.fetch(@size)}
19
+ form-checkbox
20
+ cursor-pointer
21
+ disabled:cursor-not-allowed
22
+
23
+ bg-white rounded border border-2 border-gray-300
24
+ hover:border-gray-700
25
+ focus:ring focus:ring-gray-300 focus:ring-0.5 focus:bg-white focus:ring-offset-0
26
+ active:ring active:ring-gray-300 active:ring-0.5
27
+ disabled:border-gray-300
28
+
29
+ indeterminate:border-gray-700 indeterminate:bg-gray-700 indeterminate:text-white
30
+ indeterminate:hover:border-gray-500 indeterminate:hover:bg-gray-500
31
+ indeterminate:focus:bg-gray-700
32
+ indeterminate:disabled:border-gray-300 indeterminate:disabled:bg-gray-300
33
+
34
+ checked:border-gray-700 checked:bg-gray-700 checked:text-white
35
+ checked:hover:border-gray-500 checked:hover:bg-gray-500
36
+ checked:focus:bg-gray-700
37
+ checked:disabled:border-gray-300 checked:disabled:bg-gray-300
38
+ ",
39
+ **@attributes,
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ <label class="flex flex-col gap-2 w-full">
2
+ <div class="flex gap-1 items-center">
3
+ <span class="
4
+ text-gray-700
5
+ body-tiny-bold
6
+ body-text-xs-semibold
7
+ "><%= @label %></span>
8
+
9
+ <%= render component('ui/toggletip').new(text: @tip) if @tip.present? %>
10
+ </div>
11
+
12
+ <% if @input_attributes.present? %>
13
+ <%= render component('ui/forms/input').new(**@input_attributes) %>
14
+ <% else %>
15
+ <%= content %>
16
+ <% end %>
17
+
18
+ <% if @hint.present? || @error.present? %>
19
+ <div class="
20
+ body-tiny
21
+ [:disabled~&]:text-gray-300 text-gray-500
22
+ flex gap-1 flex-col
23
+ ">
24
+ <%= tag.span @hint if @hint.present? %>
25
+ <%= tag.span safe_join(@error, tag.br), class: "text-red-400" if @error.present? %>
26
+ </div>
27
+ <% end %>
28
+ </label>
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidusAdmin::UI::Forms::Field::Component < SolidusAdmin::BaseComponent
4
+ def initialize(label:, hint: nil, tip: nil, error: nil, input_attributes: nil, **attributes)
5
+ @label = label
6
+ @hint = hint
7
+ @tip = tip
8
+ @error = [error] if error.present?
9
+ @attributes = attributes
10
+ @input_attributes = input_attributes
11
+
12
+ raise ArgumentError, "provide either a block or input_attributes" if content? && input_attributes
13
+ end
14
+
15
+ def self.text_field(form, method, hint: nil, tip: nil, size: :m, **attributes)
16
+ errors = form.object.errors.messages_for(method).presence
17
+
18
+ new(
19
+ label: form.object.class.human_attribute_name(method),
20
+ hint: hint,
21
+ tip: tip,
22
+ error: errors,
23
+ input_attributes: {
24
+ name: "#{form.object_name}[#{method}]",
25
+ tag: :input,
26
+ size: size,
27
+ value: form.object.public_send(method),
28
+ error: (errors.to_sentence.capitalize if errors),
29
+ **attributes,
30
+ }
31
+ )
32
+ end
33
+
34
+ def self.select(form, method, choices, hint: nil, tip: nil, size: :m, **attributes)
35
+ errors = form.object.errors.messages_for(method).presence
36
+
37
+ new(
38
+ label: form.object.class.human_attribute_name(method),
39
+ hint: hint,
40
+ tip: tip,
41
+ error: errors,
42
+ input_attributes: {
43
+ name: "#{form.object_name}[#{method}]",
44
+ tag: :select,
45
+ choices: choices,
46
+ size: size,
47
+ value: form.object.public_send(method),
48
+ error: (errors.to_sentence.capitalize if errors),
49
+ **attributes,
50
+ }
51
+ )
52
+ end
53
+
54
+ def self.text_area(form, method, hint: nil, tip: nil, size: :m, **attributes)
55
+ errors = form.object.errors.messages_for(method).presence
56
+
57
+ new(
58
+ label: form.object.class.human_attribute_name(method),
59
+ hint: hint,
60
+ tip: tip,
61
+ error: errors,
62
+ input_attributes: {
63
+ name: "#{form.object_name}[#{method}]",
64
+ size: size,
65
+ tag: :textarea,
66
+ value: form.object.public_send(method),
67
+ error: (errors.to_sentence.capitalize if errors),
68
+ **attributes,
69
+ }
70
+ )
71
+ end
72
+ end
@@ -0,0 +1,16 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ customValidity: String,
6
+ }
7
+
8
+ connect() {
9
+ if (this.customValidityValue)
10
+ this.element.setCustomValidity(this.customValidityValue)
11
+ }
12
+
13
+ clearCustomValidity() {
14
+ this.element.setCustomValidity('')
15
+ }
16
+ }
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidusAdmin::UI::Forms::Input::Component < SolidusAdmin::BaseComponent
4
+ SIZES = {
5
+ s: "form-control-sm px-3 py-1.5 body-small",
6
+ m: "form-control-md px-3 py-1.5 body-small",
7
+ l: "form-control-lg px-3 py-1.5 body-text"
8
+ }.freeze
9
+
10
+ HEIGHTS = {
11
+ s: "h-7",
12
+ m: "h-9",
13
+ l: "h-12"
14
+ }.freeze
15
+
16
+ MULTILINE_HEIGHTS = {
17
+ s: %w[min-h-[84px]],
18
+ m: %w[min-h-[108px]],
19
+ l: %w[min-h-[144px]],
20
+ }.freeze
21
+
22
+ TYPES = Set.new(%i[
23
+ text
24
+ password
25
+ number
26
+ email
27
+ tel
28
+ url
29
+ search
30
+ color
31
+ date
32
+ datetime-local
33
+ month
34
+ week
35
+ time
36
+ ]).freeze
37
+
38
+ def initialize(tag: :input, size: :m, error: nil, **attributes)
39
+ raise ArgumentError, "unsupported tag: #{tag}" unless %i[input textarea select].include?(tag)
40
+
41
+ specialized_classes = []
42
+
43
+ case tag
44
+ when :input
45
+ specialized_classes << "form-input"
46
+ specialized_classes << HEIGHTS[size]
47
+ if attributes[:type] && !TYPES.include?(attributes[:type])
48
+ raise ArgumentError, "unsupported type attribute: #{attributes[:type]}"
49
+ end
50
+ when :textarea
51
+ specialized_classes << "form-textarea"
52
+ specialized_classes << MULTILINE_HEIGHTS[size]
53
+ when :select
54
+ if attributes[:multiple]
55
+ specialized_classes << "form-multiselect"
56
+ specialized_classes << MULTILINE_HEIGHTS[size]
57
+ else
58
+ specialized_classes << "form-select"
59
+ specialized_classes << "bg-arrow-down-s-fill-gray-700 invalid:bg-arrow-down-s-fill-red-400 aria-invalid:bg-arrow-down-s-fill-red-400"
60
+ specialized_classes << HEIGHTS[size]
61
+ end
62
+ end
63
+
64
+ attributes[:class] = [
65
+ %w[
66
+ w-full
67
+ text-black bg-white border border-gray-300 rounded-sm placeholder:text-gray-400
68
+ hover:border-gray-500
69
+ focus:ring focus:ring-gray-300 focus:ring-0.5 focus:bg-white focus:ring-offset-0 [&:focus-visible]:outline-none
70
+ disabled:bg-gray-50 disabled:text-gray-500 disabled:placeholder:text-gray-300 disabled:cursor-not-allowed
71
+ invalid:border-red-400 invalid:hover:border-red-400 invalid:text-red-400
72
+ aria-invalid:border-red-400 aria-invalid:hover:border-red-400 aria-invalid:text-red-400
73
+ ],
74
+ SIZES[size],
75
+ specialized_classes,
76
+ attributes[:class],
77
+ ].compact.join(" ")
78
+
79
+ @tag = tag
80
+ @size = size
81
+ @error = error
82
+ @attributes = attributes
83
+ end
84
+
85
+ def call
86
+ if @tag == :select && @attributes[:choices]
87
+ with_content options_for_select(@attributes.delete(:choices), @attributes.delete(:value))
88
+ end
89
+
90
+ tag.public_send(
91
+ @tag,
92
+ content,
93
+ "data-controller": stimulus_id,
94
+ "data-#{stimulus_id}-custom-validity-value": @error.presence,
95
+ "data-action": "#{stimulus_id}#clearCustomValidity",
96
+ **@attributes
97
+ )
98
+ end
99
+ end