proscenium-ui 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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/lib/proscenium/ui/badge/index.css +75 -0
  5. data/lib/proscenium/ui/badge.rb +32 -0
  6. data/lib/proscenium/ui/breadcrumbs/computed_element.rb +71 -0
  7. data/lib/proscenium/ui/breadcrumbs/control.rb +103 -0
  8. data/lib/proscenium/ui/breadcrumbs/element.rb +16 -0
  9. data/lib/proscenium/ui/breadcrumbs/index.css +84 -0
  10. data/lib/proscenium/ui/breadcrumbs.rb +136 -0
  11. data/lib/proscenium/ui/combobox/index.css +162 -0
  12. data/lib/proscenium/ui/combobox/index.js +420 -0
  13. data/lib/proscenium/ui/combobox.rb +186 -0
  14. data/lib/proscenium/ui/component.rb +22 -0
  15. data/lib/proscenium/ui/flash/index.css +1 -0
  16. data/lib/proscenium/ui/flash/index.js +77 -0
  17. data/lib/proscenium/ui/flash.rb +15 -0
  18. data/lib/proscenium/ui/form/field_methods.rb +95 -0
  19. data/lib/proscenium/ui/form/fields/base.rb +189 -0
  20. data/lib/proscenium/ui/form/fields/checkbox/index.jsx +48 -0
  21. data/lib/proscenium/ui/form/fields/checkbox/index.module.css +9 -0
  22. data/lib/proscenium/ui/form/fields/checkbox/previews/basic.jsx +8 -0
  23. data/lib/proscenium/ui/form/fields/checkbox.rb +32 -0
  24. data/lib/proscenium/ui/form/fields/combobox.rb +117 -0
  25. data/lib/proscenium/ui/form/fields/date.module.css +27 -0
  26. data/lib/proscenium/ui/form/fields/datetime.rb +15 -0
  27. data/lib/proscenium/ui/form/fields/hidden.rb +9 -0
  28. data/lib/proscenium/ui/form/fields/input/index.jsx +71 -0
  29. data/lib/proscenium/ui/form/fields/input/index.module.css +13 -0
  30. data/lib/proscenium/ui/form/fields/input/previews/basic.jsx +8 -0
  31. data/lib/proscenium/ui/form/fields/input.rb +14 -0
  32. data/lib/proscenium/ui/form/fields/radio_group.rb +175 -0
  33. data/lib/proscenium/ui/form/fields/radio_input/index.jsx +44 -0
  34. data/lib/proscenium/ui/form/fields/radio_input/index.module.css +13 -0
  35. data/lib/proscenium/ui/form/fields/radio_input/previews/basic.jsx +8 -0
  36. data/lib/proscenium/ui/form/fields/radio_input.rb +17 -0
  37. data/lib/proscenium/ui/form/fields/rich_textarea.css +23 -0
  38. data/lib/proscenium/ui/form/fields/rich_textarea.js +6 -0
  39. data/lib/proscenium/ui/form/fields/rich_textarea.rb +18 -0
  40. data/lib/proscenium/ui/form/fields/select.jsx +47 -0
  41. data/lib/proscenium/ui/form/fields/select.module.css +46 -0
  42. data/lib/proscenium/ui/form/fields/select.rb +302 -0
  43. data/lib/proscenium/ui/form/fields/tel.css +297 -0
  44. data/lib/proscenium/ui/form/fields/tel.js +83 -0
  45. data/lib/proscenium/ui/form/fields/tel.rb +54 -0
  46. data/lib/proscenium/ui/form/fields/textarea/index.jsx +50 -0
  47. data/lib/proscenium/ui/form/fields/textarea/index.module.css +13 -0
  48. data/lib/proscenium/ui/form/fields/textarea/previews/basic.jsx +8 -0
  49. data/lib/proscenium/ui/form/fields/textarea.rb +18 -0
  50. data/lib/proscenium/ui/form/index.css +52 -0
  51. data/lib/proscenium/ui/form/translation.rb +71 -0
  52. data/lib/proscenium/ui/form.rb +197 -0
  53. data/lib/proscenium/ui/railtie.rb +23 -0
  54. data/lib/proscenium/ui/ujs/class.js +15 -0
  55. data/lib/proscenium/ui/ujs/data_confirm.js +23 -0
  56. data/lib/proscenium/ui/ujs/data_disable_with.js +68 -0
  57. data/lib/proscenium/ui/ujs/index.js +10 -0
  58. data/lib/proscenium/ui/version.rb +7 -0
  59. data/lib/proscenium/ui.rb +36 -0
  60. metadata +177 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ca2623aea45e90824299d7c043f1ed80814fc18dea6598c43a798cc9afa33a7f
