atomic_view 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c4d7794fbf6c54fdf00e69c51b02457c2af6c82da59d3e2c7590057b5e8d1ef
4
- data.tar.gz: 0d72d579d96ab8d334ff8423bbe83ebbd9f16a97ad3022d4a45c0ba6bec6d6be
3
+ metadata.gz: bada6883ff0ebcfb57bb0cf9f153c8c978bd32601b3371d0a5db2db02a57fab3
4
+ data.tar.gz: 59a99dd072921000d4dc906eafcc2d51b4638a7f78b625fd53e8c82dfe1017e6
5
5
  SHA512:
6
- metadata.gz: 3c1faa4585db6c8002ff72cf6c746894d03061a4b57d846200a380d11b7403ea32c55d176131d65a0e702a3f944da8375fa62e53a9a2c38a2945e3dcac803070
7
- data.tar.gz: 5a55bd375c66d433c226caf9f23edf7c84307d4eb131ee3cb719428f47c783ae6f156a61a25a9f6b528bd3112f957b9f61020801434f64ee7e899edb4c737251
6
+ metadata.gz: 2bd3d154f20a387d44c6fd2fbf30c6c00ca790ae8a4531d92c69291ed0a1f1c0cad1313a0807c9d576bec552e4faa297d3acab6be83feb7c14e4227440a228c6
7
+ data.tar.gz: 77657284d3f55f498dda813f30542b65077c979804ef0d64fed520162ab0e154c6b58a7719ef345d516e4d58f66e013adc6b839ec5b180570a74b9c1658f1bfd
@@ -0,0 +1,19 @@
1
+ <%= link_to(
2
+ href,
3
+ class: "flex items-center justify-between gap-3 px-3 py-1.5 text-sm text-foreground hover:bg-offset",
4
+ data: data_attributes
5
+ ) do %>
6
+ <span class="flex items-center gap-2">
7
+ <% if icon_name.present? %>
8
+ <%= icon(icon_name, options: {class: "size-4 shrink-0 text-muted-foreground"}).to_s.html_safe %>
9
+ <% end %>
10
+ <%= label %>
11
+ </span>
12
+ <% if hint.present? %>
13
+ <span class="inline-flex items-center gap-0.5">
14
+ <% Array(hint).each do |key| %>
15
+ <%= render(AtomicView::Components::KbdComponent.new) { key } %>
16
+ <% end %>
17
+ </span>
18
+ <% end %>
19
+ <% end %>
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtomicView
4
+ module Components
5
+ class CommandPaletteComponent
6
+ # A single CommandPalette result: a link carrying the JS hooks --
7
+ # `[data-atomic-view--command-palette-target="row"]` and
8
+ # `data-search-text` -- the filter/keyboard-nav controller relies on.
9
+ # Rendered via `CommandPaletteComponent#with_section`'s `results:`
10
+ # sugar, or `SectionComponent#with_row` directly -- see
11
+ # `CommandPaletteComponent`'s class docs.
12
+ #
13
+ # `icon:`, when given, renders a Heroicon before the label -- same
14
+ # `icon:` convention as `NavigationTreeComponent::ItemComponent`.
15
+ class RowComponent < AtomicView::Component
16
+ attr_reader :label, :href, :hint, :icon_name
17
+
18
+ def initialize(label:, href: "#", hint: nil, search_text: nil, icon: nil, **options)
19
+ super()
20
+ @label = label
21
+ @href = href
22
+ @hint = hint
23
+ @search_text = search_text
24
+ @icon_name = icon
25
+ @options = options
26
+ end
27
+
28
+ def data_attributes
29
+ (@options[:data] || {}).merge(
30
+ "atomic-view--command-palette-target" => "row",
31
+ "search_text" => (@search_text || label).to_s.downcase
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ <div role="group">
2
+ <p class="px-3 pb-1 pt-2 text-xs font-semibold uppercase text-muted-foreground select-none"><%= label %></p>
3
+
4
+ <% rows.each do |row| %>
5
+ <%= row %>
6
+ <% end %>
7
+ </div>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtomicView
4
+ module Components
5
+ class CommandPaletteComponent
6
+ # A labeled group of `RowComponent` results within a CommandPalette.
7
+ # Rendered via `CommandPaletteComponent#with_section`, or standalone
8
+ # (e.g. from a Turbo Stream response updating `results_id`) -- see
9
+ # `CommandPaletteComponent`'s class docs.
10
+ class SectionComponent < AtomicView::Component
11
+ renders_many :rows, "AtomicView::Components::CommandPaletteComponent::RowComponent"
12
+
13
+ attr_reader :label
14
+
15
+ def initialize(label:, results: [], **options)
16
+ super()
17
+ @label = label
18
+ @options = options
19
+ results.each { |result| with_row(**result) }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -4,32 +4,13 @@
4
4
  placeholder: placeholder,
5
5
  autocomplete: "off",
6
6
  autofocus: true,
7
- class: "w-full border-0 bg-transparent px-3 py-2 text-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-0",
7
+ class: "w-full border-0 bg-transparent px-3 py-2 text-sm text-foreground placeholder:text-placeholder focus:outline-none focus:ring-0",
8
8
  data: input_data_attributes
9
9
  ) %>
