lightning_ui_kit 0.2.3 → 0.2.5

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +9 -2
  4. data/app/assets/builds/lightning_ui_kit.css +588 -31
  5. data/app/assets/builds/lightning_ui_kit.js +9 -2
  6. data/app/assets/builds/lightning_ui_kit.js.map +4 -4
  7. data/app/assets/vendor/lightning_ui_kit.css +582 -88
  8. data/app/assets/vendor/lightning_ui_kit.js +9 -2
  9. data/app/components/lightning_ui_kit/button_component.html.erb +4 -0
  10. data/app/components/lightning_ui_kit/button_component.rb +24 -3
  11. data/app/components/lightning_ui_kit/combobox_component.html.erb +137 -0
  12. data/app/components/lightning_ui_kit/combobox_component.rb +205 -0
  13. data/app/components/lightning_ui_kit/dropdown_component.html.erb +1 -1
  14. data/app/components/lightning_ui_kit/dropdown_component.rb +1 -1
  15. data/app/components/lightning_ui_kit/dropzone_component.html.erb +13 -38
  16. data/app/components/lightning_ui_kit/dropzone_component.rb +43 -16
  17. data/app/components/lightning_ui_kit/file_input_component.html.erb +4 -34
  18. data/app/components/lightning_ui_kit/file_input_component.rb +54 -20
  19. data/app/components/lightning_ui_kit/input_component.html.erb +14 -98
  20. data/app/components/lightning_ui_kit/input_component.rb +154 -19
  21. data/app/components/lightning_ui_kit/layout_component.html.erb +118 -0
  22. data/app/components/lightning_ui_kit/layout_component.rb +26 -0
  23. data/app/components/lightning_ui_kit/modal_component.html.erb +2 -2
  24. data/app/components/lightning_ui_kit/select_component.html.erb +6 -27
  25. data/app/components/lightning_ui_kit/select_component.rb +65 -23
  26. data/app/components/lightning_ui_kit/sidebar_link_component.html.erb +6 -0
  27. data/app/components/lightning_ui_kit/sidebar_link_component.rb +33 -0
  28. data/app/components/lightning_ui_kit/sidebar_section_component.html.erb +8 -0
  29. data/app/components/lightning_ui_kit/sidebar_section_component.rb +18 -0
  30. data/app/components/lightning_ui_kit/textarea_component.html.erb +5 -37
  31. data/app/components/lightning_ui_kit/textarea_component.rb +50 -17
  32. data/app/components/lightning_ui_kit/tooltip_component.html.erb +15 -0
  33. data/app/components/lightning_ui_kit/tooltip_component.rb +26 -0
  34. data/app/javascript/lightning_ui_kit/controllers/combobox_controller.js +704 -0
  35. data/app/javascript/lightning_ui_kit/controllers/field_controller.js +23 -0
  36. data/app/javascript/lightning_ui_kit/controllers/layout_controller.js +19 -0
  37. data/app/javascript/lightning_ui_kit/controllers/modal_controller.js +7 -1
  38. data/app/javascript/lightning_ui_kit/controllers/tooltip_controller.js +235 -0
  39. data/app/javascript/lightning_ui_kit/index.js +8 -0
  40. data/config/deploy.yml +2 -5
  41. data/lib/lightning_ui_kit/engine.rb +1 -6
  42. data/lib/lightning_ui_kit/version.rb +1 -1
  43. metadata +17 -17
@@ -1,14 +1,5 @@
1
- <%= tag.div(
2
- class: classes,
3
- data:
4
- ) do %>
5
- <% if @label %>
6
- <%= tag.label(
7
- @label,
8
- class: "lui:text-base/6 lui:text-zinc-950 lui:select-none lui:data-disabled:opacity-50 lui:sm:text-sm/6",
9
- data: label_data
10
- ) %>
11
- <% end %>
1
+ <%= tag.div(class: classes, data: data) do %>
2
+ <%= render_label %>
12
3
  <% if @description %>
13
4
  <%= tag.p(
14
5
  @description,
@@ -16,94 +7,19 @@
16
7
  data: description_data
17
8
  ) %>
18
9
  <% end %>