4
+ data.tar.gz: 8c4140e7664ad61705fee670455761b2ccef3aa429b149c5733f14e374eff45a
5
+ SHA512:
6
+ metadata.gz: 83809d6e7fbe7a7bf033ef58a68468c690abe17034e1327ba326dea1d13218a571988a621c23ce339bab0ecb04345084c605cd658e20ec80cb35d9dd9e06ba8f
7
+ data.tar.gz: 407fee2ddd35600f3722b2abb7b3794288248c0b07ff666fb318555bbb51826e4c84c721b0e24c4750e6dbb07fdfc5100a3241f8b840b38d816664efa76340ce
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Joel Moss
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Proscenium::UI
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "proscenium-ui"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install proscenium-ui
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,75 @@
1
+ @layer pui {
2
+ pui-badge {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ vertical-align: baseline;
6
+ gap: 0.25em;
7
+ padding: 0.15em 0.5em;
8
+ border-radius: 0.25em;
9
+ font-size: 0.875em;
10
+ font-variant-numeric: tabular-nums;
11
+ line-height: 1.4;
12
+ white-space: nowrap;
13
+ background-color: var(--puiBadge--bg, color-mix(in srgb, currentColor 10%, transparent));
14
+ color: var(--puiBadge--color, inherit);
15
+ border: var(--puiBadge--border, 1px solid color-mix(in srgb, currentColor 20%, transparent));
16
+
17
+ &[data-size='sm'] {
18
+ font-size: 0.75em;
19
+ padding: 0.1em 0.35em;
20
+ }
21
+
22
+ &[data-size='lg'] {
23
+ font-size: 1em;
24
+ padding: 0.2em 0.6em;
25
+ }
26
+
27
+ &[data-variant='success'] {
28
+ background-color: var(
29
+ --puiBadge--success-bg,
30
+ color-mix(in srgb, light-dark(#15803d, #4ade80) 15%, transparent)
31
+ );
32
+ color: var(--puiBadge--success-color, light-dark(#15803d, #4ade80));
33
+ border-color: var(
34
+ --puiBadge--success-border-color,
35
+ color-mix(in srgb, light-dark(#15803d, #4ade80) 30%, transparent)
36
+ );
37
+ }
38
+
39
+ &[data-variant='warning'] {
40
+ background-color: var(
41
+ --puiBadge--warning-bg,
42
+ color-mix(in srgb, light-dark(#a16207, #fbbf24) 15%, transparent)
43
+ );
44
+ color: var(--puiBadge--warning-color, light-dark(#a16207, #fbbf24));
45
+ border-color: var(
46
+ --puiBadge--warning-border-color,
47
+ color-mix(in srgb, light-dark(#a16207, #fbbf24) 30%, transparent)
48
+ );
49
+ }
50
+
51
+ &[data-variant='danger'] {
52
+ background-color: var(
53
+ --puiBadge--danger-bg,
54
+ color-mix(in srgb, light-dark(#dc2626, #f87171) 15%, transparent)
55
+ );
56
+ color: var(--puiBadge--danger-color, light-dark(#dc2626, #f87171));
57
+ border-color: var(
58
+ --puiBadge--danger-border-color,
59
+ color-mix(in srgb, light-dark(#dc2626, #f87171) 30%, transparent)
60
+ );
61
+ }
62
+
63
+ &[data-variant='info'] {
64
+ background-color: var(
65
+ --puiBadge--info-bg,
66
+ color-mix(in srgb, light-dark(#2563eb, #60a5fa) 15%, transparent)
67
+ );
68
+ color: var(--puiBadge--info-color, light-dark(#2563eb, #60a5fa));
69
+ border-color: var(
70
+ --puiBadge--info-border-color,
71
+ color-mix(in srgb, light-dark(#2563eb, #60a5fa) 30%, transparent)
72
+ );
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI
4
+ class Badge < Component
5
+ register_element :pui_badge
6
+
7
+ # @param text [String] The text content of the badge.
8
+ prop :text, String, :positional
9
+
10
+ # @param variant [Symbol] The visual style variant. (:default, :success, :warning, :danger,
11
+ # :info)
12
+ prop :variant, _Union(:default, :success, :warning, :danger, :info), default: -> { :default }
13
+
14
+ # @param size [Symbol] The size of the badge. (:sm, :md, :lg)
15
+ prop :size, _Union(:sm, :md, :lg), default: -> { :md }
16
+
17
+ # @param rest [Hash] Additional HTML attributes passed to the root element.
18
+ prop :rest, Hash, :**
19
+
20
+ def self.source_path
21
+ super.sub_ext('').join('index.rb')
22
+ end
23
+
24
+ def view_template
25
+ data = {}
26
+ data[:variant] = @variant if @variant != :default
27
+ data[:size] = @size if @size != :md
28
+
29
+ pui_badge(**{ data: }.deep_merge(@rest)) { @text }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proscenium::UI::Breadcrumbs
4
+ class ComputedElement
5
+ def initialize(element, context)
6
+ @element = element
7
+ @context = context
8
+ end
9
+
10
+ delegate :options, to: :@element
11
+
12
+ # If name is a Symbol of a controller method, that method is called.
13
+ # If name is a Symbol of a controller instance variable, that variable is returned.
14
+ # If name is a Proc, it is executed in the context of the controller instance.
15
+ #
16
+ # @return [String] the content of the breadcrumb element.
17
+ def name
18
+ @name ||= case name = @element.name
19
+ when Symbol
20
+ if name.to_s.starts_with?('@')
21
+ name = get_instance_variable(name)
22
+ name.respond_to?(:for_breadcrumb) ? name.for_breadcrumb : name.to_s
23
+ else
24
+ res = @context.controller.send(name)
25
+ res.try(:for_breadcrumb) || res.to_s
26
+ end
27
+ when Proc
28
+ @context.controller.instance_exec(&name)
29
+ else
30
+ name.respond_to?(:for_breadcrumb) ? name.for_breadcrumb : name.to_s
31
+ end
32
+ end
33
+
34
+ # If path is a Symbol of a controller method, that method is called.
35
+ # If path is a Symbol of a controller instance variable, that variable is returned.
36
+ # If path is an Array, each element is processed as above.
37
+ # If path is a Proc, it is executed in the context of the controller instance.
38
+ #
39
+ # No matter what, the result is always passed to `url_for` before being returned.
40
+ #
41
+ # @return [String] the URL for the element
42
+ def path
43
+ @path ||= if !@element.path.nil?
44
+ case path = @element.path
45
+ when Array
46
+ path.map! { |x| x.to_s.starts_with?('@') ? get_instance_variable(x) : x }
47
+ when Symbol
48
+ if path.to_s.starts_with?('@')
49
+ path = get_instance_variable(path)
50
+ elsif @context.controller.respond_to?(path, true)
51
+ path = @context.controller.send(path)
52
+ end
53
+ when Proc
54
+ path = @context.controller.instance_exec(&path)
55
+ end
56
+
57
+ @context.url_for path
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def get_instance_variable(element)
64
+ if !@context.instance_variable_defined?(element)
65
+ raise NameError, "undefined instance variable `#{element}' for breadcrumb", caller
66
+ end
67
+
68
+ @context.instance_variable_get element
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proscenium::UI::Breadcrumbs
4
+ # Include this module in your controller to add support for adding breadcrumb elements. You can
5
+ # then use the `add_breadcrumb` and `prepend_breadcrumb` class methods to append and/or prepend
6
+ # breadcrumb elements.
7
+ module Control
8
+ extend ActiveSupport::Concern
9
+ include ActionView::Helpers::SanitizeHelper
10
+
11
+ included do
12
+ helper_method :breadcrumbs_as_json, :breadcrumbs_for_title if respond_to?(:helper_method)
13
+ end
14
+
15
+ module ClassMethods
16
+ # Appends a new breadcrumb element into the collection.
17
+ #
18
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
19
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
20
+ # breadcrumb.
21
+ # @param options [Hash] Filter options to pass through to the before_action filter. Options
22
+ # include :if, :unless, :only, and :except. Any options not included in the filter options
23
+ # will be passed as options to the breadcrumb element. For example, you could pass a :class
24
+ # option to add a CSS class to the breadcrumb element.
25
+ def add_breadcrumb(name, path = nil, **options)
26
+ filter_options = options.slice(:if, :unless, :only, :except)
27
+ element_options = options.except(:if, :unless, :only, :except)
28
+
29
+ before_action(filter_options) do |controller|
30
+ controller.send :add_breadcrumb, name, path, element_options
31
+ end
32
+ end
33
+
34
+ # Prepend a new breadcrumb element into the collection.
35
+ #
36
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
37
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
38
+ # breadcrumb.
39
+ # @param options [Hash] Filter options to pass through to the before_action filter. Options
40
+ # include :if, :unless, :only, and :except. Any options not included in the filter options
41
+ # will be passed as options to the breadcrumb element. For example, you could pass a :class
42
+ # option to add a CSS class to the breadcrumb element.
43
+ def prepend_breadcrumb(name, path = nil, **options)
44
+ filter_options = options.slice(:if, :unless, :only, :except)
45
+ element_options = options.except(:if, :unless, :only, :except)
46
+
47
+ before_action(filter_options) do |controller|
48
+ controller.send :prepend_breadcrumb, name, path, element_options
49
+ end
50
+ end
51
+ end
52
+
53
+ # Pushes a new breadcrumb element into the collection.
54
+ #
55
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
56
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
57
+ # breadcrumb.
58
+ # @param options [Hash]
59
+ def add_breadcrumb(name, path = nil, options = {})
60
+ breadcrumbs << Element.new(name, path, options)
61
+ end
62
+
63
+ # Prepend a new breadcrumb element into the collection.
64
+ #
65
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
66
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
67
+ # breadcrumb.
68
+ # @param options [Hash]
69
+ def prepend_breadcrumb(name, path = nil, options = {})
70
+ breadcrumbs.prepend Element.new(name, path, options)
71
+ end
72
+
73
+ def breadcrumbs
74
+ @breadcrumbs ||= []
75
+ end
76
+
77
+ def breadcrumbs_as_json
78
+ computed_breadcrumbs.map do |ele|
79
+ path = ele.path
80
+
81
+ { name: ele.name, path: ele.path.nil? || view_context.current_page?(path) ? nil : path }
82
+ end
83
+ end
84
+
85
+ # @param primary [Boolean] whether to return only the primary breadcrumb.
86
+ def breadcrumbs_for_title(primary: false)
87
+ names = computed_breadcrumbs.map(&:name)
88
+ return names.pop if primary
89
+
90
+ out = [names.pop]
91
+ out << names.join(': ') if !names.empty?
92
+ strip_tags out.join(' - ')
93
+ end
94
+
95
+ private
96
+
97
+ def computed_breadcrumbs
98
+ @computed_breadcrumbs ||= breadcrumbs.map do |ele|
99
+ ComputedElement.new ele, view_context
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents a navigation element in the breadcrumb collection.
4
+ class Proscenium::UI::Breadcrumbs::Element
5
+ attr_accessor :name, :path, :options
6
+
7
+ # @param name [String] the element/link name
8
+ # @param path [String] the element/link URL
9
+ # @param options [Hash] the element/link options
10
+ # @return [Element]
11
+ def initialize(name, path = nil, options = {})
12
+ self.name = name
13
+ self.path = path
14
+ self.options = options
15
+ end
16
+ end
@@ -0,0 +1,84 @@
1
+ @define-mixin _separator {
2
+ display: inline-block;
3
+ content: '';
4
+ height: 1em;
5
+ width: 1em;
6
+ -webkit-mask: var(--puiBreadcrumbs--separator, var(--_puiBreadcrumbs--separator)) no-repeat center
7
+ center;
8
+ mask: var(--puiBreadcrumbs--separator, var(--_puiBreadcrumbs--separator)) no-repeat center center;
9
+ vertical-align: sub;
10
+ background-color: var(--puiBreadcrumbs--separator-color, var(--_puiBreadcrumbs--separator-color));
11
+ }
12
+
13
+ @layer pui {
14
+ /*
15
+ * Properties:
16
+ *
17
+ * --puiBreadcrumbs--link-color: LinkText;
18
+ * --puiBreadcrumbs--link-hover-color: HighlightText;
19
+ * --puiBreadcrumbs--separator-color: GrayText;
20
+ * --puiBreadcrumbs--separator: url("...");
21
+ */
22
+
23
+ pui-breadcrumbs {
24
+ --_puiBreadcrumbs--separator-color: GrayText;
25
+ --_puiBreadcrumbs--separator: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z' clip-rule='evenodd'/%3E%3C/svg%3E");
26
+
27
+ display: flex;
28
+ flex-wrap: wrap;
29
+ align-items: center;
30
+
31
+ pui-breadcrumbs-home,
32
+ pui-breadcrumbs-element {
33
+ text-transform: uppercase;
34
+ display: flex;
35
+ align-items: center;
36
+
37
+ @media (max-width: 426px) {
38
+ &:not(:nth-last-child(2)) {
39
+ display: none;
40
+ }
41
+
42
+ &:nth-last-child(2)::before {
43
+ @mixin _separator;
44
+ margin: 0 0.5em 0 0;
45
+ transform: rotate(180deg);
46
+ }
47
+ }
48
+
49
+ @media (min-width: 427px) {
50
+ &:not(:last-child)::after {
51
+ @mixin _separator;
52
+ margin: 0 0.5em;
53
+ }
54
+ }
55
+
56
+ &:last-child,
57
+ &:last-child > a {
58
+ font-weight: 500;
59
+ text-transform: none;
60
+ }
61
+
62
+ a {
63
+ color: var(--puiBreadcrumbs--link-color, revert);
64
+ display: flex;
65
+ align-items: center;
66
+
67
+ &:hover {
68
+ color: var(--puiBreadcrumbs--link-hover-color, revert);
69
+ }
70
+
71
+ @media (pointer: coarse) {
72
+ min-height: 44px;
73
+ }
74
+ }
75
+ }
76
+
77
+ pui-breadcrumbs-home {
78
+ svg {
79
+ height: 1em;
80
+ width: 1em;
81
+ }
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI
4
+ # Provides breadcrumb functionality for controllers and views. Breadcrumbs are a type of
5
+ # navigation that show the user where they are in the application's hierarchy.
6
+ # The `Proscenium::UI::Breadcrumbs::Control` module provides the `add_breadcrumb` and
7
+ # `prepend_breadcrumb` class methods for adding breadcrumb elements, and is intended to be
8
+ # included in your controllers.
9
+ #
10
+ # The `add_breadcrumb` method adds a new breadcrumb element to the end of the collection, while
11
+ # the `prepend_breadcrumb` method adds a new breadcrumb element to the beginning of the
12
+ # collection. Both methods take a name, and path as arguments. The name argument is the name or
13
+ # content of the breadcrumb, while the path argument is the path (route) to use as the HREF for
14
+ # the breadcrumb.
15
+ #
16
+ # class UsersController < ApplicationController
17
+ # include Proscenium::UI::Breadcrumbs::Control
18
+ # add_breadcrumb 'Users', :users_path
19
+ # end
20
+ #
21
+ # Display the breadcrumbs in your view:
22
+ #
23
+ # render Proscenium::UI::Breadcrumbs
24
+ #
25
+ # At its simplest, you can add a breadcrumb with a name of "Users", and a path of "/users" like
26
+ # this:
27
+ #
28
+ # add_breadcrumb 'Users', '/users'
29
+ #
30
+ # The value of the path is always passed to `url_for` before being rendered. It is also optional,
31
+ # and if omitted, the breadcrumb will be rendered as plain text.
32
+ #
33
+ # Both name and path can be given a Symbol, which can be used to call a method of the same name on
34
+ # the controller. If a Symbol is given as the path, and no method of the same name exists, then
35
+ # `url_for` will be called with the Symbol as the argument. Likewise, if an Array is given as the
36
+ # path, then `url_for` will be called with the Array as the argument.
37
+ #
38
+ # If a Symbol is given as the path or name, and it begins with `@` (eg. `:@foo`), then the
39
+ # instance variable of the same name will be called.
40
+ #
41
+ # add_breadcrumb :@foo, :@bar
42
+ #
43
+ # A Proc can also be given as the name and/or path. The Proc will be called within the context of
44
+ # the controller.
45
+ #
46
+ # add_breadcrumb -> { @foo }, -> { @bar }
47
+ #
48
+ # Passing an object that responds to `#for_breadcrumb` as the name will call that method on the
49
+ # object to get the breadcrumb name.
50
+ #
51
+ class Breadcrumbs < Component
52
+ include Phlex::Rails::Helpers::URLFor
53
+
54
+ register_element :pui_breadcrumbs
55
+ register_element :pui_breadcrumbs_home
56
+ register_element :pui_breadcrumbs_element
57
+
58
+ # @param home_path [String, Symbol] The path (route) to use as the HREF for the home segment.
59
+ prop :home_path, _Union(String, Symbol), default: -> { :root }
60
+
61
+ # @param with_home [Boolean] Whether to show the home segment. Set to false to hide it.
62
+ prop :with_home, _Boolean, default: -> { true }
63
+
64
+ def self.source_path
65
+ super.sub_ext('').join('index.rb')
66
+ end
67
+
68
+ # TODO: For if and when we need to pass instance level css module.
69
+ #
70
+ # prop :css_module_path, _Nilable(Pathname)
71
+ #
72
+ # def css_module_path
73
+ # @css_module_path ||= self.class.css_module_path
74
+ # end
75
+
76
+ def view_template
77
+ pui_breadcrumbs do
78
+ if @with_home
79
+ pui_breadcrumbs_home do
80
+ yield if block_given?
81
+ home_template
82
+ end
83
+ end
84
+
85
+ breadcrumbs.each do |ce|
86
+ pui_breadcrumbs_element(**ce.options) do
87
+ path = ce.path
88
+ path.nil? ? ce.name : a(href: url_for(path)) { ce.name }
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Override this to customise the home breadcrumb. You can call super with a block to use the
95
+ # default template, but with custom content.
96
+ #
97
+ # @example
98
+ # def home_template
99
+ # super { 'hello' }
100
+ # end
101
+ def home_template(&block)
102
+ a href: url_for(@home_path) do
103
+ if block
104
+ yield
105
+ else
106
+ svg role: 'img', aria_label: 'Home', xmlns: 'http://www.w3.org/2000/svg',
107
+ viewBox: '0 0 24 24', fill: 'currentColor' do |s|
108
+ s.path d: <<~SVG.delete("\n")
109
+ M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689
110
+ -8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.69Z
111
+ SVG
112
+ s.path d: <<~SVG.delete("\n")
113
+ M12 5.432l8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875
114
+ 1.875H15.75a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0
115
+ 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198
116
+ a2.29 2.29 0 0 0 .091-.086L12 5.432Z
117
+ SVG
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # Don't render if @hide_breadcrumbs is true.
124
+ def render?
125
+ controller.instance_variable_get(:@hide_breadcrumbs) != true
126
+ end
127
+
128
+ private
129
+
130
+ def breadcrumbs
131
+ controller.breadcrumbs.map do |e|
132
+ Breadcrumbs::ComputedElement.new e, view_context
133
+ end
134
+ end
135
+ end
136
+ end