10
10
 
11
- <div class="max-h-80 overflow-y-auto border-t border-border py-2">
11
+ <div id="<%= results_id %>" class="max-h-80 overflow-y-auto border-t border-border py-2">
12
12
  <% sections.each do |section| %>
13
- <div role="group">
14
- <p class="px-3 pb-1 pt-2 text-xs uppercase text-muted-foreground"><%= section[:label] %></p>
15
-
16
- <% section[:results].each do |result| %>
17
- <%= link_to(
18
- result[:href] || "#",
19
- class: "flex items-center justify-between gap-3 px-3 py-1.5 text-sm text-foreground hover:bg-offset",
20
- data: row_data_attributes(result)
21
- ) do %>
22
- <span><%= result[:label] %></span>
23
- <% if result[:hint].present? %>
24
- <span class="inline-flex items-center gap-0.5">
25
- <% Array(result[:hint]).each do |key| %>
26
- <%= render(AtomicView::Components::KbdComponent.new) { key } %>
27
- <% end %>
28
- </span>
29
- <% end %>
30
- <% end %>
31
- <% end %>
32
- </div>
13
+ <%= section %>
33
14
  <% end %>
34
15
  </div>
35
16
 
@@ -24,15 +24,31 @@ module AtomicView
24
24
  #
25
25
  # `SegmentedControlComponent` and `PaginationComponent` already use this
26
26
  # array-of-hashes shape for link collections, and it's the better fit
27
- # here (over slots, `DropdownComponent`'s approach): every row needs the
28
- # same two JS hooks -- a `[data-atomic-view--command-palette-target="row"]`
29
- # marker and a `data-search-text` attribute -- generated consistently so
30
- # the filter/keyboard-nav controller can rely on them, which a
31
- # free-form slot would push onto every consumer to wire up by hand. The
32
- # component stays agnostic about what a result *links to* (any `href`
33
- # works -- a path, an external URL, a `turbo_frame`-aware route, etc.),
34
- # which is the same kind of agnosticism `DropdownComponent` keeps about
35
- # its menu content, just expressed as data instead of markup.
27
+ # here (over slots, `DropdownComponent`'s approach) as the *default*
28
+ # entry point: every row needs the same two JS hooks -- a
29
+ # `[data-atomic-view--command-palette-target="row"]` marker and a
30
+ # `data-search-text` attribute -- generated consistently so the
31
+ # filter/keyboard-nav controller can rely on them, which a free-form
32
+ # slot would push onto every consumer to wire up by hand. The component
33
+ # stays agnostic about what a result *links to* (any `href` works -- a
34
+ # path, an external URL, a `turbo_frame`-aware route, etc.), which is
35
+ # the same kind of agnosticism `DropdownComponent` keeps about its menu
36
+ # content, just expressed as data instead of markup.
37
+ #
38
+ # Under the hood, `sections:`/`results:` are sugar over real
39
+ # `SectionComponent`/`RowComponent` slots -- `with_section`/`with_row`
40
+ # accept the same keyword shape:
41
+ #
42
+ # render(CommandPaletteComponent.new(id: "x")) do |palette|
43
+ # palette.with_section(label: "Campers") do |section|
44
+ # section.with_row(label: "Joel", href: "/campers/1")
45
+ # end
46
+ # end
47
+ #
48
+ # Because they're real components, `SectionComponent` and `RowComponent`
49
+ # are independently renderable outside the dialog -- e.g. from a Turbo
50
+ # Stream response that re-renders `results_id` while server-side
51
+ # filtering, without forking this markup. See those classes' docs.
36
52
  #
