lightning_ui_kit 0.1.1 → 0.1.3

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/app/assets/builds/{lightning_ui.css → lightning_ui_kit.css} +422 -100
  4. data/app/assets/builds/lightning_ui_kit.js +6 -0
  5. data/app/assets/builds/{lightning_ui.js.map → lightning_ui_kit.js.map} +4 -4
  6. data/app/components/{lightning_ui → lightning_ui_kit}/alert_component.rb +1 -1
  7. data/app/components/{lightning_ui → lightning_ui_kit}/avatar_component.rb +1 -1
  8. data/app/components/lightning_ui_kit/badge_component.rb +68 -0
  9. data/app/components/{lightning_ui → lightning_ui_kit}/banner_component.rb +2 -2
  10. data/app/components/{lightning_ui → lightning_ui_kit}/base_component.rb +2 -2
  11. data/app/components/{lightning_ui → lightning_ui_kit}/button_component.rb +1 -1
  12. data/app/components/{lightning_ui → lightning_ui_kit}/checkbox_component.html.erb +1 -1
  13. data/app/components/{lightning_ui → lightning_ui_kit}/checkbox_component.rb +1 -1
  14. data/app/components/{lightning_ui → lightning_ui_kit}/description_list/item_component.rb +1 -1
  15. data/app/components/lightning_ui_kit/description_list_component.rb +5 -0
  16. data/app/components/lightning_ui_kit/dropdown/item_component.rb +5 -0
  17. data/app/components/{lightning_ui → lightning_ui_kit}/dropdown_component.rb +1 -1
  18. data/app/components/lightning_ui_kit/dropzone_component.html.erb +82 -0
  19. data/app/components/lightning_ui_kit/dropzone_component.rb +61 -0
  20. data/app/components/lightning_ui_kit/errors.rb +34 -0
  21. data/app/components/lightning_ui_kit/file_input_component.html.erb +50 -0
  22. data/app/components/lightning_ui_kit/file_input_component.rb +62 -0
  23. data/app/components/{lightning_ui → lightning_ui_kit}/input_component.html.erb +21 -14
  24. data/app/components/{lightning_ui → lightning_ui_kit}/input_component.rb +18 -3
  25. data/app/components/{lightning_ui → lightning_ui_kit}/link_component.rb +1 -1
  26. data/app/components/{lightning_ui → lightning_ui_kit}/modal_component.rb +1 -1
  27. data/app/components/{lightning_ui → lightning_ui_kit}/pagination_component.rb +1 -1
  28. data/app/components/lightning_ui_kit/select_component.html.erb +40 -0
  29. data/app/components/lightning_ui_kit/select_component.rb +55 -0
  30. data/app/components/lightning_ui_kit/sidebar_component.rb +4 -0
  31. data/app/components/{lightning_ui → lightning_ui_kit}/skeleton_component.rb +1 -1
  32. data/app/components/lightning_ui_kit/spinner_component.rb +4 -0
  33. data/app/components/{lightning_ui → lightning_ui_kit}/switch_component.html.erb +8 -1
  34. data/app/components/{lightning_ui → lightning_ui_kit}/switch_component.rb +13 -2
  35. data/app/components/{lightning_ui → lightning_ui_kit}/table/action_component.rb +1 -1
  36. data/app/components/{lightning_ui → lightning_ui_kit}/table/column_component.rb +1 -1
  37. data/app/components/{lightning_ui → lightning_ui_kit}/table_component.rb +3 -3
  38. data/app/components/{lightning_ui → lightning_ui_kit}/text_component.rb +1 -1
  39. data/app/components/{lightning_ui → lightning_ui_kit}/textarea_component.html.erb +15 -8
  40. data/app/components/{lightning_ui → lightning_ui_kit}/textarea_component.rb +18 -3
  41. data/app/helpers/lightning_ui_kit/application_helper.rb +7 -0
  42. data/app/helpers/{lightning_ui → lightning_ui_kit}/heroicon_helper.rb +1 -1
  43. data/app/javascript/lightning_ui_kit/controllers/dropzone_controller.js +76 -0
  44. data/app/javascript/{lightning_ui → lightning_ui_kit}/index.js +5 -0
  45. data/config/locales/en.yml +3 -0
  46. data/lib/{lightning_ui → lightning_ui_kit}/engine.rb +5 -6
  47. data/lib/lightning_ui_kit/version.rb +3 -0
  48. data/lib/lightning_ui_kit.rb +6 -0
  49. metadata +78 -72
  50. data/app/assets/builds/lightning_ui.js +0 -6
  51. data/app/components/lightning_ui/badge_component.rb +0 -43
  52. data/app/components/lightning_ui/description_list_component.rb +0 -5
  53. data/app/components/lightning_ui/dropdown/item_component.rb +0 -5
  54. data/app/components/lightning_ui/select_component.html.erb +0 -20
  55. data/app/components/lightning_ui/select_component.rb +0 -25
  56. data/app/components/lightning_ui/sidebar_component.rb +0 -4
  57. data/app/components/lightning_ui/spinner_component.rb +0 -4
  58. data/app/helpers/lightning_ui/application_helper.rb +0 -7
  59. data/lib/lightning_ui/version.rb +0 -3
  60. data/lib/lightning_ui.rb +0 -6
  61. data/lib/tasks/lightning_ui_tasks.rake +0 -4
  62. /data/app/assets/stylesheets/{lightning_ui → lightning_ui_kit}/application.css +0 -0
  63. /data/app/components/{lightning_ui → lightning_ui_kit}/alert_component.html.erb +0 -0
  64. /data/app/components/{lightning_ui → lightning_ui_kit}/avatar_component.html.erb +0 -0
  65. /data/app/components/{lightning_ui → lightning_ui_kit}/badge_component.html.erb +0 -0
  66. /data/app/components/{lightning_ui → lightning_ui_kit}/banner_component.html.erb +0 -0
  67. /data/app/components/{lightning_ui → lightning_ui_kit}/button_component.html.erb +0 -0
  68. /data/app/components/{lightning_ui → lightning_ui_kit}/description_list/item_component.html.erb +0 -0
  69. /data/app/components/{lightning_ui → lightning_ui_kit}/description_list_component.html.erb +0 -0
  70. /data/app/components/{lightning_ui → lightning_ui_kit}/dropdown_component.html.erb +0 -0
  71. /data/app/components/{lightning_ui → lightning_ui_kit}/link_component.html.erb +0 -0
  72. /data/app/components/{lightning_ui → lightning_ui_kit}/modal_component.html.erb +0 -0
  73. /data/app/components/{lightning_ui → lightning_ui_kit}/pagination_component.html.erb +0 -0
  74. /data/app/components/{lightning_ui → lightning_ui_kit}/sidebar_component.html.erb +0 -0
  75. /data/app/components/{lightning_ui → lightning_ui_kit}/skeleton_component.html.erb +0 -0
  76. /data/app/components/{lightning_ui → lightning_ui_kit}/spinner_component.html.erb +0 -0
  77. /data/app/components/{lightning_ui → lightning_ui_kit}/table_component.html.erb +0 -0
  78. /data/app/components/{lightning_ui → lightning_ui_kit}/text_component.html.erb +0 -0
  79. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/accordion_controller.js +0 -0
  80. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/banner_controller.js +0 -0
  81. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/checkbox_controller.js +0 -0
  82. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/clipboard_controller.js +0 -0
  83. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/dropdown_controller.js +0 -0
  84. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/main_controller.js +0 -0
  85. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/modal_controller.js +0 -0
  86. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/reveal_controller.js +0 -0
  87. /data/app/javascript/{lightning_ui → lightning_ui_kit}/controllers/switch_controller.js +0 -0
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::AlertComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::AlertComponent < LightningUiKit::BaseComponent
4
4
  def initialize(type: :info, **options)
