uchi 0.1.6 → 0.1.7

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/app/assets/javascripts/controllers/fields/belongs_to_controller.js +130 -0
  4. data/app/assets/javascripts/controllers/fields/has_many_controller.js +146 -0
  5. data/app/assets/javascripts/uchi/application.js +804 -3
  6. data/app/assets/javascripts/uchi.js +9 -0
  7. data/app/assets/stylesheets/uchi/application.css +81 -1549
  8. data/app/assets/tailwind/uchi.css +2 -2
  9. data/app/components/uchi/field/belongs_to/edit.html.erb +73 -1
  10. data/app/components/uchi/field/belongs_to.rb +25 -25
  11. data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +1 -1
  12. data/app/components/uchi/field/has_many/edit.html.erb +86 -1
  13. data/app/components/uchi/field/has_many/show.html.erb +1 -1
  14. data/app/components/uchi/field/has_many.rb +59 -11
  15. data/app/components/uchi/ui/navigation/navigation.html.erb +1 -1
  16. data/app/components/uchi/ui/page_header/page_header.html.erb +7 -7
  17. data/app/controllers/uchi/belongs_to/associated_records_controller.rb +89 -0
  18. data/app/controllers/uchi/has_many/associated_records_controller.rb +89 -0
  19. data/app/views/layouts/uchi/_javascript.html.erb +1 -0
  20. data/app/views/layouts/uchi/_stylesheets.html.erb +1 -0
  21. data/app/views/layouts/uchi/application.html.erb +4 -4
  22. data/app/views/uchi/belongs_to/associated_records/index.html.erb +13 -0
  23. data/app/views/uchi/has_many/associated_records/index.html.erb +26 -0
  24. data/app/views/uchi/navigation/_main.html.erb +83 -0
  25. data/lib/generators/uchi/controller/controller_generator.rb +0 -4
  26. data/lib/generators/uchi/install/install_generator.rb +1 -1
  27. data/lib/uchi/field/configuration.rb +1 -1
  28. data/lib/uchi/repository.rb +13 -2
  29. data/lib/uchi/routes.rb +45 -0
  30. data/lib/uchi/version.rb +1 -1
  31. data/lib/uchi.rb +4 -1
  32. metadata +10 -1
@@ -17,5 +17,5 @@ running the following command:
17
17
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
18
18
  @import "flowbite/src/themes/default"; /* This imports them from node_modules/flowbite/src/themes/default.css */
19
19
 
20
- /* Configure the source files of Flowbite */
21
- @source "../../../node_modules/flowbite";
20
+ /* Don't include classes used in docs into Uchis stylesheet */
21
+ @source not "docs";
@@ -1 +1,73 @@
1
- <%= render(Uchi::Flowbite::InputField::Select.new(**options)) %>
1
+ <div
2
+ class="relative"
3
+ data-action="keydown.esc->belongs-to#closeDropdown"
4
+ data-controller="belongs-to"
5
+ data-belongs-to-backend-url-value="<%= helpers.belongs_to_associated_records_path(field: field.name, model: repository.model, record_id: record&.id) %>"
6
+ >
7
+ <%= render(Uchi::Flowbite::Input::Label.new(attribute: field.name, form: form, options: {for: dom_id_for_toggle})) { label } %>
8
+
9
+ <%# Actual input. This is the hidden part which is what is sent to the server %>
10
+ <%= form.hidden_field "#{field.name}_id",
11
+ data: {
12
+ 'belongs-to-target': 'id',
13
+ }
14
+ %>
15
+
16
+ <button
17
+ class="<%= Uchi::Flowbite::Input::Field.classes.join(" ") %> text-left"
18
+ data-action="belongs-to#toggle"
19
+ id="<%= dom_id_for_toggle %>"
20
+ type="button"
21
+ >
22
+ <div class="inline-flex items-center justify-between w-full">
23
+ <div class="min-h-[1em]" data-belongs-to-target="label"><%= record_title(associated_record) %></div>
24
+ <svg
25
+ class="w-4 h-4 ms-1.5 -me-0.5"
26
+ aria-hidden="true"
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width="24"
29
+ height="24"
30
+ fill="none"
31
+ viewBox="0 0 24 24"
32
+ >
33
+ <path
34
+ stroke="currentColor"
35
+ stroke-linecap="round"
36
+ stroke-linejoin="round"
37
+ stroke-width="2"
38
+ d="m19 9-7 7-7-7"
39
+ />
40
+ </svg>
41
+ </div>
42
+ </button>
43
+
44
+ <div class="absolute z-10 bg-neutral-primary-medium border border-default-medium rounded-base shadow-lg w-full mt-1" data-belongs-to-target="dropdown">
45
+ <div class="bg-neutral-primary-medium border-b border-default-medium p-2 rounded-t-base">
46
+ <%# Search input. This is the visible part that users type into %>
47
+ <label for="<%= dom_id_for_filter_query_input %>" class="sr-only">Search</label>
48
+ <%=
49
+ text_field_tag(
50
+ "filter_query",
51
+ nil,
52
+ class: "bg-neutral-secondary-strong border border-default-strong text-heading text-sm rounded focus:ring-brand focus:border-brand block w-full px-2.5 py-2 shadow-xs placeholder:text-body",
53
+ data: {
54
+ 'belongs-to-target': 'input',
55
+ action: 'belongs-to#handleChange focus->belongs-to#handleFocus',
56
+ autocomplete: 'off'
57
+ },
58
+ id: dom_id_for_filter_query_input,
59
+ )
60
+ %>
61
+ </div>
62
+ <div class="max-h-96 overflow-y-auto" data-belongs-to-target="list">
63
+ <!-- Options will be dynamically inserted here -->
64
+ <div class="grid place-items-center p-8">
65
+ <%= render(Uchi::Ui::Spinner.new(message: associated_repository.translate.loading_message)) %>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <% if hint.present? %>
71
+ <%= render(Uchi::Flowbite::Input::Hint.new(attribute: field.name, form: form)) { hint } %>
72
+ <% end %>
73
+ </div>
@@ -12,6 +12,13 @@ module Uchi
12
12
 
