proscenium 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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