5
5
  @type = type
6
6
  @options = options
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::AvatarComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::AvatarComponent < LightningUiKit::BaseComponent
4
4
  SIZE_CLASSES = {
5
5
  sm: "size-6",
6
6
  md: "size-8",
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LightningUiKit::BadgeComponent < LightningUiKit::BaseComponent
4
+ def initialize(status: :default, progress: nil)
5
+ @status = status
6
+ @progress = progress
7
+ end
8
+
9
+ def classes
10
+ status_classes = case @status
11
+ when :success
12
+ "bg-green-200 text-zinc-500"
13
+ when :warning
14
+ "bg-yellow-200 text-zinc-500"
15
+ when :error
16
+ "bg-red-600/80 text-white"
17
+ else
18
+ "bg-zinc-400/20 text-zinc-500"
19
+ end
20
+ [defalt_classes, status_classes].join(" ")
21
+ end
22
+
23
+ def progress_classes
24
+ progress_classes = case @progress
25
+ when :complete
26
+ case @status
27
+ when :success
28
+ "bg-green-600 border-green-600"
29
+ when :warning
30
+ "bg-yellow-400 border-yellow-400"
31
+ when :error
32
+ "bg-red-700 border-red-700"
33
+ else
34
+ "bg-zinc-400 border-zinc-400"
35
+ end
36
+ when :incomplete
37
+ case @status
38
+ when :error
39
+ "bg-transparent border-white"
40
+ else
41
+ "bg-transparent border-zinc-400"
42
+ end
43
+ when :partialy_complete
44
+ partialy_complete_classes = "relative after:w-[3.75px] after:h-[8.2px]\
45
+ after:border-transparent after:border-[1px] after:-rotate-45 after:absolute after:-top-[1px]\
46
+ after:left-[1px] after:margin-0 after-margin-y-[1px]"
47
+ case @status
48
+ when :success
49
+ "#{partialy_complete_classes} border-green-600 after:border-l-green-600 after:border-r-green-600"
50
+ when :warning
51
+ "#{partialy_complete_classes} border-yellow-400 after:border-l-yellow-400 after:border-r-yellow-400"
52
+ when :error
53
+ "#{partialy_complete_classes} border-white after:border-l-white after:border-r-white"
54
+ else
55
+ "#{partialy_complete_classes} border-zinc-400 after:border-l-zinc-400 after:border-r-zinc-400"
56
+ end
57
+ end
58
+ [default_progress_classes, progress_classes].join(" ")
59
+ end
60
+
61
+ def default_progress_classes
62
+ "h-2 w-2 rounded-[3px] border-[1px]"
63
+ end
64
+
65
+ def defalt_classes
66
+ "relative inline-flex items-center relative gap-x-1.5 rounded-[10px] px-2.5 py-1 text-sm font-medium sm:text-xs forced-colors:outline"
67
+ end
68
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::BannerComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::BannerComponent < LightningUiKit::BaseComponent
4
4
  renders_one :footer
5
5
 
6
6
  def initialize(title:, type: :info, **options)
@@ -12,7 +12,7 @@ class LightningUi::BannerComponent < LightningUi::BaseComponent
12
12
  def classes
13
13
  type_classes = case @type
14
14
  when :error
15
- "*:data-[slot=header]:bg-red-600/90 *:data-[slot=header]:text-white"
15
+ "*:data-[slot=header]:bg-red-600/80 *:data-[slot=header]:text-white"
16
16
  else
17
17
  "*:data-[slot=header]:bg-gray-50"
18
18
  end
@@ -1,7 +1,7 @@
1
1
  require "tailwind_merge"
2
2
 
3
- class LightningUi::BaseComponent < ViewComponent::Base
4
- include LightningUi::HeroiconHelper
3
+ class LightningUiKit::BaseComponent < ViewComponent::Base
4
+ include LightningUiKit::HeroiconHelper
5
5
 
6
6
  def merge_classes(classes)
7
7
  TailwindMerge::Merger.new.merge(classes)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::ButtonComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::ButtonComponent < LightningUiKit::BaseComponent
4
4
  def initialize(type: :button, style: :default, size: :default, disabled: false, url: nil, **options)
5
5
  @type = type
6
6
  @style = style
@@ -5,7 +5,7 @@
5
5
  class="group inline-flex focus:outline-hidden" id="headlessui-control-:r2m:" role="checkbox" aria-checked="false" tabindex="0" data-headlessui-state="" aria-labelledby="headlessui-label-:r2o:" aria-describedby="headlessui-description-:r2p:"
6
6
  data-action="click->lui-checkbox#toggle"
7
7
  >
8
- <span class="relative isolate flex size-[1.125rem] items-center justify-center rounded-[0.3125rem] sm:size-4 before:absolute before:inset-0 before:-z-10 before:rounded-[calc(0.3125rem-1px)] before:bg-white before:shadow-sm group-data-checked:before:bg-(--checkbox-checked-bg) border border-zinc-950/15 group-data-checked:border-transparent group-data-hover:group-data-checked:border-transparent group-data-hover:border-zinc-950/30 group-data-checked:bg-(--checkbox-checked-border) after:absolute after:inset-0 after:rounded-[calc(0.3125rem-1px)] after:shadow-[inset_0_1px_--theme(--color-white/15%)] group-data-focus:outline group-data-focus:outline-2 group-data-focus:outline-offset-2 group-data-focus:outline-blue-500 group-data-disabled:opacity-50 group-data-disabled:border-zinc-950/25 group-data-disabled:bg-zinc-950/5 group-data-disabled:[--checkbox-check:var(--color-zinc-950)]/50 group-data-disabled:before:bg-transparent forced-colors:[--checkbox-check:HighlightText] forced-colors:[--checkbox-checked-bg:Highlight] forced-colors:group-data-disabled:[--checkbox-check:Highlight] [--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--color-zinc-900)] [--checkbox-checked-border:var(--color-zinc-950)]/90">
8
+ <span class="relative isolate flex size-[1.125rem] items-center justify-center rounded-[0.3125rem] sm:size-4 before:absolute before:inset-0 before:-z-10 before:rounded-[calc(0.3125rem-1px)] before:bg-white before:shadow-sm group-data-checked:before:bg-(--checkbox-checked-bg) border border-zinc-950/15 group-data-checked:border-transparent group-hover:group-data-checked:border-transparent group-hover:border-zinc-950/30 group-data-checked:bg-(--checkbox-checked-border) after:absolute after:inset-0 after:rounded-[calc(0.3125rem-1px)] after:shadow-[inset_0_1px_--theme(--color-white/15%)] group-data-focus:outline group-data-focus:outline-2 group-data-focus:outline-offset-2 group-data-focus:outline-blue-500 group-data-disabled:opacity-50 group-data-disabled:border-zinc-950/25 group-data-disabled:bg-zinc-950/5 group-data-disabled:[--checkbox-check:var(--color-zinc-950)]/50 group-data-disabled:before:bg-transparent forced-colors:[--checkbox-check:HighlightText] forced-colors:[--checkbox-checked-bg:Highlight] forced-colors:group-data-disabled:[--checkbox-check:Highlight] [--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--color-zinc-900)] [--checkbox-checked-border:var(--color-zinc-950)]/90">
9
9
  <svg class="size-4 stroke-(--checkbox-check) opacity-0 group-data-checked:opacity-100 sm:h-3.5 sm:w-3.5" viewBox="0 0 14 14" fill="none">
10
10
  <path class="opacity-100 group-data-indeterminate:opacity-0" d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
11
11
  <path class="opacity-0 group-data-indeterminate:opacity-100" d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::CheckboxComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::CheckboxComponent < LightningUiKit::BaseComponent
4
4
  def initialize(name:, value: nil, label: nil, description: nil, form: nil, checked: false, disabled: false, **options)
5
5
  @form = form
6
6
  @name = name
@@ -1,4 +1,4 @@
1
- class LightningUi::DescriptionList::ItemComponent < LightningUi::BaseComponent
1
+ class LightningUiKit::DescriptionList::ItemComponent < LightningUiKit::BaseComponent
2
2
  def initialize(label:, value: nil, **options)
3
3
  @label = label
4
4
  @value = value
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LightningUiKit::DescriptionListComponent < LightningUiKit::BaseComponent
4
+ renders_many :items, LightningUiKit::DescriptionList::ItemComponent
5
+ end
@@ -0,0 +1,5 @@
1
+ class LightningUiKit::Dropdown::ItemComponent < LightningUiKit::BaseComponent
2
+ def initialize(title:)
3
+ @title = title
4
+ end
5
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::DropdownComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::DropdownComponent < LightningUiKit::BaseComponent
4
4
  renders_one :trigger
5
5
  renders_many :items
6
6
 
@@ -0,0 +1,82 @@
1
+ <%= tag.div(class: classes, data:) do %>
2
+ <template data-lui-dropzone-target="template">
3
+ <div class="w-full grid gap-1 my-4" id="#NEW_FILE" data-lui-dropzone-target="file">
4
+ <div class="flex items-center justify-between gap-2">
5
+ <div class="flex items-center gap-2">
6
+ <%= heroicon("document", variant: :outline, options: {class: "w-10 h-10 text-indigo-500"}) %>
7
+ <div class="grid gap-1">
8
+ <h4 class="text-gray-900 text-sm font-normal leading-snug" data-lui-dropzone-target="filename"></h4>
9
+ <h5 class="text-gray-400 text-xs font-normal leading-[18px]" data-lui-dropzone-target="status"></h5>
10
+ </div>
11
+ </div>
12
+ <%= heroicon(
13
+ "x-circle",
14
+ variant: :outline,
15
+ options: {
16
+ class: "w-6 h-6 text-zinc-300 hover:text-indigo-500 hover:cursor-pointer",
17
+ data_action: "click->lui-dropzone#removeFile"
18
+ }) %>
19
+ </div>
20
+ <div class="relative flex items-center gap-2.5 py-1.5">
21
+ <div class="relative w-full h-2.5 overflow-hidden rounded-3xl bg-gray-100">
22
+ <div data-lui-dropzone-target="progressbar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 0%" class="flex h-full items-center justify-center bg-indigo-600 text-white rounded-3xl"></div>
23
+ </div>
24
+ <span data-lui-dropzone-target="percentage-progress" class="ml-2 bg-white rounded-full text-gray-800 text-xs font-medium flex justify-center items-center">0%</span>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <% if @label %>
30
+ <%= tag.label(
31
+ @label,
32
+ class: "text-base/6 text-zinc-950 select-none data-disabled:opacity-50 sm:text-sm/6",
33
+ data: label_data
34
+ ) %>
35
+ <% end %>
36
+ <% if @description %>
37
+ <%= tag.p(
38
+ @description,
39
+ class: "text-base/6 text-zinc-500 data-disabled:opacity-50 sm:text-sm/6",
40
+ data: description_data
41
+ ) %>
42
+ <% end %>
43
+
44
+ <div class="py-9 bg-zinc-50 rounded-2xl border border-gray-300 border-dotted hover:cursor-pointer hover:bg-zinc-100" data-action="click->lui-dropzone#selectFiles">
45
+ <div class="mb-3 flex items-center justify-center">
46
+ <%= heroicon("cloud-arrow-up", variant: :outline, options: {class: "w-10 h-10 text-indigo-500"}) %>
47
+ </div>
48
+ <% if @placeholder %>
49
+ <h2 class="text-center text-gray-400 text-xs font-normal leading-4 mb-1"><%= @placeholder %></h2>
50
+ <% end %>
51
+ <h4 class="text-center text-gray-900 text-sm font-medium leading-snug"><%= t("dropzone.default_message") %></h4>
52
+ <% if @form %>
53
+ <%= @form.file_field(
54
+ @name,
55
+ multiple: @multiple,
56
+ class: "hidden",
57
+ direct_upload: true,
58
+ data: input_data,
59
+ accept: @accept
60
+ ) %>
61
+ <% else %>
62
+ <%= file_field_tag(
63
+ @name,
64
+ value: @value,
65
+ multiple: @multiple,
66
+ class: "hidden",
67
+ direct_upload: true,
68
+ data: input_data,
69
+ accept: @accept
70
+ ) %>
71
+ <% end %>
72
+ </div>
73
+
74
+ <% if has_errors? %>
75
+ <%= tag.p(
76
+ error_messages,
77
+ class: "text-base/6 text-red-600 data-disabled:opacity-50 sm:text-sm/6",
78
+ data: error_data
79
+ ) %>
80
+ <% end %>
81
+ <div data-lui-dropzone-target="files"></div>
82
+ <% end %>
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LightningUiKit::DropzoneComponent < LightningUiKit::BaseComponent
4
+ include LightningUiKit::Errors
5
+
6
+ def initialize(name:, value: nil, label: nil, form: nil, description: nil, multiple: false, accept: nil, disabled: false, placeholder: nil, error: nil, **options)
7
+ @name = name
8
+ @value = value
9
+ @disabled = disabled
10
+ @accept = accept
11
+ @multiple = multiple
12
+ @error = error
13
+ @label = label
14
+ @form = form
15
+ @description = description
16
+ @placeholder = placeholder
17
+ @options = options
18
+ end
19
+
20
+ def classes
21
+ merge_classes(["space-y-4 [&>[data-slot=label]+[data-slot=control]]:mt-3 [&>[data-slot=label]+[data-slot=description]]:mt-1 [&>[data-slot=description]+[data-slot=control]]:mt-3 [&>[data-slot=control]+[data-slot=description]]:mt-3 [&>[data-slot=control]+[data-slot=error]]:mt-3 *:data-[slot=label]:font-medium", @options[:class]].compact.join(" "))
22
+ end
23
+
24
+ def data
25
+ {controller: "lui-dropzone"}.merge(@options[:data] || {})
26
+ end
27
+
28
+ def input_data
29
+ (@options[:input_data] || {}).tap do |data|
30
+ data[:lui_dropzone_target] = "input"
31
+ data[:action] = "dragover->lui-dropzone#activate drop->lui-dropzone#uploadFiles change->lui-dropzone#uploadFiles"
32
+ if has_errors?
33
+ data[:invalid] = "true"
34
+ end
35
+ end
36
+ end
37
+
38
+ def label_data
39
+ {slot: "label"}.merge(@options[:label_data] || {}).tap do |data|
40
+ if @disabled
41
+ data[:disabled] = "true"
42
+ end
43
+ end
44
+ end
45
+
46
+ def description_data
47
+ {slot: "description"}.merge(@options[:description_data] || {}).tap do |data|
48
+ if @disabled
49
+ data[:disabled] = "true"
50
+ end
51
+ end
52
+ end
53
+
54
+ def error_data
55
+ {slot: "error"}.merge(@options[:error_data] || {}).tap do |data|
56
+ if @disabled
57
+ data[:disabled] = "true"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,34 @@
1
+ module LightningUiKit
2
+ module Errors
3
+ def has_errors?
4
+ error_messages.present?
5
+ end
6
+
7
+ # TODO: simplify this code
8
+ def error_messages
9
+ return @error if @error.present?
10
+ return @error_messages if @error_messages.present?
11
+
12
+ if @form.present?
13
+ @error_messages = @form.object.errors.full_messages_for(@name.to_sym)
14
+ if @error_messages.blank?
15
+ @error_messages = infer_errors_from_association
16
+ end
17
+ end
18
+ @error_messages = @error_messages&.to_a&.join(". ")
19
+ end
20
+
21
+ def infer_errors_from_association
22
+ association = if @name.to_s.end_with?("_ids")
23
+ return unless @form.object.respond_to?(:reflect_on_association)
24
+ @form.object.class.reflect_on_association(@name.to_s.chomp("_ids").pluralize.to_sym)
25
+ else
26
+ return unless @form.object.respond_to?(:reflect_on_all_associations)
27
+ @form.object.class.reflect_on_all_associations.find do |a|
28
+ a.macro == :belongs_to && a.foreign_key.to_s == @name.to_s
29
+ end
30
+ end
31
+ @form.object.errors.full_messages_for(association&.name&.to_sym) if association
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ <%= tag.div(class: classes, data:) do %>
2
+ <% if @label %>
3
+ <%= label_tag(
4
+ @name,
5
+ @label.is_a?(String) ? @label : nil,
6
+ class: "text-base/6 text-zinc-950 select-none data-disabled:opacity-50 sm:text-sm/6",
7
+ data: label_data
8
+ ) %>
9
+ <% end %>
10
+ <% if @description %>
11
+ <%= tag.p(
12
+ @description,
13
+ class: "text-base/6 text-zinc-500 data-disabled:opacity-50 sm:text-sm/6",
14
+ data: description_data
15
+ ) %>
16
+ <% end %>
17
+ <span data-slot="control" class="relative block w-full before:absolute before:inset-px before:rounded-[calc(var(--radius-lg)-1px)] before:bg-white before:shadow-sm dark:before:hidden after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-blue-500 has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none has-data-invalid:before:shadow-red-500/10">
18
+ <% if @form %>
19
+ <%= @form.file_field(
20
+ @name,
21
+ data: input_data,
22
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 data-disabled:hover:none data-disabled:cursor-not-allowed bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
23
+ disabled: @disabled,
24
+ autofocus: @autofocus,
25
+ multiple: @multiple,
26
+ accept: @accept,
27
+ placeholder: @placeholder
28
+ ) %>
29
+ <% else %>
30
+ <%= file_field_tag(
31
+ @name,
32
+ value: @value,
33
+ data: input_data,
34
+ class: "relative block w-full appearance-none rounded-lg text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:hover:none data-disabled:cursor-not-allowed data-disabled:border-zinc-950/20 data-disabled:file:cursor-not-allowed data-disabled:file:cursor-not-allowed file:cursor-pointer cursor-pointer file:border-0 file:px-[calc(--spacing(3.5)-1px)] file:py-[calc(--spacing(2.5)-1px)] file:sm:px-[calc(--spacing(3)-1px)] file:sm:py-[calc(--spacing(1.5)-1px)] file:mr-4 file:bg-zinc-100",
35
+ disabled: @disabled,
36
+ autofocus: @autofocus,
37
+ multiple: @multiple,
38
+ accept: @accept,
39
+ placeholder: @placeholder
40
+ ) %>
41
+ <% end %>
42
+ </span>
43
+ <% if has_errors? %>
44
+ <%= tag.p(
45
+ error_messages,
46
+ class: "text-base/6 text-red-600 data-disabled:opacity-50 sm:text-sm/6",
47
+ data: error_data
48
+ ) %>
49
+ <% end %>
50
+ <% end %>
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LightningUiKit::FileInputComponent < LightningUiKit::BaseComponent
4
+ include LightningUiKit::Errors
5
+
6
+ def initialize(name:, value: nil, autofocus: false, label: nil, form: nil, description: nil, multiple: false, accept: nil, disabled: false, error: nil, **options)
7
+ @name = name
8
+ @value = value
9
+ @disabled = disabled
10
+ @autofocus = autofocus
11
+ @accept = accept
12
+ @multiple = multiple
13
+ @error = error
14
+ @label = label
15
+ @form = form
16
+ @description = description
17
+ @options = options
18
+ end
19
+
20
+ def classes
21
+ merge_classes(["[&>[data-slot=label]+[data-slot=control]]:mt-3 [&>[data-slot=label]+[data-slot=description]]:mt-1 [&>[data-slot=description]+[data-slot=control]]:mt-3 [&>[data-slot=control]+[data-slot=description]]:mt-3 [&>[data-slot=control]+[data-slot=error]]:mt-3 *:data-[slot=label]:font-medium", @options[:class]].compact.join(" "))
22
+ end
23
+
24
+ def data
25
+ @options[:data] || {}
26
+ end
27
+
28
+ def input_data
29
+ (@options[:input_data] || {}).tap do |data|
30
+ if @disabled
31
+ data[:disabled] = "true"
32
+ end
33
+ if has_errors?
34
+ data[:invalid] = "true"
35
+ end
36
+ end
37
+ end
38
+
39
+ def label_data
40
+ {slot: "label"}.merge(@options[:label_data] || {}).tap do |data|
41
+ if @disabled
42
+ data[:disabled] = "true"
43
+ end
44
+ end
45
+ end
46
+
47
+ def description_data
48
+ {slot: "description"}.merge(@options[:description_data] || {}).tap do |data|
49
+ if @disabled
50
+ data[:disabled] = "true"
51
+ end
52
+ end
53
+ end
54
+
55
+ def error_data
56
+ {slot: "error"}.merge(@options[:error_data] || {}).tap do |data|
57
+ if @disabled
58
+ data[:disabled] = "true"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -2,12 +2,12 @@
2
2
  class: classes,
3
3
  data:
4
4
  ) do %>
