uchi 0.1.2 → 0.1.4
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/README.md +222 -0
- data/app/assets/config/uchi_manifest.js +2 -0
- data/app/assets/javascripts/uchi/application.js +6095 -0
- data/app/assets/javascripts/uchi.js +4 -0
- data/app/assets/stylesheets/uchi/application.css +3971 -0
- data/app/assets/stylesheets/uchi/uchi.css +17 -0
- data/app/assets/tailwind/uchi.css +21 -0
- data/app/components/flowbite/breadcrumb.rb +33 -0
- data/app/components/flowbite/breadcrumb_home.rb +26 -0
- data/app/components/flowbite/breadcrumb_item/current.rb +33 -0
- data/app/components/flowbite/breadcrumb_item/first.rb +35 -0
- data/app/components/flowbite/breadcrumb_item.rb +48 -0
- data/app/components/flowbite/breadcrumb_separator.rb +30 -0
- data/app/components/flowbite/button/outline.rb +22 -0
- data/app/components/flowbite/button/pill.rb +40 -0
- data/app/components/flowbite/button.rb +92 -0
- data/app/components/flowbite/card.rb +45 -0
- data/app/components/flowbite/input/checkbox.rb +73 -0
- data/app/components/flowbite/input/date.rb +11 -0
- data/app/components/flowbite/input/date_time.rb +11 -0
- data/app/components/flowbite/input/email.rb +12 -0
- data/app/components/flowbite/input/field.rb +117 -0
- data/app/components/flowbite/input/file.rb +30 -0
- data/app/components/flowbite/input/hint.rb +57 -0
- data/app/components/flowbite/input/label.rb +82 -0
- data/app/components/flowbite/input/number.rb +11 -0
- data/app/components/flowbite/input/password.rb +11 -0
- data/app/components/flowbite/input/phone.rb +11 -0
- data/app/components/flowbite/input/radio_button.rb +50 -0
- data/app/components/flowbite/input/select.rb +49 -0
- data/app/components/flowbite/input/textarea.rb +42 -0
- data/app/components/flowbite/input/url.rb +12 -0
- data/app/components/flowbite/input/validation_error.rb +11 -0
- data/app/components/flowbite/input_field/checkbox.html.erb +14 -0
- data/app/components/flowbite/input_field/checkbox.rb +54 -0
- data/app/components/flowbite/input_field/date.rb +13 -0
- data/app/components/flowbite/input_field/date_time.rb +13 -0
- data/app/components/flowbite/input_field/email.rb +13 -0
- data/app/components/flowbite/input_field/file.rb +13 -0
- data/app/components/flowbite/input_field/input_field.html.erb +8 -0
- data/app/components/flowbite/input_field/number.rb +13 -0
- data/app/components/flowbite/input_field/password.rb +13 -0
- data/app/components/flowbite/input_field/phone.rb +13 -0
- data/app/components/flowbite/input_field/radio_button.html.erb +14 -0
- data/app/components/flowbite/input_field/radio_button.rb +86 -0
- data/app/components/flowbite/input_field/select.rb +31 -0
- data/app/components/flowbite/input_field/text.rb +8 -0
- data/app/components/flowbite/input_field/textarea.rb +13 -0
- data/app/components/flowbite/input_field/url.rb +13 -0
- data/app/components/flowbite/input_field.rb +192 -0
- data/app/components/flowbite/link.rb +21 -0
- data/app/components/flowbite/style.rb +13 -0
- data/app/components/flowbite/toast/icon.html.erb +5 -0
- data/app/components/flowbite/toast/icon.rb +57 -0
- data/app/components/flowbite/toast/toast.html.erb +11 -0
- data/app/components/flowbite/toast.rb +34 -0
- data/app/components/uchi/field/base.rb +57 -0
- data/app/components/uchi/field/belongs_to/edit.html.erb +1 -0
- data/app/components/uchi/field/belongs_to/index.html.erb +1 -0
- data/app/components/uchi/field/belongs_to/show.html.erb +3 -0
- data/app/components/uchi/field/belongs_to.rb +105 -0
- data/app/components/uchi/field/blank/edit.html.erb +1 -0
- data/app/components/uchi/field/blank/index.html.erb +1 -0
- data/app/components/uchi/field/blank/show.html.erb +1 -0
- data/app/components/uchi/field/blank.rb +16 -0
- data/app/components/uchi/field/boolean/edit.html.erb +1 -0
- data/app/components/uchi/field/boolean/index.html.erb +9 -0
- data/app/components/uchi/field/boolean/show.html.erb +9 -0
- data/app/components/uchi/field/boolean.rb +27 -0
- data/app/components/uchi/field/date/edit.html.erb +1 -0
- data/app/components/uchi/field/date/index.html.erb +1 -0
- data/app/components/uchi/field/date/show.html.erb +1 -0
- data/app/components/uchi/field/date.rb +27 -0
- data/app/components/uchi/field/date_time/edit.html.erb +1 -0
- data/app/components/uchi/field/date_time/index.html.erb +1 -0
- data/app/components/uchi/field/date_time/show.html.erb +1 -0
- data/app/components/uchi/field/date_time.rb +27 -0
- data/app/components/uchi/field/file/edit.html.erb +1 -0
- data/app/components/uchi/field/file/index.html.erb +6 -0
- data/app/components/uchi/field/file/show.html.erb +8 -0
- data/app/components/uchi/field/file.rb +37 -0
- data/app/components/uchi/field/has_and_belongs_to_many/edit.html.erb +9 -0
- data/app/components/uchi/field/has_and_belongs_to_many/index.html.erb +1 -0
- data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +28 -0
- data/app/components/uchi/field/has_and_belongs_to_many.rb +131 -0
- data/app/components/uchi/field/has_many/edit.html.erb +1 -0
- data/app/components/uchi/field/has_many/index.html.erb +1 -0
- data/app/components/uchi/field/has_many/show.html.erb +28 -0
- data/app/components/uchi/field/has_many.rb +107 -0
- data/app/components/uchi/field/id/index.html.erb +4 -0
- data/app/components/uchi/field/id/show.html.erb +4 -0
- data/app/components/uchi/field/id.rb +26 -0
- data/app/components/uchi/field/image/edit.html.erb +1 -0
- data/app/components/uchi/field/image/index.html.erb +6 -0
- data/app/components/uchi/field/image/show.html.erb +6 -0
- data/app/components/uchi/field/image.rb +38 -0
- data/app/components/uchi/field/number/edit.html.erb +1 -0
- data/app/components/uchi/field/number/index.html.erb +1 -0
- data/app/components/uchi/field/number/show.html.erb +1 -0
- data/app/components/uchi/field/number.rb +32 -0
- data/app/components/uchi/field/string/edit.html.erb +1 -0
- data/app/components/uchi/field/string/index.html.erb +1 -0
- data/app/components/uchi/field/string/show.html.erb +1 -0
- data/app/components/uchi/field/string.rb +33 -0
- data/app/components/uchi/field/text/edit.html.erb +1 -0
- data/app/components/uchi/field/text/index.html.erb +1 -0
- data/app/components/uchi/field/text/show.html.erb +1 -0
- data/app/components/uchi/field/text.rb +38 -0
- data/app/components/uchi/ui/breadcrumb/breadcrumb.html.erb +13 -0
- data/app/components/uchi/ui/breadcrumb.rb +14 -0
- data/app/components/uchi/ui/form/footer/footer.html.erb +5 -0
- data/app/components/uchi/ui/form/footer.rb +15 -0
- data/app/components/uchi/ui/form/input/collection_checkboxes.html.erb +32 -0
- data/app/components/uchi/ui/form/input/collection_checkboxes.rb +125 -0
- data/app/components/uchi/ui/frame/frame.html.erb +3 -0
- data/app/components/uchi/ui/frame.rb +10 -0
- data/app/components/uchi/ui/index/records_table/records_table.html.erb +67 -0
- data/app/components/uchi/ui/index/records_table/search_form/search_form.html.erb +21 -0
- data/app/components/uchi/ui/index/records_table/search_form.rb +49 -0
- data/app/components/uchi/ui/index/records_table.rb +29 -0
- data/app/components/uchi/ui/index/turbo_frame.rb +50 -0
- data/app/components/uchi/ui/page_header/page_header.html.erb +24 -0
- data/app/components/uchi/ui/page_header.rb +18 -0
- data/app/components/uchi/ui/pagination/current_link.html.erb +3 -0
- data/app/components/uchi/ui/pagination/current_link.rb +10 -0
- data/app/components/uchi/ui/pagination/gap.html.erb +3 -0
- data/app/components/uchi/ui/pagination/gap.rb +10 -0
- data/app/components/uchi/ui/pagination/item.rb +24 -0
- data/app/components/uchi/ui/pagination/link.html.erb +3 -0
- data/app/components/uchi/ui/pagination/link.rb +10 -0
- data/app/components/uchi/ui/pagination/next_link.html.erb +8 -0
- data/app/components/uchi/ui/pagination/next_link.rb +22 -0
- data/app/components/uchi/ui/pagination/pagination.html.erb +15 -0
- data/app/components/uchi/ui/pagination/previous_link.html.erb +8 -0
- data/app/components/uchi/ui/pagination/previous_link.rb +22 -0
- data/app/components/uchi/ui/pagination.rb +48 -0
- data/app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb +14 -0
- data/app/components/uchi/ui/show/attribute_fields.rb +18 -0
- data/app/components/uchi/ui/spinner/spinner.html.erb +7 -0
- data/app/components/uchi/ui/spinner.rb +15 -0
- data/app/controllers/uchi/application_controller.rb +4 -0
- data/app/controllers/uchi/controller.rb +13 -0
- data/app/controllers/uchi/repository_controller.rb +166 -0
- data/app/helpers/uchi/application_helper.rb +17 -0
- data/app/jobs/uchi/application_job.rb +4 -0
- data/app/mailers/uchi/application_mailer.rb +6 -0
- data/app/views/layouts/uchi/_flash_messages.html.erb +10 -0
- data/app/views/layouts/uchi/application.html.erb +33 -0
- data/app/views/uchi/repository/edit.html.erb +40 -0
- data/app/views/uchi/repository/index.html.erb +49 -0
- data/app/views/uchi/repository/new.html.erb +37 -0
- data/app/views/uchi/repository/show.html.erb +41 -0
- data/lib/generators/uchi/controller/controller_generator.rb +16 -0
- data/lib/generators/uchi/controller/templates/controller.rb.tt +11 -0
- data/lib/generators/uchi/install/install_generator.rb +13 -0
- data/lib/generators/uchi/repository/repository_generator.rb +16 -0
- data/lib/generators/uchi/repository/templates/repository.rb.tt +11 -0
- data/lib/tasks/uchi_tasks.rake +4 -0
- data/lib/uchi/application_record.rb +5 -0
- data/lib/uchi/engine.rb +24 -0
- data/lib/uchi/field/configuration.rb +142 -0
- data/lib/uchi/field.rb +80 -0
- data/lib/uchi/i18n.rb +13 -0
- data/lib/uchi/pagination/controller.rb +26 -0
- data/lib/uchi/pagination/page.rb +20 -0
- data/lib/uchi/pagy/LICENSE.txt +21 -0
- data/lib/uchi/pagy/classes/exceptions.rb +35 -0
- data/lib/uchi/pagy/classes/offset/offset.rb +56 -0
- data/lib/uchi/pagy/classes/request.rb +38 -0
- data/lib/uchi/pagy/modules/abilities/configurable.rb +38 -0
- data/lib/uchi/pagy/modules/abilities/linkable.rb +62 -0
- data/lib/uchi/pagy/modules/abilities/rangeable.rb +17 -0
- data/lib/uchi/pagy/modules/abilities/shiftable.rb +14 -0
- data/lib/uchi/pagy/modules/console.rb +40 -0
- data/lib/uchi/pagy/toolbox/helpers/loader.rb +19 -0
- data/lib/uchi/pagy/toolbox/helpers/page_url.rb +25 -0
- data/lib/uchi/pagy/toolbox/paginators/method.rb +21 -0
- data/lib/uchi/pagy/toolbox/paginators/offset.rb +35 -0
- data/lib/uchi/pagy.rb +60 -0
- data/lib/uchi/repository/routes.rb +62 -0
- data/lib/uchi/repository/translate.rb +284 -0
- data/lib/uchi/repository.rb +156 -0
- data/lib/uchi/sort_order.rb +35 -0
- data/lib/uchi/version.rb +5 -0
- data/lib/uchi.rb +18 -0
- data/uchi.gemspec +35 -0
- metadata +197 -107
- data/.github/dependabot.yml +0 -17
- data/.github/workflows/build.yml +0 -23
- data/.github/workflows/lint.yml +0 -30
- data/package.json +0 -31
- data/sig/uchi.rbs +0 -4
- data/test/components/uchi/field/belongs_to_test.rb +0 -134
- data/test/components/uchi/field/blank_test.rb +0 -119
- data/test/components/uchi/field/boolean_test.rb +0 -163
- data/test/components/uchi/field/date_test.rb +0 -163
- data/test/components/uchi/field/date_time_test.rb +0 -152
- data/test/components/uchi/field/has_many_test.rb +0 -138
- data/test/components/uchi/field/id_test.rb +0 -113
- data/test/components/uchi/field/number_test.rb +0 -163
- data/test/components/uchi/field/string_test.rb +0 -159
- data/test/controllers/uchi/authors_controller_test.rb +0 -119
- data/test/controllers/uchi/repository_controller_test.rb +0 -93
- data/test/controllers/uchi/scoped_repository_controller_test.rb +0 -73
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -4
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/controllers/uchi/authors_controller.rb +0 -7
- data/test/dummy/app/controllers/uchi/books_controller.rb +0 -7
- data/test/dummy/app/controllers/uchi/titles_controller.rb +0 -7
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/jobs/application_job.rb +0 -7
- data/test/dummy/app/mailers/application_mailer.rb +0 -4
- data/test/dummy/app/models/application_record.rb +0 -3
- data/test/dummy/app/models/author.rb +0 -3
- data/test/dummy/app/models/book.rb +0 -3
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/models/title.rb +0 -3
- data/test/dummy/app/uchi/repositories/author.rb +0 -20
- data/test/dummy/app/uchi/repositories/book.rb +0 -16
- data/test/dummy/app/uchi/repositories/title.rb +0 -17
- data/test/dummy/app/views/layouts/application.html.erb +0 -27
- data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
- data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
- data/test/dummy/app/views/pwa/manifest.json.erb +0 -22
- data/test/dummy/app/views/pwa/service-worker.js +0 -26
- data/test/dummy/bin/dev +0 -2
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -34
- data/test/dummy/config/application.rb +0 -29
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/cable.yml +0 -10
- data/test/dummy/config/database.yml +0 -32
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -69
- data/test/dummy/config/environments/production.rb +0 -89
- data/test/dummy/config/environments/test.rb +0 -53
- data/test/dummy/config/initializers/content_security_policy.rb +0 -25
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -8
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/locales/da.yml +0 -51
- data/test/dummy/config/locales/en.yml +0 -31
- data/test/dummy/config/puma.rb +0 -38
- data/test/dummy/config/routes.rb +0 -9
- data/test/dummy/config/storage.yml +0 -34
- data/test/dummy/config.ru +0 -6
- data/test/dummy/db/migrate/20251002183635_create_authors.rb +0 -11
- data/test/dummy/db/migrate/20251005131726_create_books.rb +0 -9
- data/test/dummy/db/migrate/20251005131811_create_titles.rb +0 -11
- data/test/dummy/db/schema.rb +0 -38
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/400.html +0 -114
- data/test/dummy/public/404.html +0 -114
- data/test/dummy/public/406-unsupported-browser.html +0 -114
- data/test/dummy/public/422.html +0 -114
- data/test/dummy/public/500.html +0 -114
- data/test/dummy/public/icon.png +0 -0
- data/test/dummy/public/icon.svg +0 -3
- data/test/dummy/storage/.keep +0 -0
- data/test/dummy/test/fixtures/authors.yml +0 -11
- data/test/dummy/test/models/author_test.rb +0 -7
- data/test/dummy/tmp/.keep +0 -0
- data/test/dummy/tmp/pids/.keep +0 -0
- data/test/dummy/tmp/storage/.keep +0 -0
- data/test/test_helper.rb +0 -15
- data/test/uchi/field_test.rb +0 -63
- data/test/uchi/i18n_test.rb +0 -18
- data/test/uchi/repository/routes_test.rb +0 -49
- data/test/uchi/repository/translate_test.rb +0 -263
- data/test/uchi/repository_test.rb +0 -137
- data/test/uchi/sort_order_test.rb +0 -47
- data/test/uchi_test.rb +0 -7
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class String < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label}
|
|
14
|
+
}
|
|
15
|
+
options[:hint] = {content: hint} if hint.present?
|
|
16
|
+
options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Index < Uchi::Field::Base::Index
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Show < Uchi::Field::Base::Show
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def default_searchable?
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::Textarea.new(**options)) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class Text < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label},
|
|
14
|
+
input: {options: {rows: 8}}
|
|
15
|
+
}
|
|
16
|
+
options[:hint] = {content: hint} if hint.present?
|
|
17
|
+
options
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Index < Uchi::Field::Base::Index
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Show < Uchi::Field::Base::Show
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def default_on
|
|
30
|
+
[:edit, :show]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def default_searchable?
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= render(Flowbite::Breadcrumb.new) do |breadcrumb| %>
|
|
2
|
+
<% items.each.with_index do |item, index| %>
|
|
3
|
+
<% breadcrumb.with_item do %>
|
|
4
|
+
<% if index.zero? %>
|
|
5
|
+
<%= render Flowbite::BreadcrumbItem::First.new(href: item[:href]).with_content(item[:label]) %>
|
|
6
|
+
<% elsif index == items.size - 1 %>
|
|
7
|
+
<%= render Flowbite::BreadcrumbItem::Current.new.with_content(item[:label]) %>
|
|
8
|
+
<% else %>
|
|
9
|
+
<%= render Flowbite::BreadcrumbItem.new(href: item[:href]).with_content(item[:label]) %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<% if render_label? %>
|
|
3
|
+
<%= form.label attribute, label_text, class: label_classes.join(" ") %>
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<div class="mt-2 space-y-3">
|
|
7
|
+
<%= form.collection_check_boxes(
|
|
8
|
+
attribute,
|
|
9
|
+
collection,
|
|
10
|
+
value_method,
|
|
11
|
+
text_method,
|
|
12
|
+
collection_check_boxes_options
|
|
13
|
+
) do |b| %>
|
|
14
|
+
<div class="<%= checkbox_item_classes.join(" ") %>">
|
|
15
|
+
<div class="flex items-center h-5">
|
|
16
|
+
<%= b.check_box(class: checkbox_classes.join(" "), disabled: disabled?) %>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="ms-2 text-sm">
|
|
19
|
+
<%= b.label(class: checkbox_label_classes.join(" ")) %>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<% if render_hint? %>
|
|
26
|
+
<p class="<%= hint_classes.join(" ") %>"><%= hint_text %></p>
|
|
27
|
+
<% end %>
|
|
28
|
+
|
|
29
|
+
<% errors.each do |error| %>
|
|
30
|
+
<%= render(Flowbite::Input::ValidationError.new) { error.upcase_first } %>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
module Form
|
|
6
|
+
module Input
|
|
7
|
+
# A component that wraps Rails' collection_check_boxes form helper
|
|
8
|
+
# to render multiple checkboxes from a collection using Flowbite styling.
|
|
9
|
+
#
|
|
10
|
+
# Example usage:
|
|
11
|
+
# <%= render Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
12
|
+
# attribute: :tag_ids,
|
|
13
|
+
# collection: @tags,
|
|
14
|
+
# form: form,
|
|
15
|
+
# label: "Select Tags",
|
|
16
|
+
# value_method: :id,
|
|
17
|
+
# text_method: :name
|
|
18
|
+
# ) %>
|
|
19
|
+
class CollectionCheckboxes < ViewComponent::Base
|
|
20
|
+
attr_reader :attribute, :collection, :form, :label_text, :hint_text,
|
|
21
|
+
:value_method, :text_method, :disabled
|
|
22
|
+
|
|
23
|
+
def initialize(
|
|
24
|
+
attribute:,
|
|
25
|
+
collection:,
|
|
26
|
+
form:,
|
|
27
|
+
label: nil,
|
|
28
|
+
hint: nil,
|
|
29
|
+
value_method: :id,
|
|
30
|
+
text_method: :name,
|
|
31
|
+
disabled: false,
|
|
32
|
+
options: {}
|
|
33
|
+
)
|
|
34
|
+
super()
|
|
35
|
+
@attribute = attribute
|
|
36
|
+
@collection = collection
|
|
37
|
+
@form = form
|
|
38
|
+
@label_text = label
|
|
39
|
+
@hint_text = hint
|
|
40
|
+
@value_method = value_method
|
|
41
|
+
@text_method = text_method
|
|
42
|
+
@disabled = disabled
|
|
43
|
+
@options = options || {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def object
|
|
47
|
+
@object ||= form.object
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def errors
|
|
51
|
+
@errors ||= object.errors[attribute] || []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def errors?
|
|
55
|
+
errors.any?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def disabled?
|
|
59
|
+
!!disabled
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def render_label?
|
|
63
|
+
label_text.present?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def render_hint?
|
|
67
|
+
hint_text.present?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def label_classes
|
|
71
|
+
base = ["block", "mb-2", "text-sm", "font-medium"]
|
|
72
|
+
if disabled?
|
|
73
|
+
base + ["text-gray-400", "dark:text-gray-500"]
|
|
74
|
+
elsif errors?
|
|
75
|
+
base + ["text-red-700", "dark:text-red-500"]
|
|
76
|
+
else
|
|
77
|
+
base + ["text-gray-900", "dark:text-white"]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def hint_classes
|
|
82
|
+
base = ["text-xs", "font-normal", "mt-1"]
|
|
83
|
+
if disabled?
|
|
84
|
+
base + ["text-gray-400", "dark:text-gray-500"]
|
|
85
|
+
else
|
|
86
|
+
base + ["text-gray-500", "dark:text-gray-300"]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def checkbox_item_classes
|
|
91
|
+
["flex", "items-start", "mb-3"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def checkbox_classes
|
|
95
|
+
base = ["w-4", "h-4", "rounded-sm", "focus:ring-2", "focus:ring-offset-2"]
|
|
96
|
+
if disabled?
|
|
97
|
+
base + ["text-blue-600", "bg-gray-100", "border-gray-300",
|
|
98
|
+
"dark:bg-gray-700", "dark:border-gray-600"]
|
|
99
|
+
elsif errors?
|
|
100
|
+
base + ["text-red-600", "bg-red-50", "border-red-500",
|
|
101
|
+
"focus:ring-red-500", "dark:focus:ring-red-600",
|
|
102
|
+
"dark:bg-gray-700", "dark:border-red-500"]
|
|
103
|
+
else
|
|
104
|
+
base + ["text-blue-600", "bg-gray-100", "border-gray-300",
|
|
105
|
+
"focus:ring-blue-500", "dark:focus:ring-blue-600",
|
|
106
|
+
"dark:bg-gray-700", "dark:border-gray-600"]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def checkbox_label_classes
|
|
111
|
+
if disabled?
|
|
112
|
+
["ms-2", "text-sm", "font-medium", "text-gray-400", "dark:text-gray-500"]
|
|
113
|
+
else
|
|
114
|
+
["ms-2", "text-sm", "font-medium", "text-gray-900", "dark:text-gray-300"]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def collection_check_boxes_options
|
|
119
|
+
@options
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<table class="w-full text-left text-gray-500 rtl:text-right dark:text-gray-400">
|
|
2
|
+
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
|
3
|
+
<tr>
|
|
4
|
+
<% columns.each do |field| %>
|
|
5
|
+
<% component_class = field.index_component_class %>
|
|
6
|
+
<th scope="col" class="px-4 py-3 md:px-6 whitespace-nowrap <%= component_class.classes_for_table_cell.join(" ") %>">
|
|
7
|
+
<% if field.sortable? %>
|
|
8
|
+
<% if field.name == sort_order&.name %>
|
|
9
|
+
<% if sort_order&.ascending? %>
|
|
10
|
+
<%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :desc})) do %>
|
|
11
|
+
<%= repository.translate.field_label(field) %>
|
|
12
|
+
<svg class="inline w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
13
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19V5m0 14-4-4m4 4 4-4"/>
|
|
14
|
+
</svg>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% else %>
|
|
17
|
+
<%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :asc})) do %>
|
|
18
|
+
<%= repository.translate.field_label(field) %>
|
|
19
|
+
<svg class="inline w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
20
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v13m0-13 4 4m-4-4-4 4"/>
|
|
21
|
+
</svg>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% else %>
|
|
25
|
+
<%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :asc})) do %>
|
|
26
|
+
<%= repository.translate.field_label(field) %>
|
|
27
|
+
<svg class="inline w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
28
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 20V7m0 13-4-4m4 4 4-4m4-12v13m0-13 4 4m-4-4-4 4"/>
|
|
29
|
+
</svg>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% else %>
|
|
33
|
+
<%= repository.translate.field_label(field) %>
|
|
34
|
+
<% end %>
|
|
35
|
+
</th>
|
|
36
|
+
<% end %>
|
|
37
|
+
<th scope="col" class="px-4 py-3 md:px-6"></th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
<% records.each do |record| %>
|
|
42
|
+
<tr class="bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
|
43
|
+
<% columns.each do |field| %>
|
|
44
|
+
<% component_class = field.index_component_class %>
|
|
45
|
+
<td class="px-4 py-4 md:px-6 <%= component_class.classes_for_table_cell.join(" ") %>">
|
|
46
|
+
<%= render(field.index_component(record: record, repository: repository)) %>
|
|
47
|
+
</td>
|
|
48
|
+
<% end %>
|
|
49
|
+
<td class="px-4 py-4 text-right md:px-6">
|
|
50
|
+
<div class="flex justify-end space-x-2">
|
|
51
|
+
<%= link_to(repository.routes.path_for(:show, id: record.id), class: "inline-block hover:text-blue-600 hover:dark:text-blue-500", :data => {:"turbo-frame" => "_top"}) do %>
|
|
52
|
+
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
53
|
+
<path stroke="currentColor" stroke-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
|
|
54
|
+
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
|
|
55
|
+
</svg>
|
|
56
|
+
<% end %>
|
|
57
|
+
<%= link_to(path_for_edit(record), class: "inline-block hover:text-blue-600 hover:dark:text-blue-500", :data => {:"turbo-frame" => "_top"}) do %>
|
|
58
|
+
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
59
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
|
|
60
|
+
</svg>
|
|
61
|
+
<% end %>
|
|
62
|
+
</div>
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<% end %>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= form_tag(repository.routes.path_for(:index), class: "flex items-center max-w-sm", method: :get) do %>
|
|
2
|
+
<%= label_tag(:query, repository.translate.search_label, class: "sr-only") %>
|
|
3
|
+
<div class="relative w-full">
|
|
4
|
+
<%= search_field_tag(:query, query, class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") %>
|
|
5
|
+
</div>
|
|
6
|
+
<%= button_tag(class: "p-2.5 ms-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800") do %>
|
|
7
|
+
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
|
8
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
|
|
9
|
+
</svg>
|
|
10
|
+
<span class="sr-only"><%= repository.translate.search_button %></span>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<% if scoped? %>
|
|
14
|
+
<%= hidden_field_tag("scope[field]", scope[:field]) %>
|
|
15
|
+
<%= hidden_field_tag("scope[id]", scope[:id]) %>
|
|
16
|
+
<%= hidden_field_tag("scope[inverse_of]", scope[:inverse_of]) %>
|
|
17
|
+
<%= hidden_field_tag("scope[model]", scope[:model]) %>
|
|
18
|
+
<% end %>
|
|
19
|
+
<%= hidden_field_tag("sort[by]", sort_by) if sort_by? %>
|
|
20
|
+
<%= hidden_field_tag("sort[direction]", sort_direction) if sort_direction? %>
|
|
21
|
+
<% end %>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Uchi
|
|
2
|
+
module Ui
|
|
3
|
+
module Index
|
|
4
|
+
class RecordsTable
|
|
5
|
+
# Renders a search for the records table.
|
|
6
|
+
#
|
|
7
|
+
# Based on Flowbites Simple search input
|
|
8
|
+
# (https://flowbite.com/docs/forms/search-input/#simple-search-input)
|
|
9
|
+
class SearchForm < ViewComponent::Base
|
|
10
|
+
attr_reader :params, :repository
|
|
11
|
+
|
|
12
|
+
def initialize(params:, repository:)
|
|
13
|
+
super()
|
|
14
|
+
@params = params
|
|
15
|
+
@repository = repository
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def query
|
|
19
|
+
params[:query]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def scope
|
|
23
|
+
params[:scope]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def scoped?
|
|
27
|
+
scope.present?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def sort_by
|
|
31
|
+
params.dig(:sort, :by)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def sort_by?
|
|
35
|
+
sort_by.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def sort_direction
|
|
39
|
+
params.dig(:sort, :direction)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def sort_direction?
|
|
43
|
+
sort_direction.present?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
module Index
|
|
6
|
+
class RecordsTable < ViewComponent::Base
|
|
7
|
+
# Returns the columns to be displayed in this table. Each column is a
|
|
8
|
+
# representation of a Field from repository. Defaults to all fields.
|
|
9
|
+
attr_reader :columns
|
|
10
|
+
|
|
11
|
+
attr_reader :query, :sort_order, :records, :repository, :scope
|
|
12
|
+
|
|
13
|
+
def initialize(columns:, records:, repository:, query: nil, scope: nil, sort_order: nil)
|
|
14
|
+
super()
|
|
15
|
+
@columns = columns
|
|
16
|
+
@query = query
|
|
17
|
+
@sort_order = sort_order
|
|
18
|
+
@records = records
|
|
19
|
+
@repository = repository
|
|
20
|
+
@scope = scope
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def path_for_edit(record)
|
|
24
|
+
repository.routes.path_for(:edit, id: record.id, scope: scope)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
module Index
|
|
6
|
+
class TurboFrame < ViewComponent::Base
|
|
7
|
+
attr_reader :repository, :scope, :src
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
helpers
|
|
11
|
+
.turbo_frame_tag(turbo_frame_id, **options) {
|
|
12
|
+
content
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(repository:, scope: nil, src: nil)
|
|
17
|
+
super()
|
|
18
|
+
@repository = repository
|
|
19
|
+
@scope = scope
|
|
20
|
+
@src = src
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def options
|
|
26
|
+
options = {}
|
|
27
|
+
options[:src] = src if src.present?
|
|
28
|
+
options
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def scoped?
|
|
32
|
+
scope.present?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def turbo_frame_id
|
|
36
|
+
parts = if scoped?
|
|
37
|
+
[
|
|
38
|
+
scope[:model],
|
|
39
|
+
scope[:id],
|
|
40
|
+
scope[:field]
|
|
41
|
+
]
|
|
42
|
+
else
|
|
43
|
+
[repository.controller_name]
|
|
44
|
+
end
|
|
45
|
+
parts.compact.join("_")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<header class="px-4 mb-6 space-y-6 md:px-0">
|
|
2
|
+
<div>
|
|
3
|
+
<% if breadcrumb? %>
|
|
4
|
+
<%= breadcrumb %>
|
|
5
|
+
<% end %>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="items-center justify-between space-x-6 space-y-3 md:flex md:px-0">
|
|
9
|
+
<div>
|
|
10
|
+
<h1 class="mb-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white group"><%= title %></h1>
|
|
11
|
+
<% if description.present? %>
|
|
12
|
+
<div class="text-lg text-gray-500 lg:mb-0 dark:text-gray-400 lg:max-w-2xl"><%= description %></div>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<% if actions.any? %>
|
|
17
|
+
<div class="flex items-center space-x-2">
|
|
18
|
+
<% actions.each do |action| %>
|
|
19
|
+
<%= action %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
</header>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class PageHeader < ViewComponent::Base
|
|
6
|
+
attr_reader :description, :title
|
|
7
|
+
|
|
8
|
+
renders_many :actions
|
|
9
|
+
renders_one :breadcrumb, Uchi::Ui::Breadcrumb
|
|
10
|
+
|
|
11
|
+
def initialize(title:, description: nil)
|
|
12
|
+
super()
|
|
13
|
+
@description = description
|
|
14
|
+
@title = title
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(@paginator.page, page_url(page_number), aria: { current: "page" }, class: "z-10 flex items-center justify-center px-3 h-8 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white") %>
|
|
3
|
+
</li>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class Pagination
|
|
6
|
+
class Item < ViewComponent::Base
|
|
7
|
+
attr_reader :page_number, :paginator
|
|
8
|
+
|
|
9
|
+
# @param paginator [Uchi::Pagination::Page] The page object
|
|
10
|
+
def initialize(paginator:, page_number: nil)
|
|
11
|
+
super()
|
|
12
|
+
@page_number = page_number
|
|
13
|
+
@paginator = paginator
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the URL for a given page. page can be a number, of :first,
|
|
17
|
+
# :last, :previous, :next, :current.
|
|
18
|
+
def page_url(page)
|
|
19
|
+
paginator.page_url(page)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(page_number, page_url(page_number), class: "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") %>
|
|
3
|
+
</li>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(url, class: "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") do %>
|
|
3
|
+
<span class="sr-only">Next</span>
|
|
4
|
+
<svg class="w-3 h-3 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
5
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
6
|
+
</svg>
|
|
7
|
+
<% end %>
|
|
8
|
+
</li>
|