19
- <% if @form %>
20
- <span data-slot="control" class="lui:relative lui:block lui:w-full lui:before:absolute lui:before:inset-px lui:before:rounded-[calc(var(--radius-lg)-1px)] lui:before:bg-white lui:before:shadow-sm lui:after:pointer-events-none lui:after:absolute lui:after:inset-0 lui:after:rounded-lg lui:after:ring-transparent lui:after:ring-inset lui:sm:focus-within:after:ring-2 lui:sm:focus-within:after:ring-blue-500 lui:has-data-disabled:opacity-50 lui:has-data-disabled:before:bg-zinc-950/5 lui:has-data-disabled:before:shadow-none lui:has-data-invalid:before:shadow-red-500/10">
21
- <% case @type %>
22
- <% when :text %>
23
- <%= @form.text_field(
24
- @name,
25
- data: input_data,
26
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
27
- disabled: @disabled,
28
- autofocus: @autofocus,
29
- placeholder: @placeholder
30
- ) %>
31
- <% when :email %>
32
- <%= @form.email_field(
33
- @name,
34
- data: input_data,
35
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
36
- disabled: @disabled,
37
- autofocus: @autofocus,
38
- placeholder: @placeholder
39
- ) %>
40
- <% when :password %>
41
- <%= @form.password_field(
42
- @name,
43
- data: input_data,
44
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
45
- disabled: @disabled,
46
- autofocus: @autofocus,
47
- placeholder: @placeholder
48
- ) %>
49
- <% when :number %>
50
- <%= @form.number_field(
51
- @name,
52
- data: input_data,
53
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
54
- disabled: @disabled,
55
- autofocus: @autofocus,
56
- placeholder: @placeholder,
57
- min: @options[:min],
58
- max: @options[:max],
59
- step: @options[:step]
60
- ) %>
61
- <% end %>
10
+ <% if @type == :hidden %>
11
+ <%= render_hidden_input %>
12
+ <% elsif @type == :range %>
13
+ <span data-slot="control" class="lui:block lui:w-full lui:py-2">
14
+ <%= render_range_input %>
15
+ </span>
16
+ <% elsif @type == :color %>
17
+ <span data-slot="control" class="lui:block lui:py-1">
18
+ <%= render_color_input %>
62
19
  </span>
63
- <% else %>
64
- <span data-slot="control" class="lui:relative lui:block lui:w-full lui:before:absolute lui:before:inset-px lui:before:rounded-[calc(var(--radius-lg)-1px)] lui:before:bg-white lui:before:shadow-sm lui:after:pointer-events-none lui:after:absolute lui:after:inset-0 lui:after:rounded-lg lui:after:ring-transparent lui:after:ring-inset lui:sm:focus-within:after:ring-2 lui:sm:focus-within:after:ring-blue-500 lui:has-data-disabled:opacity-50 lui:has-data-disabled:before:bg-zinc-950/5 lui:has-data-disabled:before:shadow-none lui:has-data-invalid:before:shadow-red-500/10">
65
- <% case @type %>
66
- <% when :text %>
67
- <%= text_field_tag(
68
- @name,
69
- @value,
70
- data: input_data,
71
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
72
- disabled: @disabled,
73
- autofocus: @autofocus,
74
- placeholder: @placeholder
75
- ) %>
76
- <% when :email %>
77
- <%= email_field_tag(
78
- @name,
79
- @value,
80
- data: input_data,
81
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
82
- disabled: @disabled,
83
- autofocus: @autofocus,
84
- placeholder: @placeholder
85
- ) %>
86
- <% when :password %>
87
- <%= password_field_tag(
88
- @name,
89
- @value,
90
- data: input_data,
91
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
92
- disabled: @disabled,
93
- autofocus: @autofocus,
94
- placeholder: @placeholder
95
- ) %>
96
- <% when :number %>
97
- <%= number_field_tag(
98
- @name,
99
- @value,
100
- data: input_data,
101
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500/60 lui:data-disabled:border-zinc-950/20",
102
- disabled: @disabled,
103
- autofocus: @autofocus,
104
- placeholder: @placeholder,
105
- ) %>
106
- <% end %>
20
+ <% elsif standard_input_type? %>
21
+ <span data-slot="control" class="<%= control_classes %>">
22
+ <%= render_input %>
107
23
  </span>
108
24
  <% end %>
109
25
  <% if has_errors? %>
@@ -3,17 +3,36 @@
3
3
  class LightningUiKit::InputComponent < LightningUiKit::BaseComponent
4
4
  include LightningUiKit::Errors
5
5
 
6
- def initialize(name:, value: nil, autofocus: false, label: nil, form: nil, type: :text, description: nil, disabled: false, placeholder: nil, error: nil, **options)
6
+ INPUT_TYPES = {
7
+ text: :text_field,
8
+ email: :email_field,
9
+ password: :password_field,
10
+ number: :number_field,
11
+ date: :date_field,
12
+ datetime: :datetime_local_field,
13
+ datetime_local: :datetime_local_field,
14
+ month: :month_field,
15
+ week: :week_field,
16
+ time: :time_field,
17
+ url: :url_field,
18
+ search: :search_field,
19
+ telephone: :telephone_field,
20
+ phone: :telephone_field
21
+ }.freeze
22
+
23
+ STANDARD_INPUT_TYPES = INPUT_TYPES.keys.freeze
24
+
25
+ def initialize(name:, value: nil, label: nil, form: nil, type: :text, description: nil, disabled: false, placeholder: nil, error: nil, input_options: {}, **options)
7
26
  @name = name
8
27
  @value = value