5
- <% if @label %>
6
- <%= tag.label(
7
- @label,
8
- class: "text-base/6 text-zinc-950 select-none data-disabled:opacity-50 sm:text-sm/6",
9
- data: label_data
10
- ) %>
5
+ <% if @label %>
6
+ <%= tag.label(
7
+ @label,
8
+ class: "text-base/6 text-zinc-950 select-none data-disabled:opacity-50 sm:text-sm/6",
9
+ data: label_data
10
+ ) %>
11
11
  <% end %>
12
12
  <% if @description %>
13
13
  <%= tag.p(
@@ -23,7 +23,7 @@
23
23
  <%= @form.text_field(
24
24
  @name,
25
25
  data: input_data,
26
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
26
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
27
27
  disabled: @disabled,
28
28
  autofocus: @autofocus,
29
29
  placeholder: @placeholder
@@ -32,7 +32,7 @@
32
32
  <%= @form.email_field(
33
33
  @name,
34
34
  data: input_data,
35
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
35
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
36
36
  disabled: @disabled,
37
37
  autofocus: @autofocus
38
38
  ) %>
@@ -40,7 +40,7 @@
40
40
  <%= @form.password_field(
41
41
  @name,
42
42
  data: input_data,
43
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
43
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
44
44
  disabled: @disabled,
45
45
  autofocus: @autofocus
46
46
  ) %>
@@ -48,7 +48,7 @@
48
48
  <%= @form.number_field(
49
49
  @name,
50
50
  data: input_data,
51
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
51
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
52
52
  disabled: @disabled,
53
53
  autofocus: @autofocus
54
54
  ) %>
@@ -62,7 +62,7 @@
62
62
  @name,
63
63
  @value,
64
64
  data: input_data,
65
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
65
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
66
66
  disabled: @disabled,
67
67
  autofocus: @autofocus
68
68
  ) %>
@@ -71,7 +71,7 @@
71
71
  @name,
72
72
  @value,
73
73
  data: input_data,
74
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
74
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
75
75
  disabled: @disabled,
76
76
  autofocus: @autofocus
77
77
  ) %>
