ariadne_view_components 0.0.82 → 0.0.83

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/app/assets/javascripts/ariadne_view_components.js +14 -14
  4. data/app/assets/javascripts/ariadne_view_components.js.br +0 -0
  5. data/app/assets/javascripts/ariadne_view_components.js.gz +0 -0
  6. data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
  7. data/app/assets/stylesheets/ariadne_view_components.css +1 -1
  8. data/app/assets/stylesheets/ariadne_view_components.css.br +0 -0
  9. data/app/assets/stylesheets/ariadne_view_components.css.gz +0 -0
  10. data/app/components/ariadne/behaviors/captionable.rb +1 -1
  11. data/app/components/ariadne/behaviors/tooltipable.rb +1 -1
  12. data/app/components/ariadne/form/radio_button/component.html.erb +1 -1
  13. data/app/components/ariadne/form/radio_button/component.rb +1 -1
  14. data/app/components/ariadne/form/select/component.html.erb +10 -0
  15. data/app/components/ariadne/form/select/component.rb +81 -0
  16. data/app/components/ariadne/ui/accordion/component.html.erb +7 -0
  17. data/app/components/ariadne/ui/accordion/component.rb +27 -0
  18. data/app/components/ariadne/ui/accordion/component.ts +69 -0
  19. data/app/components/ariadne/ui/accordion/item/component.html.erb +15 -0
  20. data/app/components/ariadne/ui/accordion/item/component.rb +54 -0
  21. data/app/components/ariadne/ui/badge/component.html.erb +1 -1
  22. data/app/components/ariadne/ui/badge/component.rb +0 -1
  23. data/app/components/ariadne/ui/button/component.html.erb +2 -1
  24. data/app/components/ariadne/ui/button/component.rb +48 -10
  25. data/app/components/ariadne/ui/card/component.html.erb +12 -2
  26. data/app/components/ariadne/ui/card/component.rb +21 -2
  27. data/app/components/ariadne/ui/card/header/component.html.erb +3 -4
  28. data/app/components/ariadne/ui/card/header/component.rb +8 -5
  29. data/app/components/ariadne/ui/combobox/component.rb +4 -2
  30. data/app/components/ariadne/ui/combobox/component.ts +27 -11
  31. data/app/components/ariadne/ui/link/component.rb +11 -2
  32. data/app/components/ariadne/ui/list/component.html.erb +8 -1
  33. data/app/components/ariadne/ui/popover/component.rb +1 -1
  34. data/app/frontend/controllers/form_validity_controller.ts +32 -0
  35. data/app/lib/ariadne/view_helper.rb +6 -0
  36. data/lib/ariadne/forms/dsl/form_object.rb +1 -1
  37. data/lib/ariadne/forms/dsl/input.rb +1 -1
  38. data/lib/ariadne/forms/dsl/select_input.rb +50 -0
  39. data/lib/ariadne/view_components/version.rb +1 -1
  40. metadata +11 -2
@@ -16,10 +16,32 @@ module Ariadne
16
16
  option :type, default: proc { "button" }
17
17
  option :state, default: proc { "" }
18
18
  option :theme, default: proc { :primary }
19
- option :size, default: proc { :base }
19
+ option :size, default: proc { :md }
20
20
  option :width, default: proc { :narrow }
21
21
 
22
- renders_one :leading_visual_icon, Ariadne::UI::Heroicon::Component
22
+ # Leading visuals appear to the left of the button text.
23
+ #
24
+ # Use:
25
+ #
26
+ # - `leading_visual_heroicon` for a <%= link_to_component(Ariadne::UI::Heroicon::Component) %>.
27
+ renders_one :leading_visual, types: {
28
+ heroicon: lambda { |**options|
29
+ options[:size] = @size
30
+ Ariadne::UI::Heroicon::Component.new(**options)
31
+ },
32
+ }
33
+
34
+ # Trailing visuals appear to the right of the button text.
35
+ #
36
+ # Use:
37
+ #
38
+ # - `trailing_visual_heroicon` for a <%= link_to_component(Ariadne::UI::Heroicon::Component) %>.
39
+ renders_one :trailing_visual, types: {
40
+ heroicon: lambda { |**options|
41
+ options[:size] = @size
42
+ Ariadne::UI::Heroicon::Component.new(**options)
43
+ },
44
+ }
23
45
 