13
13
  def associated_repository
14
14
  reflection = record.class.reflect_on_association(field.name)
15
+
16
+ unless reflection
17
+ raise \
18
+ ArgumentError,
19
+ "No association named #{field.name.inspect} found on #{record.class}"
20
+ end
21
+
15
22
  model = reflection.klass
16
23
  repository_class = Uchi::Repository.for_model(model)
17
24
  repository_class.new
@@ -23,48 +30,41 @@ module Uchi
23
30
  end
24
31
 
25
32
  class Edit < Uchi::Field::Base::Edit
33
+ include Helpers
34
+
26
35
  def associated_repository
27
- model = reflection.klass
28
- repository_class = Uchi::Repository.for_model(model)
29
- repository_class.new
36
+ @associated_repository ||= begin
37
+ model = reflection.klass
38
+ repository_class = Uchi::Repository.for_model(model)
39
+ repository_class.new
40
+ end
30
41
  end
31
42
 
32
43
  def attribute_name
33
44
  reflection.foreign_key
34
45
  end
35
46
 
36
- def collection
37
- query = associated_repository.find_all
38
- field.collection_query.call(query)
47
+ def dom_id_for_filter_query_input
48
+ "#{form.object_name}_#{attribute_name}_belongs_to_filter_query"
39
49
  end
40
50
 
41
- private
51
+ def dom_id_for_toggle
52
+ "#{form.object_name}_#{attribute_name}_belongs_to_toggle"
53
+ end
42
54
 
43
- def collection_for_select
44
- repository = associated_repository
45
- items = []
46
- items << ["", nil] if optional?
47
- items + collection.map do |item|
48
- [repository.title(item), item.id]
49
- end
55
+ def record_title(record)
56
+ return "" if record.nil?
57
+
58
+ associated_repository.title(record)
50
59
  end
51
60
 
61
+ private
62
+
52
63
  # Returns true if the association is optional.
53
64
  def optional?
54
65
  reflection.options[:optional] == true
55
66
  end
56
67
 
57
- def options
58
- options = {
59
- attribute: attribute_name,
60
- collection: collection_for_select,
61
- form: form,
62
- label: {content: label}
63
- }
64
- options[:hint] = {content: hint} if hint.present?
65
- options
66
- end
67
-
68
68
  def reflection
69
69
  @reflection ||= record.class.reflect_on_association(field.name)
70
70
  end
@@ -22,7 +22,7 @@
22
22
  )
23
23
  )) do %>
24
24
  <%= render(Uchi::Ui::Frame.new) do %>
25
- <div class="grid grid place-items-center p-8">
25
+ <div class="grid place-items-center p-8">
26
26
  <%= render(Uchi::Ui::Spinner.new(message: repository.translate.loading_message)) %>