@@ -80,7 +80,7 @@
80
80
  @name,
81
81
  @value,
82
82
  data: input_data,
83
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
83
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
84
84
  disabled: @disabled,
85
85
  autofocus: @autofocus
86
86
  ) %>
@@ -89,11 +89,18 @@
89
89
  @name,
90
90
  @value,
91
91
  data: input_data,
92
- class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 data-hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:data-hover:border-red-500 dark:data-invalid:data-hover:border-red-500 data-disabled:border-zinc-950/20",
92
+ class: "relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 border border-zinc-950/10 hover:border-zinc-950/20 bg-transparent focus:outline-hidden data-invalid:border-red-500 data-invalid:hover:border-red-500 dark:data-invalid:hover:border-red-500 data-disabled:border-zinc-950/20",
93
93
  disabled: @disabled,
94
94
  autofocus: @autofocus
95
95
  ) %>
96
96
  <% end %>
97
97
  </span>
98
98
  <% end %>
99
+ <% if has_errors? %>
100
+ <%= tag.p(
101
+ error_messages,
102
+ class: "text-base/6 text-red-600 data-disabled:opacity-50 sm:text-sm/6",
103
+ data: error_data
104
+ ) %>
105
+ <% end %>
99
106
  <% end %>
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::InputComponent < LightningUi::BaseComponent
4
- def initialize(name:, value: nil, autofocus: false, label: nil, form: nil, type: :text, description: nil, disabled: false, placeholder: nil, **options)
3
+ class LightningUiKit::InputComponent < LightningUiKit::BaseComponent
4
+ include LightningUiKit::Errors
5
+
6
+ def initialize(name:, value: nil, autofocus: false, label: nil, form: nil, type: :text, description: nil, disabled: false, placeholder: nil, error: nil, **options)
5
7
  @name = name