37
53
  # `result[:hint]`, when given, renders as a `<kbd>` -- e.g. a keyboard
38
54
  # shortcut like "G D" -- next to the result label.
@@ -43,15 +59,21 @@ module AtomicView
43
59
  # structured data.
44
60
  class CommandPaletteComponent < AtomicView::Component
45
61
  renders_one :footer
62
+ renders_many :sections, "AtomicView::Components::CommandPaletteComponent::SectionComponent"
46
63
 
47
- attr_reader :id, :placeholder, :sections
64
+ attr_reader :id, :placeholder
48
65
 
49
- def initialize(id:, sections: [], placeholder: "Search...", **options)
66
+ def self.results_id(id)
67
+ "#{id}-results"
68
+ end
69
+
70
+ def initialize(id:, sections: [], placeholder: "Search...", input_data: {}, **options)
50
71
  super()
51
72
  @id = id
52
- @sections = sections
53
73
  @placeholder = placeholder
74
+ @input_data = input_data
54
75
  @options = options
76
+ sections.each { |section| with_section(**section) }
55
77
  end
56
78
 
57
79
  def html_options
@@ -62,26 +84,18 @@ module AtomicView
62
84
  class_names("mx-auto mt-[15vh] w-full max-w-lg overflow-hidden rounded-card bg-surface p-0 text-foreground shadow-panel backdrop:bg-backdrop", @options[:class])
63
85
  end
64
86
 
65
- def data_attributes
66
- (@options[:data] || {}).merge(controller: "atomic-view--command-palette")
87
+ def results_id
88
+ self.class.results_id(id)
67
89
  end
68
90
 
69
- def input_data_attributes
70
- {
71
- "atomic-view--command-palette-target" => "input",
72
- :action => "input->atomic-view--command-palette#filter keydown->atomic-view--command-palette#navigate"
73
- }
74
- end
75
-
76
- def row_data_attributes(result)
77
- {
78
- "atomic-view--command-palette-target" => "row",
79
- :search_text => search_text_for(result)
80
- }
91
+ def data_attributes
92
+ controllers = ["atomic-view--command-palette", @options.dig(:data, :controller)].compact.join(" ")
93
+ (@options[:data] || {}).except(:controller).merge(controller: controllers)
81
94
  end
82
95
 
83
- def search_text_for(result)
84
- (result[:search_text] || result[:label]).to_s.downcase
96
+ def input_data_attributes
97
+ actions = ["input->atomic-view--command-palette#filter", "keydown->atomic-view--command-palette#navigate", @input_data[:action]].compact.join(" ")
98
+ {"atomic-view--command-palette-target" => "input", **@input_data.except(:action), "action" => actions}
85
99
  end
86
100
  end
87
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtomicView
4
- VERSION = "0.1.14"
4
+ VERSION = "0.1.15"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atomic_view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Warrington
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-07 00:00:00.000000000 Z
11
+ date: 2026-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -181,6 +181,10 @@ files:
181
181
  - lib/atomic_view/components/color_field_component.rb
182
182
  - lib/atomic_view/components/command_palette_component.html.erb
183
183
  - lib/atomic_view/components/command_palette_component.rb
184
+ - lib/atomic_view/components/command_palette_component/row_component.html.erb
185
+ - lib/atomic_view/components/command_palette_component/row_component.rb
186
+ - lib/atomic_view/components/command_palette_component/section_component.html.erb
187
+ - lib/atomic_view/components/command_palette_component/section_component.rb
184
188
  - lib/atomic_view/components/concerns/button_variants.rb
185
189
  - lib/atomic_view/components/concerns/field_chrome.rb
186
190
  - lib/atomic_view/components/concerns/section_support.rb