27
27
  </div>
28
28
  <% end %>
@@ -1 +1,86 @@
1
- <%# This space intentionally left blank %>
1
+ <div
2
+ class="relative"
3
+ data-action="keydown.esc->has-many#closeDropdown"
4
+ data-controller="has-many"
5
+ data-has-many-backend-url-value="<%= helpers.has_many_associated_records_path(field: field.name, model: repository.model, record_id: record&.id) %>"
6
+ data-has-many-field-name-value="<%= field_name_for_input %>"
7
+ >
8
+ <%= render(Uchi::Flowbite::Input::Label.new(attribute: field.name, form: form, options: {for: dom_id_for_toggle})) { label } %>
9
+
10
+ <%# Hidden fields to store selected IDs as an array %>
11
+ <div data-has-many-target="idsContainer">
12
+ <% associated_records.each do |assoc_record| %>
13
+ <input
14
+ type="hidden"
15
+ name="<%= field_name_for_input %>"
16
+ value="<%= assoc_record.id %>"
17
+ data-has-many-target="idField"
18
+ data-title="<%= record_title(assoc_record) %>"
19
+ >
20
+ <% end %>
21
+ </div>
22
+
23
+ <button
24
+ class="<%= Uchi::Flowbite::Input::Field.classes.join(" ") %> text-left"
25
+ data-action="has-many#toggle"
26
+ id="<%= dom_id_for_toggle %>"
27
+ type="button"
28
+ >
29
+ <div class="inline-flex items-center justify-between w-full">
30
+ <div class="min-h-[1em] truncate" data-has-many-target="label">
31
+ <% if associated_records.any? %>
32
+ <%= selected_titles %>
33
+ <% else %>
34
+ <span class="text-body-subtle">Select items...</span>
35
+ <% end %>
36
+ </div>
37
+ <svg
38
+ class="w-4 h-4 ms-1.5 -me-0.5 flex-shrink-0"
39
+ aria-hidden="true"
40
+ xmlns="http://www.w3.org/2000/svg"
41
+ width="24"
42
+ height="24"
43
+ fill="none"
44
+ viewBox="0 0 24 24"
45
+ >
46
+ <path
47
+ stroke="currentColor"
48
+ stroke-linecap="round"
49
+ stroke-linejoin="round"
50
+ stroke-width="2"
51
+ d="m19 9-7 7-7-7"
52
+ />
53
+ </svg>
54
+ </div>
55
+ </button>
56
+
57
+ <div class="absolute z-10 bg-neutral-primary-medium border border-default-medium rounded-base shadow-lg w-full mt-1" data-has-many-target="dropdown">
58
+ <div class="p-2 border-b border-default-medium">
59
+ <%# Search input. This is the visible part that users type into %>
60
+ <label for="<%= dom_id_for_filter_query_input %>" class="sr-only">Search</label>
61
+ <%=
62
+ text_field_tag(
63
+ "filter_query",
64
+ nil,
65
+ class: "bg-neutral-secondary-strong border border-default-strong text-heading text-sm rounded focus:ring-brand focus:border-brand block w-full px-2.5 py-2 shadow-xs placeholder:text-body",
66
+ data: {
67
+ 'has-many-target': 'input',
68
+ action: 'has-many#handleChange focus->has-many#handleFocus',
69
+ autocomplete: 'off'
70
+ },
71
+ id: dom_id_for_filter_query_input,
72
+ )
73
+ %>
74
+ </div>
75
+ <div class="max-h-96 overflow-y-auto" data-has-many-target="list">
76
+ <!-- Options will be dynamically inserted here -->
77
+ <div class="grid place-items-center p-8">
78
+ <%= render(Uchi::Ui::Spinner.new(message: associated_repository.translate.loading_message)) %>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <% if hint.present? %>
84
+ <%= render(Uchi::Flowbite::Input::Hint.new(attribute: field.name, form: form)) { hint } %>
85
+ <% end %>
86
+ </div>
@@ -22,7 +22,7 @@
22
22
  )
23
23
  )) do %>
24
24
  <%= render(Uchi::Ui::Frame.new) do %>
25
- <div class="grid grid place-items-center p-8">
25
+ <div class="grid place-items-center p-8">
26
26
  <%= render(Uchi::Ui::Spinner.new(message: repository.translate.loading_message)) %>
27
27
  </div>
