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,14 @@
|
|
|
1
|
+
<div class="flex">
|
|
2
|
+
<div class="flex items-center h-5">
|
|
3
|
+
<%= input %>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="ms-2 text-sm">
|
|
7
|
+
<%= label %>
|
|
8
|
+
<%= hint %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<% errors.each do |error| %>
|
|
12
|
+
<%= render(Flowbite::Input::ValidationError.new) { error.upcase_first } %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class InputField
|
|
5
|
+
class RadioButton < InputField
|
|
6
|
+
def initialize(attribute:, form:, value:, disabled: false, hint: nil, input: {}, label: {})
|
|
7
|
+
super(attribute: attribute, form: form, disabled: disabled, hint: hint, input: input, label: label)
|
|
8
|
+
@value = value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def default_input
|
|
14
|
+
args = {
|
|
15
|
+
attribute: @attribute,
|
|
16
|
+
disabled: disabled?,
|
|
17
|
+
form: @form,
|
|
18
|
+
options: default_input_options.merge(@input[:options] || {}),
|
|
19
|
+
value: @value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
input_component.new(**args)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns options for the default label element. This includes CSS classes
|
|
26
|
+
# since they are specific to RadioButton labels (and Checkbox ones).
|
|
27
|
+
def default_label_options
|
|
28
|
+
super.merge({
|
|
29
|
+
options: {
|
|
30
|
+
class: label_classes,
|
|
31
|
+
for: id_for_input_element
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the HTML to use for the hint element if any
|
|
37
|
+
def hint
|
|
38
|
+
return unless hint?
|
|
39
|
+
|
|
40
|
+
component = Flowbite::Input::Hint.new(
|
|
41
|
+
attribute: @attribute,
|
|
42
|
+
form: @form,
|
|
43
|
+
options: {
|
|
44
|
+
class: hint_classes,
|
|
45
|
+
id: id_for_hint_element
|
|
46
|
+
}
|
|
47
|
+
).with_content(@hint)
|
|
48
|
+
render(component)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def input_component
|
|
52
|
+
::Flowbite::Input::RadioButton
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def hint_classes
|
|
58
|
+
if disabled?
|
|
59
|
+
"text-xs font-normal text-gray-400 dark:text-gray-500"
|
|
60
|
+
else
|
|
61
|
+
"text-xs font-normal text-gray-500 dark:text-gray-300"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def id_for_input_element
|
|
66
|
+
[
|
|
67
|
+
@form.object_name,
|
|
68
|
+
@attribute,
|
|
69
|
+
@value
|
|
70
|
+
].join("_")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def id_for_hint_element
|
|
74
|
+
[id_for_input_element, "hint"].join("_")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def label_classes
|
|
78
|
+
if disabled?
|
|
79
|
+
"font-medium text-gray-400 dark:text-gray-500"
|
|
80
|
+
else
|
|
81
|
+
"font-medium text-gray-900 dark:text-gray-300"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class InputField
|
|
5
|
+
class Select < InputField
|
|
6
|
+
def initialize(attribute:, form:, collection: [], disabled: false, hint: nil, input: {}, label: {}, size: :default)
|
|
7
|
+
super(attribute: attribute, disabled: disabled, form: form, hint: hint, input: input, label: label, size: size)
|
|
8
|
+
@collection = collection
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def input
|
|
12
|
+
render(
|
|
13
|
+
input_component.new(
|
|
14
|
+
attribute: @attribute,
|
|
15
|
+
collection: @collection,
|
|
16
|
+
disabled: @disabled,
|
|
17
|
+
form: @form,
|
|
18
|
+
options: input_options,
|
|
19
|
+
size: @size
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def input_component
|
|
27
|
+
::Flowbite::Input::Select
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
# A form element for a single field, containing label, input field, error
|
|
5
|
+
# messages, helper text and whatever else is needed for a user friendly input
|
|
6
|
+
# experience.
|
|
7
|
+
#
|
|
8
|
+
# @see https://flowbite.com/docs/forms/input-field/
|
|
9
|
+
#
|
|
10
|
+
# The input field is an important part of the form element that can be used to
|
|
11
|
+
# create interactive controls to accept data from the user based on multiple
|
|
12
|
+
# input types, such as text, email, number, password, URL, phone number, and
|
|
13
|
+
# more.
|
|
14
|
+
#
|
|
15
|
+
# Usually you'd use one of the subclasses of this class which implement the
|
|
16
|
+
# different input types, like `Flowbite::InputField::Text`,
|
|
17
|
+
# `Flowbite::InputField::Email`, etc.
|
|
18
|
+
#
|
|
19
|
+
# Expects 2 arguments:
|
|
20
|
+
#
|
|
21
|
+
# @param attribute [Symbol] The name of the attribute to render in this input
|
|
22
|
+
# field.
|
|
23
|
+
#
|
|
24
|
+
# @param form [ActionView::Helpers::FormBuilder] The form builder object that
|
|
25
|
+
# will be used to generate the input field.
|
|
26
|
+
#
|
|
27
|
+
# Supports additional arguments:
|
|
28
|
+
#
|
|
29
|
+
# @param hint [String] A hint to display below the input field, providing
|
|
30
|
+
# additional context or instructions for the user. This is optional. See
|
|
31
|
+
# https://flowbite.com/docs/forms/input-field/#helper-text
|
|
32
|
+
#
|
|
33
|
+
# @param label [Hash] A hash with options for the label. These are passed to
|
|
34
|
+
# Flowbite::Input::Label, see that for details. Can contain:
|
|
35
|
+
# - `content`: The content of the label. If not provided, the label will
|
|
36
|
+
# default to the attribute name.
|
|
37
|
+
# - `options`: A hash of additional options to pass to the label component.
|
|
38
|
+
# This can be used to set the class, for example.
|
|
39
|
+
#
|
|
40
|
+
# @param disabled [Boolean] Whether the input field should be disabled.
|
|
41
|
+
# Defaults to `false`.
|
|
42
|
+
#
|
|
43
|
+
# @param input [Hash] A hash with options for the default input component.
|
|
44
|
+
# These are passed to the input components constructor, so see whatever
|
|
45
|
+
# component is being used for details. Can contain:
|
|
46
|
+
# - `options`: Additional HTML attributes to pass to the input element.
|
|
47
|
+
#
|
|
48
|
+
# @param size [Symbol] The size of the input field. Can be one of `:sm`,
|
|
49
|
+
# `:md`, or `:lg`. Defaults to `:md`.
|
|
50
|
+
#
|
|
51
|
+
# Sample usage
|
|
52
|
+
#
|
|
53
|
+
# <% form_for @person do |form| %>
|
|
54
|
+
# <%= render(
|
|
55
|
+
# Flowbite::InputField::Number.new(
|
|
56
|
+
# :attribute => :name,
|
|
57
|
+
# :form => form
|
|
58
|
+
# )
|
|
59
|
+
# ) %>
|
|
60
|
+
# <% end %>
|
|
61
|
+
#
|
|
62
|
+
# To render an input without labels or error messages etc, use
|
|
63
|
+
# `Flowbite::Input::Field` instead.
|
|
64
|
+
class InputField < ViewComponent::Base
|
|
65
|
+
renders_one :hint
|
|
66
|
+
renders_one :input
|
|
67
|
+
renders_one :label
|
|
68
|
+
|
|
69
|
+
# Returns the errors for attribute
|
|
70
|
+
def errors
|
|
71
|
+
@object.errors[@attribute] || []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def initialize(attribute:, form:, disabled: false, hint: nil, input: {}, label: {}, size: :default)
|
|
75
|
+
@attribute = attribute
|
|
76
|
+
@disabled = disabled
|
|
77
|
+
@form = form
|
|
78
|
+
@hint = hint
|
|
79
|
+
@input = input
|
|
80
|
+
@label = label
|
|
81
|
+
@object = form.object
|
|
82
|
+
@size = size
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def input_component
|
|
86
|
+
::Flowbite::Input::Field
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
protected
|
|
90
|
+
|
|
91
|
+
# Returns the HTML to use for the hint element if any
|
|
92
|
+
def default_hint
|
|
93
|
+
return unless hint?
|
|
94
|
+
|
|
95
|
+
component = Flowbite::Input::Hint.new(
|
|
96
|
+
attribute: @attribute,
|
|
97
|
+
form: @form,
|
|
98
|
+
options: default_hint_options
|
|
99
|
+
).with_content(default_hint_content)
|
|
100
|
+
render(component)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def default_hint_content
|
|
104
|
+
return nil unless @hint
|
|
105
|
+
|
|
106
|
+
@hint[:content]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns a Hash with the default attributes to apply to the hint element.
|
|
110
|
+
#
|
|
111
|
+
# The default attributes can be overriden by passing the `hint[options]`
|
|
112
|
+
# argument to the constructor.
|
|
113
|
+
def default_hint_options
|
|
114
|
+
return {} unless @hint
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
id: id_for_hint_element
|
|
118
|
+
}.merge(@hint[:options] || {})
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns a Hash with the default attributes to apply to the input element.
|
|
122
|
+
#
|
|
123
|
+
# The default attributes can be overriden by passing the `input[options]`
|
|
124
|
+
# argument to the constructor.
|
|
125
|
+
def default_input_options
|
|
126
|
+
if hint?
|
|
127
|
+
{
|
|
128
|
+
"aria-describedby": id_for_hint_element
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
{}
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns the HTML to use for the default input element.
|
|
136
|
+
def default_input
|
|
137
|
+
render(input_component.new(**input_arguments))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def default_label
|
|
141
|
+
component = Flowbite::Input::Label.new(**default_label_options)
|
|
142
|
+
if default_label_content
|
|
143
|
+
component.with_content(default_label_content)
|
|
144
|
+
else
|
|
145
|
+
component
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def default_label_content
|
|
150
|
+
@label[:content]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def default_label_options
|
|
154
|
+
label_options = @label.dup
|
|
155
|
+
label_options.delete(:content)
|
|
156
|
+
|
|
157
|
+
{
|
|
158
|
+
attribute: @attribute,
|
|
159
|
+
form: @form
|
|
160
|
+
}.merge(label_options)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns true if the input field is disabled, false otherwise.
|
|
164
|
+
def disabled?
|
|
165
|
+
!!@disabled
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns true if the input field has a hint, false otherwise.
|
|
169
|
+
def hint?
|
|
170
|
+
@hint.present?
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def id_for_hint_element
|
|
174
|
+
"#{@form.object_name}_#{@attribute}_hint"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @return [Hash] The keyword arguments for the input component.
|
|
178
|
+
def input_arguments
|
|
179
|
+
{
|
|
180
|
+
attribute: @attribute,
|
|
181
|
+
disabled: @disabled,
|
|
182
|
+
form: @form,
|
|
183
|
+
options: input_options,
|
|
184
|
+
size: @size
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def input_options
|
|
189
|
+
default_input_options.merge(@input[:options] || {})
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Flowbite
|
|
2
|
+
class Link < ViewComponent::Base
|
|
3
|
+
attr_reader :href, :text, :options
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def classes
|
|
7
|
+
["font-medium", "text-blue-600", "dark:text-blue-500", "hover:underline"].join(" ")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(href:, **options)
|
|
12
|
+
super()
|
|
13
|
+
@href = href
|
|
14
|
+
@options = options
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
link_to(content, href, {class: self.class.classes}.merge(options))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class Toast
|
|
5
|
+
# Renders an icon for a toast notification.
|
|
6
|
+
#
|
|
7
|
+
# @param style [Symbol] The color style of the icon (:default, :success, :danger, :warning).
|
|
8
|
+
class Icon < ViewComponent::Base
|
|
9
|
+
class << self
|
|
10
|
+
def classes(style: :default)
|
|
11
|
+
styles.fetch(style).fetch(:classes)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def svg_path(style: :default)
|
|
15
|
+
styles.fetch(style).fetch(:svg_path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# rubocop:disable Layout/LineLength
|
|
19
|
+
def styles
|
|
20
|
+
{
|
|
21
|
+
default: {
|
|
22
|
+
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-blue-500", "bg-blue-100", "rounded-lg", "dark:bg-blue-800", "dark:text-blue-200"],
|
|
23
|
+
svg_path: "M15.147 15.085a7.159 7.159 0 0 1-6.189 3.307A6.713 6.713 0 0 1 3.1 15.444c-2.679-4.513.287-8.737.888-9.548A4.373 4.373 0 0 0 5 1.608c1.287.953 6.445 3.218 5.537 10.5 1.5-1.122 2.706-3.01 2.853-6.14 1.433 1.049 3.993 5.395 1.757 9.117Z"
|
|
24
|
+
},
|
|
25
|
+
success: {
|
|
26
|
+
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-green-500", "bg-green-100", "rounded-lg", "dark:bg-green-800", "dark:text-green-200"],
|
|
27
|
+
svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"
|
|
28
|
+
},
|
|
29
|
+
danger: {
|
|
30
|
+
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-red-500", "bg-red-100", "rounded-lg", "dark:bg-red-800", "dark:text-red-200"],
|
|
31
|
+
svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"
|
|
32
|
+
},
|
|
33
|
+
warning: {
|
|
34
|
+
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-orange-500", "bg-orange-100", "rounded-lg", "dark:bg-orange-700", "dark:text-orange-200"],
|
|
35
|
+
svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"
|
|
36
|
+
}
|
|
37
|
+
}.freeze
|
|
38
|
+
end
|
|
39
|
+
# rubocop:enable Layout/LineLength
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attr_reader :style
|
|
43
|
+
|
|
44
|
+
def initialize(style: :default)
|
|
45
|
+
@style = style
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def container_classes
|
|
49
|
+
self.class.classes(style: style)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def svg_path
|
|
53
|
+
self.class.svg_path(style: style)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="<%= container_classes.join(" ") %>" role="alert" <%= options.map { |k, v| "#{k}=\"#{v}\"" }.join(" ").html_safe %>>
|
|
2
|
+
<%= render Flowbite::Toast::Icon.new(style: style) %>
|
|
3
|
+
<div class="ms-3 text-sm font-normal"><%= message %></div>
|
|
4
|
+
<% if dismissible %>
|
|
5
|
+
<button type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" aria-label="Close">
|
|
6
|
+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
7
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
8
|
+
</svg>
|
|
9
|
+
</button>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
# Renders a toast notification element.
|
|
5
|
+
#
|
|
6
|
+
# See https://flowbite.com/docs/components/toast/
|
|
7
|
+
#
|
|
8
|
+
# @param message [String] The message to display in the toast.
|
|
9
|
+
# @param style [Symbol] The color style of the toast (:default, :success, :danger, :warning).
|
|
10
|
+
# @param dismissible [Boolean] Whether the toast can be dismissed (default: true).
|
|
11
|
+
# @param class [Array<String>] Additional CSS classes for the toast container.
|
|
12
|
+
# @param options [Hash] Additional HTML options for the toast container.
|
|
13
|
+
class Toast < ViewComponent::Base
|
|
14
|
+
class << self
|
|
15
|
+
def classes
|
|
16
|
+
["flex", "items-center", "w-full", "max-w-xs", "p-4", "text-gray-500", "bg-white", "rounded-lg", "shadow-sm", "dark:text-gray-400", "dark:bg-gray-800"]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :message, :style, :dismissible, :additional_classes, :options
|
|
21
|
+
|
|
22
|
+
def initialize(message:, style: :default, dismissible: true, class: [], **options)
|
|
23
|
+
@message = message
|
|
24
|
+
@style = style
|
|
25
|
+
@dismissible = dismissible
|
|
26
|
+
@additional_classes = Array(binding.local_variable_get(:class)) || []
|
|
27
|
+
@options = options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def container_classes
|
|
31
|
+
self.class.classes + additional_classes
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class Base < Field
|
|
6
|
+
# Uchi::Field::Base::Edit components render fields in the edit view.
|
|
7
|
+
class Edit < ViewComponent::Base
|
|
8
|
+
attr_reader :field, :form, :record, :repository, :label, :hint
|
|
9
|
+
|
|
10
|
+
def initialize(field:, form:, repository:, label: nil, hint: nil)
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
@field = field
|
|
14
|
+
@form = form
|
|
15
|
+
@label = label
|
|
16
|
+
@hint = hint
|
|
17
|
+
@record = form.object
|
|
18
|
+
@repository = repository
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Uchi::Field::Base::Show components render fields in the show view.
|
|
23
|
+
class Show < ViewComponent::Base
|
|
24
|
+
attr_reader :field, :record, :repository
|
|
25
|
+
|
|
26
|
+
def initialize(field:, record:, repository:)
|
|
27
|
+
super()
|
|
28
|
+
|
|
29
|
+
@field = field
|
|
30
|
+
@record = record
|
|
31
|
+
@repository = repository
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Uchi::Field::Base::Index components render fields in the index view.
|
|
36
|
+
class Index < ViewComponent::Base
|
|
37
|
+
attr_reader :field, :record, :repository
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Returns the CSS classes to apply to the td or th of the table where
|
|
41
|
+
# this field is rendered.
|
|
42
|
+
def classes_for_table_cell
|
|
43
|
+
[]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(field:, record:, repository:)
|
|
48
|
+
super()
|
|
49
|
+
|
|
50
|
+
@field = field
|
|
51
|
+
@record = record
|
|
52
|
+
@repository = repository
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::Select.new(**options)) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class BelongsTo < Field
|
|
6
|
+
DEFAULT_COLLECTION_QUERY = ->(query) { query }.freeze
|
|
7
|
+
|
|
8
|
+
class Edit < Uchi::Field::Base::Edit
|
|
9
|
+
def associated_repository
|
|
10
|
+
model = reflection.klass
|
|
11
|
+
repository_class = Uchi::Repository.for_model(model)
|
|
12
|
+
repository_class.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def attribute_name
|
|
16
|
+
reflection.foreign_key
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def collection
|
|
20
|
+
query = associated_repository.find_all
|
|
21
|
+
field.collection_query.call(query)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def collection_for_select
|
|
27
|
+
repository = associated_repository
|
|
28
|
+
collection.map do |item|
|
|
29
|
+
[repository.title(item), item.id]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def options
|
|
34
|
+
options = {
|
|
35
|
+
attribute: attribute_name,
|
|
36
|
+
collection: collection_for_select,
|
|
37
|
+
form: form,
|
|
38
|
+
label: {content: label}
|
|
39
|
+
}
|
|
40
|
+
options[:hint] = {content: hint} if hint.present?
|
|
41
|
+
options
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reflection
|
|
45
|
+
@reflection ||= record.class.reflect_on_association(field.name)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class Index < Uchi::Field::Base::Index
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Show < Uchi::Field::Base::Show
|
|
53
|
+
def associated_record
|
|
54
|
+
field.value(record)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def associated_repository
|
|
58
|
+
reflection = record.class.reflect_on_association(field.name)
|
|
59
|
+
model = reflection.klass
|
|
60
|
+
repository_class = Uchi::Repository.for_model(model)
|
|
61
|
+
repository_class.new
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def initialize(name)
|
|
66
|
+
super
|
|
67
|
+
@collection_query = DEFAULT_COLLECTION_QUERY
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Sets or gets a custom query for filtering the collection of associated records.
|
|
71
|
+
#
|
|
72
|
+
# When called with an argument, sets the query and returns self for chaining.
|
|
73
|
+
# When called without arguments, returns the current query.
|
|
74
|
+
#
|
|
75
|
+
# @param query_proc [Proc, Symbol] A callable that receives an ActiveRecord query
|
|
76
|
+
# and returns a modified query.
|
|
77
|
+
# @return [self, Proc] Returns self for method chaining when setting,
|
|
78
|
+
# or the query proc when getting
|
|
79
|
+
#
|
|
80
|
+
# @example Setting
|
|
81
|
+
# Field::BelongsTo.new(:company).collection_query(->(query) {
|
|
82
|
+
# query.where(active: true)
|
|
83
|
+
# })
|
|
84
|
+
#
|
|
85
|
+
# @example Getting
|
|
86
|
+
# field.collection_query # => #<Proc...>
|
|
87
|
+
def collection_query(query_proc = Configuration::Unset)
|
|
88
|
+
return @collection_query if query_proc == Configuration::Unset
|
|
89
|
+
|
|
90
|
+
@collection_query = query_proc
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def group_as(_action)
|
|
95
|
+
:attributes
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def param_key
|
|
99
|
+
# TODO: This is too naive. We need to match this to the actual foreign
|
|
100
|
+
# key of the model.
|
|
101
|
+
:"#{name}_id"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
?
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
...
|