phlexi-display 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/phlexi/display/base.rb +35 -158
- data/lib/phlexi/display/components/base.rb +1 -14
- data/lib/phlexi/display/components/concerns/displays_value.rb +54 -0
- data/lib/phlexi/display/components/date_time.rb +49 -0
- data/lib/phlexi/display/components/{error.rb → description.rb} +5 -5
- data/lib/phlexi/display/components/hint.rb +1 -1
- data/lib/phlexi/display/components/label.rb +3 -15
- data/lib/phlexi/display/components/number.rb +37 -0
- data/lib/phlexi/display/components/placeholder.rb +15 -0
- data/lib/phlexi/display/components/string.rb +17 -0
- data/lib/phlexi/display/components/wrapper.rb +4 -18
- data/lib/phlexi/display/field_options/associations.rb +2 -2
- data/lib/phlexi/display/field_options/attachments.rb +21 -0
- data/lib/phlexi/display/field_options/description.rb +22 -0
- data/lib/phlexi/display/field_options/hints.rb +1 -1
- data/lib/phlexi/display/field_options/inferred_types.rb +26 -52
- data/lib/phlexi/display/field_options/{placeholder.rb → placeholders.rb} +2 -2
- data/lib/phlexi/display/field_options/themes.rb +45 -120
- data/lib/phlexi/display/structure/dom.rb +7 -27
- data/lib/phlexi/display/structure/field_builder.rb +75 -151
- data/lib/phlexi/display/structure/field_collection.rb +5 -20
- data/lib/phlexi/display/structure/namespace.rb +22 -34
- data/lib/phlexi/display/structure/namespace_collection.rb +1 -9
- data/lib/phlexi/display/structure/node.rb +9 -3
- data/lib/phlexi/display/version.rb +1 -1
- data/lib/phlexi/display.rb +0 -1
- metadata +11 -31
- data/lib/phlexi/display/components/checkbox.rb +0 -48
- data/lib/phlexi/display/components/collection_checkboxes.rb +0 -44
- data/lib/phlexi/display/components/collection_radio_buttons.rb +0 -35
- data/lib/phlexi/display/components/concerns/handles_array_input.rb +0 -21
- data/lib/phlexi/display/components/concerns/handles_input.rb +0 -53
- data/lib/phlexi/display/components/concerns/has_options.rb +0 -37
- data/lib/phlexi/display/components/concerns/submits_form.rb +0 -39
- data/lib/phlexi/display/components/file_input.rb +0 -32
- data/lib/phlexi/display/components/full_error.rb +0 -21
- data/lib/phlexi/display/components/input.rb +0 -84
- data/lib/phlexi/display/components/input_array.rb +0 -45
- data/lib/phlexi/display/components/radio_button.rb +0 -41
- data/lib/phlexi/display/components/select.rb +0 -69
- data/lib/phlexi/display/components/submit_button.rb +0 -41
- data/lib/phlexi/display/components/textarea.rb +0 -34
- data/lib/phlexi/display/field_options/autofocus.rb +0 -18
- data/lib/phlexi/display/field_options/collection.rb +0 -54
- data/lib/phlexi/display/field_options/disabled.rb +0 -18
- data/lib/phlexi/display/field_options/errors.rb +0 -92
- data/lib/phlexi/display/field_options/length.rb +0 -53
- data/lib/phlexi/display/field_options/limit.rb +0 -66
- data/lib/phlexi/display/field_options/min_max.rb +0 -92
- data/lib/phlexi/display/field_options/multiple.rb +0 -65
- data/lib/phlexi/display/field_options/pattern.rb +0 -38
- data/lib/phlexi/display/field_options/readonly.rb +0 -18
- data/lib/phlexi/display/field_options/required.rb +0 -37
- data/lib/phlexi/display/field_options/validators.rb +0 -48
@@ -10,47 +10,32 @@ module Phlexi
|
|
10
10
|
@inferred_db_type ||= infer_db_type
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
@
|
15
|
-
end
|
16
|
-
|
17
|
-
def inferred_input_type
|
18
|
-
@inferred_input_type ||= infer_input_type(inferred_input_component)
|
13
|
+
def inferred_display_component
|
14
|
+
@inferred_display_component ||= infer_display_component
|
19
15
|
end
|
20
16
|
|
21
17
|
private
|
22
18
|
|
23
|
-
|
24
|
-
# one of :input, :textarea, :select, :botton
|
25
|
-
def infer_input_component
|
26
|
-
return :select unless collection.blank?
|
27
|
-
|
19
|
+
def infer_display_component
|
28
20
|
case inferred_db_type
|
29
|
-
when :
|
30
|
-
|
31
|
-
else
|
32
|
-
:input
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# this only applies when input_component is `:input`
|
37
|
-
# resolves the type attribute of input components
|
38
|
-
def infer_input_type(component)
|
39
|
-
case inferred_db_type
|
40
|
-
when :string
|
41
|
-
infer_string_input_type(key)
|
21
|
+
when :string, :text
|
22
|
+
infer_string_display_type(key)
|
42
23
|
when :integer, :float, :decimal
|
43
24
|
:number
|
44
|
-
when :date
|
25
|
+
when :date, :datetime, :time
|
45
26
|
:date
|
46
|
-
when :datetime
|
47
|
-
:datetime
|
48
|
-
when :time
|
49
|
-
:time
|
50
27
|
when :boolean
|
51
|
-
:
|
28
|
+
:boolean
|
29
|
+
when :json, :jsonb, :hstore
|
30
|
+
:code
|
52
31
|
else
|
53
|
-
|
32
|
+
if association_reflection
|
33
|
+
:association
|
34
|
+
elsif attachment_reflection
|
35
|
+
:attachment
|
36
|
+
else
|
37
|
+
:text
|
38
|
+
end
|
54
39
|
end
|
55
40
|
end
|
56
41
|
|
@@ -81,40 +66,39 @@ module Phlexi
|
|
81
66
|
case value
|
82
67
|
when Integer
|
83
68
|
:integer
|
84
|
-
when Float
|
69
|
+
when Float
|
85
70
|
:float
|
71
|
+
when BigDecimal
|
72
|
+
:decimal
|
86
73
|
when TrueClass, FalseClass
|
87
74
|
:boolean
|
88
75
|
when Date
|
89
76
|
:date
|
90
77
|
when Time, DateTime
|
91
78
|
:datetime
|
79
|
+
when Hash
|
80
|
+
:json
|
92
81
|
else
|
93
82
|
:string
|
94
83
|
end
|
95
84
|
end
|
96
85
|
|
97
|
-
def
|
86
|
+
def infer_string_display_type(key)
|
98
87
|
key = key.to_s.downcase
|
99
88
|
|
100
89
|
return :password if is_password_field?
|
101
90
|
|
102
|
-
custom_type =
|
91
|
+
custom_type = custom_string_display_type(key)
|
103
92
|
return custom_type if custom_type
|
104
93
|
|
105
|
-
|
106
|
-
infer_string_input_type_from_validations
|
107
|
-
else
|
108
|
-
:text
|
109
|
-
end
|
94
|
+
:text
|
110
95
|
end
|
111
96
|
|
112
|
-
def
|
97
|
+
def custom_string_display_type(key)
|
113
98
|
custom_mappings = {
|
114
99
|
/url$|^link|^site/ => :url,
|
115
100
|
/^email/ => :email,
|
116
|
-
|
117
|
-
/phone|tel(ephone)?/ => :tel,
|
101
|
+
/phone|tel(ephone)?/ => :phone,
|
118
102
|
/^time/ => :time,
|
119
103
|
/^date/ => :date,
|
120
104
|
/^number|_count$|_amount$/ => :number,
|
@@ -128,16 +112,6 @@ module Phlexi
|
|
128
112
|
nil
|
129
113
|
end
|
130
114
|
|
131
|
-
def infer_string_input_type_from_validations
|
132
|
-
if attribute_validators.find { |v| v.kind == :numericality }
|
133
|
-
:number
|
134
|
-
elsif attribute_validators.find { |v| v.kind == :format && v.options[:with] == URI::MailTo::EMAIL_REGEXP }
|
135
|
-
:email
|
136
|
-
else
|
137
|
-
:text
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
115
|
def is_password_field?
|
142
116
|
key = self.key.to_s.downcase
|
143
117
|
|
@@ -3,10 +3,10 @@
|
|
3
3
|
module Phlexi
|
4
4
|
module Display
|
5
5
|
module FieldOptions
|
6
|
-
module
|
6
|
+
module Placeholders
|
7
7
|
def placeholder(placeholder = nil)
|
8
8
|
if placeholder.nil?
|
9
|
-
options[:placeholder]
|
9
|
+
options[:placeholder] || "-"
|
10
10
|
else
|
11
11
|
options[:placeholder] = placeholder
|
12
12
|
self
|
@@ -4,41 +4,33 @@ module Phlexi
|
|
4
4
|
module Display
|
5
5
|
module FieldOptions
|
6
6
|
module Themes
|
7
|
-
# Resolves theme classes for components based on their type
|
7
|
+
# Resolves theme classes for components based on their type.
|
8
8
|
#
|
9
|
-
# This method is responsible for determining the appropriate CSS classes for a given
|
10
|
-
# It
|
11
|
-
# component's validity state (valid, invalid, or neutral). The method supports a hierarchical
|
12
|
-
# theming system, allowing for cascading themes and easy customization.
|
9
|
+
# This method is responsible for determining the appropriate CSS classes for a given display component.
|
10
|
+
# It supports a hierarchical theming system, allowing for cascading themes and easy customization.
|
13
11
|
#
|
14
|
-
# @param component [Symbol, String] The type of
|
12
|
+
# @param component [Symbol, String] The type of display component (e.g., :text, :date, :boolean)
|
15
13
|
#
|
16
14
|
# @return [String, nil] A string of CSS classes for the component, or nil if no theme is applied
|
17
15
|
#
|
18
16
|
# @example Basic usage
|
19
|
-
# themed(:
|
20
|
-
# # => "
|
21
|
-
#
|
22
|
-
# @example Usage with validity state
|
23
|
-
# # Assuming the field has errors
|
24
|
-
# themed(:input)
|
25
|
-
# # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700 bg-red-50 border-red-500 text-red-900"
|
17
|
+
# themed(:text)
|
18
|
+
# # => "text-gray-700 text-sm"
|
26
19
|
#
|
27
20
|
# @example Cascading themes
|
28
|
-
# # Assuming
|
29
|
-
# themed(:
|
30
|
-
# # => "
|
21
|
+
# # Assuming email inherits from text in the theme definition
|
22
|
+
# themed(:email)
|
23
|
+
# # => "text-gray-700 text-sm text-blue-600 underline"
|
31
24
|
#
|
32
25
|
# @note The actual CSS classes returned will depend on the theme definitions in the `theme` hash
|
33
26
|
# and any overrides specified in the `options` hash.
|
34
27
|
#
|
35
28
|
# @see #resolve_theme
|
36
|
-
# @see #resolve_validity_theme
|
37
29
|
# @see #theme
|
38
30
|
def themed(component)
|
39
31
|
return unless component
|
40
32
|
|
41
|
-
|
33
|
+
resolve_theme(component)
|
42
34
|
end
|
43
35
|
|
44
36
|
protected
|
@@ -50,9 +42,9 @@ module Phlexi
|
|
50
42
|
# @return [String, nil] The resolved theme value or nil if not found
|
51
43
|
#
|
52
44
|
# @example Resolving a nested theme
|
53
|
-
# # Assuming the theme is: {
|
54
|
-
# resolve_theme(:
|
55
|
-
# # => "
|
45
|
+
# # Assuming the theme is: { email: :text, text: "text-gray-700" }
|
46
|
+
# resolve_theme(:email)
|
47
|
+
# # => "text-gray-700"
|
56
48
|
def resolve_theme(property, visited = Set.new)
|
57
49
|
return nil if !property.present? || visited.include?(property)
|
58
50
|
visited.add(property)
|
@@ -65,52 +57,23 @@ module Phlexi
|
|
65
57
|
end
|
66
58
|
end
|
67
59
|
|
68
|
-
#
|
69
|
-
#
|
70
|
-
# This method determines the validity state of the field (valid, invalid, or neutral)
|
71
|
-
# and returns the corresponding theme by prepending the state to the component name.
|
72
|
-
#
|
73
|
-
# @param property [Symbol, String] The base theme property to resolve
|
74
|
-
# @return [String, nil] The resolved validity-specific theme or nil if not found
|
75
|
-
#
|
76
|
-
# @example Resolving a validity theme
|
77
|
-
# # Assuming the field has errors and the theme includes { invalid_input: "error-class" }
|
78
|
-
# resolve_validity_theme(:input)
|
79
|
-
# # => "error-class"
|
80
|
-
def resolve_validity_theme(property)
|
81
|
-
validity_property = if has_errors?
|
82
|
-
:"invalid_#{property}"
|
83
|
-
elsif object_valid?
|
84
|
-
:"valid_#{property}"
|
85
|
-
else
|
86
|
-
:"neutral_#{property}"
|
87
|
-
end
|
88
|
-
|
89
|
-
resolve_theme(validity_property)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Retrieves or initializes the theme hash for the form builder.
|
60
|
+
# Retrieves or initializes the theme hash for the display builder.
|
93
61
|
#
|
94
|
-
# This method returns a hash containing theme definitions for various
|
62
|
+
# This method returns a hash containing theme definitions for various display components.
|
95
63
|
# If a theme has been explicitly set in the options, it returns that. Otherwise, it
|
96
64
|
# initializes and returns a default theme.
|
97
65
|
#
|
98
66
|
# The theme hash defines CSS classes or references to other theme keys for different
|
99
|
-
# components
|
67
|
+
# components.
|
100
68
|
#
|
101
|
-
# @return [Hash] A hash containing theme definitions for
|
69
|
+
# @return [Hash] A hash containing theme definitions for display components
|
102
70
|
#
|
103
71
|
# @example Accessing the theme
|
104
|
-
# theme[:
|
105
|
-
# # => "
|
106
|
-
#
|
107
|
-
# @example Accessing a validity-specific theme
|
108
|
-
# theme[:invalid_input]
|
109
|
-
# # => "bg-red-50 border-red-500 text-red-900 placeholder-red-700 focus:ring-red-500 focus:border-red-500"
|
72
|
+
# theme[:text]
|
73
|
+
# # => "text-gray-700 text-sm"
|
110
74
|
#
|
111
75
|
# @example Theme inheritance
|
112
|
-
# theme[:
|
113
|
-
# theme[:valid_textarea] # Returns :valid_input
|
76
|
+
# theme[:email] # Returns :text, indicating email inherits text's theme
|
114
77
|
#
|
115
78
|
# @note The actual content of the theme hash depends on the default_theme method
|
116
79
|
# and any theme overrides specified in the options when initializing the field builder.
|
@@ -120,85 +83,47 @@ module Phlexi
|
|
120
83
|
@theme ||= options[:theme] || default_theme
|
121
84
|
end
|
122
85
|
|
123
|
-
# Defines and returns the default theme hash for the
|
86
|
+
# Defines and returns the default theme hash for the display builder.
|
124
87
|
#
|
125
88
|
# This method returns a hash containing the base theme definitions for various components.
|
126
|
-
# It sets up the default styling and relationships between different components
|
89
|
+
# It sets up the default styling and relationships between different components.
|
127
90
|
# The theme uses a combination of explicit CSS classes and symbolic references to other theme keys,
|
128
91
|
# allowing for a flexible and inheritance-based theming system.
|
129
92
|
#
|
130
93
|
# @return [Hash] A frozen hash containing default theme definitions for components
|
131
94
|
#
|
132
95
|
# @example Accessing the default theme
|
133
|
-
# default_theme[:
|
134
|
-
# # =>
|
96
|
+
# default_theme[:text]
|
97
|
+
# # => "text-gray-700 text-sm"
|
135
98
|
#
|
136
99
|
# @example Theme inheritance
|
137
|
-
# default_theme[:
|
138
|
-
# # => :
|
139
|
-
#
|
140
|
-
# @example Validity state theming
|
141
|
-
# default_theme[:valid_textarea]
|
142
|
-
# # => :valid_input (indicates that :valid_textarea inherits from :valid_input)
|
100
|
+
# default_theme[:email]
|
101
|
+
# # => :text (indicates that :email inherits from :text)
|
143
102
|
#
|
144
103
|
# @note This method returns a frozen hash to prevent accidental modifications.
|
145
|
-
# To customize the theme, users should provide their own theme hash when initializing the
|
146
|
-
# @note Most theme values are set to nil or commented out in the default theme to encourage users
|
147
|
-
# to define their own styles while maintaining the relationships between components and states.
|
104
|
+
# To customize the theme, users should provide their own theme hash when initializing the display builder.
|
148
105
|
#
|
149
106
|
# @see #theme
|
150
107
|
def default_theme
|
151
108
|
{
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
#
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
# file
|
171
|
-
file: :input,
|
172
|
-
valid_file: :valid_input,
|
173
|
-
invalid_file: :invalid_input,
|
174
|
-
neutral_file: :neutral_input,
|
175
|
-
|
176
|
-
# misc
|
177
|
-
# label: nil,
|
178
|
-
# hint: nil,
|
179
|
-
# error: nil,
|
180
|
-
full_error: :error,
|
181
|
-
# wrapper: nil,
|
182
|
-
# inner_wrapper: nil,
|
183
|
-
submit_button: :button
|
184
|
-
|
185
|
-
# # label themes
|
186
|
-
# label: "md:w-1/6 mt-2 block mb-2 text-sm font-medium",
|
187
|
-
# invalid_label: "text-red-700 dark:text-red-500",
|
188
|
-
# valid_label: "text-green-700 dark:text-green-500",
|
189
|
-
# neutral_label: "text-gray-700 dark:text-white",
|
190
|
-
# # input themes
|
191
|
-
# input: "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700",
|
192
|
-
# invalid_input: "bg-red-50 border-red-500 dark:border-red-500 text-red-900 dark:text-red-500 placeholder-red-700 dark:placeholder-red-500 focus:ring-red-500 focus:border-red-500",
|
193
|
-
# valid_input: "bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500",
|
194
|
-
# neutral_input: "border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-primary-500 focus:border-primary-500",
|
195
|
-
# # hint themes
|
196
|
-
# hint: "mt-2 text-sm text-gray-500 dark:text-gray-200",
|
197
|
-
# # error themes
|
198
|
-
# error: "mt-2 text-sm text-red-600 dark:text-red-500",
|
199
|
-
# # wrapper themes
|
200
|
-
# wrapper: "flex flex-col md:flex-row items-start space-y-2 md:space-y-0 md:space-x-2 mb-4",
|
201
|
-
# inner_wrapper: "md:w-5/6 w-full",
|
109
|
+
label: "text-base font-bold text-gray-500 dark:text-gray-400 mb-1",
|
110
|
+
description: "text-sm text-gray-400 dark:text-gray-500",
|
111
|
+
placeholder: "text-xl font-semibold text-gray-500 dark:text-gray-300 mb-1 italic",
|
112
|
+
string: "text-xl font-semibold text-gray-900 dark:text-white mb-1",
|
113
|
+
# text: :string,
|
114
|
+
number: :string,
|
115
|
+
datetime: :string,
|
116
|
+
# boolean: :string,
|
117
|
+
# code: :string,
|
118
|
+
# email: :text,
|
119
|
+
# url: :text,
|
120
|
+
# phone: :text,
|
121
|
+
# color: :text,
|
122
|
+
# search: :text,
|
123
|
+
# password: :string,
|
124
|
+
# association: :string,
|
125
|
+
attachment: :string,
|
126
|
+
wrapper: nil
|
202
127
|
}.freeze
|
203
128
|
end
|
204
129
|
end
|
@@ -3,21 +3,21 @@
|
|
3
3
|
module Phlexi
|
4
4
|
module Display
|
5
5
|
module Structure
|
6
|
-
# Generates DOM IDs
|
7
|
-
# norms that were established by Rails. These can be used
|
8
|
-
# other Ruby web frameworks since it has
|
6
|
+
# Generates DOM IDs for a Field, Namespace, or Node based on
|
7
|
+
# norms that were established by Rails. These can be used outside of Rails in
|
8
|
+
# other Ruby web frameworks since it has no dependencies on Rails.
|
9
9
|
class DOM
|
10
10
|
def initialize(field:)
|
11
11
|
@field = field
|
12
12
|
end
|
13
13
|
|
14
14
|
# Converts the value of the field to a String, which is required to work
|
15
|
-
# with Phlex. Assumes that `Object#to_s` emits a format suitable for
|
15
|
+
# with Phlex. Assumes that `Object#to_s` emits a format suitable for display.
|
16
16
|
def value
|
17
17
|
@field.value.to_s
|
18
18
|
end
|
19
19
|
|
20
|
-
# Walks from the current node to the parent node, grabs the names, and
|
20
|
+
# Walks from the current node to the parent node, grabs the names, and separates
|
21
21
|
# them with a `_` for a DOM ID.
|
22
22
|
def id
|
23
23
|
@id ||= begin
|
@@ -27,34 +27,14 @@ module Phlexi
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
# The `name` attribute of a node, which is influenced by Rails.
|
31
|
-
# All node names, except the parent node, are wrapped in a `[]` and collections
|
32
|
-
# are left empty. For example, `user[addresses][][street]` would be created for a form with
|
33
|
-
# data shaped like `{user: {addresses: [{street: "Sesame Street"}]}}`.
|
34
|
-
def name
|
35
|
-
@name ||= begin
|
36
|
-
root, *names = keys
|
37
|
-
names.map { |name| "[#{name}]" }.unshift(root).join
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
30
|
# One-liner way of walking from the current node all the way up to the parent.
|
42
31
|
def lineage
|
43
32
|
@lineage ||= Enumerator.produce(@field, &:parent).take_while(&:itself).reverse
|
44
33
|
end
|
45
34
|
|
46
|
-
# Emit the id
|
35
|
+
# Emit the id and value in an HTML tag-ish that doesn't have an element.
|
47
36
|
def inspect
|
48
|
-
"<#{self.class.name} id=#{id.inspect}
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def keys
|
54
|
-
@keys ||= lineage.map do |node|
|
55
|
-
# If the parent of a field is a field, the name should be nil.
|
56
|
-
node.key unless node.parent.is_a? FieldBuilder
|
57
|
-
end
|
37
|
+
"<#{self.class.name} id=#{id.inspect} value=#{value.inspect}/>"
|
58
38
|
end
|
59
39
|
end
|
60
40
|
end
|