28
28
  <% end %>
@@ -6,6 +6,52 @@ module Uchi
6
6
  DEFAULT_COLLECTION_QUERY = ->(query) { query }.freeze
7
7
 
8
8
  class Edit < Uchi::Field::Base::Edit
9
+ def associated_records
10
+ records = field.value(record)
11
+ return [] if records.nil?
12
+
13
+ associated_repository.find_all(scope: records)
14
+ end
15
+
16
+ def associated_repository
17
+ @associated_repository ||= begin
18
+ model = reflection.klass
19
+ repository_class = Uchi::Repository.for_model(model)
20
+ repository_class.new
21
+ end
22
+ end
23
+
24
+ def attribute_name
25
+ "#{field.name.to_s.singularize}_ids"
26
+ end
27
+
28
+ def dom_id_for_filter_query_input
29
+ "#{form.object_name}_#{attribute_name}_has_many_filter_query"
30
+ end
31
+
32
+ def dom_id_for_toggle
33
+ "#{form.object_name}_#{attribute_name}_has_many_toggle"
34
+ end
35
+
36
+ def field_name_for_input
37
+ "#{form.object_name}[#{attribute_name}][]"
38
+ end
39
+
40
+ def record_title(record)
41
+ return "" if record.nil?
42
+
43
+ associated_repository.title(record)
44
+ end
45
+
46
+ def selected_titles
47
+ associated_records.map { |record| record_title(record) }.join(", ")
48
+ end
49
+
50
+ private
51
+
52
+ def reflection
53
+ @reflection ||= record.class.reflect_on_association(field.name)
54
+ end
9
55
  end
10
56
 
11
57
  class Index < Uchi::Field::Base::Index
@@ -19,10 +65,15 @@ module Uchi
19
65
  end
20
66
 
21
67
  def associated_repository
22
- reflection = record.class.reflect_on_association(field.name)
23
- model = reflection.klass
24
- repository_class = Uchi::Repository.for_model(model)
25
- raise NameError, "No repository found for associated model #{model}" unless repository_class
68
+ raise NameError, "No association named #{field.name.inspect} found on #{record.class}" unless reflection
69
+
70
+ associated_model = reflection.klass
71
+ repository_class = Uchi::Repository.for_model(associated_model)
72
+ unless repository_class
73
+ raise \
74
+ NameError,
75
+ "No repository found for associated model #{associated_model}"
76
+ end
26
77
 
27
78
  repository_class.new
28
79
  end
@@ -46,13 +97,12 @@ module Uchi
46
97
  #
47
98
  # @return [ActiveRecord::Reflection, nil]
48
99
  def inverse_association
49
- reflection = record.class.reflect_on_association(field.name)
50
100
  reflection&.inverse_of
51
101
  end
52
102
 
53
103
  # Returns the ActiveRecord::Reflection for this association.
54
104
  #
55
- # @return [ActiveRecord::Reflection]
105
+ # @return [ActiveRecord::Reflection, nil]
56
106
  def reflection
57
107
  @reflection ||= record.class.reflect_on_association(field.name)
58
108
  end
@@ -94,13 +144,11 @@ module Uchi
94
144
  def param_key
95
145
  # TODO: This is too naive. We need to match this to the actual foreign
96
146
  # key of the model.
97
- :"#{name}_id"
147
+ :"#{name.to_s.singularize}_ids"
98
148
  end
99
149
 
100
- protected
101
-
102
- def default_on
103
- [:show]
150
+ def permitted_param
151
+ {param_key => []}
104
152
  end
105
153
  end
106
154
  end
@@ -1,7 +1,7 @@
1
1
  <ul class="space-y-2 font-medium">
2
2
  <% items.each do |item| %>
3
3
  <li>
4
- <%= link_to(item[:path], :class => "flex items-center px-2 py-1.5 text-body rounded-base hover:bg-neutral-tertiary hover:text-fg-brand group") do %>
4
+ <%= link_to(item[:path], :class => "flex items-center py-1.5 text-body rounded-base hover:bg-neutral-tertiary hover:text-fg-brand group md:px-2") do %>
5
5
  <span class="ms-3"><%= item[:label] %></span>
6
6
  <% end %>
7
7
  </li>
@@ -1,13 +1,13 @@
1
- <header class="px-4 mb-6 space-y-6 md:px-0">
2
- <div>
3
- <% if breadcrumb? %>
1
+ <header class="px-0 mb-6 space-y-6 md:px-0">
2
+ <% if breadcrumb? %>
3
+ <div class="px-2">
4
4
  <%= breadcrumb %>
