orbital_ui 0.3.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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +224 -0
  4. data/app/assets/config/orbital_ui_manifest.js +2 -0
  5. data/app/assets/javascripts/orbital_ui/index.js +6 -0
  6. data/app/assets/javascripts/orbital_ui/index.js.map +1 -0
  7. data/app/assets/stylesheets/orbital_ui/index.css +3 -0
  8. data/app/assets/stylesheets/orbital_ui/index.css.map +1 -0
  9. data/app/assets/stylesheets/orbital_ui/theme.css +113 -0
  10. data/app/assets/stylesheets/orbital_ui/themes/blue.css +43 -0
  11. data/app/assets/stylesheets/orbital_ui/themes/green.css +43 -0
  12. data/app/assets/stylesheets/orbital_ui/themes/orange.css +43 -0
  13. data/app/assets/stylesheets/orbital_ui/themes/rose.css +43 -0
  14. data/app/assets/stylesheets/orbital_ui/themes/violet.css +43 -0
  15. data/app/components/orbital_ui/accordion/item.rb +32 -0
  16. data/app/components/orbital_ui/accordion.rb +23 -0
  17. data/app/components/orbital_ui/alert.rb +80 -0
  18. data/app/components/orbital_ui/avatar.rb +87 -0
  19. data/app/components/orbital_ui/badge.rb +28 -0
  20. data/app/components/orbital_ui/button.rb +46 -0
  21. data/app/components/orbital_ui/button_group.rb +20 -0
  22. data/app/components/orbital_ui/card/action.rb +17 -0
  23. data/app/components/orbital_ui/card/content.rb +17 -0
  24. data/app/components/orbital_ui/card/description.rb +17 -0
  25. data/app/components/orbital_ui/card/footer.rb +17 -0
  26. data/app/components/orbital_ui/card/header.rb +25 -0
  27. data/app/components/orbital_ui/card/title.rb +17 -0
  28. data/app/components/orbital_ui/card.rb +30 -0
  29. data/app/components/orbital_ui/check_box.rb +59 -0
  30. data/app/components/orbital_ui/component.rb +12 -0
  31. data/app/components/orbital_ui/concerns/icon_prop.rb +14 -0
  32. data/app/components/orbital_ui/confirmation_dialog.rb +142 -0
  33. data/app/components/orbital_ui/delete_dialog.rb +143 -0
  34. data/app/components/orbital_ui/dialog.rb +46 -0
  35. data/app/components/orbital_ui/dialog_frame.rb +17 -0
  36. data/app/components/orbital_ui/dropdown.rb +58 -0
  37. data/app/components/orbital_ui/expander.rb +20 -0
  38. data/app/components/orbital_ui/grid.rb +22 -0
  39. data/app/components/orbital_ui/heading.rb +65 -0
  40. data/app/components/orbital_ui/icon.rb +21 -0
  41. data/app/components/orbital_ui/image.rb +26 -0
  42. data/app/components/orbital_ui/inline_stack.rb +31 -0
  43. data/app/components/orbital_ui/kbd.rb +15 -0
  44. data/app/components/orbital_ui/kbd_group.rb +15 -0
  45. data/app/components/orbital_ui/menu/item.rb +74 -0
  46. data/app/components/orbital_ui/menu/label.rb +20 -0
  47. data/app/components/orbital_ui/menu/separator.rb +21 -0
  48. data/app/components/orbital_ui/menu/sub.rb +68 -0
  49. data/app/components/orbital_ui/menu.rb +36 -0
  50. data/app/components/orbital_ui/modal.rb +36 -0
  51. data/app/components/orbital_ui/modal_frame.rb +17 -0
  52. data/app/components/orbital_ui/navigation_menu.rb +51 -0
  53. data/app/components/orbital_ui/popcard.rb +18 -0
  54. data/app/components/orbital_ui/popover.rb +50 -0
  55. data/app/components/orbital_ui/progress_bar.rb +33 -0
  56. data/app/components/orbital_ui/prose.rb +15 -0
  57. data/app/components/orbital_ui/select.rb +140 -0
  58. data/app/components/orbital_ui/separator.rb +22 -0
  59. data/app/components/orbital_ui/spinner.rb +14 -0
  60. data/app/components/orbital_ui/stack.rb +29 -0
  61. data/app/components/orbital_ui/text.rb +41 -0
  62. data/app/components/orbital_ui/text_field.rb +132 -0
  63. data/app/components/orbital_ui/tooltip.rb +42 -0
  64. data/app/components/orbital_ui/typography.rb +38 -0
  65. data/lib/orbital_ui/attribute_utils.rb +152 -0
  66. data/lib/orbital_ui/components/attributes.rb +164 -0
  67. data/lib/orbital_ui/components/styles.rb +40 -0
  68. data/lib/orbital_ui/engine.rb +7 -0
  69. data/lib/orbital_ui/icons.rb +17312 -0
  70. data/lib/orbital_ui/type/any.rb +17 -0
  71. data/lib/orbital_ui/type/array.rb +27 -0
  72. data/lib/orbital_ui/type/breakpoints.rb +46 -0
  73. data/lib/orbital_ui/type/symbol.rb +17 -0
  74. data/lib/orbital_ui/version.rb +5 -0
  75. data/lib/orbital_ui.rb +49 -0
  76. metadata +162 -0
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class OrbitalUi::Text < OrbitalUi::Component
4
+ SIZE_CLASSES = { xs: "text-body-xs", sm: "text-body-sm", lg: "text-body-lg", xl: "text-body-xl" }.freeze
5
+ WEIGHT_CLASSES = { light: "font-light", medium: "font-medium", semibold: "font-semibold", bold: "font-bold" }.freeze
6
+
7
+ attribute :size, :symbol, only: %i[xs sm md lg xl], default: :md
8
+ attribute :weight, :symbol, only: %i[light normal medium semibold bold], default: :normal
9
+ attribute :align, :symbol, only: %i[left center right], default: :left
10
+ attribute :tone, :symbol, only: %i[default subdued success warning danger info magic], default: :default
11
+ attribute :as, :string, default: "span"
12
+
13
+ def call
14
+ content_tag @as, **html_attributes do
15
+ content
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def default_attributes
22
+ attrs = super.merge(
23
+ class: class_names(
24
+ 'Orbital-Text',
25
+ size_classes,
26
+ weight_classes
27
+ )
28
+ )
29
+ attrs[:"data-tone"] = @tone unless @tone == :default
30
+ attrs[:"data-align"] = @align unless @align == :left
31
+ attrs
32
+ end
33
+
34
+ def size_classes
35
+ SIZE_CLASSES[@size]
36
+ end
37
+
38
+ def weight_classes
39
+ WEIGHT_CLASSES[@weight]
40
+ end
41
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrbitalUi
4
+ class TextField < Component
5
+ attribute :type, :string, default: "text"
6
+ attribute :disabled, :boolean, default: false
7
+ attribute :readonly, :boolean, default: false
8
+ attribute :name, :string, default: nil
9
+ attribute :value, :string, default: nil
10
+ attribute :label, :string, default: nil
11
+ attribute :labelHidden, :boolean, default: false
12
+ attribute :iconLeft, :any, default: nil
13
+ attribute :iconRight, :any, default: nil
14
+ attribute :prefix, :string, default: nil
15
+ attribute :suffix, :string, default: nil
16
+ attribute :multiline, :integer, default: nil
17
+ attribute :required, :boolean, default: false
18
+ attribute :placeholder, :string, default: nil
19
+ attribute :help, :string, default: nil
20
+ attribute :error, :string, default: nil
21
+ attribute :autofocus, :boolean, default: false
22
+ attribute :fullwidth, :boolean, default: false
23
+ attribute :rightAligned, :boolean, default: false
24
+ attribute :resizable, :boolean, default: false
25
+
26
+ orb_template <<-ORB
27
+ <div class="Orbital-Field" data-fullwidth={@fullwidth || nil}>
28
+ <label for={input_id} class="Orbital-Field-Label" data-hidden={@labelHidden || nil} :if={render_label?}>
29
+ <Text size="sm" weight=medium>{{label_text}}</Text>
30
+ </label>
31
+ <div class="Orbital-Input relative flex items-center">
32
+ <div class="Orbital-Input-Icon" data-position="left" :if={@iconLeft || @prefix}>
33
+ <Icon name={@iconLeft} size=md class="p-1 text-gray-300" :if={@iconLeft} />
34
+ <Text tone="subdued" :if={@prefix}>{{ @prefix }}</Text>
35
+ </div>
36
+ {{ render_input }}
37
+ <div class="Orbital-Input-Icon" data-position="right" :if={@iconRight || @suffix}>
38
+ <Icon name={@iconRight} size=md class="p-1 text-gray-300" :if={@iconRight} />
39
+ <Text tone="subdued" :if={@suffix}>{{ @suffix }}</Text>
40
+ </div>
41
+ </div>
42
+ <div class="Orbital-Field-Help leading-none" :if={help? || error?}>
43
+ {% if error? %}
44
+ <Text size="sm" weight=medium tone="danger" class="leading-none!">{{ @error }}</Text>
45
+ {% else %}
46
+ <Text size="sm" tone="subdued" class="leading-none!">{{ @help }}</Text>
47
+ {% end %}
48
+ </div>
49
+ </div>
50
+ ORB
51
+
52
+ private
53
+
54
+ def label_classes
55
+ "Orbital-Field-Label"
56
+ end
57
+
58
+ def label_text
59
+ @label || @name.to_s.humanize
60
+ end
61
+
62
+ def render_label?
63
+ (@label.present? || @name.present?) && !@labelHidden
64
+ end
65
+
66
+ def help?
67
+ @help.present?
68
+ end
69
+
70
+ def error?
71
+ @error.present?
72
+ end
73
+
74
+ def render_input
75
+ common_opts = {
76
+ id: input_id,
77
+ name: @name,
78
+ value: @value,
79
+ disabled: @disabled,
80
+ readonly: @readonly,
81
+ required: @required,
82
+ placeholder: @placeholder,
83
+ autofocus: @autofocus,
84
+ autocomplete: "off",
85
+ class: input_classes,
86
+ "data-error": error? || nil,
87
+ "data-align": @rightAligned ? "right" : nil
88
+ }
89
+
90
+ if @multiline
91
+ tag.textarea(**common_opts, rows: @multiline)
92
+ else
93
+ tag.input(type: @type, **common_opts)
94
+ end
95
+ end
96
+
97
+ def input_padding_left
98
+ if @iconLeft && @prefix
99
+ "pl-11"
100
+ elsif @iconLeft || @prefix
101
+ "pl-8"
102
+ else
103
+ "pl-2"
104
+ end
105
+ end
106
+
107
+ def input_padding_right
108
+ if @iconRight && @suffix
109
+ "pr-10"
110
+ elsif @iconRight || @suffix
111
+ "pr-8"
112
+ else
113
+ "pr-2"
114
+ end
115
+ end
116
+
117
+ def input_classes
118
+ padding_left = input_padding_left
119
+ padding_right = input_padding_right
120
+
121
+ class_names(
122
+ padding_left,
123
+ padding_right,
124
+ "noresize" => !@resizable
125
+ )
126
+ end
127
+
128
+ def input_id
129
+ @id ||= "input-#{SecureRandom.hex(4)}"
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrbitalUi
4
+ class Tooltip < Component
5
+ attribute :id, :string, default: -> { "popover-#{SecureRandom.hex(4)}" }
6
+ attribute :on, :symbol, default: :hover, only: [:click, :hover]
7
+ attribute :position, :symbol, default: :n, only: [:auto, :n, :ne, :e, :se, :s, :sw, :w, :nw]
8
+ attribute :text, :string, default: ""
9
+
10
+ orb_template <<-ORB
11
+ <div **html_attributes>
12
+ <div data-orbital-popover-target="trigger">
13
+ {{content}}
14
+ </div>
15
+ <div
16
+ popover
17
+ id={@id}
18
+ data-orbital-popover-target="content"
19
+ class="Orbital-Tooltip-Content"
20
+ role="tooltip"
21
+ data-position={@position}>
22
+ {{@text}}
23
+ </div>
24
+ </div>
25
+ ORB
26
+
27
+ private
28
+
29
+ def default_attributes
30
+ super.merge(
31
+ class: "Orbital-Tooltip",
32
+ data: {
33
+ 'controller' => "orbital-popover",
34
+ 'orbital-popover-trigger-value' => @on,
35
+ 'orbital-popover-position-value' => @position,
36
+ 'orbital-popover-show-delay-value' => 200,
37
+ 'orbital-popover-hide-delay-value' => 0
38
+ }
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrbitalUi
4
+ class Typography < Component
5
+ TAG_MAPPING = {
6
+ h1: :h1,
7
+ h2: :h2,
8
+ h3: :h3,
9
+ h4: :h4,
10
+ p: :p,
11
+ blockquote: :blockquote,
12
+ list: :ul,
13
+ lead: :p,
14
+ large: :div,
15
+ small: :small,
16
+ muted: :p
17
+ }.freeze
18
+
19
+ attribute :variant, :symbol, default: :p, only: TAG_MAPPING.keys
20
+
21
+ def call
22
+ content_tag(tag_name, content, **html_attributes)
23
+ end
24
+
25
+ private
26
+
27
+ def tag_name
28
+ TAG_MAPPING[@variant] || :p
29
+ end
30
+
31
+ def default_attributes
32
+ super.merge(
33
+ class: "Orbital-Typography",
34
+ "data-variant": @variant
35
+ )
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module OrbitalUi
6
+ module AttributeUtils
7
+ # DEFAULT_MERGEABLE_ATTRIBUTES is a list of HTML attributes where the value
8
+ # contain multiple elements separated by spaces. We use it to target values
9
+ # to split so the arrays can be cleanly merged.
10
+ #
11
+ # They are stored as nested arrays so when we're walking multiple
12
+ # levels on the deep merge the structure can be identified. This means
13
+ # the library works with the Rails-preferred format of
14
+ # `aria: { describedby: "xyz" }` rather than `"aria-describedby" => "xyz"`
15
+ DEFAULT_MERGEABLE_ATTRIBUTES = [
16
+ %i[class],
17
+ %i[style],
18
+ %i[aria controls],
19
+ %i[aria describedby],
20
+ %i[aria flowto],
21
+ %i[aria labelledby],
22
+ %i[data aria_controls],
23
+ %i[data action],
24
+ %i[data controller],
25
+ %i[aria owns],
26
+ %i[rel]
27
+ ].freeze
28
+
29
+ refine Hash do
30
+ # Merge the incoming hash into the current one in a way that suits
31
+ # HTML attributes.
32
+ #
33
+ # @param custom [Hash] the incoming hash
34
+ # @param parents [Array] used to keep track of the keys that have already been
35
+ # merged, *don't supply this unless the method is called during recursion*
36
+ # @mergeable_attributes [Array<Array<Symbol>>] Mergeable attributes are
37
+ # HTML attributes can contain lists made from space-separated strings. We
38
+ # convert them to arrays so they can be cleanly merged. Rails accepts them
39
+ # as arrays so there's no need to convert back to strings.
40
+ #
41
+ # @example
42
+ # original = { class: "red", data: { size: "medium", controller: "comment" } }
43
+ # incoming = { class: "blue", data: { controller: "reply" } }
44
+ #
45
+ # original.merge_html_attributes(incoming)
46
+ #
47
+ # => {:class=>["red", "blue"], :data=>{:size=>"medium", :controller=>"reply comment"}}
48
+ #
49
+ def merge_html_attributes(custom, parents = [], mergeable_attributes: DEFAULT_MERGEABLE_ATTRIBUTES)
50
+ return custom unless custom.is_a?(Hash)
51
+
52
+ overrides = custom.deep_symbolize_keys
53
+ originals = deep_symbolize_keys
54
+
55
+ originals.each_with_object(originals) do |(key, value), merged|
56
+ next unless overrides.key?(key)
57
+
58
+ merged[key] =
59
+ process_pair(key, value, parents: parents, overrides: overrides, mergeable_attributes: mergeable_attributes)
60
+
61
+ overrides.delete(key)
62
+ end.merge(overrides)
63
+ end
64
+
65
+ # Remove unwanted attributes from a the hash and any values that are hashes
66
+ # recursively. In particular we don't care for empty hashes, arrays,
67
+ # strings that are empty or just contain spaces and nils.
68
+ #
69
+ # It preserves +true+ and +false+.
70
+ #
71
+ # @return [Hash] the tidied hash
72
+ # @example
73
+ # { class: "blue", title: nil, lang: "", aria: { describedby: [] } }.deep_tidy
74
+ #
75
+ # => { class: "blue" }
76
+ #
77
+ def deep_tidy
78
+ each_with_object({}) { |(k, v), tidied| (tidied[k] = tidy_value(v)) unless v.nil? }.compact
79
+ end
80
+
81
+ private
82
+
83
+ def process_pair(key, value, parents:, overrides:, mergeable_attributes:)
84
+ combine_values(
85
+ value,
86
+ overrides[key],
87
+ parents: [*parents, *key],
88
+ mergeable_attributes: mergeable_attributes
89
+ )
90
+ end
91
+
92
+ def tidy_value(value)
93
+ case value
94
+ when TrueClass, FalseClass then value
95
+ when Hash then tidy_hash(value)
96
+ when Array then tidy_array(value)
97
+ else tidy_remaining(value)
98
+ end
99
+ end
100
+
101
+ def tidy_hash(hash)
102
+ return nil if hash.empty?
103
+
104
+ hash.deep_tidy
105
+ end
106
+
107
+ def tidy_array(array)
108
+ return nil if array.empty?
109
+
110
+ array.filter_map { |v| tidy_value(v) }
111
+ end
112
+
113
+ def tidy_remaining(value)
114
+ # Strip empty strings - Rails converts them to bare attributes
115
+ # for example:
116
+ # tag.div(hidden: "", data: { turbo_stream: "" })
117
+ # -> <div hidden data-turbo-stream></div>
118
+ value.to_s.strip.presence
119
+ end
120
+
121
+ def combine_values(value, override, **kwargs)
122
+ case split_attribute_list(value, **kwargs)
123
+ when Array
124
+ combine_array(value, override, **kwargs)
125
+ when Hash
126
+ combine_hash(value, override, **kwargs)
127
+ else
128
+ split_attribute_list(override, **kwargs)
129
+ end
130
+ end
131
+
132
+ def combine_array(originals, overrides, parents:, mergeable_attributes:)
133
+ return overrides if overrides.nil?
134
+ return overrides unless mergeable_attributes.include?(parents)
135
+
136
+ (try_split(originals) + try_split(overrides)).uniq
137
+ end
138
+
139
+ def try_split(value)
140
+ value.is_a?(String) ? value.split : value
141
+ end
142
+
143
+ def combine_hash(originals, overrides, parents:, mergeable_attributes:)
144
+ originals.merge_html_attributes(overrides, parents, mergeable_attributes: mergeable_attributes)
145
+ end
146
+
147
+ def split_attribute_list(value, parents:, mergeable_attributes:)
148
+ mergeable_attributes.include?(parents) ? try_split(value) : value
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrbitalUi
4
+ module Components
5
+ module Attributes
6
+ extend ActiveSupport::Concern
7
+
8
+ using OrbitalUi::AttributeUtils
9
+
10
+ included do
11
+ ActiveModel::Type.register(:any, OrbitalUi::Type::Any)
12
+ ActiveModel::Type.register(:array, OrbitalUi::Type::Array)
13
+ ActiveModel::Type.register(:symbol, OrbitalUi::Type::Symbol)
14
+ ActiveModel::Type.register(:breakpoints, OrbitalUi::Type::Breakpoints)
15
+ end
16
+
17
+ module ClassMethods
18
+ ##
19
+ # Define an attribute for the component
20
+ #
21
+ # Example:
22
+ # attribute :label, :string, default: 'More'
23
+ #
24
+ # @param name [Symbol] the name of the attribute
25
+ # @param type [Symbol] the type of the attribute
26
+ # @param default [Object] the default value of the attribute
27
+ # @param options [Hash] additional options for the attribute
28
+ def attribute(name, type = nil, default: nil, **options)
29
+ # A redefined attribute replaces the inherited one. If both stayed, initialize would
30
+ # process it twice, and the second pass would reset the caller's value to the default.
31
+ attribute_definitions.reject! { |definition| definition[:name] == name }
32
+ attribute_definitions << { name: name, type: type, default: default, **options }
33
+ end
34
+
35
+ ##
36
+ # Used to store the attribute definitions for the component
37
+ def attribute_definitions
38
+ @attribute_definitions ||= if superclass.respond_to?(:attribute_definitions)
39
+ superclass.attribute_definitions.dup
40
+ else
41
+ []
42
+ end
43
+ end
44
+ end
45
+
46
+ def initialize(kwargs = {})
47
+ # Set the default values for the attributes, or the values passed in the keyword arguments
48
+ self.class.attribute_definitions.each do |definition|
49
+ name = definition[:name]
50
+ default_value = __default_value_for_attribute_definition(definition)
51
+ raw_value = kwargs.delete(name)
52
+
53
+ raise "Missing required attribute :#{name}" if raw_value.nil? && definition[:required]
54
+
55
+ value = ActiveModel::Type.lookup(definition[:type]).cast(raw_value)
56
+
57
+ # Assign the default value to a new instance variable
58
+ instance_variable_set(:"@#{name}", default_value)
59
+
60
+ # Define a getter method for the attribute
61
+ define_singleton_method(:"attr_#{name}") { instance_variable_get(:"@#{name}") }
62
+
63
+ # Assign the value passed in the keyword arguments to the instance variable if possible
64
+ next if value.nil?
65
+
66
+ if definition[:only]&.exclude?(value)
67
+ if definition[:unknown] == :raise
68
+ raise ArgumentError, "Invalid value for attribute #{name}: #{value.inspect}"
69
+ end
70
+
71
+ next
72
+
73
+ end
74
+ instance_variable_set(:"@#{name}", value)
75
+ end
76
+
77
+ # All remaining keyword arguments are stored in the @unmatched_attributes instance variable
78
+ instance_variable_set(:@unmatched_attributes, kwargs)
79
+ end
80
+
81
+ ##
82
+ # Returns the default attributes of the component as a hash
83
+ #
84
+ def default_attributes
85
+ {}
86
+ end
87
+
88
+ ##
89
+ # Returns the HTML attributes of the component as a hash
90
+ def html_attributes
91
+ __flatten_attributes(default_attributes.merge_html_attributes(@unmatched_attributes).deep_tidy)
92
+ end
93
+
94
+ ##
95
+ # Returns the attributes of the component as a hash
96
+ #
97
+ # @param include_options [Boolean] whether to include the options in the result
98
+ # @param flattened [Boolean] whether to flatten the result
99
+ # @return [Hash] the attributes of the component
100
+ def attrs(include_options: false, flattened: false)
101
+ result = {}
102
+ self.class.attribute_definitions.each do |definition|
103
+ name = definition[:name]
104
+ value = instance_variable_get(:"@#{name}")
105
+ result[name] = value
106
+ end
107
+
108
+ result.merge!(@unmatched_attributes || {}) if include_options
109
+ if flattened
110
+ __flatten_attributes(result)
111
+ else
112
+ result
113
+ end
114
+ end
115
+
116
+ ##
117
+ # Returns the options of the component as a hash
118
+ #
119
+ # The options are the keyword arguments passed to the component that were not
120
+ # matched against the defined attributes.
121
+ #
122
+ # @param flattened [Boolean] whether to flatten the result
123
+ # @return [Hash] the options of the component
124
+ def options(flattened: true)
125
+ result = @unmatched_attributes || {}
126
+ if flattened
127
+ __flatten_attributes(result)
128
+ else
129
+ result
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def __default_value_for_attribute_definition(definition)
136
+ default_value = definition.fetch(:default, nil)
137
+ return default_value if default_value.nil?
138
+
139
+ default_value = default_value.call if default_value.respond_to?(:call)
140
+ default_value
141
+ end
142
+
143
+ def __flatten_attributes(attributes)
144
+ flattened = {}
145
+
146
+ attributes.each do |key, value|
147
+ case value
148
+ when Hash
149
+ __flatten_attributes(value).each do |k, v|
150
+ if k.nil?
151
+ flattened[key] = v
152
+ else
153
+ flattened["#{key}-#{k.to_s.tr('_', '-')}"] = v
154
+ end
155
+ end
156
+ else
157
+ flattened[key] = value if value
158
+ end
159
+ end
160
+ flattened
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrbitalUi
4
+ module Components
5
+ module Styles
6
+ extend ActiveSupport::Concern
7
+
8
+ ##
9
+ # Join the given style names together
10
+ #
11
+ # This implementation follows the same pattern as the `class_names` method
12
+ # however, it does not split the values by spaces.
13
+ #
14
+ def style_names(*args)
15
+ styles = build_style_names(*args).flat_map { |value| CGI.unescape_html(value.to_s) }.uniq
16
+ safe_join(styles.push(''), ";")
17
+ end
18
+
19
+ private
20
+
21
+ def build_style_names(*args)
22
+ values = []
23
+ args.each do |arg|
24
+ case arg
25
+ when Hash
26
+ arg.each do |k, v|
27
+ values << k if v && k.present?
28
+ end
29
+ when Array
30
+ values.concat build_style_names(*arg)
31
+ else
32
+ values << arg.to_s if arg.present?
33
+ end
34
+ end
35
+
36
+ values
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrbitalUi
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace OrbitalUi
6
+ end
7
+ end