6
8
  @value = value
7
9
  @disabled = disabled
8
10
  @autofocus = autofocus
11
+ @error = error
9
12
  @label = label
10
13
  @form = form
11
14
  @type = type
@@ -23,7 +26,11 @@ class LightningUi::InputComponent < LightningUi::BaseComponent
23
26
  end
24
27
 
25
28
  def input_data
26
- @options[:input_data] || {}
29
+ (@options[:input_data] || {}).tap do |data|
30
+ if has_errors?
31
+ data[:invalid] = "true"
32
+ end
33
+ end
27
34
  end
28
35
 
29
36
  def label_data
@@ -41,4 +48,12 @@ class LightningUi::InputComponent < LightningUi::BaseComponent
41
48
  end
42
49
  end
43
50
  end
51
+
52
+ def error_data
53
+ {slot: "error"}.merge(@options[:error_data] || {}).tap do |data|
54
+ if @disabled
55
+ data[:disabled] = "true"
56
+ end
57
+ end
58
+ end
44
59
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::LinkComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::LinkComponent < LightningUiKit::BaseComponent
4
4
  def initialize(title:, url:)
5
5
  @title = title
6
6
  @url = url
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::ModalComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::ModalComponent < LightningUiKit::BaseComponent
4
4
  renders_one :body
5
5
  renders_many :actions
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class LightningUi::PaginationComponent < LightningUi::BaseComponent
3
+ class LightningUiKit::PaginationComponent < LightningUiKit::BaseComponent
4
4
  def initialize(current_page:, total_pages:, path:, page_param: "page", with_arrows: false, **options)
5
5
  @current_page = current_page
6
6
  @total_pages = total_pages