atomic_view 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/atomic_view/controllers/tooltip_controller.js +35 -0
- data/lib/atomic_view/components/badge_component.rb +25 -5
- data/lib/atomic_view/components/card_component/section_component.rb +23 -0
- data/lib/atomic_view/components/card_component.html.erb +11 -0
- data/lib/atomic_view/components/card_component.rb +66 -5
- data/lib/atomic_view/components/concerns/button_variants.rb +1 -1
- data/lib/atomic_view/components/index_page_component.html.erb +35 -0
- data/lib/atomic_view/components/index_page_component.rb +68 -0
- data/lib/atomic_view/components/navigation_tree_component/item_component.html.erb +4 -4
- data/lib/atomic_view/components/navigation_tree_component/item_component.rb +2 -1
- data/lib/atomic_view/components/navigation_tree_component.html.erb +11 -3
- data/lib/atomic_view/components/navigation_tree_component.rb +32 -3
- data/lib/atomic_view/components/page_header_component.html.erb +17 -0
- data/lib/atomic_view/components/page_header_component.rb +53 -0
- data/lib/atomic_view/components/show_page_component.html.erb +28 -0
- data/lib/atomic_view/components/show_page_component.rb +72 -0
- data/lib/atomic_view/components/timeline_component/item_component.rb +9 -2
- data/lib/atomic_view/components/timeline_component.html.erb +2 -2
- data/lib/atomic_view/components/timeline_component.rb +2 -2
- data/lib/atomic_view/components/tooltip_component.html.erb +13 -0
- data/lib/atomic_view/components/tooltip_component.rb +97 -0
- data/lib/atomic_view/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35571b6bf96da5419dcc5d10709fb6e391bc3d47991b0914934099003d4e2873
|
|
4
|
+
data.tar.gz: '09145bc057a978d84c5d21c98fe3c86b5e3db12c52d3726480eb79025a9c0d93'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26248796be4512d71197dae22674290aeab4c8b1b13dbaa4bc220e4c779858369f11e82ed9c645da13f101e8c9da130c8529694543426ff6c84bff0a73677072
|
|
7
|
+
data.tar.gz: 7220d17e20fca18bff3896b2a31e65846b499a2a453e315af1625c9d181bb118f63659e276bfa0cc3e20766be564525d1d02dec1c12c50d8310d51037c1f8202
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { computePosition, offset, flip, shift, autoUpdate } from "@floating-ui/dom"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static targets = ["trigger", "content"]
|
|
6
|
+
static values = { placement: { type: String, default: "top" } }
|
|
7
|
+
|
|
8
|
+
show() {
|
|
9
|
+
this.contentTarget.classList.remove("hidden")
|
|
10
|
+
this.cleanup = autoUpdate(this.triggerTarget, this.contentTarget, () => {
|
|
11
|
+
computePosition(this.triggerTarget, this.contentTarget, {
|
|
12
|
+
strategy: "fixed",
|
|
13
|
+
placement: this.placementValue,
|
|
14
|
+
middleware: [offset(8), flip(), shift({ padding: 8 })]
|
|
15
|
+
}).then(({ x, y }) => {
|
|
16
|
+
Object.assign(this.contentTarget.style, { left: `${x}px`, top: `${y}px` })
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
document.addEventListener("keydown", this.onKeydown)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hide() {
|
|
23
|
+
this.contentTarget.classList.add("hidden")
|
|
24
|
+
if (this.cleanup) this.cleanup()
|
|
25
|
+
document.removeEventListener("keydown", this.onKeydown)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onKeydown = (event) => {
|
|
29
|
+
if (event.key === "Escape") this.hide()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
disconnect() {
|
|
33
|
+
this.hide()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module AtomicView
|
|
4
4
|
module Components
|
|
5
|
+
# Badge
|
|
6
|
+
#
|
|
7
|
+
# A small rectangular status/label pill. `default`/`secondary` are solid
|
|
8
|
+
# fills for general-purpose emphasis (a count, a category tag);
|
|
9
|
+
# `success`/`warning`/`destructive`/`info` are soft, bordered/tinted
|
|
10
|
+
# semantic colors for actual state (active/upcoming/cancelled/etc.) --
|
|
11
|
+
# the same `border-x bg-x/10 text-x` treatment AlertComponent already
|
|
12
|
+
# uses for its variants, just as a compact pill instead of a banner.
|
|
13
|
+
# `outline` is the neutral, colorless version of that same bordered
|
|
14
|
+
# style, for a status that doesn't map to any of the semantic colors.
|
|
15
|
+
#
|
|
16
|
+
# Every variant carries a `border` (transparent on the solid fills) so
|
|
17
|
+
# badges of different variants sitting side by side stay the same
|
|
18
|
+
# height regardless of which ones happen to have a visible border.
|
|
5
19
|
class BadgeComponent < AtomicView::Component
|
|
6
20
|
attr_reader :variant
|
|
7
21
|
|
|
@@ -18,19 +32,25 @@ module AtomicView
|
|
|
18
32
|
private
|
|
19
33
|
|
|
20
34
|
def base_classes
|
|
21
|
-
"inline-flex items-center rounded-
|
|
35
|
+
"inline-flex items-center rounded-btn border px-1.5 py-0.5 text-xs font-medium"
|
|
22
36
|
end
|
|
23
37
|
|
|
24
38
|
def variant_classes
|
|
25
39
|
case variant
|
|
26
40
|
when :secondary
|
|
27
|
-
"bg-secondary text-secondary-foreground"
|
|
41
|
+
"border-transparent bg-secondary text-secondary-foreground"
|
|
28
42
|
when :destructive
|
|
29
|
-
"bg-destructive text-destructive
|
|
43
|
+
"border-destructive bg-destructive/10 text-destructive"
|
|
44
|
+
when :success
|
|
45
|
+
"border-success bg-success/10 text-success"
|
|
46
|
+
when :warning
|
|
47
|
+
"border-warning bg-warning/10 text-warning"
|
|
48
|
+
when :info
|
|
49
|
+
"border-info bg-info/10 text-info"
|
|
30
50
|
when :outline
|
|
31
|
-
"
|
|
51
|
+
"border-border bg-transparent text-foreground"
|
|
32
52
|
else
|
|
33
|
-
"bg-primary text-primary-foreground"
|
|
53
|
+
"border-transparent bg-primary text-primary-foreground"
|
|
34
54
|
end
|
|
35
55
|
end
|
|
36
56
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtomicView
|
|
4
|
+
module Components
|
|
5
|
+
class CardComponent
|
|
6
|
+
# CardComponent::Section
|
|
7
|
+
#
|
|
8
|
+
# One row of a sectioned card -- see CardComponent's docs. Just a
|
|
9
|
+
# padded, free-form content block; CardComponent's `divide-y` wrapper
|
|
10
|
+
# is what adds the border between sections, not this component.
|
|
11
|
+
class SectionComponent < AtomicView::Component
|
|
12
|
+
def initialize(**options)
|
|
13
|
+
super()
|
|
14
|
+
@options = options
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
tag.div(**@options.except(:class), class: class_names(CardComponent::PADDING_CLASSES, @options[:class])) { content }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= tag.div(**@options.except(:class), class: html_class) do %>
|
|
2
|
+
<% if sectioned? %>
|
|
3
|
+
<%= tag.div(class: divider_class) do %>
|
|
4
|
+
<% sections.each do |section| %>
|
|
5
|
+
<%= section %>
|
|
6
|
+
<% end %>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% else %>
|
|
9
|
+
<%= content %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
@@ -2,23 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
module AtomicView
|
|
4
4
|
module Components
|
|
5
|
+
# Card
|
|
6
|
+
#
|
|
7
|
+
# A generic surface container for grouping related content. `default` is
|
|
8
|
+
# a plain bordered panel; `destructive` is the same shape with a tinted
|
|
9
|
+
# red border/background, for a "danger zone" section (e.g. an archive or
|
|
10
|
+
# delete action with its consequences explained) -- the same
|
|
11
|
+
# `border-destructive bg-destructive/10` treatment AlertComponent already
|
|
12
|
+
# uses for its own error variant. The variant only colors the card
|
|
13
|
+
# itself; any heading/button inside it is the caller's own content.
|
|
14
|
+
#
|
|
15
|
+
# Pass plain content for a single padded panel. For a settings-list style
|
|
16
|
+
# card -- several rows, each with its own label/action, a divider
|
|
17
|
+
# between them but not around the outside -- use `with_section` instead:
|
|
18
|
+
#
|
|
19
|
+
# render(AtomicView::Components::CardComponent.new(variant: :destructive)) do |card|
|
|
20
|
+
# card.with_section do
|
|
21
|
+
# tag.div(class: "flex items-center justify-between gap-4") do
|
|
22
|
+
# tag.div { tag.p("Archive this rental", class: "font-medium") + tag.p("...", class: "text-sm text-muted-foreground") } +
|
|
23
|
+
# render(LinkComponent.new("#", variant: :muted)) { "Archive" }
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# card.with_section { ... }
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# Each section gets the card's own padding (rather than the card as a
|
|
31
|
+
# whole), and a `divide-y` on the section container adds a border
|
|
32
|
+
# between consecutive sections automatically -- there's no first/last
|
|
33
|
+
# index to track, Tailwind's `divide-y` only ever borders the boundary
|
|
34
|
+
# between two siblings. A card with no sections given renders exactly as
|
|
35
|
+
# before -- `with_section` is opt-in, not a second required API.
|
|
5
36
|
class CardComponent < AtomicView::Component
|
|
6
|
-
|
|
37
|
+
PADDING_CLASSES = "p-2.5 px-4"
|
|
7
38
|
|
|
8
|
-
|
|
39
|
+
renders_many :sections, "SectionComponent"
|
|
40
|
+
|
|
41
|
+
attr_reader :hoverable, :variant
|
|
42
|
+
|
|
43
|
+
def initialize(variant: :default, hoverable: false, **options)
|
|
9
44
|
super()
|
|
45
|
+
@variant = variant
|
|
10
46
|
@hoverable = hoverable
|
|
11
47
|
@options = options
|
|
12
48
|
end
|
|
13
49
|
|
|
14
|
-
def
|
|
15
|
-
|
|
50
|
+
def sectioned?
|
|
51
|
+
sections.any?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def html_class
|
|
55
|
+
class_names(base_classes, variant_classes, hoverable_classes, padding_class, @options[:class])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The divider color between sections is tied to the variant rather
|
|
59
|
+
# than a flat `divide-border` -- a neutral gray divider reads fine on
|
|
60
|
+
# the plain surface background, but loses almost all contrast against
|
|
61
|
+
# a tinted `bg-destructive/10` panel in light mode.
|
|
62
|
+
def divider_class
|
|
63
|
+
class_names("divide-y", (variant == :destructive) ? "divide-destructive/30" : "divide-border")
|
|
16
64
|
end
|
|
17
65
|
|
|
18
66
|
private
|
|
19
67
|
|
|
20
68
|
def base_classes
|
|
21
|
-
"bg-surface rounded-card border
|
|
69
|
+
"bg-surface rounded-card border"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def padding_class
|
|
73
|
+
PADDING_CLASSES unless sectioned?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def variant_classes
|
|
77
|
+
case variant
|
|
78
|
+
when :destructive
|
|
79
|
+
"border-destructive bg-destructive/10"
|
|
80
|
+
else
|
|
81
|
+
"border-border"
|
|
82
|
+
end
|
|
22
83
|
end
|
|
23
84
|
|
|
24
85
|
def hoverable_classes
|
|
@@ -7,7 +7,7 @@ module AtomicView
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def base_classes
|
|
10
|
-
"h-
|
|
10
|
+
"h-9 rounded-btn px-2.5 inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def variant_classes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<% breadcrumbs_content = breadcrumbs.to_s %>
|
|
2
|
+
<% title_content = title.to_s %>
|
|
3
|
+
<% badge_content = badge.to_s %>
|
|
4
|
+
<% subtitle_content = subtitle.to_s %>
|
|
5
|
+
<% actions_content = actions.to_s %>
|
|
6
|
+
|
|
7
|
+
<%= tag.div(**@options.except(:class, :id), id: html_id, class: html_class) do %>
|
|
8
|
+
<%= tag.div(class: "mb-4") do %>
|
|
9
|
+
<%= render(AtomicView::Components::PageHeaderComponent.new) do |header| %>
|
|
10
|
+
<% header.with_breadcrumbs { breadcrumbs_content } if breadcrumbs? %>
|
|
11
|
+
<% header.with_title { title_content } if title? %>
|
|
12
|
+
<% header.with_badge { badge_content } if badge? %>
|
|
13
|
+
<% header.with_subtitle { subtitle_content } if subtitle? %>
|
|
14
|
+
<% header.with_actions { actions_content } if actions? %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
<% if filters? %>
|
|
19
|
+
<%= tag.div(filters, class: "mb-4") %>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<% if toolbar? %>
|
|
23
|
+
<%= tag.div(toolbar, class: "mb-4") %>
|
|
24
|
+
<% end %>
|
|
25
|
+
|
|
26
|
+
<% if empty? %>
|
|
27
|
+
<%= empty_state if empty_state? %>
|
|
28
|
+
<% else %>
|
|
29
|
+
<%= content %>
|
|
30
|
+
|
|
31
|
+
<% if pagination? %>
|
|
32
|
+
<%= tag.div(pagination, class: "mt-4") %>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtomicView
|
|
4
|
+
module Components
|
|
5
|
+
# IndexPage
|
|
6
|
+
#
|
|
7
|
+
# The layout shell for a collection page: a PageHeaderComponent up top,
|
|
8
|
+
# an optional `filters` row (e.g. status pills), an optional `toolbar`
|
|
9
|
+
# row below that (search, dropdowns), then either the default content
|
|
10
|
+
# block or an `empty_state` slot, and finally an optional `pagination`
|
|
11
|
+
# slot. This is layout only -- the content region has no idea what's
|
|
12
|
+
# inside it. It doesn't reference TableComponent, doesn't assume rows or
|
|
13
|
+
# columns, and doesn't care whether the caller renders a table, a card
|
|
14
|
+
# grid, or anything else. A smarter, column-aware table is an app-level
|
|
15
|
+
# concern to build on top of this, not something this component owns.
|
|
16
|
+
#
|
|
17
|
+
# render(AtomicView::Components::IndexPageComponent.new(empty: @rentals.empty?, dom_id: :rentals)) do |page|
|
|
18
|
+
# page.with_title { "Rentals" }
|
|
19
|
+
# page.with_subtitle { "#{@rental_count} rentals across #{@park_count} parks" }
|
|
20
|
+
# page.with_actions { render(LinkComponent.new(new_rental_path)) { "New rental" } }
|
|
21
|
+
# page.with_filters { render(SegmentedControlComponent.new(options: STATUS_OPTIONS, selected: params[:status] || "All")) }
|
|
22
|
+
# page.with_toolbar { render(SearchFieldComponent.new(...)) }
|
|
23
|
+
# page.with_pagination { render(PaginationComponent.new(current_page: @pagy.page, total_pages: @pagy.pages, path_for_page: ->(p) { rentals_path(page: p) })) }
|
|
24
|
+
# page.with_empty_state { render(EmptyStateComponent.new(title: "No rentals yet")) }
|
|
25
|
+
#
|
|
26
|
+
# render(AtomicView::Components::TableComponent.new) { ... }
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# `breadcrumbs`/`title`/`badge`/`subtitle`/`actions` are forwarded
|
|
30
|
+
# straight into an internally-rendered PageHeaderComponent -- see its
|
|
31
|
+
# docs for what each renders. `empty:` and `dom_id:` stay plain
|
|
32
|
+
# caller-supplied values rather than being derived from a model/Pagy
|
|
33
|
+
# object, so this component never needs to know about ActiveRecord or a
|
|
34
|
+
# pagination library -- an app-level wrapper (e.g. a `ListViewComponent`
|
|
35
|
+
# that already knows about `Pagy`/routes) is the right place to compute
|
|
36
|
+
# them before calling this one.
|
|
37
|
+
class IndexPageComponent < AtomicView::Component
|
|
38
|
+
renders_one :breadcrumbs
|
|
39
|
+
renders_one :title
|
|
40
|
+
renders_one :badge
|
|
41
|
+
renders_one :subtitle
|
|
42
|
+
renders_one :actions
|
|
43
|
+
renders_one :filters
|
|
44
|
+
renders_one :toolbar
|
|
45
|
+
renders_one :empty_state
|
|
46
|
+
renders_one :pagination
|
|
47
|
+
|
|
48
|
+
attr_reader :dom_id
|
|
49
|
+
|
|
50
|
+
def initialize(empty: false, dom_id: nil, **options)
|
|
51
|
+
super()
|
|
52
|
+
@empty = empty
|
|
53
|
+
@dom_id = dom_id
|
|
54
|
+
@options = options
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def empty? = @empty
|
|
58
|
+
|
|
59
|
+
def html_class
|
|
60
|
+
class_names(@options[:class])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def html_id
|
|
64
|
+
@options[:id] || dom_id
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<% if icon_name.present? %>
|
|
5
5
|
<%= icon(icon_name, options: {class: "size-4 shrink-0"}).to_s.html_safe %>
|
|
6
6
|
<% end %>
|
|
7
|
-
|
|
8
|
-
<%= icon("chevron-right", options: {class: "ml-auto size-4 shrink-0 transition-transform"}).to_s.html_safe %>
|
|
7
|
+
<span class="truncate group-data-[collapsed]/nav:hidden"><%= label %></span>
|
|
8
|
+
<%= icon("chevron-right", options: {class: "ml-auto size-4 shrink-0 transition-transform group-data-[collapsed]/nav:hidden"}).to_s.html_safe %>
|
|
9
9
|
<% end %>
|
|
10
|
-
<div class="ml-6 mt-1 flex flex-col gap-1">
|
|
10
|
+
<div class="ml-6 mt-1 flex flex-col gap-1 group-data-[collapsed]/nav:hidden">
|
|
11
11
|
<% items.each do |item| %>
|
|
12
12
|
<%= item %>
|
|
13
13
|
<% end %>
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
<% if icon_name.present? %>
|
|
19
19
|
<%= icon(icon_name, options: {class: "size-4 shrink-0"}).to_s.html_safe %>
|
|
20
20
|
<% end %>
|
|
21
|
-
|
|
21
|
+
<span class="truncate group-data-[collapsed]/nav:hidden"><%= label %></span>
|
|
22
22
|
<% end %>
|
|
23
23
|
<% end %>
|
|
@@ -38,7 +38,8 @@ module AtomicView
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
40
|
def base_classes
|
|
41
|
-
"flex items-center gap-2 rounded-btn px-2.5 py-1.5 text-sm font-medium"
|
|
41
|
+
"flex items-center gap-2 rounded-btn px-2.5 py-1.5 text-sm font-medium " \
|
|
42
|
+
"group-data-[collapsed]/nav:justify-center group-data-[collapsed]/nav:px-2"
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def active_classes
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
<%= tag.nav(
|
|
2
|
-
<% if label.present? %>
|
|
1
|
+
<%= tag.nav(**html_options, class: container_class, data: data_attributes) do %>
|
|
2
|
+
<% if label.present? && !collapsed? %>
|
|
3
3
|
<div class="px-2.5 pb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground"><%= label %></div>
|
|
4
4
|
<% end %>
|
|
5
5
|
<% items.each do |item| %>
|
|
6
|
-
|
|
6
|
+
<% if collapsed? %>
|
|
7
|
+
<%= render(AtomicView::Components::TooltipComponent.new(text: item.label, placement: tooltip_placement)) do |tooltip| %>
|
|
8
|
+
<% tooltip.with_trigger do %>
|
|
9
|
+
<%= item %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% else %>
|
|
13
|
+
<%= item %>
|
|
14
|
+
<% end %>
|
|
7
15
|
<% end %>
|
|
8
16
|
<% end %>
|
|
@@ -21,19 +21,48 @@ module AtomicView
|
|
|
21
21
|
# as a disclosure (a `<summary>` row with a trailing chevron) whose own
|
|
22
22
|
# `with_item` children render nested inside it once open -- see
|
|
23
23
|
# `ItemComponent` for the per-item API.
|
|
24
|
+
#
|
|
25
|
+
# Pass `collapsed: true` for an icon-only rail -- labels, chevrons, and
|
|
26
|
+
# any open group's nested items are hidden with CSS (a `group/nav`
|
|
27
|
+
# marker plus `group-data-[collapsed]/nav:*` variants on each item, the
|
|
28
|
+
# same "no JS" approach as the disclosure groups above), not swapped out
|
|
29
|
+
# in Ruby. Each top-level item is automatically wrapped in a
|
|
30
|
+
# `TooltipComponent` showing its `label:` on hover, since the icon alone
|
|
31
|
+
# loses the item's name -- nested items stay unwrapped, since their
|
|
32
|
+
# container is hidden while collapsed (unreachable in a rail without a
|
|
33
|
+
# flyout, which is out of scope here):
|
|
34
|
+
#
|
|
35
|
+
# render(NavigationTreeComponent.new(collapsed: true)) do |tree|
|
|
36
|
+
# tree.with_item(label: "Overview", href: "#", icon: "home", active: true)
|
|
37
|
+
# end
|
|
24
38
|
class NavigationTreeComponent < AtomicView::Component
|
|
25
39
|
renders_many :items, "ItemComponent"
|
|
26
40
|
|
|
27
|
-
attr_reader :label
|
|
41
|
+
attr_reader :label, :tooltip_placement
|
|
28
42
|
|
|
29
|
-
def initialize(label: nil, **options)
|
|
43
|
+
def initialize(label: nil, collapsed: false, tooltip_placement: "right", **options)
|
|
30
44
|
super()
|
|
31
45
|
@label = label
|
|
46
|
+
@collapsed = collapsed
|
|
47
|
+
@tooltip_placement = tooltip_placement
|
|
32
48
|
@options = options
|
|
33
49
|
end
|
|
34
50
|
|
|
51
|
+
def collapsed?
|
|
52
|
+
@collapsed
|
|
53
|
+
end
|
|
54
|
+
|
|
35
55
|
def container_class
|
|
36
|
-
class_names("flex flex-col gap-1", @options[:class])
|
|
56
|
+
class_names("group/nav flex flex-col gap-1", collapsed? ? "items-center" : nil, @options[:class])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def html_options
|
|
60
|
+
@options.except(:class, :data)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def data_attributes
|
|
64
|
+
attributes = @options[:data] || {}
|
|
65
|
+
collapsed? ? attributes.merge(collapsed: true) : attributes
|
|
37
66
|
end
|
|
38
67
|
end
|
|
39
68
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%= tag.div(**@options.except(:class), class: html_class) do %>
|
|
2
|
+
<div>
|
|
3
|
+
<% if breadcrumbs? %>
|
|
4
|
+
<div class="mb-1 text-xs font-semibold text-primary"><%= breadcrumbs %></div>
|
|
5
|
+
<% end %>
|
|
6
|
+
<div class="flex items-center gap-2">
|
|
7
|
+
<h1 class="text-2xl font-bold text-foreground sm:text-xl"><%= title %></h1>
|
|
8
|
+
<% if badge? %><%= badge %><% end %>
|
|
9
|
+
</div>
|
|
10
|
+
<% if subtitle? %>
|
|
11
|
+
<div class="mt-1 text-sm text-muted-foreground"><%= subtitle %></div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
<% if actions? %>
|
|
15
|
+
<div class="flex shrink-0 items-center gap-2"><%= actions %></div>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtomicView
|
|
4
|
+
module Components
|
|
5
|
+
# PageHeader
|
|
6
|
+
#
|
|
7
|
+
# The title/breadcrumb/badge/actions cluster shared by IndexPageComponent
|
|
8
|
+
# and ShowPageComponent (and usable standalone, e.g. above a lazily-loaded
|
|
9
|
+
# turbo-frame fragment that has no page shell of its own). Every region is
|
|
10
|
+
# a free-form slot -- this component only lays them out, it has no
|
|
11
|
+
# opinion about hrefs, badge variants, or what an action is.
|
|
12
|
+
#
|
|
13
|
+
# render(AtomicView::Components::PageHeaderComponent.new) do |header|
|
|
14
|
+
# header.with_breadcrumbs { link_to "Rentals", rentals_path, class: "text-xs font-semibold text-primary" }
|
|
15
|
+
# header.with_title { "RS002 · Karen Wilson" }
|
|
16
|
+
# header.with_badge { render(BadgeComponent.new) { "Active" } }
|
|
17
|
+
# header.with_subtitle { "rental_9f21ba7c" }
|
|
18
|
+
# header.with_actions { render(LinkComponent.new(edit_rental_path(@rental), variant: :outline)) { "Edit" } }
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# `title` is the one slot every caller is expected to fill -- there's no
|
|
22
|
+
# runtime enforcement of that (this gem doesn't raise on missing slots
|
|
23
|
+
# elsewhere either, e.g. DropdownComponent's `menu`), just a documented
|
|
24
|
+
# expectation. `badge` renders inline right after the title (e.g. a
|
|
25
|
+
# status pill); `subtitle` renders on its own line below (a record count,
|
|
26
|
+
# a copyable reference id); `breadcrumbs` renders above the title as a
|
|
27
|
+
# small eyebrow link; `actions` renders as a button/link cluster on the
|
|
28
|
+
# opposite side of the row, wrapping above the title/breadcrumb column on
|
|
29
|
+
# narrow viewports.
|
|
30
|
+
class PageHeaderComponent < AtomicView::Component
|
|
31
|
+
renders_one :breadcrumbs
|
|
32
|
+
renders_one :title
|
|
33
|
+
renders_one :badge
|
|
34
|
+
renders_one :subtitle
|
|
35
|
+
renders_one :actions
|
|
36
|
+
|
|
37
|
+
def initialize(**options)
|
|
38
|
+
super()
|
|
39
|
+
@options = options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def html_class
|
|
43
|
+
class_names(base_classes, @options[:class])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def base_classes
|
|
49
|
+
"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<% breadcrumbs_content = breadcrumbs.to_s %>
|
|
2
|
+
<% title_content = title.to_s %>
|
|
3
|
+
<% badge_content = badge.to_s %>
|
|
4
|
+
<% subtitle_content = subtitle.to_s %>
|
|
5
|
+
<% actions_content = actions.to_s %>
|
|
6
|
+
|
|
7
|
+
<%= tag.article(**@options.except(:class), class: html_class) do %>
|
|
8
|
+
<%= tag.div(class: "mb-8") do %>
|
|
9
|
+
<%= render(AtomicView::Components::PageHeaderComponent.new) do |header| %>
|
|
10
|
+
<% header.with_breadcrumbs { breadcrumbs_content } if breadcrumbs? %>
|
|
11
|
+
<% header.with_title { title_content } if title? %>
|
|
12
|
+
<% header.with_badge { badge_content } if badge? %>
|
|
13
|
+
<% header.with_subtitle { subtitle_content } if subtitle? %>
|
|
14
|
+
<% header.with_actions { actions_content } if actions? %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
<% if highlight? %>
|
|
19
|
+
<%= tag.div(highlight, class: "mb-8") %>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<%= tag.div(class: body_class) do %>
|
|
23
|
+
<% if sidebar? %>
|
|
24
|
+
<%= tag.div(sidebar, class: sidebar_class) %>
|
|
25
|
+
<% end %>
|
|
26
|
+
<%= tag.div(content, class: main_class) %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtomicView
|
|
4
|
+
module Components
|
|
5
|
+
# ShowPage
|
|
6
|
+
#
|
|
7
|
+
# The layout shell for a single-record detail page: a PageHeaderComponent
|
|
8
|
+
# up top, an optional full-width `highlight` region below it, then a
|
|
9
|
+
# two-column body -- the default content block as the main column, and an
|
|
10
|
+
# optional `sidebar` slot as a second column. This is layout only -- it
|
|
11
|
+
# has no opinion about what a record's detail page actually contains.
|
|
12
|
+
# `highlight` is deliberately not called "stepper" or "progress": it's
|
|
13
|
+
# just a full-width slot for whatever sits between the header and the
|
|
14
|
+
# body (a lifecycle stepper, a banner, nothing at all) -- naming it after
|
|
15
|
+
# a specific piece of content would smuggle a content assumption into a
|
|
16
|
+
# layout component. Same reasoning for `sidebar`: it's a column, not a
|
|
17
|
+
# "details panel" -- fill it with a CardComponent, a plain `<dl>`,
|
|
18
|
+
# whatever the record calls for.
|
|
19
|
+
#
|
|
20
|
+
# render(AtomicView::Components::ShowPageComponent.new) do |page|
|
|
21
|
+
# page.with_breadcrumbs { link_to "Rentals", rentals_path, class: "text-xs font-semibold text-primary" }
|
|
22
|
+
# page.with_title { "RS002 · Karen Wilson" }
|
|
23
|
+
# page.with_badge { render(BadgeComponent.new) { "Active" } }
|
|
24
|
+
# page.with_actions { render(LinkComponent.new(edit_rental_path(@rental), variant: :outline)) { "Edit" } }
|
|
25
|
+
# page.with_highlight { render(RentalStepperComponent.new(rental: @rental)) }
|
|
26
|
+
# page.with_sidebar { render "rentals/details_panel", rental: @rental }
|
|
27
|
+
#
|
|
28
|
+
# tag.section { tag.h2("Overview", class: "text-lg font-semibold mb-2") + render(CardComponent.new) { @rental.notes } }
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# `breadcrumbs`/`title`/`badge`/`subtitle`/`actions` are forwarded
|
|
32
|
+
# straight into an internally-rendered PageHeaderComponent -- see its
|
|
33
|
+
# docs for what each renders. The default content block is the main
|
|
34
|
+
# column; when `sidebar` is omitted, the main column spans full width
|
|
35
|
+
# rather than leaving an empty second column.
|
|
36
|
+
#
|
|
37
|
+
# The two-column grid (sidebar first in document order, reordered after
|
|
38
|
+
# the main column from `sm:` up) matches every existing hand-rolled show
|
|
39
|
+
# page in the consuming app -- that's the "good defaults" this component
|
|
40
|
+
# exists to stop re-typing, not a novel layout choice.
|
|
41
|
+
class ShowPageComponent < AtomicView::Component
|
|
42
|
+
renders_one :breadcrumbs
|
|
43
|
+
renders_one :title
|
|
44
|
+
renders_one :badge
|
|
45
|
+
renders_one :subtitle
|
|
46
|
+
renders_one :actions
|
|
47
|
+
renders_one :highlight
|
|
48
|
+
renders_one :sidebar
|
|
49
|
+
|
|
50
|
+
def initialize(**options)
|
|
51
|
+
super()
|
|
52
|
+
@options = options
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def html_class
|
|
56
|
+
class_names(@options[:class])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def body_class
|
|
60
|
+
class_names("grid grid-cols-1 gap-8", {"sm:grid-cols-3 sm:gap-12" => sidebar?})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def sidebar_class
|
|
64
|
+
"flex flex-col gap-8 sm:order-2 sm:col-span-1"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def main_class
|
|
68
|
+
class_names("flex flex-col gap-4 sm:gap-8", {"sm:order-1 sm:col-span-2" => sidebar?})
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -16,16 +16,23 @@ module AtomicView
|
|
|
16
16
|
# (aging into a full date over time) client-side once the host app
|
|
17
17
|
# loads local_time's JS -- see `config/importmap.rb`'s "local-time"
|
|
18
18
|
# pin comment for that setup.
|
|
19
|
+
#
|
|
20
|
+
# Any other keyword argument (`id:`, `data:`, `class:`, ...) passes
|
|
21
|
+
# through to the row's outer wrapper `<div>` in
|
|
22
|
+
# `TimelineComponent`'s template, so a caller can address a whole
|
|
23
|
+
# row -- marker, meta line, and content -- for e.g. a Turbo Stream
|
|
24
|
+
# `remove`/`replace`, or wire up a Stimulus target on the row itself.
|
|
19
25
|
class ItemComponent < AtomicView::Component
|
|
20
|
-
attr_reader :icon_name, :avatar, :actor, :description, :time
|
|
26
|
+
attr_reader :icon_name, :avatar, :actor, :description, :time, :html_options
|
|
21
27
|
|
|
22
|
-
def initialize(description:, icon: nil, avatar: nil, actor: nil, time: nil)
|
|
28
|
+
def initialize(description:, icon: nil, avatar: nil, actor: nil, time: nil, **html_options)
|
|
23
29
|
super()
|
|
24
30
|
@icon_name = icon
|
|
25
31
|
@avatar = avatar
|
|
26
32
|
@actor = actor
|
|
27
33
|
@description = description
|
|
28
34
|
@time = time
|
|
35
|
+
@html_options = html_options
|
|
29
36
|
end
|
|
30
37
|
|
|
31
38
|
def marker
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<%= tag.div(**@options.except(:class), class: container_class) do %>
|
|
2
2
|
<% items.each_with_index do |item, index| %>
|
|
3
|
-
|
|
3
|
+
<%= tag.div(**item.html_options.except(:class), class: item_wrapper_class(index, item)) do %>
|
|
4
4
|
<% if render_line?(index) %>
|
|
5
5
|
<div class="absolute left-[13px] top-7 -bottom-6 w-px bg-border"></div>
|
|
6
6
|
<% end %>
|
|
7
7
|
<%= item %>
|
|
8
|
-
|
|
8
|
+
<% end %>
|
|
9
9
|
<% end %>
|
|
10
10
|
<% end %>
|
|
@@ -47,8 +47,8 @@ module AtomicView
|
|
|
47
47
|
# `continues:` says this render is one page of a longer feed -- the connector
|
|
48
48
|
# keeps going past the last item, into whatever the next turbo frame appends
|
|
49
49
|
# or the stream prepends above.
|
|
50
|
-
def item_wrapper_class(index)
|
|
51
|
-
class_names("relative flex gap-3.5", "pb-6" => !last?(index) || @continues)
|
|
50
|
+
def item_wrapper_class(index, item)
|
|
51
|
+
class_names("relative flex gap-3.5", {"pb-6" => !last?(index) || @continues}, item.html_options[:class])
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def render_line?(index)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= tag.span(**html_options, class: html_class, data: data_attributes) do %>
|
|
2
|
+
<%= tag.span(class: "inline-block", data: trigger_data_attributes, aria: {describedby: content_id}) do %>
|
|
3
|
+
<%= trigger %>
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<%= tag.span(id: content_id, role: "tooltip", class: content_class, data: content_data_attributes) do %>
|
|
7
|
+
<% if body? %>
|
|
8
|
+
<%= body %>
|
|
9
|
+
<% else %>
|
|
10
|
+
<%= text %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtomicView
|
|
4
|
+
module Components
|
|
5
|
+
# Tooltip
|
|
6
|
+
#
|
|
7
|
+
# A trigger plus a floating label, positioned by `@floating-ui/dom` and
|
|
8
|
+
# wired to `atomic-view--tooltip` (see
|
|
9
|
+
# `app/assets/javascripts/atomic_view/controllers/tooltip_controller.js`).
|
|
10
|
+
# The controller shows the tooltip on hover/focus of the trigger and
|
|
11
|
+
# hides it on mouseleave/blur/Esc, keeping it anchored to the trigger
|
|
12
|
+
# while the page scrolls or resizes.
|
|
13
|
+
#
|
|
14
|
+
# The bubble uses `strategy: "fixed"` (and a `fixed` class, not
|
|
15
|
+
# `absolute`) rather than `DropdownComponent`'s approach -- an
|
|
16
|
+
# absolutely-positioned bubble's containing block is the trigger's own
|
|
17
|
+
# `relative` wrapper span, so its shrink-to-fit width silently caps out
|
|
18
|
+
# at the *trigger's* rendered width no matter how generous `max-w-*` is.
|
|
19
|
+
# `fixed` makes the viewport the containing block instead, so
|
|
20
|
+
# `content_class` (below) actually controls the bubble's width.
|
|
21
|
+
#
|
|
22
|
+
# Pass `text:` for the common case of a short plain-text label:
|
|
23
|
+
#
|
|
24
|
+
# render(TooltipComponent.new(text: "Delete")) do |tooltip|
|
|
25
|
+
# tooltip.with_trigger { icon_button }
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# or a `body` slot for anything richer than plain text.
|
|
29
|
+
#
|
|
30
|
+
# The bubble defaults to `max-w-xs` (20rem) -- wide enough for a short
|
|
31
|
+
# label or two lines of `body` content. Pass `content_class:` to widen
|
|
32
|
+
# (or otherwise restyle) it for longer text:
|
|
33
|
+
#
|
|
34
|
+
# render(TooltipComponent.new(text: "...", content_class: "max-w-sm"))
|
|
35
|
+
#
|
|
36
|
+
# It's a separate option from `class:`, which targets the outer trigger
|
|
37
|
+
# wrapper -- `content_class:` is merged (via TailwindMerge) onto the
|
|
38
|
+
# floating bubble itself, so `max-w-sm` here replaces the default
|
|
39
|
+
# `max-w-xs` rather than fighting it.
|
|
40
|
+
class TooltipComponent < AtomicView::Component
|
|
41
|
+
renders_one :trigger
|
|
42
|
+
renders_one :body
|
|
43
|
+
|
|
44
|
+
PLACEMENTS = %w[top bottom left right].freeze
|
|
45
|
+
|
|
46
|
+
attr_reader :text, :placement
|
|
47
|
+
|
|
48
|
+
def initialize(text: nil, placement: "top", content_class: nil, **options)
|
|
49
|
+
super()
|
|
50
|
+
@text = text
|
|
51
|
+
@placement = PLACEMENTS.include?(placement) ? placement : "top"
|
|
52
|
+
@id = "tooltip-#{SecureRandom.hex(4)}"
|
|
53
|
+
@content_class = content_class
|
|
54
|
+
@options = options
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def html_options
|
|
58
|
+
@options.except(:class, :data)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def html_class
|
|
62
|
+
class_names("relative inline-block", @options[:class])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def data_attributes
|
|
66
|
+
(@options[:data] || {}).merge(
|
|
67
|
+
:controller => "atomic-view--tooltip",
|
|
68
|
+
"atomic-view--tooltip-placement-value" => placement
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def trigger_data_attributes
|
|
73
|
+
{
|
|
74
|
+
"atomic-view--tooltip-target" => "trigger",
|
|
75
|
+
:action => "mouseenter->atomic-view--tooltip#show mouseleave->atomic-view--tooltip#hide " \
|
|
76
|
+
"focusin->atomic-view--tooltip#show focusout->atomic-view--tooltip#hide"
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def content_id
|
|
81
|
+
@id
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def content_data_attributes
|
|
85
|
+
{"atomic-view--tooltip-target" => "content"}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def content_class
|
|
89
|
+
class_names(
|
|
90
|
+
"pointer-events-none hidden fixed z-50 max-w-xs rounded-well bg-foreground px-2 py-1 text-xs " \
|
|
91
|
+
"font-medium text-surface shadow-soft",
|
|
92
|
+
@content_class
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/atomic_view/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
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-
|
|
11
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
@@ -148,6 +148,7 @@ files:
|
|
|
148
148
|
- app/assets/javascripts/atomic_view/controllers/modal_controller.js
|
|
149
149
|
- app/assets/javascripts/atomic_view/controllers/theme_toggle_controller.js
|
|
150
150
|
- app/assets/javascripts/atomic_view/controllers/toast_controller.js
|
|
151
|
+
- app/assets/javascripts/atomic_view/controllers/tooltip_controller.js
|
|
151
152
|
- app/assets/stylesheets/atomic_view/application.css
|
|
152
153
|
- app/assets/stylesheets/atomic_view/application.tailwind.css
|
|
153
154
|
- app/assets/tailwind/atomic_view/engine.css
|
|
@@ -171,7 +172,9 @@ files:
|
|
|
171
172
|
- lib/atomic_view/components/avatar_component.rb
|
|
172
173
|
- lib/atomic_view/components/badge_component.rb
|
|
173
174
|
- lib/atomic_view/components/button_component.rb
|
|
175
|
+
- lib/atomic_view/components/card_component.html.erb
|
|
174
176
|
- lib/atomic_view/components/card_component.rb
|
|
177
|
+
- lib/atomic_view/components/card_component/section_component.rb
|
|
175
178
|
- lib/atomic_view/components/check_box_component.rb
|
|
176
179
|
- lib/atomic_view/components/chip_component.rb
|
|
177
180
|
- lib/atomic_view/components/collection_check_boxes_component.html.erb
|
|
@@ -212,6 +215,8 @@ files:
|
|
|
212
215
|
- lib/atomic_view/components/gantt_component/row_component.html.erb
|
|
213
216
|
- lib/atomic_view/components/gantt_component/row_component.rb
|
|
214
217
|
- lib/atomic_view/components/grouped_collection_select_component.rb
|
|
218
|
+
- lib/atomic_view/components/index_page_component.html.erb
|
|
219
|
+
- lib/atomic_view/components/index_page_component.rb
|
|
215
220
|
- lib/atomic_view/components/kbd_component.rb
|
|
216
221
|
- lib/atomic_view/components/label_component.rb
|
|
217
222
|
- lib/atomic_view/components/link_component.html.erb
|
|
@@ -224,6 +229,8 @@ files:
|
|
|
224
229
|
- lib/atomic_view/components/navigation_tree_component/item_component.html.erb
|
|
225
230
|
- lib/atomic_view/components/navigation_tree_component/item_component.rb
|
|
226
231
|
- lib/atomic_view/components/number_field_component.rb
|
|
232
|
+
- lib/atomic_view/components/page_header_component.html.erb
|
|
233
|
+
- lib/atomic_view/components/page_header_component.rb
|
|
227
234
|
- lib/atomic_view/components/pagination_component.html.erb
|
|
228
235
|
- lib/atomic_view/components/pagination_component.rb
|
|
229
236
|
- lib/atomic_view/components/password_field_component.rb
|
|
@@ -234,6 +241,8 @@ files:
|
|
|
234
241
|
- lib/atomic_view/components/segmented_control_component.rb
|
|
235
242
|
- lib/atomic_view/components/select_component.html.erb
|
|
236
243
|
- lib/atomic_view/components/select_component.rb
|
|
244
|
+
- lib/atomic_view/components/show_page_component.html.erb
|
|
245
|
+
- lib/atomic_view/components/show_page_component.rb
|
|
237
246
|
- lib/atomic_view/components/submit_component.rb
|
|
238
247
|
- lib/atomic_view/components/table_component.rb
|
|
239
248
|
- lib/atomic_view/components/tabs_component.html.erb
|
|
@@ -250,6 +259,8 @@ files:
|
|
|
250
259
|
- lib/atomic_view/components/timeline_component/item_component.rb
|
|
251
260
|
- lib/atomic_view/components/toast_component.html.erb
|
|
252
261
|
- lib/atomic_view/components/toast_component.rb
|
|
262
|
+
- lib/atomic_view/components/tooltip_component.html.erb
|
|
263
|
+
- lib/atomic_view/components/tooltip_component.rb
|
|
253
264
|
- lib/atomic_view/components/url_field_component.rb
|
|
254
265
|
- lib/atomic_view/components/week_field_component.rb
|
|
255
266
|
- lib/atomic_view/components/weekday_select_component.rb
|