24
46
  accepts_html_attributes do |html_attrs|
25
47
  html_attrs[:class] = Ariadne::ViewComponents.tailwind_merger.merge([style(theme:, size:, icon_only:, width:), html_attrs[:class]].join(" "))
@@ -45,7 +67,8 @@ module Ariadne
45
67
  @icon_only = true
46
68
  @aria_label = aria(html_attrs, "label")
47
69
  @aria_description = aria(html_attrs, "description")
48
- options.delete(:size)
70
+
71
+ options[:size] = @size
49
72
 
50
73
  @icon = Ariadne::UI::Heroicon::Component.new(**options)
51
74
  self
@@ -60,7 +83,7 @@ module Ariadne
60
83
  "ariadne-gap-x-1.5",
61
84
  "ariadne-rounded",
62
85
  "ariadne-px-2",
63
- "ariadne-py-1",
86
+ # "ariadne-py-1",
64
87
  "ariadne-font-semibold",
65
88
  "ariadne-shadow-sm",
66
89
  "ariadne-text-black",
@@ -140,6 +163,12 @@ module Ariadne
140
163
  "dark:active:ariadne-bg-red-400",
141
164
  ]
142
165
  end
166
+
167
+ none do
168
+ [
169
+ "ariadne-shadow-none",
170
+ ]
171
+ end
143
172
  end
144
173
 
145
174
  size do
@@ -154,7 +183,7 @@ module Ariadne
154
183
  "ariadne-leading-5",
155
184
  ]
156
185
  end
