better_ui 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +257 -212
- data/Rakefile +11 -2
- data/app/components/better_ui/action_messages_component/action_messages_component.html.erb +48 -0
- data/app/components/better_ui/action_messages_component.rb +544 -0
- data/app/components/better_ui/application_component.rb +66 -0
- data/app/components/better_ui/button_component/button_component.html.erb +31 -0
- data/app/components/better_ui/button_component.rb +307 -0
- data/app/components/better_ui/card_component/card_component.html.erb +17 -0
- data/app/components/better_ui/card_component.rb +460 -0
- data/app/components/better_ui/drawer/header_component/header_component.html.erb +24 -0
- data/app/components/better_ui/drawer/header_component.rb +238 -0
- data/app/components/better_ui/drawer/layout_component/layout_component.html.erb +44 -0
- data/app/components/better_ui/drawer/layout_component.rb +270 -0
- data/app/components/better_ui/drawer/nav_group_component/nav_group_component.html.erb +10 -0
- data/app/components/better_ui/drawer/nav_group_component.rb +155 -0
- data/app/components/better_ui/drawer/nav_item_component/nav_item_component.html.erb +13 -0
- data/app/components/better_ui/drawer/nav_item_component.rb +225 -0
- data/app/components/better_ui/drawer/sidebar_component/sidebar_component.html.erb +17 -0
- data/app/components/better_ui/drawer/sidebar_component.rb +263 -0
- data/app/components/better_ui/forms/base_component.rb +450 -0
- data/app/components/better_ui/forms/checkbox_component/checkbox_component.html.erb +28 -0
- data/app/components/better_ui/forms/checkbox_component.rb +419 -0
- data/app/components/better_ui/forms/checkbox_group_component/checkbox_group_component.html.erb +40 -0
- data/app/components/better_ui/forms/checkbox_group_component.rb +363 -0
- data/app/components/better_ui/forms/number_input_component/number_input_component.html.erb +40 -0
- data/app/components/better_ui/forms/number_input_component.rb +320 -0
- data/app/components/better_ui/forms/password_input_component/password_input_component.html.erb +71 -0
- data/app/components/better_ui/forms/password_input_component.rb +206 -0
- data/app/components/better_ui/forms/text_input_component/text_input_component.html.erb +40 -0
- data/app/components/better_ui/forms/text_input_component.rb +258 -0
- data/app/components/better_ui/forms/textarea_component/textarea_component.html.erb +40 -0
- data/app/components/better_ui/forms/textarea_component.rb +329 -0
- data/app/form_builders/better_ui/ui_form_builder.rb +467 -0
- data/app/helpers/better_ui/application_helper.rb +325 -58
- data/app/views/layouts/better_ui/application.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/lib/better_ui/engine.rb +34 -5
- data/lib/better_ui/version.rb +1 -1
- data/lib/better_ui.rb +32 -5
- data/lib/generators/better_ui/install/USAGE +44 -0
- data/lib/generators/better_ui/install/install_generator.rb +87 -0
- data/lib/generators/better_ui/install/templates/better_ui_theme.css.tt +280 -0
- data/lib/tasks/better_ui_tasks.rake +39 -4
- metadata +55 -203
- data/app/components/better_ui/application/card/component.html.erb +0 -20
- data/app/components/better_ui/application/card/component.rb +0 -214
- data/app/components/better_ui/application/main/component.html.erb +0 -9
- data/app/components/better_ui/application/main/component.rb +0 -123
- data/app/components/better_ui/application/navbar/component.html.erb +0 -92
- data/app/components/better_ui/application/navbar/component.rb +0 -136
- data/app/components/better_ui/application/sidebar/component.html.erb +0 -249
- data/app/components/better_ui/application/sidebar/component.rb +0 -187
- data/app/components/better_ui/general/accordion/component.html.erb +0 -5
- data/app/components/better_ui/general/accordion/component.rb +0 -92
- data/app/components/better_ui/general/accordion/item_component.html.erb +0 -12
- data/app/components/better_ui/general/accordion/item_component.rb +0 -176
- data/app/components/better_ui/general/alert/component.html.erb +0 -32
- data/app/components/better_ui/general/alert/component.rb +0 -242
- data/app/components/better_ui/general/avatar/component.html.erb +0 -20
- data/app/components/better_ui/general/avatar/component.rb +0 -301
- data/app/components/better_ui/general/badge/component.html.erb +0 -23
- data/app/components/better_ui/general/badge/component.rb +0 -248
- data/app/components/better_ui/general/breadcrumb/component.html.erb +0 -15
- data/app/components/better_ui/general/breadcrumb/component.rb +0 -187
- data/app/components/better_ui/general/button/component.html.erb +0 -34
- data/app/components/better_ui/general/button/component.rb +0 -214
- data/app/components/better_ui/general/divider/component.html.erb +0 -10
- data/app/components/better_ui/general/divider/component.rb +0 -226
- data/app/components/better_ui/general/dropdown/component.html.erb +0 -28
- data/app/components/better_ui/general/dropdown/component.rb +0 -192
- data/app/components/better_ui/general/dropdown/divider_component.html.erb +0 -1
- data/app/components/better_ui/general/dropdown/divider_component.rb +0 -41
- data/app/components/better_ui/general/dropdown/item_component.html.erb +0 -6
- data/app/components/better_ui/general/dropdown/item_component.rb +0 -119
- data/app/components/better_ui/general/field/component.html.erb +0 -27
- data/app/components/better_ui/general/field/component.rb +0 -37
- data/app/components/better_ui/general/grid/cell_component.html.erb +0 -3
- data/app/components/better_ui/general/grid/cell_component.rb +0 -390
- data/app/components/better_ui/general/grid/component.html.erb +0 -3
- data/app/components/better_ui/general/grid/component.rb +0 -301
- data/app/components/better_ui/general/heading/component.html.erb +0 -22
- data/app/components/better_ui/general/heading/component.rb +0 -257
- data/app/components/better_ui/general/icon/component.html.erb +0 -7
- data/app/components/better_ui/general/icon/component.rb +0 -240
- data/app/components/better_ui/general/input/checkbox/component.html.erb +0 -5
- data/app/components/better_ui/general/input/checkbox/component.rb +0 -238
- data/app/components/better_ui/general/input/datetime/component.html.erb +0 -5
- data/app/components/better_ui/general/input/datetime/component.rb +0 -223
- data/app/components/better_ui/general/input/pin/component.html.erb +0 -1
- data/app/components/better_ui/general/input/pin/component.rb +0 -201
- data/app/components/better_ui/general/input/radio/component.html.erb +0 -5
- data/app/components/better_ui/general/input/radio/component.rb +0 -230
- data/app/components/better_ui/general/input/rating/component.html.erb +0 -4
- data/app/components/better_ui/general/input/rating/component.rb +0 -272
- data/app/components/better_ui/general/input/select/component.html.erb +0 -78
- data/app/components/better_ui/general/input/select/component.rb +0 -249
- data/app/components/better_ui/general/input/select/select_component.html.erb +0 -5
- data/app/components/better_ui/general/input/select/select_component.rb +0 -37
- data/app/components/better_ui/general/input/text/component.html.erb +0 -5
- data/app/components/better_ui/general/input/text/component.rb +0 -171
- data/app/components/better_ui/general/input/textarea/component.html.erb +0 -5
- data/app/components/better_ui/general/input/textarea/component.rb +0 -166
- data/app/components/better_ui/general/input/toggle/component.html.erb +0 -5
- data/app/components/better_ui/general/input/toggle/component.rb +0 -242
- data/app/components/better_ui/general/link/component.html.erb +0 -18
- data/app/components/better_ui/general/link/component.rb +0 -258
- data/app/components/better_ui/general/modal/component.html.erb +0 -5
- data/app/components/better_ui/general/modal/component.rb +0 -47
- data/app/components/better_ui/general/modal/modal_component.html.erb +0 -52
- data/app/components/better_ui/general/modal/modal_component.rb +0 -160
- data/app/components/better_ui/general/pagination/component.html.erb +0 -85
- data/app/components/better_ui/general/pagination/component.rb +0 -216
- data/app/components/better_ui/general/panel/component.html.erb +0 -28
- data/app/components/better_ui/general/panel/component.rb +0 -249
- data/app/components/better_ui/general/progress/component.html.erb +0 -11
- data/app/components/better_ui/general/progress/component.rb +0 -160
- data/app/components/better_ui/general/spinner/component.html.erb +0 -35
- data/app/components/better_ui/general/spinner/component.rb +0 -93
- data/app/components/better_ui/general/table/component.html.erb +0 -5
- data/app/components/better_ui/general/table/component.rb +0 -217
- data/app/components/better_ui/general/table/tbody_component.html.erb +0 -3
- data/app/components/better_ui/general/table/tbody_component.rb +0 -30
- data/app/components/better_ui/general/table/td_component.html.erb +0 -3
- data/app/components/better_ui/general/table/td_component.rb +0 -44
- data/app/components/better_ui/general/table/tfoot_component.html.erb +0 -3
- data/app/components/better_ui/general/table/tfoot_component.rb +0 -28
- data/app/components/better_ui/general/table/th_component.html.erb +0 -6
- data/app/components/better_ui/general/table/th_component.rb +0 -51
- data/app/components/better_ui/general/table/thead_component.html.erb +0 -3
- data/app/components/better_ui/general/table/thead_component.rb +0 -28
- data/app/components/better_ui/general/table/tr_component.html.erb +0 -3
- data/app/components/better_ui/general/table/tr_component.rb +0 -30
- data/app/components/better_ui/general/tabs/component.html.erb +0 -11
- data/app/components/better_ui/general/tabs/component.rb +0 -120
- data/app/components/better_ui/general/tabs/panel_component.html.erb +0 -3
- data/app/components/better_ui/general/tabs/panel_component.rb +0 -37
- data/app/components/better_ui/general/tabs/tab_component.html.erb +0 -13
- data/app/components/better_ui/general/tabs/tab_component.rb +0 -111
- data/app/components/better_ui/general/tag/component.html.erb +0 -3
- data/app/components/better_ui/general/tag/component.rb +0 -104
- data/app/components/better_ui/general/text/component.html.erb +0 -1
- data/app/components/better_ui/general/text/component.rb +0 -194
- data/app/components/better_ui/general/tooltip/component.html.erb +0 -7
- data/app/components/better_ui/general/tooltip/component.rb +0 -239
- data/app/helpers/better_ui/application/components/card/card_helper.rb +0 -96
- data/app/helpers/better_ui/application/components/card.rb +0 -11
- data/app/helpers/better_ui/application/components/main/main_helper.rb +0 -64
- data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +0 -77
- data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +0 -51
- data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +0 -73
- data/app/helpers/better_ui/general/components/alert/alert_helper.rb +0 -57
- data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +0 -29
- data/app/helpers/better_ui/general/components/badge/badge_helper.rb +0 -53
- data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +0 -37
- data/app/helpers/better_ui/general/components/button/button_helper.rb +0 -65
- data/app/helpers/better_ui/general/components/container/container_helper.rb +0 -60
- data/app/helpers/better_ui/general/components/divider/divider_helper.rb +0 -63
- data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +0 -32
- data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +0 -88
- data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +0 -68
- data/app/helpers/better_ui/general/components/field/field_helper.rb +0 -26
- data/app/helpers/better_ui/general/components/grid/grid_helper.rb +0 -145
- data/app/helpers/better_ui/general/components/heading/heading_helper.rb +0 -72
- data/app/helpers/better_ui/general/components/icon/icon_helper.rb +0 -16
- data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +0 -81
- data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +0 -91
- data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +0 -76
- data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +0 -79
- data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +0 -124
- data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +0 -70
- data/app/helpers/better_ui/general/components/input/select/select_helper.rb +0 -86
- data/app/helpers/better_ui/general/components/input/text/text_helper.rb +0 -138
- data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +0 -73
- data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +0 -77
- data/app/helpers/better_ui/general/components/link/link_helper.rb +0 -89
- data/app/helpers/better_ui/general/components/modal/modal_helper.rb +0 -85
- data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +0 -82
- data/app/helpers/better_ui/general/components/panel/panel_helper.rb +0 -83
- data/app/helpers/better_ui/general/components/progress/progress_helper.rb +0 -53
- data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +0 -19
- data/app/helpers/better_ui/general/components/table/table_helper.rb +0 -53
- data/app/helpers/better_ui/general/components/table/tbody_helper.rb +0 -13
- data/app/helpers/better_ui/general/components/table/td_helper.rb +0 -19
- data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +0 -13
- data/app/helpers/better_ui/general/components/table/th_helper.rb +0 -19
- data/app/helpers/better_ui/general/components/table/thead_helper.rb +0 -13
- data/app/helpers/better_ui/general/components/table/tr_helper.rb +0 -13
- data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +0 -62
- data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +0 -55
- data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +0 -95
- data/app/helpers/better_ui/general/components/tag/tag_helper.rb +0 -26
- data/app/helpers/better_ui/general/components/text/text_helper.rb +0 -83
- data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +0 -60
- data/app/jobs/better_ui/application_job.rb +0 -4
- data/app/mailers/better_ui/application_mailer.rb +0 -6
- data/config/initializers/lookbook.rb +0 -23
- data/lib/better_ui/railtie.rb +0 -20
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterUi
|
|
4
|
+
module Forms
|
|
5
|
+
# Base component for all form input components in the BetterUi::Forms namespace.
|
|
6
|
+
#
|
|
7
|
+
# This abstract class provides a common structure and behavior for form input components,
|
|
8
|
+
# including label positioning (vertical/above input), hint text display, error message
|
|
9
|
+
# handling, and consistent sizing/styling across all input types.
|
|
10
|
+
#
|
|
11
|
+
# @abstract Subclasses must implement the {#call} method to render the specific input type.
|
|
12
|
+
#
|
|
13
|
+
# @example Extending BaseComponent to create a custom input
|
|
14
|
+
# module BetterUi
|
|
15
|
+
# module Forms
|
|
16
|
+
# class CustomInputComponent < BaseComponent
|
|
17
|
+
# def call
|
|
18
|
+
# content_tag :div, class: wrapper_classes do
|
|
19
|
+
# # Render label, input, hint, and errors
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @example Usage with Rails form builder (via BetterUi::UiFormBuilder)
|
|
27
|
+
# <%= form_with model: @user, builder: BetterUi::UiFormBuilder do |f| %>
|
|
28
|
+
# <%= f.ui_text_input :email, label: "Email Address", hint: "We'll never share your email" %>
|
|
29
|
+
# <% end %>
|
|
30
|
+
#
|
|
31
|
+
# @see TextInputComponent
|
|
32
|
+
# @see NumberInputComponent
|
|
33
|
+
# @see BetterUi::UiFormBuilder
|
|
34
|
+
class BaseComponent < ApplicationComponent
|
|
35
|
+
# Available size variants for form inputs.
|
|
36
|
+
# Each size adjusts font size, padding, and spacing proportionally.
|
|
37
|
+
#
|
|
38
|
+
# @return [Array<Symbol>] the list of valid size options (:xs, :sm, :md, :lg, :xl)
|
|
39
|
+
SIZES = %i[xs sm md lg xl].freeze
|
|
40
|
+
|
|
41
|
+
# Initializes a new form input component with common form field attributes.
|
|
42
|
+
#
|
|
43
|
+
# @param name [String] the name attribute for the input field (required for form submission)
|
|
44
|
+
# @param value [String, nil] the current value of the input field
|
|
45
|
+
# @param label [String, nil] the label text to display above the input
|
|
46
|
+
# @param hint [String, nil] helpful hint text displayed below the input
|
|
47
|
+
# @param placeholder [String, nil] placeholder text shown when input is empty
|
|
48
|
+
# @param size [Symbol] the size variant (:xs, :sm, :md, :lg, :xl), defaults to :md
|
|
49
|
+
# @param disabled [Boolean] whether the input should be disabled (non-interactive), defaults to false
|
|
50
|
+
# @param readonly [Boolean] whether the input should be readonly (viewable but not editable), defaults to false
|
|
51
|
+
# @param required [Boolean] whether the field is required (shows asterisk indicator), defaults to false
|
|
52
|
+
# @param errors [Array<String>, String, nil] validation error messages to display below the input
|
|
53
|
+
# @param container_classes [String, Array<String>, nil] additional CSS classes for the outer wrapper
|
|
54
|
+
# @param label_classes [String, Array<String>, nil] additional CSS classes for the label element
|
|
55
|
+
# @param input_classes [String, Array<String>, nil] additional CSS classes for the input element
|
|
56
|
+
# @param hint_classes [String, Array<String>, nil] additional CSS classes for the hint text
|
|
57
|
+
# @param error_classes [String, Array<String>, nil] additional CSS classes for error messages
|
|
58
|
+
# @param options [Hash] additional HTML attributes to pass through to the input element
|
|
59
|
+
#
|
|
60
|
+
# @raise [ArgumentError] if size is not one of the valid SIZES
|
|
61
|
+
#
|
|
62
|
+
# @example Basic initialization
|
|
63
|
+
# BetterUi::Forms::TextInputComponent.new(
|
|
64
|
+
# name: "user[email]",
|
|
65
|
+
# label: "Email Address",
|
|
66
|
+
# hint: "We'll never share your email",
|
|
67
|
+
# required: true
|
|
68
|
+
# )
|
|
69
|
+
#
|
|
70
|
+
# @example With errors
|
|
71
|
+
# BetterUi::Forms::TextInputComponent.new(
|
|
72
|
+
# name: "user[email]",
|
|
73
|
+
# value: "invalid",
|
|
74
|
+
# errors: ["Email is invalid", "Email can't be blank"]
|
|
75
|
+
# )
|
|
76
|
+
#
|
|
77
|
+
# @example Custom styling
|
|
78
|
+
# BetterUi::Forms::TextInputComponent.new(
|
|
79
|
+
# name: "search",
|
|
80
|
+
# size: :lg,
|
|
81
|
+
# container_classes: "my-4",
|
|
82
|
+
# input_classes: "font-mono"
|
|
83
|
+
# )
|
|
84
|
+
def initialize(
|
|
85
|
+
name:,
|
|
86
|
+
value: nil,
|
|
87
|
+
label: nil,
|
|
88
|
+
hint: nil,
|
|
89
|
+
placeholder: nil,
|
|
90
|
+
size: :md,
|
|
91
|
+
disabled: false,
|
|
92
|
+
readonly: false,
|
|
93
|
+
required: false,
|
|
94
|
+
errors: nil,
|
|
95
|
+
container_classes: nil,
|
|
96
|
+
label_classes: nil,
|
|
97
|
+
input_classes: nil,
|
|
98
|
+
hint_classes: nil,
|
|
99
|
+
error_classes: nil,
|
|
100
|
+
**options
|
|
101
|
+
)
|
|
102
|
+
@name = name
|
|
103
|
+
@value = value
|
|
104
|
+
@label = label
|
|
105
|
+
@hint = hint
|
|
106
|
+
@placeholder = placeholder
|
|
107
|
+
@size = validate_size(size)
|
|
108
|
+
@disabled = disabled
|
|
109
|
+
@readonly = readonly
|
|
110
|
+
@required = required
|
|
111
|
+
@errors = Array(errors).compact.reject(&:blank?)
|
|
112
|
+
@container_classes = container_classes
|
|
113
|
+
@label_classes = label_classes
|
|
114
|
+
@input_classes = input_classes
|
|
115
|
+
@hint_classes = hint_classes
|
|
116
|
+
@error_classes = error_classes
|
|
117
|
+
@options = options
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Renders the component markup.
|
|
121
|
+
#
|
|
122
|
+
# This method must be implemented by subclasses to define the specific
|
|
123
|
+
# rendering logic for each input type (text, number, select, etc.).
|
|
124
|
+
#
|
|
125
|
+
# @abstract Subclasses must implement this method
|
|
126
|
+
# @raise [NotImplementedError] if called directly on BaseComponent
|
|
127
|
+
# @return [String] the rendered HTML markup
|
|
128
|
+
def call
|
|
129
|
+
raise NotImplementedError, "Subclasses must implement the #call method"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
# Validates that the provided size is one of the allowed SIZES.
|
|
135
|
+
#
|
|
136
|
+
# @param size [Symbol] the size to validate
|
|
137
|
+
# @return [Symbol] the validated size
|
|
138
|
+
# @raise [ArgumentError] if size is not in SIZES
|
|
139
|
+
# @api private
|
|
140
|
+
def validate_size(size)
|
|
141
|
+
unless SIZES.include?(size)
|
|
142
|
+
raise ArgumentError, "Invalid size: #{size}. Must be one of #{SIZES.join(', ')}"
|
|
143
|
+
end
|
|
144
|
+
size
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Checks if the component has any validation errors to display.
|
|
148
|
+
#
|
|
149
|
+
# @return [Boolean] true if errors are present, false otherwise
|
|
150
|
+
# @api private
|
|
151
|
+
def has_errors?
|
|
152
|
+
@errors.present?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns the CSS classes for the outermost wrapper element.
|
|
156
|
+
#
|
|
157
|
+
# Merges base wrapper classes with any custom container_classes provided.
|
|
158
|
+
#
|
|
159
|
+
# @return [String] the merged CSS class string
|
|
160
|
+
# @api private
|
|
161
|
+
def wrapper_classes
|
|
162
|
+
css_classes([
|
|
163
|
+
"form-field-wrapper",
|
|
164
|
+
@container_classes
|
|
165
|
+
].flatten.compact)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns the CSS classes for the label element.
|
|
169
|
+
#
|
|
170
|
+
# Combines base label styles with size-specific classes and custom label_classes.
|
|
171
|
+
#
|
|
172
|
+
# @return [String] the merged CSS class string for the label
|
|
173
|
+
# @api private
|
|
174
|
+
def label_element_classes
|
|
175
|
+
css_classes([
|
|
176
|
+
"block",
|
|
177
|
+
"font-medium",
|
|
178
|
+
"text-gray-700",
|
|
179
|
+
"mb-1",
|
|
180
|
+
label_size_classes,
|
|
181
|
+
@label_classes
|
|
182
|
+
].flatten.compact)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Returns size-specific CSS classes for the label text.
|
|
186
|
+
#
|
|
187
|
+
# @return [String] the text size class for the current component size
|
|
188
|
+
# @api private
|
|
189
|
+
def label_size_classes
|
|
190
|
+
case @size
|
|
191
|
+
when :xs then "text-xs"
|
|
192
|
+
when :sm then "text-sm"
|
|
193
|
+
when :md then "text-sm"
|
|
194
|
+
when :lg then "text-base"
|
|
195
|
+
when :xl then "text-lg"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Returns the CSS classes for the input wrapper element.
|
|
200
|
+
#
|
|
201
|
+
# This wrapper is used to position prefix/suffix icons relative to the input.
|
|
202
|
+
#
|
|
203
|
+
# @return [String] the merged CSS class string for the input wrapper
|
|
204
|
+
# @api private
|
|
205
|
+
def input_wrapper_classes
|
|
206
|
+
css_classes([
|
|
207
|
+
"relative",
|
|
208
|
+
"flex",
|
|
209
|
+
"items-center"
|
|
210
|
+
].flatten.compact)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns the CSS classes for the input element itself.
|
|
214
|
+
#
|
|
215
|
+
# Combines base input styles, size-specific classes, state-specific classes,
|
|
216
|
+
# and any custom input_classes.
|
|
217
|
+
#
|
|
218
|
+
# @return [String] the merged CSS class string for the input element
|
|
219
|
+
# @api private
|
|
220
|
+
def input_element_classes
|
|
221
|
+
css_classes([
|
|
222
|
+
base_input_classes,
|
|
223
|
+
size_input_classes,
|
|
224
|
+
state_input_classes,
|
|
225
|
+
@input_classes
|
|
226
|
+
].flatten.compact)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Returns the base CSS classes common to all input elements.
|
|
230
|
+
#
|
|
231
|
+
# These classes apply regardless of size, state, or variant.
|
|
232
|
+
#
|
|
233
|
+
# @return [Array<String>] array of base CSS class strings
|
|
234
|
+
# @api private
|
|
235
|
+
def base_input_classes
|
|
236
|
+
[
|
|
237
|
+
"block",
|
|
238
|
+
"w-full",
|
|
239
|
+
"rounded-md",
|
|
240
|
+
"border",
|
|
241
|
+
"shadow-sm",
|
|
242
|
+
"transition-colors",
|
|
243
|
+
"duration-200"
|
|
244
|
+
]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Returns size-specific CSS classes for the input element.
|
|
248
|
+
#
|
|
249
|
+
# Controls font size, padding, and spacing based on the component size.
|
|
250
|
+
#
|
|
251
|
+
# @return [Array<String>] array of size-specific CSS class strings
|
|
252
|
+
# @api private
|
|
253
|
+
def size_input_classes
|
|
254
|
+
case @size
|
|
255
|
+
when :xs
|
|
256
|
+
[ "text-xs", "py-1", "px-2" ]
|
|
257
|
+
when :sm
|
|
258
|
+
[ "text-sm", "py-1.5", "px-3" ]
|
|
259
|
+
when :md
|
|
260
|
+
[ "text-base", "py-2", "px-4" ]
|
|
261
|
+
when :lg
|
|
262
|
+
[ "text-lg", "py-2.5", "px-5" ]
|
|
263
|
+
when :xl
|
|
264
|
+
[ "text-xl", "py-3", "px-6" ]
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Returns state-specific CSS classes for the input element.
|
|
269
|
+
#
|
|
270
|
+
# Determines which state classes to apply based on the input's current state:
|
|
271
|
+
# disabled, readonly, error, or normal. Only one state is applied at a time,
|
|
272
|
+
# with priority: disabled > readonly > error > normal.
|
|
273
|
+
#
|
|
274
|
+
# @return [Array<String>] array of state-specific CSS class strings
|
|
275
|
+
# @api private
|
|
276
|
+
def state_input_classes
|
|
277
|
+
if @disabled
|
|
278
|
+
disabled_classes
|
|
279
|
+
elsif @readonly
|
|
280
|
+
readonly_classes
|
|
281
|
+
elsif has_errors?
|
|
282
|
+
error_state_classes
|
|
283
|
+
else
|
|
284
|
+
normal_state_classes
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Returns CSS classes for the normal (default) input state.
|
|
289
|
+
#
|
|
290
|
+
# Applied when the input is not disabled, readonly, or in error state.
|
|
291
|
+
# Includes border, background, text colors, and focus ring styles.
|
|
292
|
+
#
|
|
293
|
+
# @return [Array<String>] array of CSS class strings for normal state
|
|
294
|
+
# @api private
|
|
295
|
+
def normal_state_classes
|
|
296
|
+
[
|
|
297
|
+
"border-gray-300",
|
|
298
|
+
"bg-white",
|
|
299
|
+
"text-gray-900",
|
|
300
|
+
"placeholder-gray-400",
|
|
301
|
+
"focus:border-primary-500",
|
|
302
|
+
"focus:ring-2",
|
|
303
|
+
"focus:ring-primary-500",
|
|
304
|
+
"focus:ring-opacity-20",
|
|
305
|
+
"focus:outline-none"
|
|
306
|
+
]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Returns CSS classes for the error input state.
|
|
310
|
+
#
|
|
311
|
+
# Applied when the input has validation errors. Uses danger color variant
|
|
312
|
+
# for border and focus ring to indicate the error state visually.
|
|
313
|
+
#
|
|
314
|
+
# @return [Array<String>] array of CSS class strings for error state
|
|
315
|
+
# @api private
|
|
316
|
+
def error_state_classes
|
|
317
|
+
[
|
|
318
|
+
"border-danger-500",
|
|
319
|
+
"bg-white",
|
|
320
|
+
"text-gray-900",
|
|
321
|
+
"placeholder-gray-400",
|
|
322
|
+
"focus:border-danger-600",
|
|
323
|
+
"focus:ring-2",
|
|
324
|
+
"focus:ring-danger-500",
|
|
325
|
+
"focus:ring-opacity-20",
|
|
326
|
+
"focus:outline-none"
|
|
327
|
+
]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Returns CSS classes for the disabled input state.
|
|
331
|
+
#
|
|
332
|
+
# Applied when the input is disabled (non-interactive).
|
|
333
|
+
# Reduces opacity and changes cursor to indicate non-interactive state.
|
|
334
|
+
#
|
|
335
|
+
# @return [Array<String>] array of CSS class strings for disabled state
|
|
336
|
+
# @api private
|
|
337
|
+
def disabled_classes
|
|
338
|
+
[
|
|
339
|
+
"border-gray-300",
|
|
340
|
+
"bg-gray-100",
|
|
341
|
+
"text-gray-500",
|
|
342
|
+
"placeholder-gray-400",
|
|
343
|
+
"cursor-not-allowed",
|
|
344
|
+
"opacity-60"
|
|
345
|
+
]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Returns CSS classes for the readonly input state.
|
|
349
|
+
#
|
|
350
|
+
# Applied when the input is readonly (viewable but not editable).
|
|
351
|
+
# Uses lighter background and default cursor to differentiate from disabled state.
|
|
352
|
+
#
|
|
353
|
+
# @return [Array<String>] array of CSS class strings for readonly state
|
|
354
|
+
# @api private
|
|
355
|
+
def readonly_classes
|
|
356
|
+
[
|
|
357
|
+
"border-gray-300",
|
|
358
|
+
"bg-gray-50",
|
|
359
|
+
"text-gray-700",
|
|
360
|
+
"cursor-default"
|
|
361
|
+
]
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Returns the CSS classes for the hint text element.
|
|
365
|
+
#
|
|
366
|
+
# Combines base hint styles with size-specific classes and custom hint_classes.
|
|
367
|
+
#
|
|
368
|
+
# @return [String] the merged CSS class string for the hint text
|
|
369
|
+
# @api private
|
|
370
|
+
def hint_element_classes
|
|
371
|
+
css_classes([
|
|
372
|
+
"block",
|
|
373
|
+
"text-gray-600",
|
|
374
|
+
"mt-1",
|
|
375
|
+
hint_size_classes,
|
|
376
|
+
@hint_classes
|
|
377
|
+
].flatten.compact)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Returns size-specific CSS classes for the hint text.
|
|
381
|
+
#
|
|
382
|
+
# Hint text is typically smaller than the input text for visual hierarchy.
|
|
383
|
+
#
|
|
384
|
+
# @return [String] the text size class for the current component size
|
|
385
|
+
# @api private
|
|
386
|
+
def hint_size_classes
|
|
387
|
+
case @size
|
|
388
|
+
when :xs then "text-xs"
|
|
389
|
+
when :sm then "text-xs"
|
|
390
|
+
when :md then "text-sm"
|
|
391
|
+
when :lg then "text-sm"
|
|
392
|
+
when :xl then "text-base"
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Returns the CSS classes for the error messages container.
|
|
397
|
+
#
|
|
398
|
+
# Combines base error styles with size-specific classes and custom error_classes.
|
|
399
|
+
#
|
|
400
|
+
# @return [String] the merged CSS class string for the error messages container
|
|
401
|
+
# @api private
|
|
402
|
+
def errors_element_classes
|
|
403
|
+
css_classes([
|
|
404
|
+
"text-danger-600",
|
|
405
|
+
"mt-1",
|
|
406
|
+
"space-y-0.5",
|
|
407
|
+
error_size_classes,
|
|
408
|
+
@error_classes
|
|
409
|
+
].flatten.compact)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Returns size-specific CSS classes for error message text.
|
|
413
|
+
#
|
|
414
|
+
# Error text sizing matches hint text sizing for consistency.
|
|
415
|
+
#
|
|
416
|
+
# @return [String] the text size class for the current component size
|
|
417
|
+
# @api private
|
|
418
|
+
def error_size_classes
|
|
419
|
+
case @size
|
|
420
|
+
when :xs then "text-xs"
|
|
421
|
+
when :sm then "text-xs"
|
|
422
|
+
when :md then "text-sm"
|
|
423
|
+
when :lg then "text-sm"
|
|
424
|
+
when :xl then "text-base"
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Returns the complete set of HTML attributes for the input element.
|
|
429
|
+
#
|
|
430
|
+
# Combines standard form attributes (name, value, placeholder, etc.)
|
|
431
|
+
# with state attributes (disabled, readonly, required) and any custom
|
|
432
|
+
# options passed during initialization. Nil values are removed via compact.
|
|
433
|
+
#
|
|
434
|
+
# @return [Hash] hash of HTML attributes for the input element
|
|
435
|
+
# @api private
|
|
436
|
+
def input_attributes
|
|
437
|
+
{
|
|
438
|
+
name: @name,
|
|
439
|
+
value: @value,
|
|
440
|
+
placeholder: @placeholder,
|
|
441
|
+
disabled: @disabled || nil,
|
|
442
|
+
readonly: @readonly || nil,
|
|
443
|
+
required: @required || nil,
|
|
444
|
+
class: input_element_classes,
|
|
445
|
+
**@options
|
|
446
|
+
}.compact
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="<%= wrapper_classes %>">
|
|
2
|
+
<div class="<%= checkbox_wrapper_classes %>">
|
|
3
|
+
<input <%= tag.attributes(checkbox_attributes) %> />
|
|
4
|
+
|
|
5
|
+
<% if @label.present? %>
|
|
6
|
+
<label for="<%= input_id %>" class="<%= label_element_classes %>">
|
|
7
|
+
<%= @label %>
|
|
8
|
+
<% if @required %>
|
|
9
|
+
<span class="text-danger-600 ml-0.5">*</span>
|
|
10
|
+
<% end %>
|
|
11
|
+
</label>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<% if @hint.present? %>
|
|
16
|
+
<div class="<%= hint_element_classes %>">
|
|
17
|
+
<%= @hint %>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<% if has_errors? %>
|
|
22
|
+
<div class="<%= errors_element_classes %>">
|
|
23
|
+
<% @errors.each do |error| %>
|
|
24
|
+
<div><%= error %></div>
|
|
25
|
+
<% end %>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|