motif-breadcrumbs 0.4.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 58b85b39ff42181e6eec1001c1b8121bea0a1b842c3a97a2213e0abc0365bf9d
4
+ data.tar.gz: '01800f3aeae6ff1d248d1c30b74eea3ec0751ff1dc1df207a214b928be282b53'
5
+ SHA512:
6
+ metadata.gz: ce067650e5e59d0ba7eb8cf783b33905b355962e687ceeca77ff92981551135ae5b6753f5facdb294c2a7fafcdaacab49d692affde41a4bbbf059cdbd59136c0
7
+ data.tar.gz: d459e96bb31f8df2f0ad42eb6a3d006cbb34ec13efbb4b932dbaef5ff939060bd27068d8ad841695b019d67eff0b122684c58bd1b3e7e1d5e19591b3521926ad
data/README.md ADDED
@@ -0,0 +1,418 @@
1
+ # Motif::Breadcrumbs
2
+
3
+ Breadcrumb trail management for Rails applications.
4
+
5
+ Provides a typed data model, ViewComponents for rendering, schema.org JSON-LD
6
+ structured data, and a composable Segment pattern for encapsulating complex
7
+ breadcrumb logic.
8
+
9
+ ## Installation
10
+
11
+ Add to your `Gemfile`:
12
+
13
+ ```ruby
14
+ gem "motif-breadcrumbs"
15
+ ```
16
+
17
+ Run `bundle install`.
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Include the controller concern
22
+
23
+ ```ruby
24
+ class ApplicationController < ActionController::Base
25
+ include Motif::Breadcrumbs::Controller
26
+
27
+ # if needed, override initialize_breadcrumbs to add a default crumb
28
+ def initialize_breadcrumbs
29
+ super
30
+ breadcrumbs.push("Home", root_path)
31
+ end
32
+ end
33
+ ```
34
+
35
+ The concern provides:
36
+ - `@breadcrumbs` — a `Motif::Breadcrumbs::Trail` initialized before each action
37
+ - `breadcrumbs` — a helper method available in controllers and views
38
+
39
+ ### 2. Push crumbs in your controllers
40
+
41
+ ```ruby
42
+ class PostsController < ApplicationController
43
+ def index
44
+ breadcrumbs.push("Posts", posts_path)
45
+ end
46
+
47
+ def show
48
+ @post = Post.find(params[:id])
49
+ breadcrumbs.push("Posts", posts_path)
50
+ breadcrumbs.push(@post.title)
51
+ end
52
+ end
53
+ ```
54
+
55
+ ### 3. Render in your layout
56
+
57
+ ```erb
58
+ <%# app/views/layouts/application.html.erb %>
59
+ <head>
60
+ <%= render Motif::Breadcrumbs::JsonLdComponent.new(
61
+ trail: breadcrumbs,
62
+ base_url: root_url,
63
+ current_url: request.original_url
64
+ ) %>
65
+ </head>
66
+ <body>
67
+ <%= render Motif::Breadcrumbs::NavigationComponent.new(
68
+ trail: breadcrumbs,
69
+ html_class: "breadcrumbs text-sm"
70
+ ) %>
71
+ <%= yield %>
72
+ </body>
73
+ ```
74
+
75
+ ## Core Concepts
76
+
77
+ ### Trail
78
+
79
+ `Motif::Breadcrumbs::Trail` is the core data structure — an ordered stack of
80
+ crumbs. It includes `Enumerable` and provides `push`, `pop`, `size`, `any?`,
81
+ `empty?`, `each`, `last`, and `[]`.
82
+
83
+ ```ruby
84
+ trail = Motif::Breadcrumbs::Trail.new
85
+ trail.push("Home", "/")
86
+ trail.push("Posts", "/posts")
87
+ trail.push("My Post") # no href = current page
88
+ trail.push("Settings", "/settings", "gear") # with icon identifier
89
+
90
+ trail.size # => 4
91
+ trail.last # => Crumb(text: "Settings", href: "/settings", icon: "gear")
92
+ trail.map(&:text) # => ["Home", "Posts", "My Post", "Settings"]
93
+ ```
94
+
95
+ Trail does **not** include Rails URL helpers. It is a pure data structure with
96
+ no dependency on routing.
97
+
98
+ ### Crumb
99
+
100
+ `Motif::Breadcrumbs::Crumb` is a Sorbet `T::Struct` with three fields:
101
+
102
+ | Field | Type | Description |
103
+ |--------|-------------------|------------------------------------|
104
+ | `text` | `String` | Display text for the breadcrumb |
105
+ | `href` | `String?` | URL path (nil for the current page)|
106
+ | `icon` | `String?` | Icon identifier (app-interpreted) |
107
+
108
+ ```ruby
109
+ crumb = Motif::Breadcrumbs::Crumb.new(text: "Home", href: "/", icon: "home")
110
+ crumb.text # => "Home"
111
+ crumb.href # => "/"
112
+ crumb.icon # => "home"
113
+ ```
114
+
115
+ ### Segment
116
+
117
+ `Motif::Breadcrumbs::Segment` is an abstract base class for encapsulating
118
+ reusable breadcrumb-building logic. A Segment is a self-contained object that
119
+ produces an array of `Crumb` structs via `to_crumbs`.
120
+
121
+ Segments can be pushed directly onto a trail — `Trail#push` detects objects that
122
+ respond to `to_crumbs` and expands them automatically:
123
+
124
+ ```ruby
125
+ breadcrumbs.push("Home", root_path)
126
+ breadcrumbs.push(Motif::Breadcrumbs::WorkspaceCrumbs.new(workspace: @workspace))
127
+ breadcrumbs.push("Settings", settings_path)
128
+ ```
129
+
130
+ This makes complex multi-crumb logic **encapsulated**, **testable**, and
131
+ **composable**.
132
+
133
+ ## Creating Segments
134
+
135
+ ### Directory convention
136
+
137
+ Place your segments in `app/motif/breadcrumbs/`. Rails treats `app/motif` as a
138
+ Zeitwerk autoload root, so the `motif` directory name is stripped from the
139
+ namespace. Files map to `Breadcrumbs::*`:
140
+
141
+ ```
142
+ app/motif/breadcrumbs/application_segment.rb → Breadcrumbs::ApplicationSegment
143
+ app/motif/breadcrumbs/workspace_crumbs.rb → Breadcrumbs::WorkspaceCrumbs
144
+ app/motif/breadcrumbs/foo/bar_crumbs.rb → Breadcrumbs::Foo::BarCrumbs
145
+ ```
146
+
147
+ Segments inherit from `Motif::Breadcrumbs::Segment` using the fully qualified
148
+ name.
149
+
150
+ ### ApplicationSegment
151
+
152
+ Create an app-level base class that includes Rails URL helpers. This mirrors the
153
+ `ApplicationRecord` / `ApplicationController` pattern:
154
+
155
+ ```ruby
156
+ # app/motif/breadcrumbs/application_segment.rb
157
+ module Breadcrumbs
158
+ class ApplicationSegment < Motif::Breadcrumbs::Segment
159
+ # Route helpers are included at runtime via `send` to avoid Sorbet
160
+ # error 4002 (dynamic constant in `include`).
161
+ send(:include, Rails.application.routes.url_helpers)
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Writing a segment
167
+
168
+ Subclass `ApplicationSegment`, accept dependencies via `initialize`, and
169
+ implement `to_crumbs` returning an array of `Crumb` structs. Use the `crumb`
170
+ factory method for convenience:
171
+
172
+ ```ruby
173
+ # app/motif/breadcrumbs/workspace_crumbs.rb
174
+ module Breadcrumbs
175
+ class WorkspaceCrumbs < ApplicationSegment
176
+ def initialize(workspace:)
177
+ @workspace = workspace
178
+ end
179
+
180
+ def to_crumbs
181
+ [crumb("Workspaces", workspaces_path),
182
+ crumb(@workspace.name, workspace_path(@workspace))]
183
+ end
184
+ end
185
+ end
186
+ ```
187
+
188
+ ### Composing segments
189
+
190
+ Segments can include other segments by concatenating their `to_crumbs` output:
191
+
192
+ ```ruby
193
+ # app/motif/breadcrumbs/github/repository_crumbs.rb
194
+ module Breadcrumbs
195
+ class Github::RepositoryCrumbs < ApplicationSegment
196
+ def initialize(repository:, user: nil)
197
+ @repository = repository
198
+ @user = user
199
+ end
200
+
201
+ def to_crumbs
202
+ crumbs = []
203
+ if @user && (workspace = shared_workspace)
204
+ crumbs.concat(Breadcrumbs::WorkspaceCrumbs.new(workspace: workspace).to_crumbs)
205
+ crumbs << crumb("GitHub", github_installations_path)
206
+ end
207
+ crumbs << crumb(@repository.full_name, github_repository_path(@repository))
208
+ crumbs
209
+ end
210
+
211
+ private
212
+
213
+ def shared_workspace
214
+ workspaces = @repository.common_workspaces_with(user: @user)
215
+ workspaces.first if workspaces.size == 1
216
+ end
217
+ end
218
+ end
219
+ ```
220
+
221
+ ### Testing segments
222
+
223
+ Segments are independently testable — no controller or request context needed:
224
+
225
+ ```ruby
226
+ RSpec.describe Breadcrumbs::Github::RepositoryCrumbs do
227
+ let(:repository) { create(:github_repository) }
228
+
229
+ it "includes repository name" do
230
+ crumbs = described_class.new(repository: repository).to_crumbs
231
+
232
+ expect(crumbs.last.text).to eq(repository.full_name)
233
+ end
234
+
235
+ it "includes workspace segment for single-workspace users" do
236
+ user = create(:user_with_workspace)
237
+ crumbs = described_class.new(repository: repository, user: user).to_crumbs
238
+
239
+ expect(crumbs.first.text).to eq("Workspaces")
240
+ end
241
+ end
242
+ ```
243
+
244
+ ## View Components
245
+
246
+ ### NavigationComponent
247
+
248
+ Renders the breadcrumb trail as a `<nav>` element with semantic HTML.
249
+
250
+ ```ruby
251
+ Motif::Breadcrumbs::NavigationComponent.new(
252
+ trail: breadcrumbs,
253
+ html_class: "breadcrumbs text-sm", # CSS classes on <nav> (default)
254
+ aria_current_on_last: true, # aria-current="page" on last crumb (default)
255
+ min_crumbs: 2, # minimum crumbs to render (default)
256
+ icon_renderer: nil # optional lambda for icon rendering (default: nil)
257
+ )
258
+ ```
259
+
260
+ Output structure:
261
+
262
+ ```html
263
+ <nav class="breadcrumbs text-sm" aria-label="Breadcrumb">
264
+ <ul>
265
+ <li><a href="/">Home</a></li>
266
+ <li><a href="/posts">Posts</a></li>
267
+ <li aria-current="page"><span>My Post</span></li>
268
+ </ul>
269
+ </nav>
270
+ ```
271
+
272
+ The component uses the DaisyUI `.breadcrumbs` class by default. Override
273
+ `html_class` to customize styling per layout:
274
+
275
+ ```erb
276
+ <%# Public layout %>
277
+ <%= render Motif::Breadcrumbs::NavigationComponent.new(
278
+ trail: breadcrumbs,
279
+ html_class: "px-4 breadcrumbs text-sm font-light text-base-content/65"
280
+ ) %>
281
+
282
+ <%# Admin layout %>
283
+ <%= render Motif::Breadcrumbs::NavigationComponent.new(
284
+ trail: breadcrumbs,
285
+ html_class: "breadcrumbs text-sm",
286
+ min_crumbs: 1
287
+ ) %>
288
+ ```
289
+
290
+ The component does not render when the trail has fewer crumbs than `min_crumbs`.
291
+
292
+ ### CrumbComponent
293
+
294
+ Renders a single breadcrumb item. Used internally by `NavigationComponent`, but
295
+ can be used directly for custom rendering.
296
+
297
+ ```ruby
298
+ Motif::Breadcrumbs::CrumbComponent.new(
299
+ text: "Home", # display text (required)
300
+ href: "/", # URL (optional — <a> if present, <span> if nil)
301
+ icon: "home", # icon identifier (optional)
302
+ current: false # adds aria-current="page" (default: false)
303
+ )
304
+ ```
305
+
306
+ #### Icon rendering
307
+
308
+ The `icon` field is an opaque string identifier. The gem does not depend on any
309
+ icon library — your app provides the rendering logic.
310
+
311
+ **Via `icon_renderer` (recommended):** Pass a lambda to `NavigationComponent`
312
+ that receives an icon name and returns HTML. The renderer is forwarded to each
313
+ `CrumbComponent` automatically:
314
+
315
+ ```erb
316
+ <%= render Motif::Breadcrumbs::NavigationComponent.new(
317
+ trail: breadcrumbs,
318
+ icon_renderer: ->(name) { lucide_icon(name, class: "w-4 h-4") }
319
+ ) %>
320
+ ```
321
+
322
+ The lambda must return an `html_safe` string (most icon helpers do this).
323
+
324
+ **Via icon slot (full control):** For standalone `CrumbComponent` usage, the
325
+ `renders_one :icon` slot gives complete control over icon markup:
326
+
327
+ ```erb
328
+ <%= render Motif::Breadcrumbs::CrumbComponent.new(text: "Home", href: "/") do |c| %>
329
+ <% c.with_icon do %>
330
+ <%= lucide_icon("home", class: "w-4 h-4") %>
331
+ <% end %>
332
+ <% end %>
333
+ ```
334
+
335
+ When both an icon slot and an `icon_renderer` are provided, the slot takes
336
+ priority.
337
+
338
+ ### JsonLdComponent
339
+
340
+ Renders schema.org `BreadcrumbList` structured data as JSON-LD.
341
+
342
+ ```ruby
343
+ Motif::Breadcrumbs::JsonLdComponent.new(
344
+ trail: breadcrumbs,
345
+ base_url: root_url, # absolute base URL for building full URLs
346
+ current_url: request.original_url # fallback URL for crumbs without href
347
+ )
348
+ ```
349
+
350
+ Output:
351
+
352
+ ```html
353
+ <script type="application/ld+json">
354
+ {"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[
355
+ {"@type":"ListItem","position":1,"name":"Home","item":"https://example.com/"},
356
+ {"@type":"ListItem","position":2,"name":"Posts","item":"https://example.com/posts"}
357
+ ]}
358
+ </script>
359
+ ```
360
+
361
+ The component does not render when the trail has 1 or fewer crumbs.
362
+
363
+ ## Customization
364
+
365
+ ### CSS classes
366
+
367
+ Pass `html_class` to `NavigationComponent` to customize the CSS classes on the
368
+ `<nav>` element. The default is `"breadcrumbs text-sm"` which uses DaisyUI's
369
+ breadcrumb styling.
370
+
371
+ ### Template overrides
372
+
373
+ For deeper customization, subclass a component and provide your own sidecar
374
+ `.html.erb` template. ViewComponent resolves templates from the subclass
375
+ directory first:
376
+
377
+ ```ruby
378
+ # app/components/app_breadcrumbs_nav.rb
379
+ class AppBreadcrumbsNav < Motif::Breadcrumbs::NavigationComponent
380
+ end
381
+ ```
382
+
383
+ ```erb
384
+ <%# app/components/app_breadcrumbs_nav.html.erb %>
385
+ <div class="my-custom-breadcrumbs">
386
+ <%# your custom markup %>
387
+ </div>
388
+ ```
389
+
390
+ ### Icon rendering
391
+
392
+ Pass an `icon_renderer` lambda to `NavigationComponent` to integrate with your
393
+ icon library:
394
+
395
+ ```erb
396
+ <%= render Motif::Breadcrumbs::NavigationComponent.new(
397
+ trail: breadcrumbs,
398
+ icon_renderer: ->(name) { heroicon(name, class: "w-4 h-4") }
399
+ ) %>
400
+ ```
401
+
402
+ The `CrumbComponent`'s `renders_one :icon` slot is also available for
403
+ fine-grained control when rendering crumbs individually.
404
+
405
+ ## Sorbet
406
+
407
+ All core classes are typed (`# typed: true`) with Sorbet signatures.
408
+
409
+ After adding the gem to your app, generate RBIs:
410
+
411
+ ```bash
412
+ bundle exec tapioca gems
413
+ bundle exec tapioca dsl
414
+ ```
415
+
416
+ ## License
417
+
418
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+
4
+ begin
5
+ require "rspec/core/rake_task"
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ task default: :spec
8
+ rescue LoadError
9
+ end
@@ -0,0 +1,13 @@
1
+ <li<%= " aria-current=\"page\"".html_safe if @current %>>
2
+ <% if @href.present? %>
3
+ <a href="<%= @href %>">
4
+ <% if icon? %><%= icon %><% elsif rendered_icon %><%= rendered_icon %><% end %>
5
+ <%= @text %>
6
+ </a>
7
+ <% else %>
8
+ <span>
9
+ <% if icon? %><%= icon %><% elsif rendered_icon %><%= rendered_icon %><% end %>
10
+ <%= @text %>
11
+ </span>
12
+ <% end %>
13
+ </li>
@@ -0,0 +1,26 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ class CrumbComponent < ViewComponent::Base
7
+ renders_one :icon
8
+
9
+ #: (text: String, ?href: String?, ?icon: String?, ?current: bool, ?icon_renderer: ^(String) -> String | nil) -> void
10
+ def initialize(text:, href: nil, icon: nil, current: false, icon_renderer: nil)
11
+ @text = text
12
+ @href = href
13
+ @icon = icon
14
+ @current = current
15
+ @icon_renderer = icon_renderer
16
+ end
17
+
18
+ #: -> String?
19
+ def rendered_icon
20
+ return unless @icon.present? && @icon_renderer
21
+
22
+ @icon_renderer.call(@icon)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ <script type="application/ld+json">
2
+ <%== json_ld %>
3
+ </script>
@@ -0,0 +1,41 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ class JsonLdComponent < ViewComponent::Base
7
+ #: (trail: Trail, base_url: String, current_url: String) -> void
8
+ def initialize(trail:, base_url:, current_url:)
9
+ @trail = trail
10
+ @base_url = base_url.chomp("/")
11
+ @current_url = current_url
12
+ end
13
+
14
+ #: -> bool
15
+ def render?
16
+ @trail.size > 1
17
+ end
18
+
19
+ private
20
+
21
+ #: -> String
22
+ def json_ld
23
+ items = @trail.each_with_index.map do |crumb, idx|
24
+ item = {
25
+ "@type" => "ListItem",
26
+ "position" => idx + 1,
27
+ "name" => crumb.text
28
+ }
29
+ item["item"] = crumb.href.present? ? "#{@base_url}#{crumb.href}" : @current_url
30
+ item
31
+ end
32
+
33
+ {
34
+ "@context" => "https://schema.org",
35
+ "@type" => "BreadcrumbList",
36
+ "itemListElement" => items
37
+ }.to_json
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ <nav class="<%= @html_class %>" aria-label="Breadcrumb">
2
+ <ul>
3
+ <% @trail.each_with_index do |crumb, idx| %>
4
+ <% current = @aria_current_on_last && idx == @trail.size - 1 %>
5
+ <%= render Motif::Breadcrumbs::CrumbComponent.new(
6
+ text: crumb.text,
7
+ href: current ? nil : crumb.href,
8
+ icon: crumb.icon,
9
+ current: current,
10
+ icon_renderer: @icon_renderer
11
+ ) %>
12
+ <% end %>
13
+ </ul>
14
+ </nav>
@@ -0,0 +1,28 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ class NavigationComponent < ViewComponent::Base
7
+ #: (
8
+ #| trail: Trail,
9
+ #| ?html_class: String,
10
+ #| ?aria_current_on_last: bool,
11
+ #| ?min_crumbs: Integer,
12
+ #| ?icon_renderer: ^(String) -> String | nil
13
+ #| ) -> void
14
+ def initialize(trail:, html_class: "breadcrumbs text-sm", aria_current_on_last: true, min_crumbs: 2, icon_renderer: nil)
15
+ @trail = trail
16
+ @html_class = html_class
17
+ @aria_current_on_last = aria_current_on_last
18
+ @min_crumbs = min_crumbs
19
+ @icon_renderer = icon_renderer
20
+ end
21
+
22
+ #: -> bool
23
+ def render?
24
+ @trail.size >= @min_crumbs
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ module Controller
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_action :initialize_breadcrumbs
11
+ helper_method :breadcrumbs
12
+ end
13
+
14
+ def initialize_breadcrumbs
15
+ @breadcrumbs = Trail.new
16
+ end
17
+
18
+ def breadcrumbs
19
+ @breadcrumbs
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ class Crumb
7
+ #: String
8
+ attr_reader :text
9
+
10
+ #: String?
11
+ attr_reader :href
12
+
13
+ #: String?
14
+ attr_reader :icon
15
+
16
+ #: (text: String, ?href: String?, ?icon: String?) -> void
17
+ def initialize(text:, href: nil, icon: nil)
18
+ @text = text
19
+ @href = href
20
+ @icon = icon
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ # typed: false
2
+
3
+ require "rails/railtie"
4
+
5
+ module Motif
6
+ module Breadcrumbs
7
+ class Railtie < ::Rails::Railtie
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ class Segment
7
+ #: -> void
8
+ def initialize
9
+ raise "#{self.class} is abstract and cannot be instantiated directly" if instance_of?(Segment)
10
+ end
11
+
12
+ #: -> Array[Crumb]
13
+ def to_crumbs
14
+ raise NotImplementedError
15
+ end
16
+
17
+ private
18
+
19
+ #: (String, ?String?, ?String?) -> Crumb
20
+ def crumb(text, href = nil, icon = nil)
21
+ Crumb.new(text: text, href: href, icon: icon)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,61 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Motif
5
+ module Breadcrumbs
6
+ class Trail
7
+ include Enumerable
8
+
9
+ #: Array[Crumb]
10
+ attr_reader :crumbs
11
+
12
+ #: -> void
13
+ def initialize
14
+ @crumbs = [] #: Array[Crumb]
15
+ end
16
+
17
+ #: (String | Segment, ?String?, ?String?) -> void
18
+ def push(text_or_segment, href = nil, icon = nil)
19
+ if text_or_segment.respond_to?(:to_crumbs)
20
+ @crumbs.concat(text_or_segment.to_crumbs)
21
+ else
22
+ @crumbs.push(Crumb.new(text: text_or_segment, href: href, icon: icon))
23
+ end
24
+ end
25
+
26
+ #: -> Crumb?
27
+ def pop
28
+ @crumbs.pop
29
+ end
30
+
31
+ def each(&block)
32
+ @crumbs.each(&block)
33
+ end
34
+
35
+ #: -> Integer
36
+ def size
37
+ @crumbs.size
38
+ end
39
+
40
+ #: -> bool
41
+ def any?
42
+ @crumbs.any?
43
+ end
44
+
45
+ #: -> bool
46
+ def empty?
47
+ @crumbs.empty?
48
+ end
49
+
50
+ #: -> Crumb?
51
+ def last
52
+ @crumbs.last
53
+ end
54
+
55
+ #: (Integer) -> Crumb?
56
+ def [](index)
57
+ @crumbs[index]
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ require "motif/breadcrumbs/railtie"
4
+
5
+ require "motif/breadcrumbs/controller"
6
+ require "motif/breadcrumbs/crumb"
7
+ require "motif/breadcrumbs/segment"
8
+ require "motif/breadcrumbs/trail"
9
+
10
+ require "motif/breadcrumbs/components/navigation_component"
11
+ require "motif/breadcrumbs/components/crumb_component"
12
+ require "motif/breadcrumbs/components/json_ld_component"
13
+
14
+ module Motif
15
+ module Breadcrumbs
16
+ # Your code goes here...
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motif-breadcrumbs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Marc Weistroff
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.0.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 8.0.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: view_component
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '3.12'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec-rails
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '7.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: capybara
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ description: A Rails Engine providing breadcrumb trail management, ViewComponents
69
+ for rendering, and schema.org JSON-LD structured data.
70
+ email:
71
+ - marc@weistroff.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - Rakefile
78
+ - lib/motif/breadcrumbs.rb
79
+ - lib/motif/breadcrumbs/components/crumb_component.html.erb
80
+ - lib/motif/breadcrumbs/components/crumb_component.rb
81
+ - lib/motif/breadcrumbs/components/json_ld_component.html.erb
82
+ - lib/motif/breadcrumbs/components/json_ld_component.rb
83
+ - lib/motif/breadcrumbs/components/navigation_component.html.erb
84
+ - lib/motif/breadcrumbs/components/navigation_component.rb
85
+ - lib/motif/breadcrumbs/controller.rb
86
+ - lib/motif/breadcrumbs/crumb.rb
87
+ - lib/motif/breadcrumbs/railtie.rb
88
+ - lib/motif/breadcrumbs/segment.rb
89
+ - lib/motif/breadcrumbs/trail.rb
90
+ homepage: https://github.com/marcw/motif
91
+ licenses:
92
+ - MIT
93
+ metadata:
94
+ allowed_push_host: https://rubygems.org
95
+ homepage_uri: https://github.com/marcw/motif
96
+ source_code_uri: https://github.com/marcw/motif
97
+ changelog_uri: https://github.com/marcw/motif/blob/main/CHANGELOG.md
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.6.9
113
+ specification_version: 4
114
+ summary: Breadcrumb trails for Rails applications
115
+ test_files: []