9
28
  @disabled = disabled
10
- @autofocus = autofocus
11
29
  @error = error
12
30
  @label = label
13
31
  @form = form
14
32
  @type = type
15
33
  @description = description
16
34
  @placeholder = placeholder
35
+ @input_options = input_options
17
36
  @options = options
18
37
  end
19
38
 
@@ -22,38 +41,154 @@ class LightningUiKit::InputComponent < LightningUiKit::BaseComponent
22
41
  end
23
42
 
24
43
  def data
25
- @options[:data] || {}
44
+ {controller: "lui-field"}.merge(@options[:data] || {})
26
45
  end
27
46
 
28
47
  def input_data
29
- (@options[:input_data] || {}).tap do |data|
30
- if has_errors?
31
- data[:invalid] = "true"
32
- end
48
+ {lui_field_target: "field"}.merge(@options[:input_data] || {}).dup.tap do |data|
49
+ data[:invalid] = "true" if has_errors?
33
50
  end
34
51
  end
35
52
 
36
53
  def label_data
37
- {slot: "label"}.merge(@options[:label_data] || {}).tap do |data|
38
- if @disabled
39
- data[:disabled] = "true"
40
- end
54
+ {slot: "label"}.merge(@options[:label_data] || {}).dup.tap do |data|
55
+ data[:disabled] = "true" if @disabled
41
56
  end
42
57
  end
43
58
 
44
59
  def description_data
45
- {slot: "description"}.merge(@options[:description_data] || {}).tap do |data|
46
- if @disabled
47
- data[:disabled] = "true"
48
- end
60
+ {slot: "description"}.merge(@options[:description_data] || {}).dup.tap do |data|
61
+ data[:disabled] = "true" if @disabled
49
62
  end
50
63
  end
51
64
 
52
65
  def error_data