5
- <% end %>
6
- </div>
5
+ </div>
6
+ <% end %>
7
7
 
8
- <div class="items-start justify-between space-x-6 md:flex md:px-0">
8
+ <div class="items-start justify-between space-y-2 space-x-6 md:flex md:px-0 md:space-y-0">
9
9
  <div>
10
- <h1 class="text-3xl font-semibold tracking-tight text-heading group">
10
+ <h1 class="px-1 text-3xl font-semibold tracking-tight text-heading group">
11
11
  <%= title %>
12
12
  </h1>
13
13
 
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uchi/pagination/controller"
4
+
5
+ module Uchi
6
+ module BelongsTo
7
+ # Companion controller for the Stimulus-based belongs_to_controller.
8
+ #
9
+ # Provides backend support for fetching associated records for a belongs_to
10
+ # field via AJAX.
11
+ class AssociatedRecordsController < Uchi::ApplicationController
12
+ layout false
13
+
14
+ def index
15
+ @current_value = field.value(parent_record)
16
+
17
+ @field_name = params[:field]
18
+ @records = field.collection_query.call(find_all_records_from_association)
19
+ end
20
+
21
+ protected
22
+
23
+ helper_method def record_title(record)
24
+ return "" if record.nil?
25
+
26
+ associated_repository.title(record)
27
+ end
28
+
29
+ helper_method def source_repository
30
+ @source_repository ||= begin
31
+ model_name = params[:model]
32
+ repository_class = Uchi::Repository.for_model(model_name)
33
+ raise NameError, "No repository found for model #{model_name}" unless repository_class
34
+
35
+ repository_class.new
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def associated_repository
42
+ @associated_repository ||= begin
43
+ associated_repository = Uchi::Repository.for_model(association.klass)&.new
44
+ raise NameError, "No repository found for associated model #{association.klass}" unless associated_repository
45
+
46
+ associated_repository
47
+ end
48
+ end
49
+
50
+ def association
51
+ @association ||= begin
52
+ association = source_repository.model.reflect_on_association(field.name.to_sym)
53
+ raise NameError, "No association named #{field.name} on #{source_repository.model}" unless association
54
+
55
+ association
56
+ end
57
+ end
58
+
59
+ def field
60
+ @field ||= begin
61
+ field_name = params[:field]
62
+ field = source_repository.fields.find { |f| f.name == field_name.to_sym }
63
+ raise NameError, "No field named #{field_name} on #{source_repository.model}" unless field
64
+
65
+ field
66
+ end
67
+ end
68
+
69
+ def find_all_records(repository:, scope: nil)
70
+ # Duplicated from Uchi::RepositoryController; consider refactoring.
71
+ repository
72
+ .find_all(
73
+ scope: scope,
74
+ search: params[:query]
75
+ )
76
+ end
77
+
78
+ def find_all_records_from_association
79
+ find_all_records(repository: associated_repository)
80
+ end
81
+
82
+ def parent_record
83
+ return nil unless params[:record_id].present?
84
+
85
+ source_repository.find(params[:record_id])
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uchi/pagination/controller"
4
+
5
+ module Uchi
6
+ module HasMany
7
+ # Companion controller for the Stimulus-based has_many_controller.
8
+ #
9
+ # Provides backend support for fetching associated records for a has_many
10
+ # field via AJAX.
11
+ class AssociatedRecordsController < Uchi::ApplicationController
12
+ layout false
13
+
14
+ def index
15
+ @current_values = field.value(parent_record) || []
16
+
17
+ @field_name = params[:field]
18
+ @records = field.collection_query.call(find_all_records_from_association)
19
+ end
20
+
21
+ protected
22
+
23
+ helper_method def record_title(record)
24
+ return "" if record.nil?
25
+
26
+ associated_repository.title(record)
27
+ end
28
+
29
+ helper_method def source_repository
30
+ @source_repository ||= begin
31
+ model_name = params[:model]
32
+ repository_class = Uchi::Repository.for_model(model_name)
33
+ raise NameError, "No repository found for model #{model_name}" unless repository_class
34
+
35
+ repository_class.new
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def associated_repository
42
+ @associated_repository ||= begin
43
+ associated_repository = Uchi::Repository.for_model(association.klass)&.new
44
+ raise NameError, "No repository found for associated model #{association.klass}" unless associated_repository
45
+
46
+ associated_repository
47
+ end
48
+ end
49
+
50
+ def association
51
+ @association ||= begin
52
+ association = source_repository.model.reflect_on_association(field.name.to_sym)
53
+ raise NameError, "No association named #{field.name} on #{source_repository.model}" unless association
54
+
55
+ association
56
+ end
57
+ end
58
+
59
+ def field
60
+ @field ||= begin
61
+ field_name = params[:field]
62
+ field = source_repository.fields.find { |f| f.name == field_name.to_sym }
63
+ raise NameError, "No field named #{field_name} on #{source_repository.model}" unless field
64
+
65
+ field
66
+ end
67
+ end
68
+
69
+ def find_all_records(repository:, scope: nil)
70
+ # Duplicated from Uchi::RepositoryController; consider refactoring.
71
+ repository
72
+ .find_all(
73
+ scope: scope,
74
+ search: params[:query]
75
+ )
76
+ end
77
+
78
+ def find_all_records_from_association
79
+ find_all_records(repository: associated_repository)
80
+ end
81
+
82
+ def parent_record
83
+ return nil unless params[:record_id].present?
84
+
85
+ source_repository.find(params[:record_id])
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1 @@
1
+ <%= javascript_include_tag "uchi/application", "data-turbo-track": "reload" %>
@@ -0,0 +1 @@
1
+ <%= stylesheet_link_tag "uchi/application", media: "all", "data-turbo-track": "reload" %>
@@ -1,6 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
 
