Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 43 additions & 21 deletions partials/_accordion_nav.html.erb
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
<%
<%
def section_open?(section)
(!section[:path].nil? && current_page?(section[:path])) ||
(!section[:open].nil? && section[:open]) ||
(section[:links] && section[:links].any? { |page| (!page[:path].nil? && current_page?(page[:path])) || (page[:links] && section_open?(page)) })
end

# Ties a trigger to the panel it controls via aria-controls.
def accordion_id(section, prefix)
slug = section[:title].to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
"#{prefix}-#{slug.empty? ? "section" : slug}"
end

# The section's own index page moves into the list as "Overview" so the header
# row has exactly one job instead of being half link, half toggle. Skipped
# when the links already include that page, which most sections do.
def overview_needed?(section)
return false if section[:path].nil?
target = section[:path].to_s.chomp("/")
!Array(section[:links]).any? { |page| page[:path].to_s.chomp("/") == target }
end

nav.each { |nav| nav[:open] = section_open?(nav) unless nav[:title].nil? }

nav.each do |nav|
nav.each_with_index do |nav, nav_index|
section_id = accordion_id(nav, "nav-#{nav_index}")
%>
<% if nav[:links].nil? %>
<%= nav_link nav[:title], nav[:path], class: "title-link" %>
Expand All @@ -29,36 +45,42 @@
<% else %>
<dl class="accordion<%= nav[:open] ? " open" : "" %>">
<dt class="accordion-header">
<% if nav[:title] && nav[:path] %>
<%# One control per row: the whole header toggles. Previously the title
navigated and only the remainder of the row toggled, with no visible
seam between the two. %>
<% if nav[:links] && nav[:links].size > 0 %>
<button class="accordion-trigger" id="<%= section_id %>-btn" aria-controls="<%= section_id %>-panel" aria-expanded="<%= nav[:open] %>">
<span class="accordion-title"><%= nav[:title] %></span>
</button>
<% elsif nav[:title] && nav[:path] %>
<%= nav_link nav[:title], nav[:path], class: "title-link" %>
<% else %>
<span class="accordion-title"><%= nav[:title] %></span>
<% end %>
<%# Only show the accordion trigger if there are real subpages %>
<% if nav[:links] && nav[:links].size > 0 %>
<button class="accordion-trigger" aria-expanded="<%= nav[:open] %>">
<span class="sr-only">Toggle <%= nav[:title] %> section</span>
</button>
<% end %>
</dt>
<dd class="accordion-target">
<ul class="accordion-content" role="region" aria-label="<%= nav[:title] %>">
<% nav[:links]&.each do |page| %>
<%# inert keeps collapsed links out of the tab order and the accessibility
tree. Clipping them with grid-rows/opacity alone left every link in
every closed section focusable and announced. %>
<dd class="accordion-target" id="<%= section_id %>-panel"<%= " inert" unless nav[:open] %>>
<ul class="accordion-content">
<% if overview_needed?(nav) %>
<li><%= nav_link "Overview", nav[:path], class: "overview-link" %></li>
<% end %>
<% nav[:links]&.each_with_index do |page, page_index| %>
<% if page[:links] %>
<% page_id = accordion_id(page, "#{section_id}-#{page_index}") %>
<li>
<dl class="accordion<%= section_open?(page) ? " open" : "" %>">
<dt class="accordion-header">
<% if page[:title] && page[:path] %>
<%= nav_link page[:title], page[:path], class: "title-link" %>
<% else %>
<button class="accordion-trigger" id="<%= page_id %>-btn" aria-controls="<%= page_id %>-panel" aria-expanded="<%= section_open?(page) %>">
<span class="accordion-title"><%= page[:title] %></span>
<% end %>
<button class="accordion-trigger" aria-expanded="<%= section_open?(page) %>">
<span class="sr-only">Toggle <%= page[:title] %> section</span>
</button>
</dt>
<dd class="accordion-target">
<ul class="accordion-content" role="region" aria-label="<%= page[:title] %>">
<dd class="accordion-target" id="<%= page_id %>-panel"<%= " inert" unless section_open?(page) %>>
<ul class="accordion-content">
<% if overview_needed?(page) %>
<li><%= nav_link "Overview", page[:path], class: "overview-link" %></li>
<% end %>
<% page[:links]&.each do |subpage| %>
<li>
<% if subpage[:type] == "flyctl" %>
Expand Down Expand Up @@ -93,4 +115,4 @@
</dd>
</dl>
<% end %>
<% end %>
<% end %>
74 changes: 52 additions & 22 deletions partials/_breadcrumbs.html.erb
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
<nav aria-label="Breadcrumb" class="mb-11 flex items-center space-x-2 text-sm font-semibold text-gray-500">
<a href="/docs/" class="hover:text-violet-600 transition-colors truncate">Docs</a>
<%
# Two modes.
#
# :trail is the full path, used in the article on wide screens.
#
# :parent is one link to the level above, used in the mobile context bar. The
# full trail cannot fit a 44px bar next to the "On this page" chip — about
# 220px — so it either truncates to stubs ("D… > Fly Mac… > An introduction
# to Fly …") or turns the bar into a sideways scroller. Neither is worth it
# when the current page's own title is the h1 immediately below. Going up is
# the only thing the trail is actually needed for here.
mode = locals.key?(:mode) ? mode : :trail
wrapper = locals.key?(:wrapper_class) ? wrapper_class : "mb-11 flex"
segments = current_page.request_path.split('/').drop(2)
%>
<% if mode == :parent %>
<%
segments = current_page.request_path.split('/').drop(2)
segments.each_with_index do |segment, index|
path = "/docs/#{segments.take(index + 1).join('/')}"
title = if index == segments.size - 1
page_title
else
get_page_title(path)
end
is_last = index == segments.size - 1
parent_segments = segments[0...-1] || []
parent_path = parent_segments.empty? ? "/docs/" : "/docs/#{parent_segments.join('/')}/"
parent_title = parent_segments.empty? ? "Docs" : get_page_title(parent_path)
%>
<span data-lvl="<%= index %>" class="flex items-center truncate">
<svg class="w-4 h-4 text-gray-500/50 shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
<nav aria-label="Breadcrumb" class="breadcrumbs <%= wrapper %> items-center min-w-0 text-sm font-semibold text-gray-500">
<a href="<%= parent_path %>" class="flex items-center min-w-0 hover:text-violet-600 transition-colors">
<svg class="w-4 h-4 shrink-0 -ml-1 text-gray-500/70" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
<% if is_last %>
<span class="ml-2 text-navy-950 truncate"><%= title %></span>
<% else %>
<a href="<%= path %>" class="ml-2 hover:text-violet-600 transition-colors truncate"><%= title %></a>
<% end %>
</span>
<% end %>
</nav>
<span class="ml-1 truncate"><%= parent_title %></span>
</a>
</nav>
<% else %>
<nav aria-label="Breadcrumb" class="breadcrumbs is-trail <%= wrapper %> items-center space-x-2 text-sm font-semibold text-gray-500 overflow-x-auto scrollbar-hidden">
<a href="/docs/" class="hover:text-violet-600 transition-colors shrink-0">Docs</a>
<%
segments.each_with_index do |segment, index|
path = "/docs/#{segments.take(index + 1).join('/')}"
title = if index == segments.size - 1
page_title
else
get_page_title(path)
end
is_last = index == segments.size - 1
%>
<span data-lvl="<%= index %>" class="flex items-center shrink-0 whitespace-nowrap">
<svg class="w-4 h-4 text-gray-500/50 shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
<% if is_last %>
<span class="ml-2 text-navy-950"><%= title %></span>
<% else %>
<a href="<%= path %>" class="ml-2 hover:text-violet-600 transition-colors"><%= title %></a>
<% end %>
</span>
<% end %>
</nav>
<% end %>
Loading