53
- {slot: "error"}.merge(@options[:error_data] || {}).tap do |data|
54
- if @disabled
55
- data[:disabled] = "true"
56
- end
66
+ {slot: "error"}.merge(@options[:error_data] || {}).dup.tap do |data|
67
+ data[:disabled] = "true" if @disabled
68
+ end
69
+ end
70
+
71
+ def input_classes
72
+ "lui:peer lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:px-[calc(--spacing(3.5)-1px)] lui:py-[calc(--spacing(2.5)-1px)] lui:sm:px-[calc(--spacing(3)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:data-[hover]:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:data-[hover]:border-red-500/60 lui:data-disabled:border-zinc-950/20"
73
+ end
74
+
75
+ def input_html_options
76
+ base_options = {
77
+ data: input_data,
78
+ class: input_classes,
79
+ disabled: @disabled,
80
+ placeholder: @placeholder
81
+ }
82
+
83
+ # Add type-specific options from **options (Rails convention)
84
+ case @type
85
+ when :number
86
+ base_options[:min] = @options[:min]
87
+ base_options[:max] = @options[:max]
88
+ base_options[:step] = @options[:step]
89
+ when :date, :datetime, :datetime_local, :month, :week, :time
90
+ base_options[:min] = @options[:min]
91
+ base_options[:max] = @options[:max]
92
+ end
93
+
94
+ base_options.merge(@input_options).compact
95
+ end
96
+
97
+ def range_html_options
98
+ {
99
+ data: input_data,
100
+ class: range_classes,
101
+ disabled: @disabled,
102
+ min: @options[:min],
103
+ max: @options[:max],
104
+ step: @options[:step]
105
+ }.merge(@input_options).compact
106
+ end
107
+
108
+ def hidden_html_options
109
+ {data: input_data}.merge(@input_options).compact
110
+ end
111
+
112
+ def color_html_options
113
+ {
114
+ data: {lui_field_target: "field"},
115
+ class: color_classes,
116
+ disabled: @disabled
117
+ }.merge(@input_options).compact
118
+ end
119
+
120
+ def color_classes
121
+ "lui:h-10 lui:w-14 lui:cursor-pointer lui:appearance-none lui:rounded-lg lui:border lui:border-zinc-950/10 lui:bg-transparent lui:p-1 " \
122
+ "lui:data-[hover]:border-zinc-950/20 lui:focus:outline-none lui:focus:ring-2 lui:focus:ring-blue-500 " \
123
+ "[&::-webkit-color-swatch-wrapper]:lui:p-0 [&::-webkit-color-swatch]:lui:rounded-md [&::-webkit-color-swatch]:lui:border-0 " \
124
+ "[&::-moz-color-swatch]:lui:rounded-md [&::-moz-color-swatch]:lui:border-0 " \
125
+ "lui:disabled:opacity-50 lui:disabled:cursor-not-allowed"
126
+ end
127
+
128
+ def range_classes
129
+ "lui:w-full lui:h-2 lui:appearance-none lui:cursor-pointer lui:bg-zinc-200 lui:rounded-full lui:focus:outline-none " \
130
+ "[&::-webkit-slider-thumb]:lui:appearance-none [&::-webkit-slider-thumb]:lui:w-4 [&::-webkit-slider-thumb]:lui:h-4 [&::-webkit-slider-thumb]:lui:bg-blue-600 [&::-webkit-slider-thumb]:lui:rounded-full [&::-webkit-slider-thumb]:lui:cursor-pointer [&::-webkit-slider-thumb]:lui:shadow-sm [&::-webkit-slider-thumb]:lui:transition-transform [&::-webkit-slider-thumb]:hover:lui:scale-110 " \
131
+ "[&::-moz-range-thumb]:lui:appearance-none [&::-moz-range-thumb]:lui:w-4 [&::-moz-range-thumb]:lui:h-4 [&::-moz-range-thumb]:lui:bg-blue-600 [&::-moz-range-thumb]:lui:rounded-full [&::-moz-range-thumb]:lui:border-0 [&::-moz-range-thumb]:lui:cursor-pointer [&::-moz-range-thumb]:lui:shadow-sm [&::-moz-range-thumb]:lui:transition-transform [&::-moz-range-thumb]:hover:lui:scale-110 " \
132
+ "[&::-moz-range-track]:lui:bg-zinc-200 [&::-moz-range-track]:lui:rounded-full " \
133
+ "lui:disabled:opacity-50 lui:disabled:cursor-not-allowed"
134
+ end
135
+
136
+ def label_html_options
137
+ {
138
+ class: "lui:text-base/6 lui:text-zinc-950 lui:select-none lui:data-disabled:opacity-50 lui:sm:text-sm/6",
139
+ data: label_data
140
+ }
141
+ end
142
+
143
+ def render_label
144
+ return unless @label
145
+
146
+ if @form
147
+ @form.label(@name, @label, **label_html_options)
148
+ else
149
+ helpers.label_tag(@name, @label, **label_html_options)
150
+ end
151
+ end
152
+
153
+ def control_classes
154
+ "lui:relative lui:block lui:w-full lui:before:pointer-events-none lui:before:absolute lui:before:inset-px lui:before:rounded-[7px] lui:before:bg-white lui:before:shadow-sm lui:after:pointer-events-none lui:after:absolute lui:after:inset-0 lui:after:rounded-lg lui:after:ring-transparent lui:after:ring-inset lui:sm:focus-within:after:ring-2 lui:sm:focus-within:after:ring-blue-500 lui:has-data-disabled:opacity-50 lui:has-data-disabled:before:bg-zinc-950/5 lui:has-data-disabled:before:shadow-none lui:has-data-invalid:before:shadow-red-500/10"
155
+ end
156
+
157
+ def standard_input_type?
158
+ STANDARD_INPUT_TYPES.include?(@type)
159
+ end
160
+
161
+ def render_input
162
+ method_name = INPUT_TYPES[@type]
163
+ if @form
164
+ @form.public_send(method_name, @name, **input_html_options)
165
+ else
166
+ tag_method = "#{method_name}_tag"
167
+ helpers.public_send(tag_method, @name, @value, **input_html_options)
168
+ end
169
+ end
170
+
171
+ def render_range_input
172
+ if @form
173
+ @form.range_field(@name, **range_html_options)
174
+ else
175
+ helpers.range_field_tag(@name, @value, **range_html_options)
176
+ end
177
+ end
178
+
179
+ def render_color_input
180
+ if @form
181
+ @form.color_field(@name, **color_html_options)
182
+ else
183
+ helpers.color_field_tag(@name, @value, **color_html_options)
184
+ end
185
+ end
186
+
187
+ def render_hidden_input
188
+ if @form
189
+ @form.hidden_field(@name, **hidden_html_options)
190
+ else
191
+ helpers.hidden_field_tag(@name, @value, **hidden_html_options)
57
192
  end
58
193
  end
59
194
  end