157
- base do
186
+ md do
158
187
  [
159
188
  "ariadne-rounded-md",
160
189
  "ariadne-px-2.5",
@@ -190,9 +219,7 @@ module Ariadne
190
219
  end
191
220
  end
192
221
 
193
- private
194
-
195
- def button_tag
222
+ private def button_tag
196
223
  if link?
197
224
  "a"
198
225
  else
@@ -200,8 +227,19 @@ module Ariadne
200
227
  end
201
228
  end
202
229
 
203
- def link? = as == :link
204
- def button? = as == :button
230
+ private def link? = as == :link
231
+ private def button? = as == :button
232
+
233
+ private def trimmed_content
234
+ return if content.blank?
235
+
236
+ trimmed_content = content.strip
237
+
238
+ return trimmed_content unless content.html_safe?
239
+
240
+ # strip unsets `html_safe`, so we have to set it back again to guarantee that HTML blocks won't break
241
+ trimmed_content.html_safe # rubocop:disable Rails/OutputSafety
242
+ end
205
243
  end
206
244
  end
207
245
  end
@@ -1,7 +1,17 @@
1
1
  <div class="<%= html_attrs[:class] %>" <%= html_attributes %>>
2
+ <% if href.present? %>
3
+ <%= render(Ariadne::UI::Link::Component.new(href: href, theme: :none, html_attrs: { class: "w-full h-full"})) do %>
4
+ <%= header %>
5
+ <div class="ariadne-p-6 ariadne-pt-0">
6
+ <%= content %>
7
+ </div>
8
+ <%= footer %>
9
+ <% end %>
10
+ <% else %>
2
11
  <%= header %>
3
- <div class="ariadne-p-6 ariadne-pt-0">
4
- <%= content %>
12
+ <div class="ariadne-p-6 ariadne-pt-0">
13
+ <%= content %>
5
14
  </div>
6
15
  <%= footer %>
7
16
  </div>
17
+ <% end %>
@@ -5,10 +5,12 @@ module Ariadne
5
5
  module UI
6
6
  module Card
7
7
  class Component < Ariadne::BaseComponent
8
+ option :href, default: -> { nil }
9
+
8
10
  renders_one :header, Ariadne::UI::Card::Header::Component
9
11
 
10
12
  accepts_html_attributes do |html_attrs|
11
- html_attrs[:class] = Ariadne::ViewComponents.tailwind_merger.merge([style, html_attrs[:class]].join(" "))
13
+ html_attrs[:class] = Ariadne::ViewComponents.tailwind_merger.merge([style(link: href.present? ? :yes : :no), html_attrs[:class]].join(" "))
12
14
  end
13
15
 
14
16
  renders_one :footer, Ariadne::UI::Card::Footer::Component
@@ -18,13 +20,30 @@ module Ariadne
18
20
  [
19
21
  "ariadne-rounded-lg",
20
22
  "ariadne-border",
21
- "ariadne-shadow-sm",
22
23
  "ariadne-bg-foreground",
23
24
  "dark:ariadne-bg-foreground-dark",
24
25
  "ariadne-text-content",
25
26
  "dark:ariadne-text-content-dark",
26
27
  ]
27
28
  end
29
+
30
+ variants do
31
+ link do
32
+ no do
33
+ []
34
+ end
35
+
36
+ yes do
37
+ [
38
+ "hover:ariadne-shadow-md",
39
+ "hover:ariadne-border-indigo-600",
40
+ "dark:hover:ariadne-border-indigo-400",
41
+ "hover:ariadne-bg-foreground-700/10",
42
+ "dark:hover:ariadne-bg-foreground-300/10",
43
+ ]
44
+ end
45
+ end
46
+ end
28
47
  end
29
48
  end
30
49
  end
@@ -1,7 +1,6 @@
1
1
  <div class="<%= html_attrs[:class] %>" <%= html_attributes %>>
2
- <%= title %>
3
-
4
- <% if description %>
2
+ <%= title %>
3
+ <% if description %>
5
4
  <%= description %>
6
- <% end %>
5
+ <% end %>
7
6
  </div>
@@ -6,17 +6,20 @@ module Ariadne
6
6
  module Card
7
7
  module Header
8
8
  class Component < Ariadne::BaseComponent
9
- option :prioritize, default: proc { :title }
10
- option :size, default: proc { :base }
11
-
12
9
  renders_one :title, lambda { |type: :subheading, **options|
13
10
  options[:type] ||= type
11
+ options[:html_attrs] ||= {}
12
+ options[:html_attrs][:class] ||= ""
13
+ options[:html_attrs][:class] = [
14
+ "ariadne-grow",
15
+ "ariadne-leading-none",
16
+ options[:html_attrs][:class],
17
+ ]
14
18
  Ariadne::UI::Typography::Component.new(**options)
15
19
  }
16
20
 
17
- renders_one :description, lambda { |type: :muted, size: :sm, **options|
21
+ renders_one :description, lambda { |type: :muted, **options|
18
22
  options[:type] ||= type
19
- options[:size] ||= size
20
23
  Ariadne::UI::Typography::Component.new(**options)
21
24
  }
22
25
 
@@ -19,12 +19,14 @@ module Ariadne
19
19
  renders_one :button, Ariadne::UI::Button::Component
20
20
 
21
21
  accepts_html_attributes do |html_attrs|
22
+ html_attrs[:data] ||= {}
22
23
  html_attrs[:data] = {
23
- controller: stimulus_name,
24
+ controller: "#{stimulus_name} #{html_attrs[:data].delete(:controller)}".strip,
24
25
  "#{stimulus_name}-target": "wrapper",
25
26
  "#{stimulus_name}-placement-value": placement,
26
27
  "#{stimulus_name}-clamped-value": clamped,
27
- }
28
+ }.merge(html_attrs[:data])
29
+
28
30
  if changed_action
29
31
  html_attrs[:data][:action] = "#{stimulus_name}:changed->#{changed_action}"
30
32
  end
@@ -13,6 +13,7 @@ export default class ComboboxController extends controllerFactory<HTMLDetailsEle
13
13
  values: {
14
14
  clamped: Boolean,
15
15
  placement: String,
16
+ dynamicLabelPrefix: String,
16
17
  },
17
18
  }) {
18
19
  private changedIds = new Set<string>()
@@ -43,17 +44,19 @@ export default class ComboboxController extends controllerFactory<HTMLDetailsEle
43
44
  }
