practical 0.1.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 +7 -0
- data/README.md +37 -0
- data/Rakefile +10 -0
- data/app/components/practical/views/base_component.rb +6 -0
- data/app/components/practical/views/button_component.rb +27 -0
- data/app/components/practical/views/datatable/filter_applied.rb +25 -0
- data/app/components/practical/views/datatable/filter_section_component.html.erb +9 -0
- data/app/components/practical/views/datatable/filter_section_component.rb +19 -0
- data/app/components/practical/views/datatable/sort_link_component.rb +48 -0
- data/app/components/practical/views/datatable.rb +36 -0
- data/app/components/practical/views/flash_messages_component.rb +65 -0
- data/app/components/practical/views/form/error_list_component.rb +15 -0
- data/app/components/practical/views/form/error_list_item_component.rb +20 -0
- data/app/components/practical/views/form/error_list_item_template_component.rb +9 -0
- data/app/components/practical/views/form/fallback_errors_section_component.html.erb +7 -0
- data/app/components/practical/views/form/fallback_errors_section_component.rb +21 -0
- data/app/components/practical/views/form/field_errors_component.rb +28 -0
- data/app/components/practical/views/form/field_title_component.rb +23 -0
- data/app/components/practical/views/form/fieldset_title_component.rb +20 -0
- data/app/components/practical/views/form/input_component.html.erb +7 -0
- data/app/components/practical/views/form/input_component.rb +22 -0
- data/app/components/practical/views/form/option_label_component.rb +21 -0
- data/app/components/practical/views/form/practical_editor_component.rb +26 -0
- data/app/components/practical/views/form/required_radio_collection_wrapper_component.rb +23 -0
- data/app/components/practical/views/form_wrapper.rb +21 -0
- data/app/components/practical/views/icon_component.rb +36 -0
- data/app/components/practical/views/icon_for_file_extension_component.rb +53 -0
- data/app/components/practical/views/modal_dialog_component.html.erb +10 -0
- data/app/components/practical/views/modal_dialog_component.rb +16 -0
- data/app/components/practical/views/navigation/breadcrumb_item_component.rb +20 -0
- data/app/components/practical/views/navigation/breadcrumbs_component.html.erb +31 -0
- data/app/components/practical/views/navigation/breadcrumbs_component.rb +41 -0
- data/app/components/practical/views/navigation/navigation_link_component.rb +39 -0
- data/app/components/practical/views/navigation/pagination/goto_form_component.html.erb +31 -0
- data/app/components/practical/views/navigation/pagination/goto_form_component.rb +34 -0
- data/app/components/practical/views/navigation/pagination_component.html.erb +11 -0
- data/app/components/practical/views/navigation/pagination_component.rb +98 -0
- data/app/components/practical/views/open_dialog_button_component.rb +16 -0
- data/app/components/practical/views/page_component.html.erb +53 -0
- data/app/components/practical/views/page_component.rb +12 -0
- data/app/components/practical/views/relative_time_component.rb +13 -0
- data/app/components/practical/views/tiptap_document_component.rb +311 -0
- data/app/components/practical/views/toast_component.html.erb +26 -0
- data/app/components/practical/views/toast_component.rb +19 -0
- data/app/controllers/concerns/practical/auth/passkeys/emergency_registrations.rb +57 -0
- data/app/controllers/concerns/practical/auth/passkeys/web_authn_debug_context.rb +13 -0
- data/app/controllers/concerns/practical/views/flash_helpers.rb +37 -0
- data/app/controllers/concerns/practical/views/json_redirection.rb +7 -0
- data/app/lib/practical/defaults/shrine.rb +48 -0
- data/app/lib/practical/test/helpers/administrator/test_helpers.rb +7 -0
- data/app/lib/practical/test/helpers/extra_assertions.rb +7 -0
- data/app/lib/practical/test/helpers/flash_assertions.rb +8 -0
- data/app/lib/practical/test/helpers/integration/assertions.rb +23 -0
- data/app/lib/practical/test/helpers/passkey/system/base.rb +52 -0
- data/app/lib/practical/test/helpers/passkey/system/rack_test.rb +45 -0
- data/app/lib/practical/test/helpers/passkey/system/selenium.rb +107 -0
- data/app/lib/practical/test/helpers/passkey/test_helper.rb +128 -0
- data/app/lib/practical/test/helpers/postmark.rb +11 -0
- data/app/lib/practical/test/helpers/relation_builder_assertions.rb +18 -0
- data/app/lib/practical/test/helpers/setup/debug.rb +8 -0
- data/app/lib/practical/test/helpers/setup/faker_seed_pinning.rb +8 -0
- data/app/lib/practical/test/helpers/setup/simplecov.rb +17 -0
- data/app/lib/practical/test/helpers/shrine/test_data.rb +101 -0
- data/app/lib/practical/test/helpers/spy_assertions.rb +7 -0
- data/app/lib/practical/test/helpers/system/assertions.rb +33 -0
- data/app/lib/practical/test/helpers/system/capybara_prep.rb +10 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/base.rb +372 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/self_service.rb +66 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/base.rb +119 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/no_self_destroy.rb +13 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/no_self_signup.rb +22 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_destroy.rb +134 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_signup.rb +221 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/update.rb +220 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/sessions/base.rb +108 -0
- data/app/lib/practical/test/shared/auth/passkeys/forms/emergency_registration.rb +82 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/emergency_registration/base.rb +89 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/emergency_registration/use_for_and_notify.rb +48 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/passkey.rb +101 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/resource_with_passkeys.rb +57 -0
- data/app/lib/practical/test/shared/auth/passkeys/policies/passkey.rb +18 -0
- data/app/lib/practical/test/shared/auth/passkeys/services/send_emergency_registration.rb +41 -0
- data/app/lib/practical/test/shared/models/normalized_email.rb +23 -0
- data/app/lib/practical/test/shared/models/user.rb +27 -0
- data/app/lib/practical/test/shared/models/utility/ip_address.rb +42 -0
- data/app/lib/practical/test/shared/models/utility/user_agent.rb +43 -0
- data/app/lib/practical/views/button/styling.rb +23 -0
- data/app/lib/practical/views/error_handling.rb +33 -0
- data/app/lib/practical/views/form_builders/base.rb +152 -0
- data/app/lib/practical/views/icon_set.rb +156 -0
- data/app/lib/practical/views/web_awesome/style_utility/appearance_variant.rb +19 -0
- data/app/lib/practical/views/web_awesome/style_utility/base.rb +21 -0
- data/app/lib/practical/views/web_awesome/style_utility/color_variant.rb +17 -0
- data/app/lib/practical/views/web_awesome/style_utility/size.rb +31 -0
- data/config/locales/auth.en.yml +38 -0
- data/config/locales/devise.passkeys.en.yml +18 -0
- data/config/locales/practical_framework.en.yml +9 -0
- data/config/routes.rb +4 -0
- data/db/seeds/setup.rb +13 -0
- data/db/seeds/users/default.rb +34 -0
- data/lib/generators/practical/test/helper/USAGE +8 -0
- data/lib/generators/practical/test/helper/helper_generator.rb +9 -0
- data/lib/generators/practical/test/helper/templates/helper.rb.tt +4 -0
- data/lib/generators/practical/test/shared_test/USAGE +9 -0
- data/lib/generators/practical/test/shared_test/shared_test_generator.rb +7 -0
- data/lib/generators/practical/test/shared_test/templates/shared_test.rb.tt +9 -0
- data/lib/generators/practical/views/component/USAGE +9 -0
- data/lib/generators/practical/views/component/component_generator.rb +20 -0
- data/lib/practical/framework/engine.rb +35 -0
- data/lib/practical/helpers/form_with_helper.rb +10 -0
- data/lib/practical/helpers/icon_helper.rb +18 -0
- data/lib/practical/helpers/text_helper.rb +20 -0
- data/lib/practical/helpers/translation_helper.rb +25 -0
- data/lib/practical/version.rb +5 -0
- data/lib/practical/views/element_helper.rb +48 -0
- data/lib/practical.rb +21 -0
- data/lib/tasks/practical/coverage.rake +19 -0
- data/lib/tasks/practical/framework_tasks.rake +6 -0
- metadata +303 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::ModalDialogComponent < Practical::Views::BaseComponent
|
4
|
+
renders_one :header
|
5
|
+
attr_accessor :id, :open, :options
|
6
|
+
|
7
|
+
def initialize(id:, open: false, options: {})
|
8
|
+
self.open = open
|
9
|
+
self.id = id
|
10
|
+
self.options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def finalized_options
|
14
|
+
mix({id: id, open: open, data: {ensure_modal: true}, class: 'wa-dialog-stack-patch'}, options)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::Navigation::BreadcrumbItemComponent < Practical::Views::BaseComponent
|
4
|
+
renders_one :prefix
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(options: {})
|
8
|
+
self.options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def call
|
13
|
+
tag.wa_breadcrumb_item(**mix({}, options)) {
|
14
|
+
safe_join([
|
15
|
+
(prefix if prefix?),
|
16
|
+
content
|
17
|
+
])
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<%= tag.wa_breadcrumb(**finalized_options) do %>
|
2
|
+
|
3
|
+
<% if truncate_middle? %>
|
4
|
+
<% truncate_start.each do |crumb| %>
|
5
|
+
<%= build_crumb(crumb: crumb) %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<wa-breadcrumb-item>
|
9
|
+
<wa-dropdown>
|
10
|
+
<wa-button slot="trigger" size="small" appearance="filled" pill>
|
11
|
+
<wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon>
|
12
|
+
</wa-button>
|
13
|
+
<nav class="wa-stack wa-gap-xs dropdown-navigation">
|
14
|
+
<ul class="navigation-list">
|
15
|
+
<% truncated_items.each do |crumb| %>
|
16
|
+
<li><%= tag.a(crumb.name, href: (crumb.current? ? nil : crumb.url)) %></li>
|
17
|
+
<% end %>
|
18
|
+
</ul>
|
19
|
+
</nav>
|
20
|
+
</wa-dropdown>
|
21
|
+
</wa-breadcrumb-item>
|
22
|
+
|
23
|
+
<% truncate_end.each do |crumb| %>
|
24
|
+
<%= build_crumb(crumb: crumb) %>
|
25
|
+
<% end %>
|
26
|
+
<% else %>
|
27
|
+
<% breadcrumb_trail.each do |crumb| %>
|
28
|
+
<%= build_crumb(crumb: crumb) %>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
<% end %>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::Navigation::BreadcrumbsComponent < Practical::Views::BaseComponent
|
4
|
+
attr_accessor :breadcrumb_trail
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(breadcrumb_trail:, options: {})
|
8
|
+
self.breadcrumb_trail = breadcrumb_trail
|
9
|
+
self.options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def finalized_options
|
13
|
+
mix({}, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def crumbs_for_truncation
|
17
|
+
@crumbs_for_truncation ||= breadcrumb_trail.to_a
|
18
|
+
end
|
19
|
+
|
20
|
+
def truncate_middle?
|
21
|
+
crumbs_for_truncation.size > 5
|
22
|
+
end
|
23
|
+
|
24
|
+
def truncate_start
|
25
|
+
crumbs_for_truncation.first(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def truncate_end
|
29
|
+
crumbs_for_truncation.last(2)
|
30
|
+
end
|
31
|
+
|
32
|
+
def truncated_items
|
33
|
+
crumbs_for_truncation - truncate_start - truncate_end
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_crumb(crumb:)
|
37
|
+
render(Practical::Views::Navigation::BreadcrumbItemComponent.new(options: { href: crumb.current? ? nil : crumb.url })) {
|
38
|
+
crumb.name
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::Navigation::NavigationLinkComponent < ApplicationComponent
|
4
|
+
attr_accessor :href, :options, :selected
|
5
|
+
|
6
|
+
renders_one :icon
|
7
|
+
|
8
|
+
def initialize(href:, selected:, options: {})
|
9
|
+
self.href = href
|
10
|
+
self.options = options
|
11
|
+
self.selected = selected
|
12
|
+
end
|
13
|
+
|
14
|
+
def flank_class
|
15
|
+
return "wa-flank wa-gap-2xs" if icon?
|
16
|
+
end
|
17
|
+
|
18
|
+
def selected?
|
19
|
+
return true if selected
|
20
|
+
return nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def finalized_options
|
24
|
+
mix({
|
25
|
+
href: href,
|
26
|
+
class: helpers.class_names(flank_class, "navigation-link", "wa-accent"),
|
27
|
+
data: {selected: selected?}
|
28
|
+
}, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
tag.a(**finalized_options) {
|
33
|
+
safe_join([
|
34
|
+
(icon if icon?),
|
35
|
+
tag.span{ content }
|
36
|
+
])
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<%= render Practical::Views::OpenDialogButtonComponent.new(dialog_id: dialog_id, appearance: "filled outlined", size: :small) do %>
|
2
|
+
<%= pagy_t('pagy.nav.gap').html_safe %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<%= render Practical::Views::ModalDialogComponent.new(id: dialog_id) do |component| %>
|
6
|
+
|
7
|
+
<% component.with_header do %>
|
8
|
+
<%= pagy_t("pagy.nav.goto_page_form.legend") %>
|
9
|
+
<% end %>
|
10
|
+
<section class="wa-stack">
|
11
|
+
<p><%= page_detail_text %></p>
|
12
|
+
<%= helpers.webawesome_form_with(
|
13
|
+
url: uri_parts.uri.to_s,
|
14
|
+
method: :get,
|
15
|
+
local: true,
|
16
|
+
builder: NewApplicationFormBuilder,
|
17
|
+
class: 'pagination-goto-form wa-size-s'
|
18
|
+
) do |f| %>
|
19
|
+
<% uri_parts.params.each do |key, value| %>
|
20
|
+
<%= hidden_field_for_goto_form(key: key, value: value) %>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
<section class="wa-cluster">
|
24
|
+
<%= f.number_field :page, value: pagy.page, placeholder: pagy.page.to_s, required: true, min: 1, max: pagy.last %>
|
25
|
+
<%= f.button_component(type: :submit, color_variant: :neutral, appearance: :filled) do
|
26
|
+
pagy_t("pagy.nav.goto_page_form.button")
|
27
|
+
end %>
|
28
|
+
</section>
|
29
|
+
<% end %>
|
30
|
+
</section>
|
31
|
+
<% end %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::Navigation::Pagination::GotoFormComponent < Practical::Views::BaseComponent
|
4
|
+
include Pagy::Frontend
|
5
|
+
attr_accessor :pagy, :dialog_id, :page_detail_text
|
6
|
+
|
7
|
+
URIParts = Data.define(:uri, :params)
|
8
|
+
|
9
|
+
def initialize(pagy:, dialog_id:, page_detail_text:)
|
10
|
+
self.pagy = pagy
|
11
|
+
self.dialog_id = dialog_id
|
12
|
+
self.page_detail_text = page_detail_text
|
13
|
+
end
|
14
|
+
|
15
|
+
def uri_parts
|
16
|
+
uri = URI.parse(pagy_url_for(pagy, nil))
|
17
|
+
params = Rack::Utils.parse_query(uri.query)
|
18
|
+
params.delete("page")
|
19
|
+
uri.query = ""
|
20
|
+
|
21
|
+
URIParts.new(uri: uri, params: params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def hidden_field_for_goto_form(key:, value:)
|
25
|
+
case value
|
26
|
+
when Array
|
27
|
+
value.each do |x|
|
28
|
+
helpers.hidden_field_tag key, x
|
29
|
+
end
|
30
|
+
else
|
31
|
+
helpers.hidden_field_tag key, value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::Navigation::PaginationComponent < ApplicationComponent
|
4
|
+
include Pagy::Frontend
|
5
|
+
attr_reader :request
|
6
|
+
attr_accessor :pagy, :item_name, :i18n_key
|
7
|
+
|
8
|
+
def initialize(pagy:, request:, item_name: nil, i18n_key: nil)
|
9
|
+
@pagy = pagy
|
10
|
+
@request = request
|
11
|
+
@item_name = item_name
|
12
|
+
@i18n_key = i18n_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def page_detail_text
|
16
|
+
pagy_count = pagy.count
|
17
|
+
if pagy_count == 0
|
18
|
+
key = "pagy.info.no_items"
|
19
|
+
elsif pagy.pages == 1
|
20
|
+
key = "pagy.info.single_page"
|
21
|
+
else
|
22
|
+
key = "pagy.info.multiple_pages"
|
23
|
+
end
|
24
|
+
|
25
|
+
item_name = item_name.presence || pagy_t(i18n_key || pagy.vars[:i18n_key], count: pagy_count)
|
26
|
+
|
27
|
+
item_text = pagy_t(key,
|
28
|
+
item_name: item_name,
|
29
|
+
count: pagy_count, from: pagy.from, to: pagy.to
|
30
|
+
)
|
31
|
+
|
32
|
+
page_count_text = pagy_t("pagy.info.page_count", page: pagy.page, count: pagy.pages)
|
33
|
+
|
34
|
+
return pagy_t("pagy.info.page_detail_text", item_text: item_text, page_count_text: page_count_text)
|
35
|
+
end
|
36
|
+
|
37
|
+
def previous_item
|
38
|
+
classes = helpers.class_names(:page, :previous, disabled: !pagy.prev)
|
39
|
+
|
40
|
+
text = icon_text(
|
41
|
+
icon: icon_set.previous_arrow,
|
42
|
+
text: pagy_t('pagy.nav.v2_prev')
|
43
|
+
)
|
44
|
+
|
45
|
+
tag.div(class: classes, role: :listitem){
|
46
|
+
if pagy.prev
|
47
|
+
tag.a(href: pagy_url_for(pagy, pagy.prev), title: pagy_t("pagy.nav.prev_page_title")) {
|
48
|
+
text
|
49
|
+
}
|
50
|
+
else
|
51
|
+
text
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_item
|
57
|
+
classes = helpers.class_names(:page, :next, disabled: !pagy.next)
|
58
|
+
|
59
|
+
text = icon_text(
|
60
|
+
icon: icon_set.next_arrow,
|
61
|
+
text: pagy_t('pagy.nav.v2_next')
|
62
|
+
)
|
63
|
+
|
64
|
+
tag.div(class: classes, role: :listitem){
|
65
|
+
if pagy.next
|
66
|
+
tag.a(href: pagy_url_for(pagy, pagy.next), title: pagy_t("pagy.nav.next_page_title")) {
|
67
|
+
text
|
68
|
+
}
|
69
|
+
else
|
70
|
+
text
|
71
|
+
end
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def goto_page_dialog_id
|
76
|
+
return [item_name, "pagy-goto-form"].compact.join("-")
|
77
|
+
end
|
78
|
+
|
79
|
+
def page_item(item)
|
80
|
+
case item
|
81
|
+
when Integer
|
82
|
+
tag.div(class: :page, role: :listitem) {
|
83
|
+
tag.a(item, href: pagy_url_for(pagy, item), title: pagy_t("pagy.nav.page_title", page_number: item))
|
84
|
+
}
|
85
|
+
when String
|
86
|
+
tag.div(
|
87
|
+
item,
|
88
|
+
class: "page current", role: :listitem, title: pagy_t("pagy.nav.current_page_title", page_number: item)
|
89
|
+
)
|
90
|
+
when :gap
|
91
|
+
render Practical::Views::Navigation::Pagination::GotoFormComponent.new(
|
92
|
+
pagy: pagy,
|
93
|
+
dialog_id: goto_page_dialog_id,
|
94
|
+
page_detail_text: page_detail_text
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::OpenDialogButtonComponent < Practical::Views::ButtonComponent
|
4
|
+
attr_accessor :dialog_id
|
5
|
+
|
6
|
+
def initialize(dialog_id:, appearance: nil, color_variant: nil, size: nil, options: {})
|
7
|
+
options = options.with_defaults(
|
8
|
+
onclick: self.class.inline_js_to_open_dialog(dialog_id: dialog_id)
|
9
|
+
)
|
10
|
+
super(type: :button, appearance: appearance, color_variant: color_variant, size: size, options: options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.inline_js_to_open_dialog(dialog_id:)
|
14
|
+
return "document.getElementById(`#{dialog_id}`).showModal()"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
<wa-page>
|
2
|
+
<% if banner? %>
|
3
|
+
<section slot="banner">
|
4
|
+
<%= banner %>
|
5
|
+
</section>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<% if header? %>
|
9
|
+
<header slot="header">
|
10
|
+
<%= header %>
|
11
|
+
</header>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<% if subheader? %>
|
15
|
+
<section slot="subheader">
|
16
|
+
<%= subheader %>
|
17
|
+
</section>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<% if navigation_header? %>
|
21
|
+
<nav slot="navigation-header">
|
22
|
+
<%= navigation_header %>
|
23
|
+
</nav>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<% if navigation? %>
|
27
|
+
<nav slot="navigation">
|
28
|
+
<%= navigation %>
|
29
|
+
</nav>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
<% if navigation_footer? %>
|
33
|
+
<nav slot="navigation-footer">
|
34
|
+
<%= navigation_footer %>
|
35
|
+
</nav>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
<% if main_header? %>
|
39
|
+
<aside slot="main-header">
|
40
|
+
<%= main_header %>
|
41
|
+
</aside>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<main class="wa-body-l">
|
45
|
+
<%= content %>
|
46
|
+
</main>
|
47
|
+
|
48
|
+
<% if footer? %>
|
49
|
+
<footer slot="footer">
|
50
|
+
<%= footer %>
|
51
|
+
</footer>
|
52
|
+
<% end %>
|
53
|
+
</wa-page>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::PageComponent < Practical::Views::BaseComponent
|
4
|
+
renders_one :banner
|
5
|
+
renders_one :header
|
6
|
+
renders_one :subheader
|
7
|
+
renders_one :footer
|
8
|
+
renders_one :main_header
|
9
|
+
renders_one :navigation
|
10
|
+
renders_one :navigation_header
|
11
|
+
renders_one :navigation_footer
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Practical::Views::RelativeTimeComponent < ApplicationComponent
|
4
|
+
attr_accessor :time
|
5
|
+
|
6
|
+
def initialize(time:)
|
7
|
+
@time = time
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
tag.wa_relative_time(helpers.time_ago_in_words(time), date: time.iso8601)
|
12
|
+
end
|
13
|
+
end
|