@@ -0,0 +1,118 @@
1
+ <div class="lui:relative lui:isolate lui:flex lui:h-screen lui:w-full lui:bg-white lui:max-lg:flex-col lui:max-lg:h-auto lui:max-lg:min-h-screen lui:lg:bg-zinc-100 lui:overflow-hidden" data-controller="lui-layout">
2
+ <%# Desktop Sidebar (fixed, hidden on mobile) %>
3
+ <div class="lui:fixed lui:inset-y-0 lui:left-0 <%= sidebar_width_class %> lui:max-lg:hidden lui:z-10">
4
+ <nav class="lui:flex lui:h-full lui:min-h-0 lui:flex-col">
5
+ <%# Sidebar content %>
6
+ <div class="lui:flex lui:flex-1 lui:flex-col lui:overflow-y-auto lui:p-4">
7
+ <% if header? %>
8
+ <div class="lui:mb-4">
9
+ <%= header %>
10
+ </div>
11
+ <% end %>
12
+ <% if sections? %>
13
+ <div class="lui:space-y-6">
14
+ <% sections.each do |section| %>
15
+ <%= section %>
16
+ <% end %>
17
+ </div>
18
+ <% end %>
19
+ <%= sidebar %>
20
+ </div>
21
+
22
+ <%# Footer at bottom of sidebar %>
23
+ <% if footer? %>
24
+ <div class="lui:border-t lui:border-zinc-950/5">
25
+ <%= footer %>
26
+ </div>
27
+ <% end %>
28
+ </nav>
29
+ </div>
30
+
31
+ <%# Mobile Header (hidden on desktop) %>
32
+ <header class="lui:flex lui:items-center lui:px-4 lui:lg:hidden">
33
+ <div class="lui:py-2.5">
34
+ <button
35
+ type="button"
36
+ aria-label="Open navigation"
37
+ class="lui:cursor-pointer lui:relative lui:flex lui:min-w-0 lui:items-center lui:gap-3 lui:rounded-lg lui:p-2 lui:text-left lui:text-base/6 lui:font-medium lui:text-zinc-950 lui:sm:text-sm/5 lui:hover:bg-zinc-950/5"
38
+ data-action="lui-layout#openSidebar"
39
+ >
40
+ <span class="lui:absolute lui:top-1/2 lui:left-1/2 lui:size-[max(100%,2.75rem)] lui:-translate-x-1/2 lui:-translate-y-1/2 [@media(pointer:fine)]:lui:hidden" aria-hidden="true"></span>
41
+ <svg class="lui:size-6 lui:shrink-0 lui:fill-zinc-500 lui:sm:size-5" viewBox="0 0 20 20" aria-hidden="true">
42
+ <path d="M2 6.75C2 6.33579 2.33579 6 2.75 6H17.25C17.6642 6 18 6.33579 18 6.75C18 7.16421 17.6642 7.5 17.25 7.5H2.75C2.33579 7.5 2 7.16421 2 6.75ZM2 13.25C2 12.8358 2.33579 12.5 2.75 12.5H17.25C17.6642 12.5 18 12.8358 18 13.25C18 13.6642 17.6642 14 17.25 14H2.75C2.33579 14 2 13.6642 2 13.25Z"></path>
43
+ </svg>
44
+ </button>
45
+ </div>
46
+ <div class="lui:min-w-0 lui:flex-1">
47
+ <nav class="lui:flex lui:flex-1 lui:items-center lui:gap-4 lui:py-2.5">
48
+ <% if mobile_header? %>
49
+ <%= mobile_header %>
50
+ <% end %>
51
+ </nav>
52
+ </div>
53
+ </header>
54
+
55
+ <%# Mobile Sidebar Overlay %>
56
+ <div
57
+ class="lui:group lui:fixed lui:inset-0 lui:z-50 lui:invisible lui:data-[open]:visible"
58
+ data-lui-layout-target="overlay"
59
+ >
60
+ <%# Backdrop %>
61
+ <div
62
+ class="lui:fixed lui:inset-0 lui:bg-zinc-950/25 lui:opacity-0 lui:transition-opacity lui:duration-300 lui:group-data-[open]:opacity-100"
63
+ data-action="click->lui-layout#closeSidebar"
64
+ ></div>
65
+
66
+ <%# Mobile Sidebar Panel %>
67
+ <div class="lui:fixed lui:inset-y-0 lui:left-0 <%= sidebar_width_class %> lui:bg-white lui:-translate-x-full lui:transition-transform lui:duration-300 lui:ease-out lui:shadow-xl lui:group-data-[open]:translate-x-0">
68
+ <nav class="lui:flex lui:h-full lui:min-h-0 lui:flex-col">
69
+ <%# Close button %>
70
+ <div class="lui:flex lui:items-center lui:justify-end lui:p-4">
71
+ <button
72
+ type="button"
73
+ aria-label="Close navigation"
74
+ class="lui:cursor-pointer lui:rounded-lg lui:p-2 lui:hover:bg-zinc-950/5"
75
+ data-action="lui-layout#closeSidebar"
76
+ >
77
+ <svg class="lui:size-6 lui:fill-zinc-500" viewBox="0 0 20 20" aria-hidden="true">
78
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
79
+ </svg>
80
+ </button>
81
+ </div>
82
+
83
+ <%# Sidebar content %>
84
+ <div class="lui:flex lui:flex-1 lui:flex-col lui:overflow-y-auto lui:p-4">
85
+ <% if header? %>
86
+ <div class="lui:mb-4">
87
+ <%= header %>
88
+ </div>
89
+ <% end %>
90
+ <% if sections? %>
91
+ <div class="lui:space-y-6">
92
+ <% sections.each do |section| %>
93
+ <%= section %>
94
+ <% end %>
95
+ </div>
96
+ <% end %>
97
+ <%= sidebar %>
98
+ </div>
99
+
100
+ <%# Footer %>
101
+ <% if footer? %>
102
+ <div class="lui:border-t lui:border-zinc-950/5">
103
+ <%= footer %>
104
+ </div>
105
+ <% end %>
106
+ </nav>
107
+ </div>
108
+ </div>
109
+
110
+ <%# Main Content Area %>
111
+ <main class="lui:flex lui:flex-1 lui:flex-col lui:min-h-0 lui:pb-4 lui:lg:min-w-0 lui:lg:pt-4 lui:lg:pr-4 <%= main_padding_class %>">
112
+ <div class="lui:flex-1 lui:min-h-0 lui:p-6 lui:lg:rounded-lg lui:lg:bg-white lui:lg:p-10 lui:lg:shadow-xs lui:lg:ring-1 lui:lg:ring-zinc-950/5 lui:lg:overflow-y-auto">
113
+ <div class="lui:mx-auto lui:max-w-6xl">
114
+ <%= content %>
115
+ </div>
116
+ </div>
117
+ </main>
118
+ </div>
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LightningUiKit::LayoutComponent < LightningUiKit::BaseComponent
4
+ renders_one :sidebar
5
+ renders_one :header
6
+ renders_many :sections, LightningUiKit::SidebarSectionComponent
7
+ renders_one :footer
8
+ renders_one :mobile_header
9
+
10
+ def initialize(sidebar_width: "w-64", **options)
11
+ @sidebar_width = sidebar_width
12
+ @options = options
13
+ end
14
+
15
+ def sidebar_width_class
16
+ if @sidebar_width == "w-64"
17
+ "lui:lg:w-64"
18
+ else
19
+ "lui:lg:#{@sidebar_width}"
20
+ end
21
+ end
22
+
23
+ def main_padding_class
24
+ "lui:lg:pl-64"
25
+ end
26
+ end
@@ -1,8 +1,8 @@
1
1
  <%= tag.dialog(id: @id, open: @open, data:) do %>
