fluxbit_view_components 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +86 -0
- data/app/components/fluxbit/alert_component.rb +126 -0
- data/app/components/fluxbit/avatar_component.rb +113 -0
- data/app/components/fluxbit/avatar_group_component.rb +23 -0
- data/app/components/fluxbit/badge_component.rb +79 -0
- data/app/components/fluxbit/button_component.rb +97 -0
- data/app/components/fluxbit/button_group_component.rb +43 -0
- data/app/components/fluxbit/card_component.rb +135 -0
- data/app/components/fluxbit/component.rb +86 -0
- data/app/components/fluxbit/flex_component.rb +93 -0
- data/app/components/fluxbit/form/checkbox_input_component.rb +61 -0
- data/app/components/fluxbit/form/component.rb +71 -0
- data/app/components/fluxbit/form/datepicker_component.rb +7 -0
- data/app/components/fluxbit/form/form_builder_component.rb +117 -0
- data/app/components/fluxbit/form/helper_text_component.rb +29 -0
- data/app/components/fluxbit/form/label_component.rb +65 -0
- data/app/components/fluxbit/form/radio_input_component.rb +21 -0
- data/app/components/fluxbit/form/range_input_component.rb +51 -0
- data/app/components/fluxbit/form/select_free_input_component.rb +77 -0
- data/app/components/fluxbit/form/select_input_component.rb +21 -0
- data/app/components/fluxbit/form/spacer_input_component.rb +12 -0
- data/app/components/fluxbit/form/text_input_component.rb +225 -0
- data/app/components/fluxbit/form/textarea_input_component.rb +57 -0
- data/app/components/fluxbit/form/toggle_input_component.rb +166 -0
- data/app/components/fluxbit/form/upload_image_input_component.html.erb +48 -0
- data/app/components/fluxbit/form/upload_image_input_component.rb +66 -0
- data/app/components/fluxbit/form/upload_input_component.html.erb +12 -0
- data/app/components/fluxbit/form/upload_input_component.rb +47 -0
- data/app/components/fluxbit/gravatar_component.rb +99 -0
- data/app/components/fluxbit/heading_component.rb +47 -0
- data/app/components/fluxbit/modal_component.rb +141 -0
- data/app/components/fluxbit/popover_component.rb +71 -0
- data/app/components/fluxbit/tab_component.rb +142 -0
- data/app/components/fluxbit/text_component.rb +36 -0
- data/app/components/fluxbit/tooltip_component.rb +38 -0
- data/app/helpers/fluxbit/classes_helper.rb +21 -0
- data/app/helpers/fluxbit/components_helper.rb +75 -0
- data/config/deploy.yml +37 -0
- data/config/locales/en.yml +6 -0
- data/lib/fluxbit/config/alert_component.rb +59 -0
- data/lib/fluxbit/config/avatar_component.rb +79 -0
- data/lib/fluxbit/config/badge_component.rb +77 -0
- data/lib/fluxbit/config/button_component.rb +86 -0
- data/lib/fluxbit/config/card_component.rb +32 -0
- data/lib/fluxbit/config/flex_component.rb +63 -0
- data/lib/fluxbit/config/form/helper_text_component.rb +20 -0
- data/lib/fluxbit/config/gravatar_component.rb +19 -0
- data/lib/fluxbit/config/heading_component.rb +39 -0
- data/lib/fluxbit/config/modal_component.rb +71 -0
- data/lib/fluxbit/config/paragraph_component.rb +11 -0
- data/lib/fluxbit/config/popover_component.rb +33 -0
- data/lib/fluxbit/config/tab_component.rb +131 -0
- data/lib/fluxbit/config/text_component.rb +110 -0
- data/lib/fluxbit/config/tooltip_component.rb +11 -0
- data/lib/fluxbit/view_components/codemods/v3_slot_setters.rb +222 -0
- data/lib/fluxbit/view_components/engine.rb +36 -0
- data/lib/fluxbit/view_components/version.rb +7 -0
- data/lib/fluxbit/view_components.rb +30 -0
- data/lib/fluxbit_view_components.rb +3 -0
- data/lib/install/install.rb +64 -0
- data/lib/tasks/fluxbit_view_components_tasks.rake +22 -0
- metadata +238 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fluxbit::Form::RangeInputComponent < Fluxbit::Form::Component
|
4
|
+
cattr_accessor :styles do
|
5
|
+
{
|
6
|
+
base: "w-full bg-slate-200 rounded-lg appearance-none cursor-pointer dark:bg-slate-700",
|
7
|
+
sizes: {
|
8
|
+
sm: "h-1 range-sm",
|
9
|
+
md: "h-2",
|
10
|
+
lg: "h-3 range-lg"
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(form: nil, field: nil, sizing: :md, label: nil, helper_text: nil, helper_popover: nil,
|
16
|
+
helper_popover_placement: "right", **props)
|
17
|
+
super
|
18
|
+
@form = form
|
19
|
+
@field = field
|
20
|
+
@object = form&.object
|
21
|
+
@sizing = sizing.in?(styles[:sizes].keys) ? sizing : :md
|
22
|
+
@props = props
|
23
|
+
@label = label_value(label, @object, field, id)
|
24
|
+
@helper_text = define_helper_text(helper_text, @object, field)
|
25
|
+
@helper_popover = define_helper_popover(helper_popover, @object, field)
|
26
|
+
@helper_popover_placement = helper_popover_placement
|
27
|
+
@props[:type] = "range"
|
28
|
+
|
29
|
+
add(class: styles[:sizes][@sizing], to: @props, first_element: true)
|
30
|
+
add(class: styles[:base], to: @props, first_element: true)
|
31
|
+
end
|
32
|
+
|
33
|
+
def id
|
34
|
+
return @id ||= (0...30).map { ("a".."z").to_a[rand(26)] }.join if @props[:id].nil? && @form.nil?
|
35
|
+
return @props[:id] unless @props[:id].nil?
|
36
|
+
|
37
|
+
"#{@form.object_name}_#{@field}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def range
|
41
|
+
if @form.nil?
|
42
|
+
content_tag :input, content, @props
|
43
|
+
else
|
44
|
+
@form.text_field(@field, **@props)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def call
|
49
|
+
safe_join([ label, range, helper_text ])
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fluxbit::Form::SelectFreeInputComponent < Fluxbit::Form::Component
|
4
|
+
def initialize(free_option: "Specify...", **kwargs, &block)
|
5
|
+
super
|
6
|
+
|
7
|
+
@kwargs = kwargs
|
8
|
+
@block = block
|
9
|
+
@free_option = free_option
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
if @kwargs[:with_content]
|
14
|
+
content = @kwargs.delete(:with_content)
|
15
|
+
content_tag :div do
|
16
|
+
concat render(Fluxbit::Form::SelectInputComponent.new(**select_params).with_content(content), &@block)
|
17
|
+
concat render(Fluxbit::Form::TextInputComponent.new(**text_params).with_content(content), &@block)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
content_tag :div do
|
21
|
+
concat render(Fluxbit::Form::SelectInputComponent.new(**select_params), &@block)
|
22
|
+
concat render(Fluxbit::Form::TextInputComponent.new(**text_params), &@block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def select_params
|
30
|
+
@kwargs[:options].push @free_option
|
31
|
+
@kwargs.merge(
|
32
|
+
{
|
33
|
+
class: (corresponding_options? ? "" : "hidden").to_s,
|
34
|
+
onchange: "
|
35
|
+
const select = this.selectedOptions[0];
|
36
|
+
const input = this.parentElement.querySelector('div > input');
|
37
|
+
const input_div = this.parentElement.querySelector('div');
|
38
|
+
if(select.value == '#{@free_option}') {
|
39
|
+
input.value = ''
|
40
|
+
this.classList.add('hidden');
|
41
|
+
input_div.classList.remove('hidden');
|
42
|
+
} else {
|
43
|
+
input.value = select.value;
|
44
|
+
}
|
45
|
+
"
|
46
|
+
}
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def text_params
|
51
|
+
@kwargs.merge(
|
52
|
+
{
|
53
|
+
label: false,
|
54
|
+
helper_popover: false,
|
55
|
+
helper_text: false,
|
56
|
+
right_icon: "heroicons_solid:x-mark:cursor-pointer",
|
57
|
+
right_icon_props: {
|
58
|
+
onclick: "
|
59
|
+
this.parentElement.classList.add('hidden');
|
60
|
+
this.parentElement.querySelector('input').value='';
|
61
|
+
this.parentElement.parentElement.querySelector('select').classList.remove('hidden');
|
62
|
+
this.parentElement.parentElement.querySelector('select').selectedIndex = null;
|
63
|
+
"
|
64
|
+
},
|
65
|
+
div_props: { class: (corresponding_options? ? "hidden" : "").to_s }
|
66
|
+
}
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def corresponding_options?
|
71
|
+
value.blank? || @kwargs[:options].include?(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def value
|
75
|
+
@kwargs[:value] || @kwargs[:form].object[@kwargs[:field]]
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fluxbit::Form::SelectInputComponent < Fluxbit::Form::Component
|
4
|
+
def initialize(**kwargs, &block)
|
5
|
+
super
|
6
|
+
kwargs[:type] = :select
|
7
|
+
|
8
|
+
@component_klass = "Fluxbit::Form::TextInputComponent".constantize
|
9
|
+
@kwargs = kwargs
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
if @kwargs[:with_content]
|
15
|
+
content = @kwargs.delete(:with_content)
|
16
|
+
render(@component_klass.new(**@kwargs).with_content(content), &@block)
|
17
|
+
else
|
18
|
+
render(@component_klass.new(**@kwargs), &@block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# @param
|
5
|
+
# form: The form to create a input element
|
6
|
+
# options: {
|
7
|
+
# size: :large, :base, :small,
|
8
|
+
# label: nil | 'Label',
|
9
|
+
# placeholder: nil | 'placeholder',
|
10
|
+
# validators: [:required]
|
11
|
+
# required: true | false | message,
|
12
|
+
# }
|
13
|
+
#
|
14
|
+
# Validators can be predefined or customized:
|
15
|
+
# [:required, :min_length, :max_length, :min_value, :max_value,
|
16
|
+
# { type: :regexp, regex: /dd/, success: '', failure: ''},
|
17
|
+
# ]
|
18
|
+
#
|
19
|
+
class Fluxbit::Form::TextInputComponent < Fluxbit::Form::Component
|
20
|
+
# rubocop: disable Layout/LineLength, Metrics/BlockLength
|
21
|
+
cattr_accessor :styles do
|
22
|
+
{
|
23
|
+
default: "mt-1 block w-full border disabled:cursor-not-allowed disabled:opacity-50 disabled:text-slate-900 disabled:dark:text-slate-400 disabled:bg-slate-100 disabled:dark:bg-slate-700",
|
24
|
+
text: {
|
25
|
+
default: "text-slate-900 dark:text-white",
|
26
|
+
success: "text-green-900",
|
27
|
+
failure: "text-red-900",
|
28
|
+
info: "text-cyan-900",
|
29
|
+
warning: "text-yellow-900"
|
30
|
+
},
|
31
|
+
ring: {
|
32
|
+
default: "focus:ring-blue-500 dark:focus:ring-blue-500",
|
33
|
+
success: "focus:ring-green-500",
|
34
|
+
failure: "focus:ring-red-500",
|
35
|
+
info: "focus:ring-cyan-500",
|
36
|
+
warning: "focus:ring-yellow-500"
|
37
|
+
},
|
38
|
+
bg: {
|
39
|
+
default: "bg-slate-50 dark:bg-slate-700",
|
40
|
+
success: "bg-green-50 dark:bg-green-100",
|
41
|
+
failure: "bg-red-50 dark:bg-red-100",
|
42
|
+
info: "bg-cyan-50 dark:bg-cyan-100",
|
43
|
+
warning: "bg-yellow-50 dark:bg-yellow-100"
|
44
|
+
},
|
45
|
+
placeholder: {
|
46
|
+
default: "dark:placeholder-slate-400",
|
47
|
+
success: "placeholder-green-700",
|
48
|
+
failure: "placeholder-red-700",
|
49
|
+
info: "placeholder-cyan-700",
|
50
|
+
warning: "placeholder-yellow-700"
|
51
|
+
},
|
52
|
+
border: {
|
53
|
+
default: "border-slate-300 focus:border-blue-500 dark:border-slate-600 dark:focus:border-blue-500",
|
54
|
+
success: "border-green-500 focus:border-green-500 dark:border-green-400",
|
55
|
+
failure: "border-red-500 focus:border-red-500 dark:border-red-400",
|
56
|
+
info: "border-cyan-500 focus:border-cyan-500 dark:border-cyan-400",
|
57
|
+
warning: "border-yellow-500 focus:border-yellow-500 dark:border-yellow-400"
|
58
|
+
},
|
59
|
+
shadow: "shadow-xs dark:shadow-xs-light",
|
60
|
+
icon: "pl-10",
|
61
|
+
right_icon: "pr-10",
|
62
|
+
sizing: {
|
63
|
+
md: "p-2.5 text-sm rounded-lg",
|
64
|
+
lg: "p-4 sm:text-md rounded-lg",
|
65
|
+
sm: "p-2 rounded-lg sm:text-xs",
|
66
|
+
md_addon: "p-2.5 rounded-none rounded-r-lg flex-1 min-w-0 text-sm"
|
67
|
+
},
|
68
|
+
additional_icons: {
|
69
|
+
class: {
|
70
|
+
default: "mt-1 w-4 h-4 text-slate-500 dark:text-slate-400",
|
71
|
+
success: "mt-1 w-4 h-4 text-green-500 dark:text-green-400",
|
72
|
+
failure: "mt-1 w-4 h-4 text-red-500 dark:text-red-400",
|
73
|
+
info: "mt-1 w-4 h-4 text-cyan-500 dark:text-cyan-400",
|
74
|
+
warning: "mt-1 w-4 h-4 text-yellow-500 dark:text-yellow-400"
|
75
|
+
},
|
76
|
+
icon: "absolute inset-y-0 left-0 flex items-center pl-3",
|
77
|
+
right_icon: "absolute inset-y-0 right-0 flex items-center pr-3",
|
78
|
+
addon: {
|
79
|
+
default: "mt-1 inline-flex items-center px-3 text-sm text-slate-900 bg-slate-200 border border-r-0 border-slate-300 rounded-l-md dark:bg-slate-600 dark:text-slate-400 dark:border-slate-600",
|
80
|
+
success: "mt-1 inline-flex items-center px-3 text-sm text-green-900 bg-green-200 border border-r-0 border-green-300 rounded-l-md dark:bg-green-600 dark:text-green-400 dark:border-green-600",
|
81
|
+
failure: "mt-1 inline-flex items-center px-3 text-sm text-red-900 bg-red-200 border border-r-0 border-red-300 rounded-l-md dark:bg-red-600 dark:text-red-400 dark:border-red-600",
|
82
|
+
info: "mt-1 inline-flex items-center px-3 text-sm text-cyan-900 bg-cyan-200 border border-r-0 border-cyan-300 rounded-l-md dark:bg-cyan-600 dark:text-cyan-400 dark:border-cyan-600",
|
83
|
+
warning: "mt-1 inline-flex items-center px-3 text-sm text-yellow-900 bg-yellow-200 border border-r-0 border-yellow-300 rounded-l-md dark:bg-yellow-600 dark:text-yellow-400 dark:border-yellow-600"
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
# rubocop: enable Layout/LineLength, Metrics/BlockLength
|
89
|
+
|
90
|
+
def initialize(form: nil, field: nil, id: nil, shadow: false, type: "text", helper_text: nil,
|
91
|
+
helper_popover: nil, helper_popover_placement: "right", icon: nil,
|
92
|
+
right_icon: nil, addon: nil, color: nil, label: nil, sizing: :md, i18n: nil, options: [],
|
93
|
+
validators: [], div_props: {}, icon_props: {}, right_icon_props: {}, addon_props: {}, **props)
|
94
|
+
super
|
95
|
+
@form = form
|
96
|
+
@object = form&.object
|
97
|
+
@field = field
|
98
|
+
@id = id
|
99
|
+
@shadow = shadow
|
100
|
+
@type = valid_input_type(type)
|
101
|
+
@helper_text = define_helper_text(helper_text, @object, field)
|
102
|
+
@helper_popover = define_helper_popover(helper_popover, @object, field)
|
103
|
+
@helper_popover_placement = helper_popover_placement
|
104
|
+
@icon = icon
|
105
|
+
@right_icon = right_icon
|
106
|
+
@addon = addon
|
107
|
+
@color = valid_color(color)
|
108
|
+
@label = label_value(label, @object, field, id)
|
109
|
+
@sizing = sizing_with_addon(sizing, addon)
|
110
|
+
@validators = validators
|
111
|
+
@props = props.merge(type: @type)
|
112
|
+
@options = options
|
113
|
+
@sizing = :md_addon if addon
|
114
|
+
@div_props = div_props
|
115
|
+
@icon_props = icon_props
|
116
|
+
@right_icon_props = right_icon_props
|
117
|
+
@addon_props = addon_props
|
118
|
+
|
119
|
+
declare_classes
|
120
|
+
end
|
121
|
+
|
122
|
+
def call
|
123
|
+
safe_join [ label, icon_container, helper_text ]
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def valid_input_type(type)
|
129
|
+
%w[email password number search time date datetime-local select].include?(type.to_s) ? type.to_s : "text"
|
130
|
+
end
|
131
|
+
|
132
|
+
def valid_color(color)
|
133
|
+
return color if styles[:bg].key?(color)
|
134
|
+
return :failure if errors.present?
|
135
|
+
|
136
|
+
:default
|
137
|
+
end
|
138
|
+
|
139
|
+
def sizing_with_addon(sizing, addon)
|
140
|
+
return :md_addon if addon
|
141
|
+
|
142
|
+
sizing.in?(%i[md sm lg]) ? sizing : :md
|
143
|
+
end
|
144
|
+
|
145
|
+
def declare_classes
|
146
|
+
add to: @props,
|
147
|
+
first_element: true,
|
148
|
+
class: [
|
149
|
+
styles[:default],
|
150
|
+
(@props.key?(:readonly) || @props.key?(:disabled) ? styles[:text][@color] : ""),
|
151
|
+
styles[:ring][@color],
|
152
|
+
styles[:bg][@color],
|
153
|
+
styles[:placeholder][@color],
|
154
|
+
styles[:border][@color],
|
155
|
+
styles[:sizing][@sizing],
|
156
|
+
(@shadow ? styles[:shadow] : ""),
|
157
|
+
(@right_icon ? styles[:right_icon] : ""),
|
158
|
+
(@icon ? styles[:icon] : "")
|
159
|
+
].join(" ")
|
160
|
+
end
|
161
|
+
|
162
|
+
def icon(icon_v, tag: :div, props: nil)
|
163
|
+
return "" if icon_v.blank?
|
164
|
+
|
165
|
+
content_tag(
|
166
|
+
tag,
|
167
|
+
anyicon(
|
168
|
+
icon: icon_v,
|
169
|
+
class: styles[:additional_icons][:class][@color]
|
170
|
+
),
|
171
|
+
**props
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_icon
|
176
|
+
add class: styles[:additional_icons][:icon], to: @icon_props
|
177
|
+
add(class: "pointer-events-none", to: @icon_props) unless events?(@icon_props)
|
178
|
+
icon(@icon, props: @icon_props)
|
179
|
+
end
|
180
|
+
|
181
|
+
def create_addon
|
182
|
+
add class: styles[:additional_icons][:addon][@color], to: @addon_props
|
183
|
+
icon(@addon, tag: :span, props: @addon_props)
|
184
|
+
end
|
185
|
+
|
186
|
+
def create_right_icon
|
187
|
+
add class: styles[:additional_icons][:right_icon], to: @right_icon_props
|
188
|
+
add(class: "pointer-events-none", to: @right_icon_props) unless events?(@right_icon_props)
|
189
|
+
icon(@right_icon, props: @right_icon_props)
|
190
|
+
end
|
191
|
+
|
192
|
+
def events?(props)
|
193
|
+
props.keys.intersection(
|
194
|
+
%i[onclick onsubmit onchange onkeydown onkeyup onkeypress href]
|
195
|
+
).present?
|
196
|
+
end
|
197
|
+
|
198
|
+
def input
|
199
|
+
return content_tag :input, content, @props if @form.nil?
|
200
|
+
|
201
|
+
if @type == "select"
|
202
|
+
@props[:prompt] = @props[:placeholder] if @props[:placeholder]
|
203
|
+
return @form.select(@field, @options || [], {}, @props)
|
204
|
+
end
|
205
|
+
|
206
|
+
@form.text_field(@field, **@props)
|
207
|
+
end
|
208
|
+
|
209
|
+
def icon_container_with_addon
|
210
|
+
add class: "flex", to: @div_props
|
211
|
+
content_tag :div, safe_join([ create_addon, create_right_icon, input ]), @div_props
|
212
|
+
end
|
213
|
+
|
214
|
+
def icon_container_without_addon
|
215
|
+
add class: "relative w-full", to: @div_props
|
216
|
+
content_tag :div, safe_join([ create_icon, create_right_icon, input ]), @div_props
|
217
|
+
end
|
218
|
+
|
219
|
+
def icon_container
|
220
|
+
return input if @icon.nil? && @right_icon.nil? && @addon.nil?
|
221
|
+
return icon_container_with_addon unless @addon.nil?
|
222
|
+
|
223
|
+
icon_container_without_addon
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fluxbit::Form::TextareaInputComponent < Fluxbit::Form::Component
|
4
|
+
# rubocop: disable Layout/LineLength
|
5
|
+
cattr_accessor :styles do
|
6
|
+
{
|
7
|
+
base: "mt-1 block w-full rounded-lg border disabled:cursor-not-allowed disabled:opacity-50 text-sm",
|
8
|
+
bg: {
|
9
|
+
default: "bg-slate-50 border-slate-300 text-slate-900 focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700 dark:text-white dark:placeholder-slate-400 dark:focus:border-blue-500 dark:focus:ring-blue-500",
|
10
|
+
success: "border-green-500 bg-green-50 text-green-900 placeholder-green-700 focus:border-green-500 focus:ring-green-500 dark:border-green-400 dark:bg-green-100 dark:focus:border-green-500 dark:focus:ring-green-500",
|
11
|
+
failure: "border-red-500 bg-red-50 text-red-900 placeholder-red-700 focus:border-red-500 focus:ring-red-500 dark:border-red-400 dark:bg-red-100 dark:focus:border-red-500 dark:focus:ring-red-500",
|
12
|
+
info: "border-cyan-500 bg-cyan-50 text-cyan-900 placeholder-cyan-700 focus:border-cyan-500 focus:ring-cyan-500 dark:border-cyan-400 dark:bg-cyan-100 dark:focus:border-cyan-500 dark:focus:ring-cyan-500",
|
13
|
+
warning: "border-yellow-500 bg-yellow-50 text-yellow-900 placeholder-yellow-700 focus:border-yellow-500 focus:ring-yellow-500 dark:border-yellow-400 dark:bg-yellow-100 dark:focus:border-yellow-500 dark:focus:ring-yellow-500"
|
14
|
+
},
|
15
|
+
shadow: "shadow-xs dark:shadow-xs-light"
|
16
|
+
}
|
17
|
+
end
|
18
|
+
# rubocop: enable Layout/LineLength
|
19
|
+
|
20
|
+
def initialize(form: nil, field: nil, color: nil, label: nil, helper_text: nil, helper_popover: nil,
|
21
|
+
helper_popover_placement: "right", shadow: false, **props)
|
22
|
+
super
|
23
|
+
@form = form
|
24
|
+
@object = form&.object
|
25
|
+
@field = field
|
26
|
+
@shadow = shadow
|
27
|
+
@color = valid_color(color)
|
28
|
+
@props = props
|
29
|
+
@label = label_value(label, @object, field, id)
|
30
|
+
@helper_text = define_helper_text(helper_text, @object, field)
|
31
|
+
@helper_popover = define_helper_popover(helper_popover, @object, field)
|
32
|
+
@helper_popover_placement = helper_popover_placement
|
33
|
+
|
34
|
+
add(class: styles[:shadow], to: @props, first_element: true) if shadow
|
35
|
+
add(class: styles[:bg][@color], to: @props, first_element: true)
|
36
|
+
add(class: styles[:base], to: @props, first_element: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid_color(color)
|
40
|
+
return color if styles[:bg].key?(color)
|
41
|
+
return :failure if errors.present?
|
42
|
+
|
43
|
+
:default
|
44
|
+
end
|
45
|
+
|
46
|
+
def textarea
|
47
|
+
if @form.nil?
|
48
|
+
content_tag :textarea, content, @props
|
49
|
+
else
|
50
|
+
@form.text_area(@field, **@props)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def call
|
55
|
+
safe_join([ label, textarea, helper_text ])
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fluxbit::Form::ToggleInputComponent < Fluxbit::Form::Component
|
4
|
+
# rubocop: disable Layout/LineLength, Metrics/BlockLength
|
5
|
+
cattr_accessor :styles do
|
6
|
+
{
|
7
|
+
root: {
|
8
|
+
base: "relative inline-flex items-center mr-2",
|
9
|
+
base2: "relative inline-flex items-center",
|
10
|
+
no_helper_text: "mb-5",
|
11
|
+
active: {
|
12
|
+
on: "cursor-pointer",
|
13
|
+
off: "cursor-not-allowed opacity-50"
|
14
|
+
},
|
15
|
+
label: "ml-3 text-sm font-medium text-slate-900 dark:text-slate-300",
|
16
|
+
input: "sr-only peer"
|
17
|
+
},
|
18
|
+
toggle: {
|
19
|
+
base: "toggle-bg rounded-full after:rounded-full peer peer-focus:ring-4 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:bg-white after:border after:transition-all",
|
20
|
+
checked: {
|
21
|
+
default: "peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 peer-checked:bg-blue-600",
|
22
|
+
success: "peer-focus:ring-green-300 dark:peer-focus:ring-green-800 peer-checked:bg-green-600",
|
23
|
+
failure: "peer-focus:ring-red-300 dark:peer-focus:ring-red-800 peer-checked:bg-red-600",
|
24
|
+
info: "peer-focus:ring-cyan-300 dark:peer-focus:ring-cyan-800 peer-checked:bg-cyan-600",
|
25
|
+
warning: "peer-focus:ring-yellow-300 dark:peer-focus:ring-yellow-800 peer-checked:bg-yellow-600",
|
26
|
+
dark: "peer-focus:ring-gray-300 dark:peer-focus:ring-gray-800 peer-checked:bg-gray-600",
|
27
|
+
light: "peer-focus:ring-gray-300 dark:peer-focus:ring-gray-100 peer-checked:bg-gray-100",
|
28
|
+
teal: "peer-focus:ring-teal-300 dark:peer-focus:ring-teal-800 peer-checked:bg-teal-600",
|
29
|
+
purple: "peer-focus:ring-purple-300 dark:peer-focus:ring-purple-800 peer-checked:bg-purple-600",
|
30
|
+
cyan: "peer-focus:ring-cyan-300 dark:peer-focus:ring-cyan-800 peer-checked:bg-cyan-600",
|
31
|
+
lime: "peer-focus:ring-lime-300 dark:peer-focus:ring-lime-800 peer-checked:bg-lime-600",
|
32
|
+
indigo: "peer-focus:ring-indigo-300 dark:peer-focus:ring-indigo-800 peer-checked:bg-indigo-600",
|
33
|
+
pink: "peer-focus:ring-pink-300 dark:peer-focus:ring-pink-800 peer-checked:bg-pink-600"
|
34
|
+
},
|
35
|
+
unchecked: {
|
36
|
+
default: "bg-slate-200 dark:bg-slate-700 dark:border-slate-600 after:border-slate-300",
|
37
|
+
blue: "bg-blue-600 dark:bg-blue-500 dark:border-blue-500 after:border-blue-600",
|
38
|
+
success: "bg-green-600 dark:bg-green-500 dark:border-green-500 after:border-green-600",
|
39
|
+
failure: "bg-red-600 dark:bg-red-500 dark:border-red-500 after:border-red-600",
|
40
|
+
info: "bg-cyan-600 dark:bg-cyan-500 dark:border-cyan-500 after:border-cyan-600",
|
41
|
+
warning: "bg-yellow-600 dark:bg-yellow-500 dark:border-yellow-500 after:border-yellow-600",
|
42
|
+
teal: "bg-teal-600 dark:bg-teal-500 dark:border-teal-500 after:border-teal-600",
|
43
|
+
purple: "bg-purple-600 dark:bg-purple-500 dark:border-purple-500 after:border-purple-600",
|
44
|
+
cyan: "bg-cyan-600 dark:bg-cyan-500 dark:border-cyan-500 after:border-cyan-600",
|
45
|
+
lime: "bg-lime-600 dark:bg-lime-500 dark:border-lime-500 after:border-lime-600",
|
46
|
+
indigo: "bg-indigo-600 dark:bg-indigo-500 dark:border-indigo-500 after:border-indigo-600",
|
47
|
+
pink: "bg-pink-600 dark:bg-pink-500 dark:border-pink-500 after:border-pink-600",
|
48
|
+
dark: "bg-gray-600 dark:bg-gray-800 dark:border-gray-600 after:border-gray-300",
|
49
|
+
light: "bg-gray-50 dark:bg-gray-300 dark:border-gray-200 after:border-gray-50",
|
50
|
+
light_success: "bg-green-200 dark:bg-green-700 dark:border-green-600 after:border-green-300",
|
51
|
+
light_failure: "bg-red-200 dark:bg-red-700 dark:border-red-600 after:border-red-300",
|
52
|
+
light_info: "bg-cyan-200 dark:bg-cyan-700 dark:border-cyan-600 after:border-cyan-300",
|
53
|
+
light_warning: "bg-yellow-200 dark:bg-yellow-700 dark:border-yellow-600 after:border-yellow-300",
|
54
|
+
light_teal: "bg-teal-200 dark:bg-teal-700 dark:border-teal-600 after:border-teal-300",
|
55
|
+
light_purple: "bg-purple-200 dark:bg-purple-700 dark:border-purple-600 after:border-purple-300",
|
56
|
+
light_cyan: "bg-cyan-200 dark:bg-cyan-700 dark:border-cyan-600 after:border-cyan-300",
|
57
|
+
light_lime: "bg-lime-200 dark:bg-lime-700 dark:border-lime-600 after:border-lime-300",
|
58
|
+
light_indigo: "bg-indigo-200 dark:bg-indigo-700 dark:border-indigo-600 after:border-indigo-300",
|
59
|
+
light_pink: "bg-pink-200 dark:bg-pink-700 dark:border-pink-600 after:border-pink-300"
|
60
|
+
},
|
61
|
+
sizes: {
|
62
|
+
sm: "w-9 h-5 after:top-[2px] after:left-[2px] after:h-4 after:w-4",
|
63
|
+
md: "w-11 h-6 after:top-[2px] after:left-[2px] after:h-5 after:w-5",
|
64
|
+
lg: "w-14 h-7 after:top-0.5 after:left-[4px] after:h-6 after:w-6"
|
65
|
+
}
|
66
|
+
},
|
67
|
+
right_sided: {
|
68
|
+
top: "flex items-center justify-between py-4",
|
69
|
+
inside: "flex flex-col"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
# rubocop: enable Layout/LineLength, Metrics/BlockLength
|
74
|
+
|
75
|
+
renders_one :helper, "Fluxbit::Form::HelperTextComponent"
|
76
|
+
|
77
|
+
def initialize(form: nil, field: nil, color: nil, unchecked_color: :default, sizing: :md,
|
78
|
+
right_sided: false, label: nil, helper_text: nil, helper_popover: nil,
|
79
|
+
helper_popover_placement: "right", **props)
|
80
|
+
super
|
81
|
+
@form = form
|
82
|
+
@field = field
|
83
|
+
@object = form&.object
|
84
|
+
@right_sided = right_sided
|
85
|
+
@sizing = sizing.in?(styles[:toggle][:sizes].keys) ? sizing : :md
|
86
|
+
@color = valid_color(color)
|
87
|
+
@unchecked_color = unchecked_color.in?(styles[:toggle][:unchecked].keys) ? unchecked_color : :default
|
88
|
+
@props = props
|
89
|
+
@label = label_value(label, @object, field, id)
|
90
|
+
@helper_text = define_helper_text(helper_text, @object, field)
|
91
|
+
@helper_popover = define_helper_popover(helper_popover, @object, field)
|
92
|
+
@helper_popover_placement = helper_popover_placement
|
93
|
+
@label_class = "ml-2" unless right_sided
|
94
|
+
|
95
|
+
# Input
|
96
|
+
@props[:type] = "checkbox"
|
97
|
+
declare_classes
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_color(color)
|
101
|
+
return color if styles[:toggle][:checked].key?(color)
|
102
|
+
return :failure if errors.present?
|
103
|
+
|
104
|
+
:default
|
105
|
+
end
|
106
|
+
|
107
|
+
def declare_classes
|
108
|
+
add(class: styles[:root][:input], to: @props)
|
109
|
+
|
110
|
+
# Root
|
111
|
+
@root_element = { class: "#{styles[:root][:base]} #{styles[:root][:active][(@props[:disabled] ? :off : :on)]}" }
|
112
|
+
|
113
|
+
# Toggle
|
114
|
+
@toggle_element = {
|
115
|
+
class:
|
116
|
+
[
|
117
|
+
styles[:toggle][:base],
|
118
|
+
styles[:toggle][:unchecked][@unchecked_color],
|
119
|
+
styles[:toggle][:checked][@color],
|
120
|
+
styles[:toggle][:sizes][@sizing]
|
121
|
+
].join(" ")
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def input
|
126
|
+
if @form.nil?
|
127
|
+
content_tag :input, content, @props
|
128
|
+
else
|
129
|
+
@form.check_box(@field, **@props)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def toggle
|
134
|
+
content_tag :div, "", @toggle_element
|
135
|
+
end
|
136
|
+
|
137
|
+
def toggle_input
|
138
|
+
content_tag :div, safe_join(input, toggle), class: styles[:root][:base2]
|
139
|
+
end
|
140
|
+
|
141
|
+
def label_container
|
142
|
+
content_tag(:label, safe_join(toggle_input, label), @root_element)
|
143
|
+
end
|
144
|
+
|
145
|
+
def left_sided
|
146
|
+
safe_join label_container, (helper? ? helper : helper_text)
|
147
|
+
end
|
148
|
+
|
149
|
+
def right_sided
|
150
|
+
content_tag :label, class: styles[:right_sided][:top] do
|
151
|
+
safe_join(
|
152
|
+
content_tag(
|
153
|
+
:div,
|
154
|
+
safe_join(label, (helper? ? helper : helper_text)),
|
155
|
+
class: styles[:right_sided][:inside]
|
156
|
+
),
|
157
|
+
toggle_input
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def call
|
163
|
+
add(class: styles[:root][:no_helper_text], to: @root_element) if @helper_text.nil? && !helper?
|
164
|
+
@right_sided ? right_sided : left_sided
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<div id="<%= id %>" class="mt-6 grow lg:mt-0 lg:ml-6 lg:shrink-0 lg:grow-0">
|
2
|
+
<%= label %>
|
3
|
+
<div class="mt-1 lg:hidden">
|
4
|
+
<div class="flex items-center">
|
5
|
+
<div class="inline-block h-12 w-12 shrink-0 overflow-hidden rounded-full relative" aria-hidden="true">
|
6
|
+
<%= image_element %>
|
7
|
+
</div>
|
8
|
+
<div class="ml-5 rounded-md shadow-xs">
|
9
|
+
<div class="group relative flex items-center justify-center rounded-md border border-slate-300 py-2 px-3 focus-within:ring-2 focus-within:ring-sky-500 focus-within:ring-offset-2 hover:bg-slate-50">
|
10
|
+
<label for="mobile-<%= id %>" class="pointer-events-none relative text-sm font-medium leading-4 text-slate-700">
|
11
|
+
<span>Change</span>
|
12
|
+
<span class="sr-only"><%= @label %></span>
|
13
|
+
</label>
|
14
|
+
<%= input_element(input_id: "mobile-#{id}") %>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
|
21
|
+
<div class="relative hidden overflow-hidden rounded-full lg:block w-40">
|
22
|
+
|
23
|
+
<div class="inline-block h-40 w-40 shrink-0 overflow-hidden rounded-full relative" aria-hidden="true">
|
24
|
+
<%= image_element %>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<label for="desktop-<%= id %>" class="absolute inset-0 flex flex-col h-full w-full items-center justify-center bg-blue-800/75 text-sm font-medium text-white opacity-0 hover:opacity-100">
|
28
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-white mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
29
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
30
|
+
</svg>
|
31
|
+
<span>Change</span>
|
32
|
+
<span class="sr-only"><%= @label %></span>
|
33
|
+
<%= input_element(input_id: "desktop-#{id}") %>
|
34
|
+
</label>
|
35
|
+
</div>
|
36
|
+
<%= helper_text %>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<script>
|
40
|
+
var loadFile = function(event, id) {
|
41
|
+
const elements = document.getElementById('business_logo_image').querySelectorAll('.img_photo');
|
42
|
+
|
43
|
+
elements.forEach(element => {
|
44
|
+
element.src = URL.createObjectURL(event.target.files[0]);
|
45
|
+
element.onload = function() { URL.revokeObjectURL(element.src) }
|
46
|
+
});
|
47
|
+
};
|
48
|
+
</script>
|