44
45
 
45
46
  setupForm(): void {
46
- // https://github.com/github/details-menu-element?tab=readme-ov-file#markup
47
- for (const checkbox of this.popoverTarget.querySelectorAll('input[type="checkbox"]')) {
48
- checkbox.addEventListener('change', (e: {target: HTMLInputElement}) => {
49
- const value = e.target as HTMLInputElement
50
- if (this.changedIds.has(value)) {
51
- this.changedIds.delete(value)
52
- } else {
53
- this.changedIds.add(value)
54
- }
55
- this.dispatch('clicked', {detail: value})
56
- })
47
+ for (const formType of ['checkbox', 'radio']) {
48
+ // https://github.com/github/details-menu-element?tab=readme-ov-file#markup
49
+ for (const el of this.popoverTarget.querySelectorAll(`input[type="${formType}"]`)) {
50
+ el.addEventListener('change', (e: {target: HTMLInputElement}) => {
51
+ const value = e.target as HTMLInputElement
52
+ if (this.changedIds.has(value)) {
53
+ this.changedIds.delete(value)
54
+ } else {
55
+ this.changedIds.add(value)
56
+ }
57
+ this.dispatch('clicked', {detail: value})
58
+ })
59
+ }
57
60
  }
58
61
  }
59
62
 
@@ -97,6 +100,19 @@ export default class ComboboxController extends controllerFactory<HTMLDetailsEle
97
100
  this.unsubAutoUpdate = autoUpdate(this.buttonTarget, this.popoverTarget, updatePopoverPosition)
98
101
  }
99
102
 
