proscenium 0.16.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +84 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +908 -0
  5. data/lib/proscenium/builder.rb +189 -0
  6. data/lib/proscenium/core_ext/object/css_module_ivars.rb +19 -0
  7. data/lib/proscenium/css_module/path.rb +31 -0
  8. data/lib/proscenium/css_module/rewriter.rb +44 -0
  9. data/lib/proscenium/css_module/transformer.rb +84 -0
  10. data/lib/proscenium/css_module.rb +57 -0
  11. data/lib/proscenium/ensure_loaded.rb +27 -0
  12. data/lib/proscenium/ext/proscenium +0 -0
  13. data/lib/proscenium/ext/proscenium.h +131 -0
  14. data/lib/proscenium/helper.rb +70 -0
  15. data/lib/proscenium/importer.rb +134 -0
  16. data/lib/proscenium/libs/custom_element.js +54 -0
  17. data/lib/proscenium/libs/react-manager/index.jsx +121 -0
  18. data/lib/proscenium/libs/react-manager/react.js +2 -0
  19. data/lib/proscenium/libs/stimulus-loading.js +65 -0
  20. data/lib/proscenium/libs/test.js +1 -0
  21. data/lib/proscenium/libs/ujs/class.js +15 -0
  22. data/lib/proscenium/libs/ujs/data_confirm.js +23 -0
  23. data/lib/proscenium/libs/ujs/data_disable_with.js +68 -0
  24. data/lib/proscenium/libs/ujs/index.js +9 -0
  25. data/lib/proscenium/log_subscriber.rb +37 -0
  26. data/lib/proscenium/middleware/base.rb +103 -0
  27. data/lib/proscenium/middleware/engines.rb +45 -0
  28. data/lib/proscenium/middleware/esbuild.rb +30 -0
  29. data/lib/proscenium/middleware/runtime.rb +18 -0
  30. data/lib/proscenium/middleware/url.rb +16 -0
  31. data/lib/proscenium/middleware.rb +76 -0
  32. data/lib/proscenium/monkey.rb +95 -0
  33. data/lib/proscenium/phlex/asset_inclusions.rb +17 -0
  34. data/lib/proscenium/phlex/css_modules.rb +79 -0
  35. data/lib/proscenium/phlex/react_component.rb +32 -0
  36. data/lib/proscenium/phlex.rb +42 -0
  37. data/lib/proscenium/railtie.rb +106 -0
  38. data/lib/proscenium/react_componentable.rb +95 -0
  39. data/lib/proscenium/resolver.rb +39 -0
  40. data/lib/proscenium/side_load.rb +155 -0
  41. data/lib/proscenium/source_path.rb +15 -0
  42. data/lib/proscenium/templates/rescues/build_error.html.erb +30 -0
  43. data/lib/proscenium/ui/breadcrumbs/component.module.css +14 -0
  44. data/lib/proscenium/ui/breadcrumbs/component.rb +79 -0
  45. data/lib/proscenium/ui/breadcrumbs/computed_element.rb +69 -0
  46. data/lib/proscenium/ui/breadcrumbs/control.rb +95 -0
  47. data/lib/proscenium/ui/breadcrumbs/mixins.css +83 -0
  48. data/lib/proscenium/ui/breadcrumbs.rb +72 -0
  49. data/lib/proscenium/ui/component.rb +11 -0
  50. data/lib/proscenium/ui/test.js +1 -0
  51. data/lib/proscenium/ui.rb +14 -0
  52. data/lib/proscenium/utils.rb +13 -0
  53. data/lib/proscenium/version.rb +5 -0
  54. data/lib/proscenium/view_component/css_modules.rb +11 -0
  55. data/lib/proscenium/view_component/react_component.rb +22 -0
  56. data/lib/proscenium/view_component/sideload.rb +4 -0
  57. data/lib/proscenium/view_component.rb +38 -0
  58. data/lib/proscenium.rb +70 -0
  59. metadata +228 -0
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class SideLoad
5
+ module Controller
6
+ def self.included(child)
7
+ child.class_eval do
8
+ class_attribute :sideload_assets_options
9
+ child.extend ClassMethods
10
+
11
+ append_after_action :capture_and_replace_proscenium_stylesheets,
12
+ :capture_and_replace_proscenium_javascripts,
13
+ if: -> { response.content_type&.include?('html') }
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def sideload_assets(value)
19
+ self.sideload_assets_options = value
20
+ end
21
+ end
22
+
23
+ def capture_and_replace_proscenium_stylesheets
24
+ return if response_body.nil?
25
+ return if response_body.first.blank? || !Proscenium::Importer.css_imported?
26
+ return unless response_body.first.include? '<!-- [PROSCENIUM_STYLESHEETS] -->'
27
+
28
+ imports = Proscenium::Importer.imported.dup
29
+ paths_to_build = []
30
+ Proscenium::Importer.each_stylesheet(delete: true) do |x, _|
31
+ paths_to_build << x.delete_prefix('/')
32
+ end
33
+
34
+ result = Proscenium::Builder.build_to_path(paths_to_build.join(';'),
35
+ base_url: helpers.request.base_url)
36
+
37
+ out = []
38
+ result.split(';').each do |x|
39
+ inpath, outpath = x.split('::')
40
+ inpath.prepend '/'
41
+ outpath.delete_prefix! 'public'
42
+
43
+ next unless imports.key?(inpath)
44
+
45
+ import = imports[inpath]
46
+ opts = import[:css].is_a?(Hash) ? import[:css] : {}
47
+ opts[:data] ||= {}
48
+ opts[:data][:original_href] = inpath
49
+ out << helpers.stylesheet_link_tag(outpath, extname: false, **opts)
50
+ end
51
+
52
+ response_body.first.gsub! '<!-- [PROSCENIUM_STYLESHEETS] -->', out.join.html_safe
53
+ end
54
+
55
+ def capture_and_replace_proscenium_javascripts
56
+ return if response_body.nil?
57
+ return if response_body.first.blank? || !Proscenium::Importer.js_imported?
58
+
59
+ imports = Proscenium::Importer.imported.dup
60
+ paths_to_build = []
61
+ Proscenium::Importer.each_javascript(delete: true) do |x, _|
62
+ paths_to_build << x.delete_prefix('/')
63
+ end
64
+
65
+ result = Proscenium::Builder.build_to_path(paths_to_build.join(';'),
66
+ base_url: helpers.request.base_url)
67
+
68
+ if response_body.first.include? '<!-- [PROSCENIUM_JAVASCRIPTS] -->'
69
+ out = []
70
+ scripts = {}
71
+ result.split(';').each do |x|
72
+ inpath, outpath = x.split('::')
73
+ inpath.prepend '/'
74
+ outpath.delete_prefix! 'public'
75
+
76
+ next unless imports.key?(inpath)
77
+
78
+ if (import = imports[inpath]).delete(:lazy)
79
+ scripts[inpath] = import.merge(outpath:)
80
+ else
81
+ opts = import[:js].is_a?(Hash) ? import[:js] : {}
82
+ out << helpers.javascript_include_tag(outpath, extname: false, **opts)
83
+ end
84
+ end
85
+
86
+ response_body.first.gsub! '<!-- [PROSCENIUM_JAVASCRIPTS] -->', out.join.html_safe
87
+ end
88
+
89
+ return unless response_body.first.include? '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->'
90
+
91
+ lazy_script = ''
92
+ if scripts.present?
93
+ lazy_script = helpers.content_tag 'script', type: 'application/json',
94
+ id: 'prosceniumLazyScripts' do
95
+ scripts.to_json.html_safe
96
+ end
97
+ end
98
+
99
+ response_body.first.gsub! '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->', lazy_script
100
+ end
101
+ end
102
+
103
+ class << self
104
+ # Side loads the class, and its super classes that respond to `.source_path`.
105
+ #
106
+ # Set the `abstract_class` class variable to true in any class, and it will not be side
107
+ # loaded.
108
+ #
109
+ # If the class responds to `.sideload`, it will be called instead of the regular side loading.
110
+ # You can use this to customise what is side loaded.
111
+ def sideload_inheritance_chain(obj, options)
112
+ return unless Proscenium.config.side_load
113
+
114
+ options = {} if options.nil?
115
+ options = { js: options, css: options } unless options.is_a?(Hash)
116
+
117
+ unless obj.sideload_assets_options.nil?
118
+ tpl_options = obj.sideload_assets_options
119
+ options = if tpl_options.is_a?(Hash)
120
+ options.deep_merge tpl_options
121
+ else
122
+ { js: tpl_options, css: tpl_options }
123
+ end
124
+ end
125
+
126
+ %i[css js].each do |k|
127
+ options[k] = obj.instance_eval(&options[k]) if options[k].is_a?(Proc)
128
+ end
129
+
130
+ css_imports = []
131
+
132
+ klass = obj.class
133
+ while klass.respond_to?(:source_path) && klass.source_path && !klass.abstract_class
134
+ if klass.respond_to?(:sideload)
135
+ klass.sideload options
136
+ elsif options[:css] == false
137
+ Importer.sideload klass.source_path, **options
138
+ else
139
+ Importer.sideload_js klass.source_path, **options
140
+ css_imports << klass.source_path
141
+ end
142
+
143
+ klass = klass.superclass
144
+ end
145
+
146
+ # The reason why we sideload CSS after JS is because the order of CSS is important.
147
+ # Basically, the layout should be loaded before the view so that CSS cascading works i9n the
148
+ # right direction.
149
+ css_imports.reverse_each do |it|
150
+ Importer.sideload_css it, **options
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Include this into any class to expose a `source_path` class and instance method, which will return
4
+ # the absolute file system path to the current object.
5
+ module Proscenium::SourcePath
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def source_path
12
+ @source_path ||= name.nil? ? nil : Pathname.new(const_source_location(name).first)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ <header>
2
+ <h1>
3
+ <%= Rails.gem_version >= Gem::Version.new('7.1.0') ? @exception_wrapper.exception_class_name : @exception.class.to_s %>
4
+ <% if params_valid? && @request.parameters['controller'] %>
5
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
6
+ <% end %>
7
+ </h1>
8
+ </header>
9
+
10
+ <main role="main" id="container">
11
+ <%= render "rescues/message_and_suggestions", exception: @exception, exception_wrapper: Rails.gem_version >= Gem::Version.new('7.1.0') ? @exception_wrapper : nil %>
12
+
13
+ <% if @exception.error['location'] %>
14
+ <div class="source">
15
+ <div class="data">
16
+ <pre>
17
+
18
+ <%= @exception.error['location']['file'] %>:<%= @exception.error['location']['line'] %>:<%= @exception.error['location']['column'] %>
19
+
20
+ <%= @exception.error['location']['line'].to_s.rjust 5 %> │ <%= @exception.error['location']['line_text'] %>
21
+ │ <%= (@exception.error['location']['length'] > 1 ? "~" * @exception.error['location']['length'] : "^").rjust(@exception.error['location']['column'] + @exception.error['location']['length']) %>
22
+ <%- if @exception.error['location']['suggestion'].present? -%> + │ <%= @exception.error['location']['suggestion'].rjust(@exception.error['location']['column'] + 1) %>
23
+ <% else %> <%- end -%>
24
+ </pre>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+
29
+ <%= render template: "rescues/_request_and_response" %>
30
+ </main>
@@ -0,0 +1,14 @@
1
+ @layer proscenium-ui-component {
2
+ /*
3
+ * Custom properties:
4
+ *
5
+ * --puiBreadcrumbs--link-color: LinkText;
6
+ * --puiBreadcrumbs--link-hover-color: HighlightText;
7
+ * --puiBreadcrumbs--separator-color: GrayText;
8
+ * --puiBreadcrumbs--separator: url("/proscenium/icons/angle-right-regular.svg");
9
+ */
10
+
11
+ .base {
12
+ @mixin breadcrumbs from url("./mixins.css");
13
+ }
14
+ }
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI
4
+ class Breadcrumbs::Component < Component
5
+ include Phlex::Rails::Helpers::URLFor
6
+
7
+ # The path (route) to use as the HREF for the home segment. Defaults to `:root`.
8
+ option :home_path, Types::String | Types::Symbol, default: -> { :root }
9
+
10
+ # Assign false to hide the home segment.
11
+ option :with_home, Types::Bool, default: -> { true }
12
+
13
+ # One or more class name(s) for the base div element which will be appended to the default.
14
+ option :class, Types::Coercible::String | Types::Array.of(Types::Coercible::String),
15
+ as: :class_name, default: -> { [] }
16
+
17
+ # One or more class name(s) for the base div element which will replace the default. If both
18
+ # `class` and `class!` are provided, all values will be merged. Defaults to `:@base`.
19
+ option :class!, Types::Coercible::String | Types::Array.of(Types::Coercible::String),
20
+ as: :class_name_override, default: -> { :@base }
21
+
22
+ def view_template
23
+ div class: [*class_name_override, *class_name] do
24
+ ol do
25
+ if with_home
26
+ li do
27
+ home_template
28
+ end
29
+ end
30
+
31
+ breadcrumbs.each do |ce|
32
+ li do
33
+ path = ce.path
34
+ path.nil? ? ce.name : a(href: url_for(path)) { ce.name }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Override this to customise the home breadcrumb. You can call super with a block to use the
44
+ # default template, but with custom content.
45
+ #
46
+ # @example
47
+ # def home_template
48
+ # super { 'hello' }
49
+ # end
50
+ def home_template(&block)
51
+ a(href: url_for(home_path)) do
52
+ if block
53
+ yield
54
+ else
55
+ svg role: 'img', xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 576 512' do |s|
56
+ s.path fill: 'currentColor',
57
+ d: 'M488 312.7V456c0 13.3-10.7 24-24 24H348c-6.6 0-12-5.4-12-12V356c0-6.6-5.4-' \
58
+ '12-12-12h-72c-6.6 0-12 5.4-12 12v112c0 6.6-5.4 12-12 12H112c-13.3 0-24-10.' \
59
+ '7-24-24V312.7c0-3.6 1.6-7 4.4-9.3l188-154.8c4.4-3.6 10.8-3.6 15.3 0l188 15' \
60
+ '4.8c2.7 2.3 4.3 5.7 4.3 9.3zm83.6-60.9L488 182.9V44.4c0-6.6-5.4-12-12-12h-' \
61
+ '56c-6.6 0-12 5.4-12 12V117l-89.5-73.7c-17.7-14.6-43.3-14.6-61 0L4.4 251.8c' \
62
+ '-5.1 4.2-5.8 11.8-1.6 16.9l25.5 31c4.2 5.1 11.8 5.8 16.9 1.6l235.2-193.7c4' \
63
+ '.4-3.6 10.8-3.6 15.3 0l235.2 193.7c5.1 4.2 12.7 3.5 16.9-1.6l25.5-31c4.2-5' \
64
+ '.2 3.4-12.7-1.7-16.9z'
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # Don't render if @hide_breadcrumbs is true.
71
+ def render?
72
+ helpers.assigns['hide_breadcrumbs'] != true
73
+ end
74
+
75
+ def breadcrumbs
76
+ helpers.controller.breadcrumbs.map { |e| Breadcrumbs::ComputedElement.new e, helpers }
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI::Breadcrumbs
4
+ class ComputedElement
5
+ def initialize(element, context)
6
+ @element = element
7
+ @context = context
8
+ end
9
+
10
+ # If name is a Symbol of a controller method, that method is called.
11
+ # If name is a Symbol of a controller instance variable, that variable is returned.
12
+ # If name is a Proc, it is executed in the context of the controller instance.
13
+ #
14
+ # @return [String] the content of the breadcrumb element.
15
+ def name
16
+ @name ||= case name = @element.name
17
+ when Symbol
18
+ if name.to_s.starts_with?('@')
19
+ name = get_instance_variable(name)
20
+ name.respond_to?(:for_breadcrumb) ? name.for_breadcrumb : name.to_s
21
+ else
22
+ res = @context.controller.send(name)
23
+ res.try(:for_breadcrumb) || res.to_s
24
+ end
25
+ when Proc
26
+ @context.controller.instance_exec(&name)
27
+ else
28
+ name.respond_to?(:for_breadcrumb) ? name.for_breadcrumb : name.to_s
29
+ end
30
+ end
31
+
32
+ # If path is a Symbol of a controller method, that method is called.
33
+ # If path is a Symbol of a controller instance variable, that variable is returned.
34
+ # If path is an Array, each element is processed as above.
35
+ # If path is a Proc, it is executed in the context of the controller instance.
36
+ #
37
+ # No matter what, the result is always passed to `url_for` before being returned.
38
+ #
39
+ # @return [String] the URL for the element
40
+ def path
41
+ @path ||= unless @element.path.nil?
42
+ case path = @element.path
43
+ when Array
44
+ path.map! { |x| x.to_s.starts_with?('@') ? get_instance_variable(x) : x }
45
+ when Symbol
46
+ if path.to_s.starts_with?('@')
47
+ path = get_instance_variable(path)
48
+ elsif @context.controller.respond_to?(path, true)
49
+ path = @context.controller.send(path)
50
+ end
51
+ when Proc
52
+ path = @context.controller.instance_exec(&path)
53
+ end
54
+
55
+ @context.url_for path
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def get_instance_variable(element)
62
+ unless @context.instance_variable_defined?(element)
63
+ raise NameError, "undefined instance variable `#{element}' for breadcrumb", caller
64
+ end
65
+
66
+ @context.instance_variable_get element
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module 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 filter_options [Hash] Options to pass through to the before_action filter.
22
+ def add_breadcrumb(name, path = nil, **filter_options)
23
+ element_options = filter_options.delete(:options) || {}
24
+
25
+ before_action(filter_options) do |controller|
26
+ controller.send :add_breadcrumb, name, path, element_options
27
+ end
28
+ end
29
+
30
+ # Prepend a new breadcrumb element into the collection.
31
+ #
32
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
33
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
34
+ # breadcrumb.
35
+ # @param filter_options [Hash] Options to pass through to the before_action filter.
36
+ def prepend_breadcrumb(name, path = nil, **filter_options)
37
+ element_options = filter_options.delete(:options) || {}
38
+
39
+ before_action(filter_options) do |controller|
40
+ controller.send :prepend_breadcrumb, name, path, element_options
41
+ end
42
+ end
43
+ end
44
+
45
+ # Pushes a new breadcrumb element into the collection.
46
+ #
47
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
48
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
49
+ # breadcrumb.
50
+ # @param options [Hash]
51
+ def add_breadcrumb(name, path = nil, options = {})
52
+ breadcrumbs << Element.new(name, path, options)
53
+ end
54
+
55
+ # Prepend a new breadcrumb element into the collection.
56
+ #
57
+ # @param name [String, Symbol, Proc, #for_breadcrumb] The name or content of the breadcrumb.
58
+ # @param path [String, Symbol, Array, Proc, nil] The path (route) to use as the HREF for the
59
+ # breadcrumb.
60
+ # @param options [Hash]
61
+ def prepend_breadcrumb(name, path = nil, options = {})
62
+ breadcrumbs.prepend Element.new(name, path, options)
63
+ end
64
+
65
+ def breadcrumbs
66
+ @breadcrumbs ||= []
67
+ end
68
+
69
+ def breadcrumbs_as_json
70
+ computed_breadcrumbs.map do |ele|
71
+ path = ele.path
72
+
73
+ { name: ele.name, path: ele.path.nil? || helpers.current_page?(path) ? nil : path }
74
+ end
75
+ end
76
+
77
+ # @param primary [Boolean] whether to return only the primary breadcrumb.
78
+ def breadcrumbs_for_title(primary: false)
79
+ names = computed_breadcrumbs.map(&:name)
80
+ return names.pop if primary
81
+
82
+ out = [names.pop]
83
+ out << names.join(': ') unless names.empty?
84
+ strip_tags out.join(' - ')
85
+ end
86
+
87
+ private
88
+
89
+ def computed_breadcrumbs
90
+ @computed_breadcrumbs ||= breadcrumbs.map do |ele|
91
+ ComputedElement.new ele, helpers
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,83 @@
1
+ @define-mixin breadcrumbs {
2
+ /* Default properties */
3
+ --_puiBreadcrumbs--separator-color: GrayText;
4
+ --_puiBreadcrumbs--separator: url("/proscenium/icons/angle-right-regular.svg");
5
+
6
+ margin: 10px;
7
+
8
+ ol {
9
+ list-style: none;
10
+ padding: 0;
11
+ margin: 0;
12
+ display: flex;
13
+ align-items: baseline;
14
+
15
+ li {
16
+ text-transform: uppercase;
17
+ display: flex;
18
+ align-items: center;
19
+
20
+ @media (max-width: 426px) {
21
+ &:not(:nth-last-child(2)) {
22
+ display: none;
23
+ }
24
+
25
+ &:nth-last-child(2)::before {
26
+ @mixin _separator;
27
+ margin: 0 0.5rem 0 0;
28
+ transform: rotate(180deg);
29
+ }
30
+ }
31
+
32
+ @media (min-width: 427px) {
33
+ &:not(:last-child)::after {
34
+ @mixin _separator;
35
+ margin: 0 0.5rem;
36
+ }
37
+ }
38
+
39
+ &:last-child {
40
+ font-weight: 500;
41
+ text-transform: none;
42
+ }
43
+
44
+ &:last-child > a {
45
+ font-weight: 500;
46
+ text-transform: none;
47
+ }
48
+
49
+ a {
50
+ color: var(--puiBreadcrumbs--link-color, revert);
51
+ display: flex;
52
+
53
+ &:hover {
54
+ color: var(--puiBreadcrumbs--link-hover-color, revert);
55
+ }
56
+ }
57
+
58
+ svg {
59
+ height: 1em;
60
+ width: 1em;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ @define-mixin _separator {
67
+ display: inline-block;
68
+ content: "";
69
+ height: 1rem;
70
+ width: 1rem;
71
+ -webkit-mask: var(
72
+ --puiBreadcrumbs--separator,
73
+ var(--_puiBreadcrumbs--separator)
74
+ )
75
+ no-repeat center center;
76
+ mask: var(--puiBreadcrumbs--separator, var(--_puiBreadcrumbs--separator))
77
+ no-repeat center center;
78
+ vertical-align: sub;
79
+ background-color: var(
80
+ --puiBreadcrumbs--separator-color,
81
+ var(--_puiBreadcrumbs--separator-color)
82
+ );
83
+ }
@@ -0,0 +1,72 @@
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 views with the breadcrumbs component.
22
+ # @see `Proscenium::UI::Breadcrumbs::Component`.
23
+ #
24
+ # At it's simplest, you can add a breadcrumb with a name of "User", and a path of "/users" like
25
+ # this:
26
+ #
27
+ # add_breadcrumb 'Foo', '/foo'
28
+ #
29
+ # The value of the path is always passed to `url_for` before being rendered. It is also optional,
30
+ # and if omitted, the breadcrumb will be rendered as plain text.
31
+ #
32
+ # Both name and path can be given a Symbol, which can be used to call a method of the same name on
33
+ # the controller. If a Symbol is given as the path, and no method of the same name exists, then
34
+ # `url_for` will be called with the Symbol as the argument. Likewise, if an Array is given as the
35
+ # path, then `url_for` will be called with the Array as the argument.
36
+ #
37
+ # If a Symbol is given as the path or name, and it begins with `@` (eg. `:@foo`), then the
38
+ # instance variable of the same name will be called.
39
+ #
40
+ # add_breadcrumb :@foo, :@bar
41
+ #
42
+ # A Proc can also be given as the name and/or path. The Proc will be called within the context of
43
+ # the controller.
44
+ #
45
+ # add_breadcrumb -> { @foo }, -> { @bar }
46
+ #
47
+ # Passing an object that responds to `#for_breadcrumb` as the name will call that method on the
48
+ # object to get the breadcrumb name.
49
+ #
50
+ module Breadcrumbs
51
+ extend ActiveSupport::Autoload
52
+
53
+ autoload :Control
54
+ autoload :ComputedElement
55
+ autoload :Component
56
+
57
+ # Represents a navigation element in the breadcrumb collection.
58
+ class Element
59
+ attr_accessor :name, :path, :options
60
+
61
+ # @param name [String] the element/link name
62
+ # @param path [String] the element/link URL
63
+ # @param options [Hash] the element/link options
64
+ # @return [Element]
65
+ def initialize(name, path = nil, options = {})
66
+ self.name = name
67
+ self.path = path
68
+ self.options = options
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module Proscenium::UI
6
+ class Component < Proscenium::Phlex
7
+ self.abstract_class = true
8
+
9
+ extend Dry::Initializer
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ console.log("/proscenium/ui/test.js");
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-types'
4
+
5
+ module Proscenium::UI
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :Component
9
+ autoload :Breadcrumbs
10
+
11
+ module Types
12
+ include Dry.Types()
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module Utils
5
+ module_function
6
+
7
+ # @param value [#to_s] The value to create the digest from. This will usually be a `Pathname`.
8
+ # @return [String] digest of the given value.
9
+ def digest(value)
10
+ Digest::SHA1.hexdigest(value.to_s)[..7]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ VERSION = '0.16.0'
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module ViewComponent::CssModules
5
+ include Proscenium::CssModule
6
+
7
+ def self.included(base)
8
+ base.extend CssModule::Path
9
+ end
10
+ end
11
+ end