3
- <html>
3
+ <html class="h-full">
4
4
  <head>
5
5
  <title>
6
6
  <%= content_for?(:page_title) ? yield(:page_title) : "Uchi" %>
@@ -11,11 +11,11 @@
11
11
 
12
12
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
13
 
14
- <%= javascript_include_tag "uchi/application", "data-turbo-track" => "reload" %>
15
- <%= stylesheet_link_tag "uchi/application", media: "all", "data-turbo-track": "reload" %>
14
+ <%= render "layouts/uchi/javascript" %>
15
+ <%= render "layouts/uchi/stylesheets" %>
16
16
  </head>
17
17
 
18
- <body class="antialiased bg-neutral-secondary-medium p-2">
18
+ <body class="antialiased bg-neutral-secondary-medium h-full p-2">
19
19
  <div class="md:flex">
20
20
  <%= render(partial: "uchi/navigation/main") %>
21
21
 
@@ -0,0 +1,13 @@
1
+ <ul class="p-2 text-sm text-body font-medium">
2
+ <% @records.each do |record| %>
3
+ <li
4
+ class="inline-flex items-center w-full p-2 hover:bg-neutral-tertiary-medium hover:text-heading rounded aria-selected:bg-brand aria-selected:text-white"
5
+ data-action="belongs-to#selectOption"
6
+ data-id="<%= record.id %>"
7
+ id="<%= dom_id(record, [source_repository.model, @field_name].join("-")) %>"
8
+ role="option"
9
+ >
10
+ <%= record_title(record) %>
11
+ </li>
12
+ <% end %>
13
+ </ul>
@@ -0,0 +1,26 @@
1
+ <ul class="p-4 text-sm text-body font-medium space-y-4">
2
+ <% @records.each do |record| %>
3
+ <%
4
+ checkbox_id = dom_id(record, [source_repository.model, @field_name, 'checkbox'].join("-"))
5
+ %>
6
+ <li
7
+ data-id="<%= record.id %>"
8
+ id="<%= dom_id(record, [source_repository.model, @field_name].join("-")) %>"
9
+ role="option"
10
+ >
11
+ <div class="flex items-center">
12
+ <input
13
+ type="checkbox"
14
+ id="<%= checkbox_id %>"
15
+ class="w-4 h-4 border border-default-strong rounded-xs bg-neutral-secondary-strong focus:ring-2 focus:ring-brand-soft cursor-pointer"
16
+ data-action="change->has-many#handleCheckboxChange"
17
+ data-has-many-target="checkbox"
18
+ <%= 'checked' if @current_values.include?(record) %>
19
+ >
20
+ <label for="<%= checkbox_id %>" class="ms-2 text-sm font-medium text-heading cursor-pointer">
21
+ <%= record_title(record) %>
22
+ </label>
23
+ </div>
24
+ </li>
25
+ <% end %>
26
+ </ul>