2
2
  <div class="lui:fixed lui:inset-0 lui:flex lui:w-screen lui:justify-center lui:overflow-y-auto lui:bg-zinc-950/25 lui:px-2 lui:py-2 lui:transition lui:duration-100 lui:focus:outline-0 lui:data-closed:opacity-0 lui:data-enter:ease-out lui:data-leave:ease-in lui:sm:px-6 lui:sm:py-8 lg:lui:px-8 lg:lui:py-16" aria-hidden="true" data-open=""></div>
3
3
  <div class="lui:fixed lui:inset-0 lui:w-screen lui:overflow-y-auto lui:pt-6 lui:sm:pt-0">
4
- <div class="lui:grid lui:min-h-full lui:grid-rows-[1fr_auto] lui:justify-items-center lui:sm:grid-rows-[1fr_auto_3fr] lui:sm:p-4">
5
- <div class="lui:sm:max-w-3xl lui:row-start-2 lui:w-full lui:min-w-0 lui:rounded-t-3xl lui:bg-white lui:p-(--gutter) lui:ring-1 lui:shadow-lg lui:ring-zinc-950/10 lui:[--gutter:--spacing(8)] lui:sm:mb-auto lui:sm:rounded-2xl lui:forced-colors:outline lui:transition lui:duration-100 lui:will-change-transform lui:data-closed:translate-y-12 lui:data-closed:opacity-0 lui:data-enter:ease-out lui:data-leave:ease-in lui:sm:data-closed:translate-y-0 lui:sm:data-closed:data-enter:scale-95" data-open="">
4
+ <div class="lui:grid lui:min-h-full lui:grid-rows-[1fr_auto] lui:justify-items-center lui:sm:grid-rows-[1fr_auto_3fr] lui:sm:p-4" data-action="click->lui-modal#closeOnBackdrop">
5
+ <div class="lui:sm:max-w-3xl lui:row-start-2 lui:w-full lui:min-w-0 lui:rounded-t-3xl lui:bg-white lui:p-(--gutter) lui:ring-1 lui:shadow-lg lui:ring-zinc-950/10 lui:[--gutter:--spacing(8)] lui:sm:mb-auto lui:sm:rounded-2xl lui:forced-colors:outline lui:transition lui:duration-100 lui:will-change-transform lui:data-closed:translate-y-12 lui:data-closed:opacity-0 lui:data-enter:ease-out lui:data-leave:ease-in lui:sm:data-closed:translate-y-0 lui:sm:data-closed:data-enter:scale-95" data-open="" data-lui-modal-target="panel">
6
6
  <% if @title %>
7
7
  <h2 class="lui:text-lg/6 lui:font-semibold lui:text-balance lui:text-zinc-950 lui:sm:text-base/6" data-open="">
8
8
  <%= @title %>
