fluxbit_view_components 0.4.3 โ 0.5.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 +4 -4
- data/app/assets/javascripts/fluxbit_view_components/index.js +4 -0
- data/app/assets/javascripts/fluxbit_view_components/telephone_controller.js +98 -0
- data/app/assets/javascripts/fluxbit_view_components.js +90 -1
- data/app/components/fluxbit/form/telephone_component.rb +197 -0
- data/app/helpers/fluxbit/form_builder.rb +1 -1
- data/lib/fluxbit/config/form/telephone_component.rb +72 -0
- data/lib/fluxbit/view_components/version.rb +1 -1
- data/lib/fluxbit/view_components.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f66b9f23ca4ef3e2fe559a212875eda72091ec0e44473efdeade01655b01ddf
|
|
4
|
+
data.tar.gz: 5d17abad070b64489453dde8cc700f3c464d4b97d60f0cb8aab4aa0137013385
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f893f775299b24f11ebc2c8120099c13999f525502d314d9ad87709a02fbbf6dd194bede20193e0cf5a5a7bd53b2b9e58d1298113df77f3ee7b958988c81bfd
|
|
7
|
+
data.tar.gz: 802ffce4186aa7fe55a32c56b904e084b4c346b6e50a5891bab9683eaa636cde5654b97e0c9b4502e30ad20e4d21b4bdcb2e68260cd66fd9d1aad72a7fd1ea73
|
|
@@ -8,6 +8,7 @@ import FxProgress from './progress_controller'
|
|
|
8
8
|
import FxRowClick from './row_click_controller'
|
|
9
9
|
import FxSelectAll from './select_all_controller'
|
|
10
10
|
import FxSpinnerPercent from './spinner_percent_controller'
|
|
11
|
+
import FxTelephone from './telephone_controller'
|
|
11
12
|
import FxThemeButton from './theme_button_controller'
|
|
12
13
|
|
|
13
14
|
export {
|
|
@@ -21,6 +22,7 @@ export {
|
|
|
21
22
|
FxRowClick,
|
|
22
23
|
FxSelectAll,
|
|
23
24
|
FxSpinnerPercent,
|
|
25
|
+
FxTelephone,
|
|
24
26
|
FxThemeButton
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -35,6 +37,7 @@ export function registerFluxbitControllers(application) {
|
|
|
35
37
|
application.register('fx-row-click', FxRowClick)
|
|
36
38
|
application.register('fx-select-all', FxSelectAll)
|
|
37
39
|
application.register('fx-spinner-percent', FxSpinnerPercent)
|
|
40
|
+
application.register('fx-telephone', FxTelephone)
|
|
38
41
|
application.register('fx-theme-button', FxThemeButton)
|
|
39
42
|
|
|
40
43
|
// Make controllers globally accessible for vanilla JS
|
|
@@ -50,6 +53,7 @@ export function registerFluxbitControllers(application) {
|
|
|
50
53
|
FxRowClick,
|
|
51
54
|
FxSelectAll,
|
|
52
55
|
FxSpinnerPercent,
|
|
56
|
+
FxTelephone,
|
|
53
57
|
FxThemeButton
|
|
54
58
|
}
|
|
55
59
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["countrySelect"]
|
|
5
|
+
static values = {
|
|
6
|
+
mask: { type: String, default: "(##) #####-####" }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.input = this.element.querySelector('input[type="tel"]')
|
|
11
|
+
if (this.input) {
|
|
12
|
+
this.input.addEventListener('input', this.applyMask.bind(this))
|
|
13
|
+
this.input.addEventListener('keydown', this.handleBackspace.bind(this))
|
|
14
|
+
|
|
15
|
+
// Apply mask to existing value on load
|
|
16
|
+
if (this.input.value) {
|
|
17
|
+
this.applyMask({ target: this.input })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
disconnect() {
|
|
23
|
+
if (this.input) {
|
|
24
|
+
this.input.removeEventListener('input', this.applyMask.bind(this))
|
|
25
|
+
this.input.removeEventListener('keydown', this.handleBackspace.bind(this))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
updateMask(event) {
|
|
30
|
+
const selectedOption = event.target.options[event.target.selectedIndex]
|
|
31
|
+
const newMask = selectedOption.dataset.mask
|
|
32
|
+
|
|
33
|
+
if (newMask) {
|
|
34
|
+
this.maskValue = newMask
|
|
35
|
+
// Clear the current value and reapply mask
|
|
36
|
+
const currentValue = this.input.value
|
|
37
|
+
this.input.value = ''
|
|
38
|
+
this.input.value = this.getCleanValue(currentValue)
|
|
39
|
+
this.applyMask({ target: this.input })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handleBackspace(event) {
|
|
44
|
+
if (event.key === 'Backspace' || event.keyCode === 8) {
|
|
45
|
+
const cursorPosition = this.input.selectionStart
|
|
46
|
+
const value = this.input.value
|
|
47
|
+
|
|
48
|
+
// Check if the character before cursor is a mask character
|
|
49
|
+
if (cursorPosition > 0) {
|
|
50
|
+
const charBefore = value.charAt(cursorPosition - 1)
|
|
51
|
+
if (this.isMaskCharacter(charBefore)) {
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
// Find the previous digit and remove it
|
|
54
|
+
let newPosition = cursorPosition - 1
|
|
55
|
+
while (newPosition > 0 && this.isMaskCharacter(value.charAt(newPosition))) {
|
|
56
|
+
newPosition--
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (newPosition >= 0) {
|
|
60
|
+
const cleanValue = this.getCleanValue(value.substring(0, newPosition) + value.substring(cursorPosition))
|
|
61
|
+
this.input.value = cleanValue
|
|
62
|
+
this.applyMask({ target: this.input })
|
|
63
|
+
this.input.setSelectionRange(newPosition, newPosition)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
applyMask(event) {
|
|
71
|
+
const input = event.target
|
|
72
|
+
let value = this.getCleanValue(input.value)
|
|
73
|
+
let maskedValue = ''
|
|
74
|
+
let valueIndex = 0
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < this.maskValue.length && valueIndex < value.length; i++) {
|
|
77
|
+
const maskChar = this.maskValue.charAt(i)
|
|
78
|
+
|
|
79
|
+
if (maskChar === '#') {
|
|
80
|
+
maskedValue += value.charAt(valueIndex)
|
|
81
|
+
valueIndex++
|
|
82
|
+
} else {
|
|
83
|
+
maskedValue += maskChar
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
input.value = maskedValue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getCleanValue(value) {
|
|
91
|
+
// Remove all non-numeric characters
|
|
92
|
+
return value.replace(/\D/g, '')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isMaskCharacter(char) {
|
|
96
|
+
return /[\s\-\(\)\/\.]/.test(char)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1067,6 +1067,93 @@ class FxSpinnerPercent extends Controller {
|
|
|
1067
1067
|
}
|
|
1068
1068
|
}
|
|
1069
1069
|
|
|
1070
|
+
class FxTelephone extends Controller {
|
|
1071
|
+
static targets=[ "countrySelect" ];
|
|
1072
|
+
static values={
|
|
1073
|
+
mask: {
|
|
1074
|
+
type: String,
|
|
1075
|
+
default: "(##) #####-####"
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
connect() {
|
|
1079
|
+
this.input = this.element.querySelector('input[type="tel"]');
|
|
1080
|
+
if (this.input) {
|
|
1081
|
+
this.input.addEventListener("input", this.applyMask.bind(this));
|
|
1082
|
+
this.input.addEventListener("keydown", this.handleBackspace.bind(this));
|
|
1083
|
+
if (this.input.value) {
|
|
1084
|
+
this.applyMask({
|
|
1085
|
+
target: this.input
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
disconnect() {
|
|
1091
|
+
if (this.input) {
|
|
1092
|
+
this.input.removeEventListener("input", this.applyMask.bind(this));
|
|
1093
|
+
this.input.removeEventListener("keydown", this.handleBackspace.bind(this));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
updateMask(event) {
|
|
1097
|
+
const selectedOption = event.target.options[event.target.selectedIndex];
|
|
1098
|
+
const newMask = selectedOption.dataset.mask;
|
|
1099
|
+
if (newMask) {
|
|
1100
|
+
this.maskValue = newMask;
|
|
1101
|
+
const currentValue = this.input.value;
|
|
1102
|
+
this.input.value = "";
|
|
1103
|
+
this.input.value = this.getCleanValue(currentValue);
|
|
1104
|
+
this.applyMask({
|
|
1105
|
+
target: this.input
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
handleBackspace(event) {
|
|
1110
|
+
if (event.key === "Backspace" || event.keyCode === 8) {
|
|
1111
|
+
const cursorPosition = this.input.selectionStart;
|
|
1112
|
+
const value = this.input.value;
|
|
1113
|
+
if (cursorPosition > 0) {
|
|
1114
|
+
const charBefore = value.charAt(cursorPosition - 1);
|
|
1115
|
+
if (this.isMaskCharacter(charBefore)) {
|
|
1116
|
+
event.preventDefault();
|
|
1117
|
+
let newPosition = cursorPosition - 1;
|
|
1118
|
+
while (newPosition > 0 && this.isMaskCharacter(value.charAt(newPosition))) {
|
|
1119
|
+
newPosition--;
|
|
1120
|
+
}
|
|
1121
|
+
if (newPosition >= 0) {
|
|
1122
|
+
const cleanValue = this.getCleanValue(value.substring(0, newPosition) + value.substring(cursorPosition));
|
|
1123
|
+
this.input.value = cleanValue;
|
|
1124
|
+
this.applyMask({
|
|
1125
|
+
target: this.input
|
|
1126
|
+
});
|
|
1127
|
+
this.input.setSelectionRange(newPosition, newPosition);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
applyMask(event) {
|
|
1134
|
+
const input = event.target;
|
|
1135
|
+
let value = this.getCleanValue(input.value);
|
|
1136
|
+
let maskedValue = "";
|
|
1137
|
+
let valueIndex = 0;
|
|
1138
|
+
for (let i = 0; i < this.maskValue.length && valueIndex < value.length; i++) {
|
|
1139
|
+
const maskChar = this.maskValue.charAt(i);
|
|
1140
|
+
if (maskChar === "#") {
|
|
1141
|
+
maskedValue += value.charAt(valueIndex);
|
|
1142
|
+
valueIndex++;
|
|
1143
|
+
} else {
|
|
1144
|
+
maskedValue += maskChar;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
input.value = maskedValue;
|
|
1148
|
+
}
|
|
1149
|
+
getCleanValue(value) {
|
|
1150
|
+
return value.replace(/\D/g, "");
|
|
1151
|
+
}
|
|
1152
|
+
isMaskCharacter(char) {
|
|
1153
|
+
return /[\s\-\(\)\/\.]/.test(char);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1070
1157
|
class FxThemeButton extends Controller {
|
|
1071
1158
|
static targets=[ "lightIcon", "darkIcon", "systemIcon" ];
|
|
1072
1159
|
static values={
|
|
@@ -1154,6 +1241,7 @@ function registerFluxbitControllers(application) {
|
|
|
1154
1241
|
application.register("fx-row-click", FxRowClick);
|
|
1155
1242
|
application.register("fx-select-all", FxSelectAll);
|
|
1156
1243
|
application.register("fx-spinner-percent", FxSpinnerPercent);
|
|
1244
|
+
application.register("fx-telephone", FxTelephone);
|
|
1157
1245
|
application.register("fx-theme-button", FxThemeButton);
|
|
1158
1246
|
if (typeof window !== "undefined") {
|
|
1159
1247
|
window.FluxbitControllers = {
|
|
@@ -1167,9 +1255,10 @@ function registerFluxbitControllers(application) {
|
|
|
1167
1255
|
FxRowClick: FxRowClick,
|
|
1168
1256
|
FxSelectAll: FxSelectAll,
|
|
1169
1257
|
FxSpinnerPercent: FxSpinnerPercent,
|
|
1258
|
+
FxTelephone: FxTelephone,
|
|
1170
1259
|
FxThemeButton: FxThemeButton
|
|
1171
1260
|
};
|
|
1172
1261
|
}
|
|
1173
1262
|
}
|
|
1174
1263
|
|
|
1175
|
-
export { FxAssigner, FxAutoSubmit, FxDrawer, FxMethodLink, FxModal, FxPassword, FxProgress, FxRowClick, FxSelectAll, FxSpinnerPercent, FxThemeButton, registerFluxbitControllers };
|
|
1264
|
+
export { FxAssigner, FxAutoSubmit, FxDrawer, FxMethodLink, FxModal, FxPassword, FxProgress, FxRowClick, FxSelectAll, FxSpinnerPercent, FxTelephone, FxThemeButton, registerFluxbitControllers };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::Form::TelephoneComponent` is a telephone input component that extends `Fluxbit::Form::TextFieldComponent`.
|
|
4
|
+
# It provides a styled telephone input with an integrated country code selector showing country flags and dialing codes.
|
|
5
|
+
# The input includes automatic masking for phone numbers.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# = render Fluxbit::Form::TelephoneComponent.new(name: :phone)
|
|
9
|
+
#
|
|
10
|
+
# @example With default country
|
|
11
|
+
# = render Fluxbit::Form::TelephoneComponent.new(name: :phone, default_country: "BR")
|
|
12
|
+
#
|
|
13
|
+
class Fluxbit::Form::TelephoneComponent < Fluxbit::Form::TextFieldComponent
|
|
14
|
+
include Fluxbit::Config::Form::TelephoneComponent
|
|
15
|
+
|
|
16
|
+
# Initializes the telephone component with the given properties.
|
|
17
|
+
#
|
|
18
|
+
# @param default_country [String] The default country code (ISO 3166-1 alpha-2, e.g., "BR", "US")
|
|
19
|
+
# @param country_field_name [String] Name for the hidden country code field (optional, deprecated in favor of :country)
|
|
20
|
+
# @param country [Symbol] Attribute name for the country field when using form builder (e.g., :phone_country)
|
|
21
|
+
# @param ... all other parameters from TextFieldComponent
|
|
22
|
+
def initialize(**props)
|
|
23
|
+
@default_country = props.delete(:default_country) || @@default_country
|
|
24
|
+
@country_field_name = props.delete(:country_field_name)
|
|
25
|
+
@country_attribute = props.delete(:country)
|
|
26
|
+
|
|
27
|
+
# Set default sizing from config if not specified
|
|
28
|
+
props[:sizing] = @@default_sizing unless props.key?(:sizing)
|
|
29
|
+
|
|
30
|
+
# Force type to tel
|
|
31
|
+
props[:type] = :tel
|
|
32
|
+
|
|
33
|
+
super(**props)
|
|
34
|
+
|
|
35
|
+
# Override the input classes to match our custom sizing
|
|
36
|
+
override_input_sizing
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def call
|
|
40
|
+
content_tag :div, **@wrapper_html do
|
|
41
|
+
safe_join [
|
|
42
|
+
label,
|
|
43
|
+
telephone_input_container,
|
|
44
|
+
help_text
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def override_input_sizing
|
|
52
|
+
# Remove the original size classes
|
|
53
|
+
current_classes = @props[:class].to_s
|
|
54
|
+
|
|
55
|
+
# Get size class from config
|
|
56
|
+
size_index = [@sizing, 0].max
|
|
57
|
+
size_index = [size_index, @@telephone_styles[:input][:sizes].length - 1].min
|
|
58
|
+
custom_size_class = @@telephone_styles[:input][:sizes][size_index]
|
|
59
|
+
|
|
60
|
+
# Remove the old size class and add our custom one
|
|
61
|
+
# First remove common padding/text classes that might conflict
|
|
62
|
+
current_classes = current_classes.gsub(/\bp-[\d.]+\b/, "")
|
|
63
|
+
.gsub(/\btext-(xs|sm|base|md|lg|xl)\b/, "")
|
|
64
|
+
.gsub(/\brounded-lg\b/, "")
|
|
65
|
+
.strip
|
|
66
|
+
|
|
67
|
+
@props[:class] = "#{current_classes} #{custom_size_class}".strip
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def current_country
|
|
71
|
+
@@telephone_countries.find { |c| c[:code] == @default_country } || @@telephone_countries.first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def country_select
|
|
75
|
+
content_tag :div, class: "relative flex-shrink-0" do
|
|
76
|
+
if @form.present? && @country_attribute.present?
|
|
77
|
+
# Use form builder's select to get proper name attribute
|
|
78
|
+
@form.select(
|
|
79
|
+
@country_attribute,
|
|
80
|
+
options_for_select(
|
|
81
|
+
@@telephone_countries.map { |c| [ "#{c[:flag]} #{c[:dial_code]}", c[:code], { "data-dial-code": c[:dial_code], "data-mask": c[:mask] } ] },
|
|
82
|
+
country_select_value
|
|
83
|
+
),
|
|
84
|
+
{},
|
|
85
|
+
{
|
|
86
|
+
id: country_select_id,
|
|
87
|
+
class: country_select_classes,
|
|
88
|
+
data: {
|
|
89
|
+
fx_telephone_target: "countrySelect",
|
|
90
|
+
action: "change->fx-telephone#updateMask"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
else
|
|
95
|
+
# Standalone select (no form builder)
|
|
96
|
+
select_tag(
|
|
97
|
+
country_select_name,
|
|
98
|
+
country_options_html,
|
|
99
|
+
id: country_select_id,
|
|
100
|
+
class: country_select_classes,
|
|
101
|
+
data: {
|
|
102
|
+
fx_telephone_target: "countrySelect",
|
|
103
|
+
action: "change->fx-telephone#updateMask"
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def country_options_html
|
|
111
|
+
safe_join(
|
|
112
|
+
@@telephone_countries.map do |country|
|
|
113
|
+
content_tag :option,
|
|
114
|
+
"#{country[:flag]} #{country[:dial_code]}",
|
|
115
|
+
value: country[:code],
|
|
116
|
+
selected: country[:code] == country_select_value,
|
|
117
|
+
data: {
|
|
118
|
+
dial_code: country[:dial_code],
|
|
119
|
+
mask: country[:mask]
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def country_select_name
|
|
126
|
+
# Only used in standalone mode
|
|
127
|
+
@country_field_name || "#{@name}_country"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def country_select_value
|
|
131
|
+
# Priority:
|
|
132
|
+
# 1. Value from object attribute (when using form builder with country attribute)
|
|
133
|
+
# 2. Default country
|
|
134
|
+
if @form.present? && @country_attribute.present? && @object.present?
|
|
135
|
+
@object.public_send(@country_attribute) rescue @default_country
|
|
136
|
+
else
|
|
137
|
+
@default_country
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def country_select_id
|
|
142
|
+
"#{@props[:id] || @name}_country"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def country_select_classes
|
|
146
|
+
# Get size from config
|
|
147
|
+
size_index = [@sizing, 0].max
|
|
148
|
+
size_index = [size_index, @@telephone_styles[:country_select][:sizes].length - 1].min
|
|
149
|
+
size_config = @@telephone_styles[:country_select][:sizes][size_index]
|
|
150
|
+
|
|
151
|
+
# Get color from config
|
|
152
|
+
color = @color || :default
|
|
153
|
+
color_classes = @@telephone_styles[:country_select][:colors][color] || @@telephone_styles[:country_select][:colors][:default]
|
|
154
|
+
|
|
155
|
+
[
|
|
156
|
+
@@telephone_styles[:country_select][:base],
|
|
157
|
+
@@telephone_styles[:country_select][:width],
|
|
158
|
+
size_config[:padding],
|
|
159
|
+
size_config[:text],
|
|
160
|
+
color_classes
|
|
161
|
+
].join(" ")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def telephone_input
|
|
165
|
+
# Override the props to adjust styling for the telephone input
|
|
166
|
+
input_props = @props.dup
|
|
167
|
+
|
|
168
|
+
# Remove left border radius since it connects to the country select
|
|
169
|
+
current_classes = input_props[:class] || ""
|
|
170
|
+
input_props[:class] = current_classes.gsub("rounded-lg", "rounded-r-lg")
|
|
171
|
+
|
|
172
|
+
# Store original props temporarily
|
|
173
|
+
original_props = @props
|
|
174
|
+
@props = input_props
|
|
175
|
+
|
|
176
|
+
result = input
|
|
177
|
+
|
|
178
|
+
# Restore original props
|
|
179
|
+
@props = original_props
|
|
180
|
+
|
|
181
|
+
result
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def telephone_input_container
|
|
185
|
+
content_tag :div,
|
|
186
|
+
class: "flex w-full",
|
|
187
|
+
data: {
|
|
188
|
+
controller: "fx-telephone",
|
|
189
|
+
fx_telephone_mask_value: current_country[:mask]
|
|
190
|
+
} do
|
|
191
|
+
safe_join([
|
|
192
|
+
country_select,
|
|
193
|
+
content_tag(:div, telephone_input, class: "relative flex-1")
|
|
194
|
+
])
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -68,7 +68,7 @@ module Fluxbit
|
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
[ :range, :toggle, :upload_image, :dropzone, :password ].each do |component|
|
|
71
|
+
[ :range, :toggle, :upload_image, :dropzone, :password, :telephone ].each do |component|
|
|
72
72
|
define_method("fx_#{component}") do |method, **options, &block|
|
|
73
73
|
options[:error] ||= error_for(method)
|
|
74
74
|
options[:error] = !!options[:error] if options[:error_hidden] && options[:error]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fluxbit::Config::Form::TelephoneComponent
|
|
4
|
+
mattr_accessor :default_country, default: "BR"
|
|
5
|
+
mattr_accessor :default_sizing, default: 1
|
|
6
|
+
|
|
7
|
+
# rubocop: disable Layout/LineLength, Metrics/BlockLength
|
|
8
|
+
mattr_accessor :telephone_countries do
|
|
9
|
+
[
|
|
10
|
+
{ code: "BR", name: "Brasil", dial_code: "+55", flag: "๐ง๐ท", mask: "(##) #####-####" },
|
|
11
|
+
{ code: "AR", name: "Argentina", dial_code: "+54", flag: "๐ฆ๐ท", mask: "## ####-####" },
|
|
12
|
+
{ code: "MX", name: "Mรฉxico", dial_code: "+52", flag: "๐ฒ๐ฝ", mask: "## #### ####" },
|
|
13
|
+
{ code: "CO", name: "Colombia", dial_code: "+57", flag: "๐จ๐ด", mask: "### ### ####" },
|
|
14
|
+
{ code: "CL", name: "Chile", dial_code: "+56", flag: "๐จ๐ฑ", mask: "# #### ####" },
|
|
15
|
+
{ code: "PE", name: "Perรบ", dial_code: "+51", flag: "๐ต๐ช", mask: "### ### ###" },
|
|
16
|
+
{ code: "VE", name: "Venezuela", dial_code: "+58", flag: "๐ป๐ช", mask: "###-###-####" },
|
|
17
|
+
{ code: "EC", name: "Ecuador", dial_code: "+593", flag: "๐ช๐จ", mask: "## ### ####" },
|
|
18
|
+
{ code: "BO", name: "Bolivia", dial_code: "+591", flag: "๐ง๐ด", mask: "# ### ####" },
|
|
19
|
+
{ code: "PY", name: "Paraguay", dial_code: "+595", flag: "๐ต๐พ", mask: "### ### ###" },
|
|
20
|
+
{ code: "UY", name: "Uruguay", dial_code: "+598", flag: "๐บ๐พ", mask: "# ### ## ##" },
|
|
21
|
+
{ code: "CR", name: "Costa Rica", dial_code: "+506", flag: "๐จ๐ท", mask: "#### ####" },
|
|
22
|
+
{ code: "PA", name: "Panamรก", dial_code: "+507", flag: "๐ต๐ฆ", mask: "####-####" },
|
|
23
|
+
{ code: "CU", name: "Cuba", dial_code: "+53", flag: "๐จ๐บ", mask: "# ### ####" },
|
|
24
|
+
{ code: "DO", name: "Repรบblica Dominicana", dial_code: "+1", flag: "๐ฉ๐ด", mask: "(###) ###-####" },
|
|
25
|
+
{ code: "GT", name: "Guatemala", dial_code: "+502", flag: "๐ฌ๐น", mask: "#### ####" },
|
|
26
|
+
{ code: "HN", name: "Honduras", dial_code: "+504", flag: "๐ญ๐ณ", mask: "####-####" },
|
|
27
|
+
{ code: "SV", name: "El Salvador", dial_code: "+503", flag: "๐ธ๐ป", mask: "####-####" },
|
|
28
|
+
{ code: "NI", name: "Nicaragua", dial_code: "+505", flag: "๐ณ๐ฎ", mask: "#### ####" },
|
|
29
|
+
{ code: "US", name: "United States", dial_code: "+1", flag: "๐บ๐ธ", mask: "(###) ###-####" },
|
|
30
|
+
{ code: "CA", name: "Canada", dial_code: "+1", flag: "๐จ๐ฆ", mask: "(###) ###-####" },
|
|
31
|
+
{ code: "ES", name: "Espaรฑa", dial_code: "+34", flag: "๐ช๐ธ", mask: "### ## ## ##" },
|
|
32
|
+
{ code: "PT", name: "Portugal", dial_code: "+351", flag: "๐ต๐น", mask: "### ### ###" },
|
|
33
|
+
{ code: "GB", name: "United Kingdom", dial_code: "+44", flag: "๐ฌ๐ง", mask: "#### ### ####" },
|
|
34
|
+
{ code: "DE", name: "Germany", dial_code: "+49", flag: "๐ฉ๐ช", mask: "### #########" },
|
|
35
|
+
{ code: "FR", name: "France", dial_code: "+33", flag: "๐ซ๐ท", mask: "# ## ## ## ##" },
|
|
36
|
+
{ code: "IT", name: "Italy", dial_code: "+39", flag: "๐ฎ๐น", mask: "### ### ####" },
|
|
37
|
+
{ code: "JP", name: "Japan", dial_code: "+81", flag: "๐ฏ๐ต", mask: "##-####-####" },
|
|
38
|
+
{ code: "CN", name: "China", dial_code: "+86", flag: "๐จ๐ณ", mask: "### #### ####" },
|
|
39
|
+
{ code: "IN", name: "India", dial_code: "+91", flag: "๐ฎ๐ณ", mask: "##### #####" },
|
|
40
|
+
{ code: "AU", name: "Australia", dial_code: "+61", flag: "๐ฆ๐บ", mask: "### ### ###" }
|
|
41
|
+
]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
mattr_accessor :telephone_styles do
|
|
45
|
+
{
|
|
46
|
+
country_select: {
|
|
47
|
+
base: "mt-1 block border border-r-0 rounded-l-lg focus:ring-blue-500 focus:border-blue-500",
|
|
48
|
+
colors: {
|
|
49
|
+
default: "text-slate-900 bg-slate-50 border-slate-300 dark:bg-slate-700 dark:border-slate-600 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
|
|
50
|
+
success: "text-green-900 bg-green-50 border-green-300 dark:bg-green-700 dark:border-green-600 dark:text-white",
|
|
51
|
+
danger: "text-red-900 bg-red-50 border-red-300 dark:bg-red-700 dark:border-red-600 dark:text-white",
|
|
52
|
+
warning: "text-yellow-900 bg-yellow-50 border-yellow-300 dark:bg-yellow-700 dark:border-yellow-600 dark:text-white",
|
|
53
|
+
info: "text-cyan-900 bg-cyan-50 border-cyan-300 dark:bg-cyan-700 dark:border-cyan-600 dark:text-white"
|
|
54
|
+
},
|
|
55
|
+
width: "w-24",
|
|
56
|
+
sizes: [
|
|
57
|
+
{ padding: "p-2", text: "text-xs" }, # Small (0)
|
|
58
|
+
{ padding: "p-2.5", text: "text-sm" }, # Medium (1)
|
|
59
|
+
{ padding: "p-4", text: "text-base" } # Large (2)
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
input: {
|
|
63
|
+
sizes: [
|
|
64
|
+
"p-2 text-xs rounded-lg", # Small (0)
|
|
65
|
+
"p-2.5 text-sm rounded-lg", # Medium (1)
|
|
66
|
+
"p-4 text-base rounded-lg" # Large (2)
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
# rubocop: enable Layout/LineLength, Metrics/BlockLength
|
|
72
|
+
end
|
|
@@ -16,6 +16,7 @@ module Fluxbit
|
|
|
16
16
|
require "fluxbit/config/form/password_component"
|
|
17
17
|
require "fluxbit/config/form/radio_group_button_component"
|
|
18
18
|
require "fluxbit/config/form/range_component"
|
|
19
|
+
require "fluxbit/config/form/telephone_component"
|
|
19
20
|
require "fluxbit/config/form/text_field_component"
|
|
20
21
|
require "fluxbit/config/form/toggle_component"
|
|
21
22
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluxbit_view_components
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Arthur Molina
|
|
@@ -146,6 +146,7 @@ files:
|
|
|
146
146
|
- app/assets/javascripts/fluxbit_view_components/row_click_controller.js
|
|
147
147
|
- app/assets/javascripts/fluxbit_view_components/select_all_controller.js
|
|
148
148
|
- app/assets/javascripts/fluxbit_view_components/spinner_percent_controller.js
|
|
149
|
+
- app/assets/javascripts/fluxbit_view_components/telephone_controller.js
|
|
149
150
|
- app/assets/javascripts/fluxbit_view_components/theme_button_controller.js
|
|
150
151
|
- app/components/fluxbit/accordion_component.rb
|
|
151
152
|
- app/components/fluxbit/alert_component.rb
|
|
@@ -177,6 +178,7 @@ files:
|
|
|
177
178
|
- app/components/fluxbit/form/radio_group_button_component.rb
|
|
178
179
|
- app/components/fluxbit/form/range_component.rb
|
|
179
180
|
- app/components/fluxbit/form/select_component.rb
|
|
181
|
+
- app/components/fluxbit/form/telephone_component.rb
|
|
180
182
|
- app/components/fluxbit/form/text_field_component.rb
|
|
181
183
|
- app/components/fluxbit/form/toggle_component.html.erb
|
|
182
184
|
- app/components/fluxbit/form/toggle_component.rb
|
|
@@ -233,6 +235,7 @@ files:
|
|
|
233
235
|
- lib/fluxbit/config/form/password_component.rb
|
|
234
236
|
- lib/fluxbit/config/form/radio_group_button_component.rb
|
|
235
237
|
- lib/fluxbit/config/form/range_component.rb
|
|
238
|
+
- lib/fluxbit/config/form/telephone_component.rb
|
|
236
239
|
- lib/fluxbit/config/form/text_field_component.rb
|
|
237
240
|
- lib/fluxbit/config/form/toggle_component.rb
|
|
238
241
|
- lib/fluxbit/config/gravatar_component.rb
|