keystone_ui 0.37.0 → 0.39.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/components/keystone/ui/navigation_component.html.erb +48 -0
- data/app/components/keystone/ui/navigation_component.rb +65 -0
- data/app/helpers/keystone_ui_helper.rb +4 -0
- data/lib/keystone_ui/configuration.rb +15 -2
- data/lib/keystone_ui/engine.rb +6 -0
- data/lib/keystone_ui/navigation_check.rb +37 -0
- data/lib/keystone_ui/navigation_group.rb +26 -0
- data/lib/keystone_ui/navigation_order.rb +35 -0
- data/lib/keystone_ui/safelist.rb +1 -0
- data/lib/keystone_ui/version.rb +1 -1
- data/lib/keystone_ui/visible_navigation.rb +38 -0
- data/the_local/agents/keystone_ui-develop.md +504 -606
- data/the_local/agents/keystone_ui-info.md +83 -227
- data/the_local/agents/keystone_ui-install.md +50 -6
- data/the_local/interface.yml +6 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b11fa4104f5668547e8efeff23c90f69f4c3d1b7374103c33d7dc2c208363acb
|
|
4
|
+
data.tar.gz: 719716203afcd39869e402674d72a6bf8f1e5f4e7fce163a76c3429ae2bc77e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b4849b26d1df9c8a45c7721fa966569e6d0f556780b5139a479a73e08c0602ce1db0eab5d4f8bfedcf5a30301cc4d1bba3cffdb0bf82e91a65a3d87c9fdb575c
|
|
7
|
+
data.tar.gz: 8fdb709bfbf187d4792a76c5057ebec69651fb5d4ba2e5b7a6b527b5e0ae68f437689896a492ec390f46ddc7d330fd3e00e2946588f41932d85bf82e790104e3
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<% if !anything_to_show? %>
|
|
2
|
+
<%= content %>
|
|
3
|
+
<% elsif sidebar? %>
|
|
4
|
+
<% sidebar = capture do %>
|
|
5
|
+
<nav class="<%= SIDEBAR_CLASSES %>">
|
|
6
|
+
<% if logo? %>
|
|
7
|
+
<div><%= logo %></div>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% groups.each do |group| %>
|
|
10
|
+
<div class="<%= SIDEBAR_GROUP_CLASSES %>">
|
|
11
|
+
<p class="<%= sidebar_group_label_classes(group) %>"><%= group.label %></p>
|
|
12
|
+
<% group.tabs.each do |tab| %>
|
|
13
|
+
<a class="<%= sidebar_tab_classes(tab) %>" href="<%= tab.href %>"><%= tab.label %></a>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% if menus? %>
|
|
18
|
+
<div class="<%= SIDEBAR_MENUS_CLASSES %>"><%= menus %></div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</nav>
|
|
21
|
+
<% end %>
|
|
22
|
+
<div class="<%= SIDEBAR_ROW_CLASSES %>">
|
|
23
|
+
<%= sidebar if sidebar_first? %>
|
|
24
|
+
<div class="<%= SIDEBAR_CONTENT_CLASSES %>"><%= content %></div>
|
|
25
|
+
<%= sidebar unless sidebar_first? %>
|
|
26
|
+
</div>
|
|
27
|
+
<% else %>
|
|
28
|
+
<div class="<%= TOP_BAR_CLASSES %>">
|
|
29
|
+
<%= render Keystone::Ui::NavbarComponent.new(sticky: false) do |navbar| %>
|
|
30
|
+
<% if logo? %>
|
|
31
|
+
<% navbar.with_logo do %><%= logo %><% end %>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% navbar.with_desktop_links do %>
|
|
34
|
+
<% groups.each do |group| %>
|
|
35
|
+
<%= render Keystone::Ui::NavDropdownComponent.new(title: group.label, area: group.label, active: group.active) do %>
|
|
36
|
+
<% group.tabs.each do |tab| %>
|
|
37
|
+
<%= render Keystone::Ui::NavItemComponent.new(label: tab.label, href: tab.href, active: tab.active) %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% end %>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% if menus? %>
|
|
43
|
+
<% navbar.with_desktop_right do %><%= menus %><% end %>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
<%= content %>
|
|
48
|
+
<% end %>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "keystone_ui/visible_navigation"
|
|
4
|
+
|
|
5
|
+
module Keystone
|
|
6
|
+
module Ui
|
|
7
|
+
class NavigationComponent < ViewComponent::Base
|
|
8
|
+
TOP_BAR_CLASSES = "hidden lg:block sticky top-0 z-40"
|
|
9
|
+
SIDEBAR_ROW_CLASSES = "lg:flex"
|
|
10
|
+
SIDEBAR_CLASSES = "ks-sidebar hidden lg:flex lg:sticky lg:top-0 lg:h-screen shrink-0 overflow-y-auto"
|
|
11
|
+
SIDEBAR_GROUP_CLASSES = "flex flex-col"
|
|
12
|
+
SIDEBAR_GROUP_LABEL_CLASSES = "ks-sidebar-group-label"
|
|
13
|
+
SIDEBAR_TAB_CLASSES = "ks-sidebar-tab"
|
|
14
|
+
ACTIVE_CLASS = "active"
|
|
15
|
+
SIDEBAR_MENUS_CLASSES = "mt-auto"
|
|
16
|
+
SIDEBAR_CONTENT_CLASSES = "min-w-0 flex-1"
|
|
17
|
+
SIDEBAR_PLACEMENTS = %w[left right].freeze
|
|
18
|
+
|
|
19
|
+
renders_one :logo
|
|
20
|
+
renders_one :menus
|
|
21
|
+
|
|
22
|
+
attr_reader :groups
|
|
23
|
+
|
|
24
|
+
def before_render
|
|
25
|
+
@groups = KeystoneUi::VisibleNavigation.new(
|
|
26
|
+
KeystoneUi.configuration.navigation_groups,
|
|
27
|
+
view: helpers,
|
|
28
|
+
saved_order: saved_navigation["order"] || [],
|
|
29
|
+
current_tab: KeystoneUi.configuration.supplied_current_tab(helpers)
|
|
30
|
+
).groups
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def sidebar_group_label_classes(group)
|
|
34
|
+
[ SIDEBAR_GROUP_LABEL_CLASSES, (ACTIVE_CLASS if group.active) ].compact.join(" ")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def sidebar_tab_classes(tab)
|
|
38
|
+
[ SIDEBAR_TAB_CLASSES, (ACTIVE_CLASS if tab.active) ].compact.join(" ")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def anything_to_show?
|
|
42
|
+
groups.any? || logo? || menus?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sidebar?
|
|
46
|
+
SIDEBAR_PLACEMENTS.include?(placement)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sidebar_first?
|
|
50
|
+
placement == "left"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def placement
|
|
56
|
+
saved_navigation["placement"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def saved_navigation
|
|
60
|
+
value = KeystoneUi.configuration.supplied_preference(helpers, :navigation)&.dig(:value)
|
|
61
|
+
value.is_a?(Hash) ? value : {}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -141,6 +141,10 @@ module KeystoneUiHelper
|
|
|
141
141
|
render Keystone::Ui::NavbarComponent.new(**args), &block
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
+
def ui_navigation(&block)
|
|
145
|
+
render Keystone::Ui::NavigationComponent.new, &block
|
|
146
|
+
end
|
|
147
|
+
|
|
144
148
|
def ui_nav_item(**args)
|
|
145
149
|
render Keystone::Ui::NavItemComponent.new(**args)
|
|
146
150
|
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "keystone_ui/navigation_group"
|
|
4
|
+
|
|
3
5
|
module KeystoneUi
|
|
4
6
|
class Configuration
|
|
5
|
-
attr_accessor :accent, :surface, :theme_mode_supplier, :look_supplier, :trail_supplier, :preference_supplier
|
|
6
|
-
attr_reader :tailwind_imports, :tailwind_sources, :looks, :default_look
|
|
7
|
+
attr_accessor :accent, :surface, :theme_mode_supplier, :look_supplier, :trail_supplier, :preference_supplier, :current_tab_supplier
|
|
8
|
+
attr_reader :tailwind_imports, :tailwind_sources, :looks, :default_look, :navigation_groups
|
|
7
9
|
|
|
8
10
|
def initialize
|
|
9
11
|
@accent = :blue
|
|
@@ -11,6 +13,7 @@ module KeystoneUi
|
|
|
11
13
|
@tailwind_imports = []
|
|
12
14
|
@tailwind_sources = []
|
|
13
15
|
@looks = {}
|
|
16
|
+
@navigation_groups = []
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def default_look=(name)
|
|
@@ -22,6 +25,12 @@ module KeystoneUi
|
|
|
22
25
|
@tailwind_imports << path.to_s
|
|
23
26
|
end
|
|
24
27
|
|
|
28
|
+
def navigation_group(label)
|
|
29
|
+
group = NavigationGroup.new(label)
|
|
30
|
+
yield(group)
|
|
31
|
+
@navigation_groups << group
|
|
32
|
+
end
|
|
33
|
+
|
|
25
34
|
def supplied_look(view)
|
|
26
35
|
look_supplier&.call(view)&.to_s
|
|
27
36
|
end
|
|
@@ -37,6 +46,10 @@ module KeystoneUi
|
|
|
37
46
|
def supplied_preference(view, key)
|
|
38
47
|
preference_supplier&.call(view, key)
|
|
39
48
|
end
|
|
49
|
+
|
|
50
|
+
def supplied_current_tab(view)
|
|
51
|
+
current_tab_supplier&.call(view)
|
|
52
|
+
end
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
def self.configuration
|
data/lib/keystone_ui/engine.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "keystone_ui/source_css"
|
|
4
4
|
require "keystone_ui/leftover_stylesheet"
|
|
5
5
|
require "keystone_ui/look_check"
|
|
6
|
+
require "keystone_ui/navigation_check"
|
|
6
7
|
|
|
7
8
|
module KeystoneUi
|
|
8
9
|
class Engine < ::Rails::Engine
|
|
@@ -32,8 +33,13 @@ module KeystoneUi
|
|
|
32
33
|
KeystoneUi::LookCheck.new(looks: config.looks, default: config.default_look).call
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
def self.check_navigation
|
|
37
|
+
KeystoneUi::NavigationCheck.new(groups: KeystoneUi.configuration.navigation_groups).call
|
|
38
|
+
end
|
|
39
|
+
|
|
35
40
|
config.after_initialize do
|
|
36
41
|
KeystoneUi::Engine.check_looks
|
|
42
|
+
KeystoneUi::Engine.check_navigation
|
|
37
43
|
|
|
38
44
|
tailwind_dir = Rails.root.join("app/assets/tailwind")
|
|
39
45
|
css_path = tailwind_dir.join("application.css")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
class NavigationCheck
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
def initialize(groups:)
|
|
8
|
+
@groups = groups
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
@groups.each do |group|
|
|
13
|
+
raise Error, "The #{group.label} navigation group has no tabs." if group.tabs.empty?
|
|
14
|
+
end
|
|
15
|
+
tabs.each do |tab|
|
|
16
|
+
raise Error, "The #{tab.key} tab has no label." if blank?(tab.label)
|
|
17
|
+
raise Error, "The #{tab.key} tab has no link." if blank?(tab.href)
|
|
18
|
+
end
|
|
19
|
+
raise Error, "More than one navigation tab has the key #{shared_key}." if shared_key
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def tabs
|
|
26
|
+
@groups.flat_map(&:tabs)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def shared_key
|
|
30
|
+
tabs.map(&:key).tally.find { |_key, count| count > 1 }&.first
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def blank?(value)
|
|
34
|
+
value.nil? || value.to_s.strip.empty?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
class NavigationGroup
|
|
5
|
+
Tab = Struct.new(:key, :label, :href, :permitted, keyword_init: true) do
|
|
6
|
+
def href_for(view)
|
|
7
|
+
href.respond_to?(:call) ? href.call(view) : href
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :label, :tabs
|
|
12
|
+
|
|
13
|
+
def initialize(label)
|
|
14
|
+
@label = label
|
|
15
|
+
@tabs = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tabs_permitted_for(view)
|
|
19
|
+
tabs.select { |tab| tab.permitted.call(view) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tab(key, label:, href:, permitted:)
|
|
23
|
+
@tabs << Tab.new(key: key, label: label, href: href, permitted: permitted)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
class NavigationOrder
|
|
5
|
+
def initialize(saved)
|
|
6
|
+
@saved = saved.is_a?(Array) ? saved.grep(Hash) : []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def arrange(groups)
|
|
10
|
+
in_saved_order(groups, group_labels) { |group, _tabs| group.label }
|
|
11
|
+
.map { |group, tabs| [ group, arrange_tabs(group, tabs) ] }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def in_saved_order(items, saved_names)
|
|
17
|
+
items.each_with_index
|
|
18
|
+
.sort_by { |item, declared| [ saved_names.index(yield(item)) || saved_names.size, declared ] }
|
|
19
|
+
.map(&:first)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def group_labels
|
|
23
|
+
@saved.map { |entry| entry["group"] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def arrange_tabs(group, tabs)
|
|
27
|
+
in_saved_order(tabs, saved_tab_keys(group)) { |tab| tab.key.to_s }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def saved_tab_keys(group)
|
|
31
|
+
tabs = @saved.find { |entry| entry["group"] == group.label }&.fetch("tabs", nil)
|
|
32
|
+
tabs.is_a?(Array) ? tabs : []
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/keystone_ui/safelist.rb
CHANGED
|
@@ -37,6 +37,7 @@ module Keystone
|
|
|
37
37
|
Keystone::Ui::NavItemComponent,
|
|
38
38
|
Keystone::Ui::BottomNavItemComponent,
|
|
39
39
|
Keystone::Ui::NavbarComponent,
|
|
40
|
+
Keystone::Ui::NavigationComponent,
|
|
40
41
|
Keystone::Ui::BottomNavComponent,
|
|
41
42
|
Keystone::Ui::SettingsLinkComponent,
|
|
42
43
|
Keystone::Ui::FormPageComponent,
|
data/lib/keystone_ui/version.rb
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "keystone_ui/navigation_order"
|
|
4
|
+
|
|
5
|
+
module KeystoneUi
|
|
6
|
+
class VisibleNavigation
|
|
7
|
+
Group = Struct.new(:label, :tabs, :active, keyword_init: true)
|
|
8
|
+
Tab = Struct.new(:label, :href, :active, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
def initialize(declared_groups, view:, saved_order:, current_tab:)
|
|
11
|
+
@declared_groups = declared_groups
|
|
12
|
+
@view = view
|
|
13
|
+
@saved_order = saved_order
|
|
14
|
+
@current_tab = current_tab
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def groups
|
|
18
|
+
NavigationOrder.new(@saved_order).arrange(permitted).map { |group, tabs| visible_group(group, tabs) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def permitted
|
|
24
|
+
@declared_groups
|
|
25
|
+
.map { |group| [ group, group.tabs_permitted_for(@view) ] }
|
|
26
|
+
.reject { |_group, tabs| tabs.empty? }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def visible_group(group, tabs)
|
|
30
|
+
visible_tabs = tabs.map { |tab| visible_tab(tab) }
|
|
31
|
+
Group.new(label: group.label, tabs: visible_tabs, active: visible_tabs.any?(&:active))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def visible_tab(tab)
|
|
35
|
+
Tab.new(label: tab.label, href: tab.href_for(@view), active: tab.key == @current_tab)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|