103
+ updateButtonLabel(e: Event): void {
104
+ const target = e.target as HTMLDetailsElement
105
+ const checkedRadioButton = target.querySelector('input[type=radio]:checked') as HTMLInputElement | null
106
+
107
+ if (!checkedRadioButton) return
108
+
109
+ const selectedText = (checkedRadioButton.labels?.item(0) as HTMLLabelElement).textContent
110
+
111
+ this.buttonTarget.querySelector(
112
+ '[data-ariadne-ui-button-target="content"]',
113
+ ).textContent = `${this.dynamicLabelPrefixValue}${selectedText}`
114
+ }
115
+
100
116
  toggle(): void {
101
117
  this.element.open = !this.element.open
102
118
  this.setupAutoUpdate()
@@ -19,9 +19,10 @@ module Ariadne
19
19
  style do
20
20
  base do
21
21
  [
22
+ "ariadne-cursor-pointer",
22
23
  "ariadne-text-content",
23
24
  "dark:ariadne-text-content-dark",
24
- "ariadne-inline-flex",
25
+ # "ariadne-inline-flex",
25
26
  "ariadne-items-center",
26
27
  "ariadne-border-b",
27
28
  "ariadne-border-transparent",
@@ -62,7 +63,15 @@ module Ariadne
62
63
  ]
63
64
  end
64
65
 
65
- thick { "ariadne-font-semibold" }
66
+ none do
67
+ []
68
+ end
69
+
70
+ thick do
71
+ [
72
+ "ariadne-font-semibold",
73
+ ]
74
+ end
66
75
  end
67
76
  size do
68
77
  xs { "ariadne-text-xs ariadne-gap-0.5 [&>svg]:ariadne-size-3" }
@@ -15,12 +15,19 @@
15
15
  <div class="ariadne-truncate" role="menuitemcheckbox"><%= checkbox %></div>
16
16
  </div>
17
17
  <% end %>
18
+ <% radio_buttons.each do |radio_button| %>
19
+ <div
20
+ class="<%= style(:item) %>"
21
+ data-<%= stimulus_name %>-target="searchString">
22
+ <div class="ariadne-truncate" role="menuitemradiobutton"><%= radio_button %></div>
23
+ </div>
24
+ <% end %>
18
25
  <% items.each do |item| %>
19
26
  <div
20
27
  <%= 'role="menuitem"' if as_menu %>
21
28
  class="<%= style(:item) %>"
22
29
  data-<%= stimulus_name %>-target="searchString">
23
- <div class="ariadne-relative ariadne-flex ariadne-cursor-default ariadne-select-none ariadne-items-center ariadne-rounded-sm ariadne-px-2 ariadne-py-1.5 ariadne-text-sm ariadne-outline-none ariadne-data-[disabled=true]:pointer-events-none ariadne-data-[selected=true]:bg-accent ariadne-data-[selected=true]:text-accent-foreground data-[disabled=true]:ariadne-opacity-50"><%= item %></div>
30
+ <div class="ariadne-relative ariadne-flex "><%= item %></div>
24
31
  </div>
25
32
  <% end %>
26
33
  <div class="ariadne-text-center ariadne-hidden" data-<%= stimulus_name %>-target="emptyRoot">
@@ -68,7 +68,7 @@ module Ariadne
68
68
  end
69
69
 
70
70
  def target_id
71
- @target_id ||= Ariadne.generate_id
71
+ @target_id ||= ::Ariadne::ViewHelper.generate_id
72
72
  end
73
73
 
74
74
  style do
@@ -0,0 +1,32 @@
1
+ import {controllerFactory} from '@utils/createController'
2
+ import {useMutation} from 'stimulus-use'
3
+
4
+ /**
5
+ * Simple controller that sets `disabled` attribute on submit button
6
+ * based on the basic validity of the form (HTML validation only).
7
+ */
8
+ export default class FormValidity extends controllerFactory<HTMLFormElement>()({
9
+ targets: {
10
+ button: HTMLButtonElement,
11
+ form: HTMLFormElement,
12
+ },
13
+ }) {
14
+ private get form(): HTMLFormElement {
15
+ return this.hasFormTarget ? this.formTarget : this.element
16
+ }
17
+
18
+ private runCheck() {
19
+ this.buttonTarget.disabled = !this.form.checkValidity()
20
+ }
21
+
22
+ connect() {
23
+ this.form.addEventListener('input', () => this.runCheck())
24
+ this.runCheck()
25
+
26
+ useMutation(this, {childList: true, subtree: true})
27
+ }
28
+
29
+ mutate() {
30
+ this.runCheck()
31
+ }
32
+ }
@@ -6,6 +6,12 @@ module Ariadne
6
6
  module ViewHelper
7
7
  class ViewHelperNotFound < StandardError; end
8
8
 
9
+ class << self
10
+ def generate_id
11
+ SecureRandom.hex(6)
12
+ end
13
+ end
14
+
9
15
  HELPERS = {
10
16
  heroicon: "Ariadne::UI::Heroicon::Component",
11
17
  }.freeze
@@ -9,7 +9,7 @@ module Ariadne
9
9
 
10
10
  attr_reader :builder, :form, :id
11
11
 
12
- def initialize(builder:, form:, id: ::Ariadne.generate_id)
12
+ def initialize(builder:, form:, id: ::Ariadne::ViewHelper.generate_id)
13
13
  @builder = builder
14
14
  @form = form
15
15
  @id = id
@@ -108,7 +108,7 @@ module Ariadne
108
108
 
109
109
  @options[:invalid] = "true" if invalid?
110
110
 
111
- @base_id = Ariadne.generate_id
111
+ @base_id = ::Ariadne::ViewHelper.generate_id
112
112
 
113
113
  @ids = {}.tap do |id_map|
114
114
  id_map[:validation] = "validation-#{@base_id}" if supports_validation?
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class SelectInput < Input
8
+ SELECT_ARGUMENTS = [:multiple, :include_blank, :include_hidden, :prompt].freeze
9
+
10
+ attr_reader :name, :label, :options, :select_arguments
11
+
12
+ def initialize(name:, label:, **options)
13
+ @name = name
14
+ @label = label
15
+ @options = []
16
+
17
+ @select_arguments = {}.tap do |select_args|
18
+ SELECT_ARGUMENTS.each do |select_arg|
19
+ select_args[select_arg] = options.delete(select_arg)
20
+ end
21
+ end
22
+
23
+ super(**options)
24
+
25
+ yield(self) if block_given?
26
+ end
27
+
28
+ def multiple?
29
+ @select_arguments.fetch(:multiple, false)
30
+ end
31
+
32
+ def to_component
33
+ Ariadne::Form::Select::Component.new(name:, label:, **@options)
34
+ end
35
+
36
+ # :nocov:
37
+ def type
38
+ :select_list
39
+ end
40
+ # :nocov:
41
+
42
+ # :nocov:
43
+ def focusable?
44
+ true
45
+ end
46
+ # :nocov:
47
+ end
48
+ end
49
+ end
50
+ end
@@ -3,6 +3,6 @@
3
3
  # :nocov:
4
4
  module Ariadne
5
5
  module ViewComponents
6
- VERSION = "0.0.82"
6
+ VERSION = "0.0.83"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ariadne_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.82
4
+ version: 0.0.83
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garen J. Torikian
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-19 00:00:00.000000000 Z
11
+ date: 2024-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tailwind_merge
@@ -151,6 +151,8 @@ files:
151
151
  - app/components/ariadne/form/radio_button/component.rb
152
152
  - app/components/ariadne/form/radio_button_group/component.html.erb
153
153
  - app/components/ariadne/form/radio_button_group/component.rb
154
+ - app/components/ariadne/form/select/component.html.erb
155
+ - app/components/ariadne/form/select/component.rb
154
156
  - app/components/ariadne/form/separator/component.html.erb
155
157
  - app/components/ariadne/form/separator/component.rb
156
158
  - app/components/ariadne/form/text_field/component.html.erb
@@ -174,6 +176,11 @@ files:
174
176
  - app/components/ariadne/turbo/frame/component.rb
175
177
  - app/components/ariadne/turbo/stream_action/component.html.erb
176
178
  - app/components/ariadne/turbo/stream_action/component.rb
179
+ - app/components/ariadne/ui/accordion/component.html.erb
180
+ - app/components/ariadne/ui/accordion/component.rb
181
+ - app/components/ariadne/ui/accordion/component.ts
182
+ - app/components/ariadne/ui/accordion/item/component.html.erb
183
+ - app/components/ariadne/ui/accordion/item/component.rb
177
184
  - app/components/ariadne/ui/badge/component.html.erb
178
185
  - app/components/ariadne/ui/badge/component.rb
179
186
  - app/components/ariadne/ui/blankslate/component.html.erb
@@ -234,6 +241,7 @@ files:
234
241
  - app/frontend/ariadne/stimulus_app.ts
235
242
  - app/frontend/ariadne/theme.ts
236
243
  - app/frontend/controllers/form_autosubmit_controller.ts
244
+ - app/frontend/controllers/form_validity_controller.ts
237
245
  - app/frontend/entrypoints/application.ts
238
246
  - app/frontend/stylesheets/ariadne_view_components.css
239
247
  - app/frontend/stylesheets/scrollbar.css
@@ -264,6 +272,7 @@ files:
264
272
  - lib/ariadne/forms/dsl/input_methods.rb
265
273
  - lib/ariadne/forms/dsl/radio_button_group_input.rb
266
274
  - lib/ariadne/forms/dsl/radio_button_input.rb
275
+ - lib/ariadne/forms/dsl/select_input.rb
267
276
  - lib/ariadne/forms/dsl/submit_button_input.rb
268
277
  - lib/ariadne/forms/dsl/text_field_input.rb
269
278
  - lib/ariadne/forms/utils.rb