@@ -1,35 +1,14 @@
1
- <%= tag.div data:, class: "lui:[&>[data-slot=label]+[data-slot=control]]:mt-3 lui:[&>[data-slot=label]+[data-slot=description]]:mt-1 lui:[&>[data-slot=description]+[data-slot=control]]:mt-3 lui:[&>[data-slot=control]+[data-slot=description]]:mt-3 lui:[&>[data-slot=control]+[data-slot=error]]:mt-3 lui:*:data-[slot=label]:font-medium" do %>
2
- <% if @label %>
3
- <label data-slot="label" class="lui:text-base/6 lui:text-zinc-950 lui:select-none lui:data-disabled:opacity-50 lui:sm:text-sm/6">
4
- <%= @label %>
5
- </label>
6
- <% end %>
1
+ <%= tag.div data:, class: classes do %>
2
+ <%= render_label %>
7
3
  <% if @description %>
8
- <p data-slot="description" class="lui:text-base/6 lui:text-zinc-500 lui:data-disabled:opacity-50 lui:sm:text-sm/6">
4
+ <p data-slot="description" class="lui:text-base/6 lui:text-zinc-500 lui:data-disabled:opacity-50 lui:sm:text-sm/6" <%= "data-disabled=true" if @disabled %>>
9
5
  <%= @description %>
10
6
  </p>
11
7
  <% end %>
