Module: Protege::Components::SidebarHelper
- Included in:
- ComponentsHelper
- Defined in:
- app/helpers/protege/components/sidebar_helper.rb
Overview
The console sidebar surface — the docked <aside> shell and the header/list/footer elements that
fill it. Split out of LayoutHelper so the sidebar is a single focused concern: sidebar_container
is the resizable outer shell, sidebar_content the flex column inside it, and sidebar_header /
sidebar_search_link / sidebar_body / sidebar_item / sidebar_stream compose the list.
Colors use the CSS custom properties (--surface, --border, --text, --text-muted, --text-faint) defined in tailwind/application.css so dark mode works via a single .dark class toggle.
Instance Method Summary collapse
-
#sidebar_bar { ... } ⇒ ActiveSupport::SafeBuffer
Render a fixed-height sidebar bar — the standard horizontal strip (bordered, faint, padded) used for a labeled control row such as the trace filter.
-
#sidebar_body(collection = nil, empty: nil, id: nil) {|item| ... } ⇒ ActiveSupport::SafeBuffer
Render the scrollable list area of a sidebar — the flexible middle that grows between the fixed header and pagination footer.
-
#sidebar_container { ... } ⇒ ActiveSupport::SafeBuffer
Render the sidebar shell — the resizable, full-height
<aside>that docks the navigation sidebar on the left of the console. -
#sidebar_content(frame: nil) { ... } ⇒ ActiveSupport::SafeBuffer
Wrap a sidebar's contents (header, scrollable list, pagination footer) in the flex column that fills the sidebar shell — header/footer stay fixed while the list scrolls between them.
-
#sidebar_header(title, new_path: nil, new_label: 'New') ⇒ ActiveSupport::SafeBuffer
Render a sidebar section header with an optional "new record" link.
-
#sidebar_item(title, href, detail: nil, subtitle: nil, active: false, **attrs) { ... } ⇒ ActiveSupport::SafeBuffer
Render a sidebar list entry — title, optional detail/subtitle, and an optional extra block — highlighted when
active. -
#sidebar_search_link(href, placeholder: 'Search…') ⇒ ActiveSupport::SafeBuffer
Render a sidebar search affordance — a faux search field that links to a search page.
-
#sidebar_stream(*streamables, pagy:) ⇒ ActiveSupport::SafeBuffer
Subscribe the sidebar to a Turbo Stream for live-appended rows — but only on the first page.
Instance Method Details
#sidebar_bar { ... } ⇒ ActiveSupport::SafeBuffer
Render a fixed-height sidebar bar — the standard horizontal strip (bordered, faint, padded) used for a labeled control row such as the trace filter. Its contents are yielded and laid out with space-between, matching the +sidebar_header+/+sidebar_search_link+ bar shape.
91 92 93 94 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 91 def (&) tag.div(class: 'flex h-10 flex-shrink-0 items-center justify-between px-4 text-xs', style: 'border-bottom: 1px solid var(--border); color: var(--text-faint)', &) end |
#sidebar_body(collection = nil, empty: nil, id: nil) {|item| ... } ⇒ ActiveSupport::SafeBuffer
Render the scrollable list area of a sidebar — the flexible middle that grows between the fixed header and pagination footer.
Two forms:
sidebar_body(collection, empty: '…') { |item| … }renders each item via the block, or a muted empty-state hint when the collection is empty — the common list sidebar.sidebar_body { … }(no collection) just wraps arbitrary content, for sidebars that own their own iteration/empty handling (e.g. the Inbox's Turbo Frame, the search results' two empty states).
57 58 59 60 61 62 63 64 65 66 67 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 57 def (collection = nil, empty: nil, id: nil, &block) tag.div(class: 'flex-1 overflow-y-auto', id:) do if collection.nil? capture(&block) elsif collection.any? safe_join(collection.map { |item| capture(item, &block) }) else hint(empty, classes: 'py-8 text-center') end end end |
#sidebar_container { ... } ⇒ ActiveSupport::SafeBuffer
Render the sidebar shell — the resizable, full-height <aside> that docks the navigation sidebar
on the left of the console. The user can drag its right edge to resize it (the general
resize_handle paired with resize.js), bounded by min/max and remembered across page loads. The
yielded block is the sidebar's content (typically a sidebar_content wrapper).
20 21 22 23 24 25 26 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 20 def (&block) tag.aside(class: 'relative flex w-72 flex-shrink-0 flex-col overflow-hidden', data: { resize: '', resize_edge: 'right', resize_min: 200, resize_max: 480, resize_key: 'protege:sidebar-width' }) do resize_handle(edge: :right) + capture(&block) end end |
#sidebar_content(frame: nil) { ... } ⇒ ActiveSupport::SafeBuffer
Wrap a sidebar's contents (header, scrollable list, pagination footer) in the flex column that fills the sidebar shell — header/footer stay fixed while the list scrolls between them.
Pass frame: to render the column as a Turbo Frame (an update target a form/stream can replace)
instead of a plain div; the same layout classes apply either way. A bare <turbo-frame> is
display: inline, so the classes (which set it flex) are what make the frame variant lay out at all.
38 39 40 41 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 38 def (frame: nil, &) classes = 'flex min-h-0 flex-1 flex-col' frame ? turbo_frame_tag(frame, class: classes, &) : tag.div(class: classes, &) end |
#sidebar_header(title, new_path: nil, new_label: 'New') ⇒ ActiveSupport::SafeBuffer
Render a sidebar section header with an optional "new record" link.
102 103 104 105 106 107 108 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 102 def (title, new_path: nil, new_label: 'New') tag.div(class: 'flex h-10 flex-shrink-0 items-center justify-between px-4', style: 'border-bottom: 1px solid var(--border)') do tag.span(title, class: 'text-xs font-semibold uppercase tracking-wider', style: 'color: var(--text-faint)') + (new_path ? ui_link(new_label, new_path) : ''.html_safe) end end |
#sidebar_item(title, href, detail: nil, subtitle: nil, active: false, **attrs) { ... } ⇒ ActiveSupport::SafeBuffer
Render a sidebar list entry — title, optional detail/subtitle, and an
optional extra block — highlighted when active.
136 137 138 139 140 141 142 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 136 def (title, href, detail: nil, subtitle: nil, active: false, **attrs, &block) link_to(href, class: 'block px-4 py-3 transition-colors', style: (active), **attrs) do (title, detail) + (subtitle) + (block ? tag.div(class: 'mt-1', &block) : ''.html_safe) end end |
#sidebar_search_link(href, placeholder: 'Search…') ⇒ ActiveSupport::SafeBuffer
Render a sidebar search affordance — a faux search field that links to a search page.
Styled like an input (magnifier + muted placeholder) but it is a plain link: clicking it
navigates to href. Sits between the sidebar header and its list.
118 119 120 121 122 123 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 118 def (href, placeholder: 'Search…') classes = 'flex h-10 flex-shrink-0 items-center gap-2 px-4 text-xs transition-colors hover:opacity-80' link_to(href, class: classes, style: 'border-bottom: 1px solid var(--border); color: var(--text-faint)') do search_icon + tag.span(placeholder) end end |
#sidebar_stream(*streamables, pagy:) ⇒ ActiveSupport::SafeBuffer
Subscribe the sidebar to a Turbo Stream for live-appended rows — but only on the first page.
New rows are broadcast (prepend/append) into +sidebar_body+'s id target; gating the
subscription to page 1 is how we keep live rows from landing on the wrong page — later pages
simply aren't subscribed and reconcile on their next load. Row-replace updates are id-targeted,
so they're unaffected by whether this subscription is present.
79 80 81 82 83 |
# File 'app/helpers/protege/components/sidebar_helper.rb', line 79 def (*streamables, pagy:) return ''.html_safe unless pagy.page == 1 turbo_stream_from(*streamables) end |