12
- <span data-slot="control"
13
- class="lui:group lui:relative lui:block lui:w-full lui:before:absolute lui:before:inset-px lui:before:rounded-[calc(var(--radius-lg)-1px)] lui:before:bg-white lui:before:shadow-sm lui:after:pointer-events-none lui:after:absolute lui:after:inset-0 lui:after:rounded-lg lui:after:ring-transparent lui:after:ring-inset lui:focus:after:ring-2 lui:focus:after:ring-blue-500 lui:has-data-disabled:opacity-50 lui:has-data-disabled:before:bg-zinc-950/5 lui:has-data-disabled:before:shadow-none">
14
- <% if @form %>
15
- <%= @form.select(
16
- @name,
17
- @options_for_select,
18
- { multiple: @multiple },
19
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:py-[calc(--spacing(2.5)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:pr-[calc(--spacing(10)-1px)] lui:pl-[calc(--spacing(3.5)-1px)] lui:sm:pr-[calc(--spacing(9)-1px)] lui:sm:pl-[calc(--spacing(3)-1px)] lui:[&_optgroup]:font-semibold lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500 lui:data-disabled:border-zinc-950/20 lui:data-disabled:opacity-100",
20
- data: select_data
21
- ) %>
22
- <% else %>
23
- <%= select_tag(
24
- @name,
25
- @options_for_select,
26
- multiple: @multiple,
27
- class: "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:py-[calc(--spacing(2.5)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:pr-[calc(--spacing(10)-1px)] lui:pl-[calc(--spacing(3.5)-1px)] lui:sm:pr-[calc(--spacing(9)-1px)] lui:sm:pl-[calc(--spacing(3)-1px)] lui:[&_optgroup]:font-semibold lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:hover:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:hover:border-red-500 lui:data-disabled:border-zinc-950/20 lui:data-disabled:opacity-100",
28
- data: select_data
29
- ) %>
30
- <% end %>
8
+ <span data-slot="control" class="<%= control_classes %>">
9
+ <%= render_select %>
31
10
  <span class="lui:pointer-events-none lui:absolute lui:inset-y-0 lui:right-0 lui:flex lui:items-center lui:pr-2">
32
- <svg class="lui:size-5 lui:stroke-zinc-500 lui:group-has-data-disabled:stroke-zinc-600 lui:sm:size-4 lui:forced-colors:stroke-[CanvasText]" viewBox="0 0 16 16" aria-hidden="true" fill="none">
11
+ <svg class="lui:size-5 lui:stroke-zinc-500 lui:sm:size-4 lui:forced-colors:stroke-[CanvasText]" viewBox="0 0 16 16" aria-hidden="true" fill="none">
33
12
  <path d="M5.75 10.75L8 13L10.25 10.75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
34
13
  <path d="M10.25 5.25L8 3L5.75 5.25" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
35
14
  </svg>
@@ -15,41 +15,83 @@ class LightningUiKit::SelectComponent < LightningUiKit::BaseComponent
15
15
  @options = options
16
16
  end
17
17
 
18
- def data
19
- default_data = {
20
- slot: "field",
21
- action: "click->switch#toggle",
22
- controller: "select",
23
- disabled: @disabled
24
- }
18
+ def classes
19
+ "lui:[&>[data-slot=label]+[data-slot=control]]:mt-3 lui:[&>[data-slot=label]+[data-slot=description]]:mt-1 lui:[&>[data-slot=description]+[data-slot=control]]:mt-3 lui:[&>[data-slot=control]+[data-slot=description]]:mt-3 lui:[&>[data-slot=control]+[data-slot=error]]:mt-3 lui:*:data-[slot=label]:font-medium"
20
+ end
25
21
 
26
- default_data.merge(@options[:data] || {})
22
+ def data
23
+ {controller: "lui-field"}.merge(@options[:data] || {})
27
24
  end
28
25
 
29
26
  def select_data
30
- {}.tap do |data|
31
- if has_errors?
32
- data[:invalid] = "true"
33
- end
27
+ {lui_field_target: "field"}.merge(@options[:select_data] || {}).tap do |data|
28
+ data[:invalid] = "true" if has_errors?
34
29
  end
35
30
  end
36
31
 
37
32
  def control_data
38
- {slot: "control"}.merge(@options[:control_data] || {}).tap do |data|
39
- if @disabled
40
- data[:disabled] = "true"
41
- end
42
- if has_errors?
43
- data[:invalid] = "true"
44
- end
33
+ {slot: "control"}.merge(@options[:control_data] || {}).dup.tap do |data|
34
+ data[:disabled] = "true" if @disabled
35
+ data[:invalid] = "true" if has_errors?
45
36
  end
46
37
  end
47
38
 
48
39
  def error_data
49
- {slot: "error"}.merge(@options[:error_data] || {}).tap do |data|
50
- if @disabled
51
- data[:disabled] = "true"
52
- end
40
+ {slot: "error"}.merge(@options[:error_data] || {}).dup.tap do |data|
41
+ data[:disabled] = "true" if @disabled
42
+ end
43
+ end
44
+
45
+ def label_data
46
+ {slot: "label"}.merge(@options[:label_data] || {}).dup.tap do |data|
47
+ data[:disabled] = "true" if @disabled
48
+ end
49
+ end
50
+
51
+ def description_data
52
+ {slot: "description"}.merge(@options[:description_data] || {}).dup.tap do |data|
53
+ data[:disabled] = "true" if @disabled
54
+ end
55
+ end
56
+
57
+ def label_html_options
58
+ {
59
+ class: "lui:text-base/6 lui:text-zinc-950 lui:select-none lui:data-disabled:opacity-50 lui:sm:text-sm/6",
60
+ data: label_data
61
+ }
62
+ end
63
+
64
+ def render_label
65
+ return unless @label
66
+
67
+ if @form
68
+ @form.label(@name, @label, **label_html_options)
69
+ else
70
+ helpers.label_tag(@name, @label, **label_html_options)
71
+ end
72
+ end
73
+
74
+ def select_classes
75
+ "lui:relative lui:block lui:w-full lui:appearance-none lui:rounded-lg lui:py-[calc(--spacing(2.5)-1px)] lui:sm:py-[calc(--spacing(1.5)-1px)] lui:pr-[calc(--spacing(10)-1px)] lui:pl-[calc(--spacing(3.5)-1px)] lui:sm:pr-[calc(--spacing(9)-1px)] lui:sm:pl-[calc(--spacing(3)-1px)] lui:[&_optgroup]:font-semibold lui:text-base/6 lui:text-zinc-950 lui:placeholder:text-zinc-500 lui:sm:text-sm/6 lui:border lui:border-zinc-950/10 lui:data-[hover]:border-zinc-950/20 lui:bg-transparent lui:focus:outline-hidden lui:data-invalid:border-red-500 lui:data-invalid:data-[hover]:border-red-500 lui:data-disabled:border-zinc-950/20 lui:data-disabled:opacity-100"
76
+ end
77
+
78
+ def control_classes
79
+ "lui:relative lui:block lui:w-full lui:before:pointer-events-none lui:before:absolute lui:before:inset-px lui:before:rounded-[7px] lui:before:bg-white lui:before:shadow-sm lui:after:pointer-events-none lui:after:absolute lui:after:inset-0 lui:after:rounded-lg lui:after:ring-inset lui:focus-within:after:ring-2 lui:focus-within:after:ring-blue-500 lui:has-data-disabled:opacity-50 lui:has-data-disabled:before:bg-zinc-950/5 lui:has-data-disabled:before:shadow-none"
80
+ end
81
+
82
+ def select_html_options
83
+ {
84
+ multiple: @multiple,
85
+ class: select_classes,
86
+ data: select_data
87
+ }
88
+ end
89
+
90
+ def render_select
91
+ if @form
92
+ @form.select(@name, @options_for_select, {}, select_html_options)
93
+ else
94
+ helpers.select_tag(@name, helpers.options_for_select(@options_for_select), select_html_options)
53
95
  end
54
96
  end
55
97
  end
@@ -0,0 +1,6 @@
1
+ <%= link_to @url, class: classes, data: data do %>
2
+ <% if @icon %>
3
+ <%= heroicon(@icon, options: { class: icon_classes }) %>
4
+ <% end %>
5
+ <span><%= @title %></span>
6
+ <% end %>