formatic 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/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +32 -0
- data/app/assets/javascript/formatic/components/date.ts +54 -0
- data/app/assets/javascript/formatic/components/select.ts +109 -0
- data/app/assets/javascript/formatic/components/stepper.ts +89 -0
- data/app/assets/javascript/formatic/components/textarea.ts +108 -0
- data/app/assets/javascript/formatic/components/toggle.ts +81 -0
- data/app/assets/javascript/formatic.js +350 -0
- data/app/assets/javascript/formatic.js.map +1 -0
- data/app/assets/stylesheets/formatic/components/checklist.sass +3 -0
- data/app/assets/stylesheets/formatic/components/date.sass +70 -0
- data/app/assets/stylesheets/formatic/components/select.sass +24 -0
- data/app/assets/stylesheets/formatic/components/stepper.sass +43 -0
- data/app/assets/stylesheets/formatic/components/string.sass +13 -0
- data/app/assets/stylesheets/formatic/components/textarea.sass +22 -0
- data/app/assets/stylesheets/formatic/components/toggle.sass +93 -0
- data/app/assets/stylesheets/formatic/components/wrapper.sass +51 -0
- data/app/assets/stylesheets/formatic/generics/flip.sass +3 -0
- data/app/assets/stylesheets/formatic/index.sass +16 -0
- data/app/assets/stylesheets/formatic/package.json +5 -0
- data/app/assets/stylesheets/formatic/scopes/form.sass +45 -0
- data/app/assets/stylesheets/formatic/settings/_colors.sass +13 -0
- data/app/assets/stylesheets/formatic/tools/terminal.sass +3 -0
- data/app/assets/stylesheets/formatic/utilities/container.sass +2 -0
- data/app/components/formatic/application_component.rb +39 -0
- data/app/components/formatic/base.rb +72 -0
- data/app/components/formatic/checklist.rb +65 -0
- data/app/components/formatic/date.rb +110 -0
- data/app/components/formatic/select.rb +49 -0
- data/app/components/formatic/stepper.rb +35 -0
- data/app/components/formatic/string.rb +57 -0
- data/app/components/formatic/textarea.rb +48 -0
- data/app/components/formatic/toggle.rb +57 -0
- data/app/components/formatic/wrapper.rb +122 -0
- data/config/locales/formatic.de.yml +5 -0
- data/config/locales/formatic.en.yml +5 -0
- data/lib/formatic/choices/countries.rb +14 -0
- data/lib/formatic/choices/keys.rb +27 -0
- data/lib/formatic/choices/options.rb +39 -0
- data/lib/formatic/choices/records.rb +47 -0
- data/lib/formatic/choices.rb +62 -0
- data/lib/formatic/css.rb +10 -0
- data/lib/formatic/engine.rb +40 -0
- data/lib/formatic/safe_join.rb +20 -0
- data/lib/formatic/templates/date.rb +76 -0
- data/lib/formatic/templates/select.rb +35 -0
- data/lib/formatic/templates/wrapper.rb +52 -0
- data/lib/formatic/version.rb +5 -0
- data/lib/formatic/wrappers/alternative_attribute_name.rb +18 -0
- data/lib/formatic/wrappers/error_messages.rb +39 -0
- data/lib/formatic/wrappers/required.rb +29 -0
- data/lib/formatic/wrappers/translate.rb +41 -0
- data/lib/formatic/wrappers/validators.rb +39 -0
- data/lib/formatic.rb +29 -0
- metadata +186 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
@use "iglu/font-size"
|
2
|
+
@use 'iglu/spacing'
|
3
|
+
@use "formatic/settings/colors"
|
4
|
+
@use "iglu/responsive/settings/breakpoints"
|
5
|
+
|
6
|
+
.c-formatic-wrapper
|
7
|
+
display: grid
|
8
|
+
grid-row-gap: 0.2rem
|
9
|
+
grid-template-columns: auto
|
10
|
+
grid-template-areas: 'label' 'input' 'error' 'hint'
|
11
|
+
+font-size.default
|
12
|
+
+spacing.margin-bottom
|
13
|
+
|
14
|
+
&--hint-before-input
|
15
|
+
grid-template-areas: 'label' 'hint' 'input' 'error'
|
16
|
+
|
17
|
+
@each $column in label input error hint address
|
18
|
+
&__#{$column}
|
19
|
+
grid-area: #{$column}
|
20
|
+
|
21
|
+
&__input
|
22
|
+
// Allow for children of this div to autoscroll-x
|
23
|
+
min-width: 1px
|
24
|
+
|
25
|
+
&__error
|
26
|
+
color: colors.$paradise-pink
|
27
|
+
|
28
|
+
i
|
29
|
+
display: inline-block
|
30
|
+
height: 1em
|
31
|
+
width: 1em
|
32
|
+
animation: formatic-flip 4s infinite
|
33
|
+
background-size: 1em 1em
|
34
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"1024\" height=\"1024\" viewBox=\"0 0 1024 1024\"><path d=\"M512 220.885l368.256 675.115h-736.512l368.256-675.115zM512 42.667l-512 938.667h1024l-512-938.667zM469.333 426.667h85.333v256h-85.333v-256zM512 842.667c-29.397 0-53.333-23.893-53.333-53.333s23.936-53.333 53.333-53.333 53.333 23.893 53.333 53.333-23.936 53.333-53.333 53.333z\" style=\"fill: #{colors.$paradise-pink}\"></path></svg>")
|
35
|
+
|
36
|
+
&__hint
|
37
|
+
opacity: 0.5
|
38
|
+
+spacing.margin-bottom--tiny
|
39
|
+
+font-size.smaller
|
40
|
+
|
41
|
+
&__prevent-submit-on-enter
|
42
|
+
position: absolute
|
43
|
+
margin-left: -9999px
|
44
|
+
visibility: hidden
|
45
|
+
|
46
|
+
@container (min-width: #{breakpoints.$large})
|
47
|
+
.c-formatic-wrapper
|
48
|
+
grid-template-columns: 1fr 1fr
|
49
|
+
grid-template-rows: min-content min-content min-content
|
50
|
+
grid-column-gap: 1rem
|
51
|
+
grid-template-areas: 'label input' 'hint input' 'error input'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
// ITCSS
|
2
|
+
|
3
|
+
@use "./generics/flip.sass"
|
4
|
+
|
5
|
+
@use "./components/wrapper.sass" // Wrapper comes first, rest alphabetically
|
6
|
+
@use "./components/checklist.sass"
|
7
|
+
@use "./components/date.sass"
|
8
|
+
@use "./components/select.sass"
|
9
|
+
@use "./components/stepper.sass"
|
10
|
+
@use "./components/string.sass"
|
11
|
+
@use "./components/textarea.sass"
|
12
|
+
@use "./components/toggle.sass"
|
13
|
+
|
14
|
+
@use "./utilities/container"
|
15
|
+
|
16
|
+
@use "./scopes/form"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
@use "iglu/font-size"
|
2
|
+
@use "formatic/settings/colors"
|
3
|
+
|
4
|
+
.s-formatic
|
5
|
+
input[type="email"],
|
6
|
+
input[type="number"],
|
7
|
+
input[type="password"],
|
8
|
+
input[type="search"],
|
9
|
+
input[type="tel"],
|
10
|
+
input[type="text"],
|
11
|
+
input[type="url"],
|
12
|
+
textarea,
|
13
|
+
select
|
14
|
+
-webkit-appearance: none
|
15
|
+
-moz-appearance: none
|
16
|
+
font-family: 'Roboto Condensed'
|
17
|
+
width: 100%
|
18
|
+
outline-offset: 0
|
19
|
+
outline-color: colors.$neon-yellow
|
20
|
+
border: 1px solid colors.$silver-gray
|
21
|
+
color: colors.$black
|
22
|
+
height: 2em
|
23
|
+
padding-top: 0.2em
|
24
|
+
padding-right: 0.4em
|
25
|
+
padding-left: 0.4em
|
26
|
+
box-shadow: 0 1px 2px rgba(10, 10, 10, 0.1) inset
|
27
|
+
box-sizing: border-box
|
28
|
+
+font-size.default
|
29
|
+
|
30
|
+
.formatic-wrapper.is-required &
|
31
|
+
border-left-color: colors.$paradise-pink
|
32
|
+
border-left-width: 2px
|
33
|
+
|
34
|
+
textarea
|
35
|
+
min-height: 7em
|
36
|
+
|
37
|
+
select
|
38
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"32\" height=\"24\" viewBox=\"0 0 32 24\"><polygon points=\"0,0 32,0 16,24\" style=\"fill: rgb(51, 51, 51)\"></polygon></svg>")
|
39
|
+
background-position: right 0.5rem center
|
40
|
+
background-repeat: no-repeat
|
41
|
+
background-size: 9px 6px
|
42
|
+
box-shadow: none
|
43
|
+
|
44
|
+
::placeholder
|
45
|
+
color: colors.$silver-gray
|
@@ -0,0 +1,13 @@
|
|
1
|
+
@use "sass:color"
|
2
|
+
|
3
|
+
$black: #222
|
4
|
+
$white: #fff
|
5
|
+
|
6
|
+
$paradise-pink: rgb(240, 30, 80)
|
7
|
+
$neon-yellow: #ffd400
|
8
|
+
$jade-green: rgb(0, 190, 130)
|
9
|
+
|
10
|
+
$granite-gray: rgb(70, 70, 70)
|
11
|
+
$silver-gray: color.adjust($granite-gray, $lightness: 50%)
|
12
|
+
|
13
|
+
// $neon-red: #ec5840
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# A component that every other component inherits from in this gem.
|
5
|
+
# It adds convenience methods for initialization of a component.
|
6
|
+
class ApplicationComponent < ViewComponent::Base
|
7
|
+
extend Dry::Initializer
|
8
|
+
|
9
|
+
# By default, Dry::Initializer doesn't complain about invalid arguments.
|
10
|
+
# We want it to raise an error.
|
11
|
+
def initialize(...)
|
12
|
+
__check_for_unknown_options(...)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def __check_for_unknown_options(*args, **kwargs)
|
19
|
+
return if __defined_options.empty?
|
20
|
+
|
21
|
+
# Checking params
|
22
|
+
opts = args.drop(__defined_params.length).first || kwargs
|
23
|
+
raise ArgumentError, "Unexpected argument #{opts}" unless opts.is_a? Hash
|
24
|
+
|
25
|
+
# Checking options
|
26
|
+
unknown_options = opts.keys - __defined_options
|
27
|
+
message = "Key(s) #{unknown_options} not found in #{__defined_options} of #{self.class}"
|
28
|
+
raise KeyError, message if unknown_options.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def __defined_options
|
32
|
+
self.class.dry_initializer.options.map(&:source)
|
33
|
+
end
|
34
|
+
|
35
|
+
def __defined_params
|
36
|
+
self.class.dry_initializer.params.map(&:source)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# All inputs inherit from this class.
|
5
|
+
class Base < ApplicationComponent
|
6
|
+
# Rails form builder. Usually with a model as `f.object`.
|
7
|
+
option :f
|
8
|
+
|
9
|
+
# The method that is called on the form object.
|
10
|
+
#
|
11
|
+
# This is uncontroversial, both Rails and SimpleForm have this.
|
12
|
+
# Rails: `f.text_field(:title)`
|
13
|
+
# SimpleForm: `f.input(:title)`
|
14
|
+
option :attribute_name, type: proc(&:to_sym)
|
15
|
+
|
16
|
+
# If passed in, used as the `<input value="...">`
|
17
|
+
# If not passed in, it is derived from the form object.
|
18
|
+
option :value, as: :manual_value, default: -> { :_fetch_from_record }
|
19
|
+
|
20
|
+
# CSS class(es) applied to the <input> element
|
21
|
+
# and the wrapper <div> respectively.
|
22
|
+
option :class, as: :manual_class, optional: true
|
23
|
+
option :wrapper_class, optional: true
|
24
|
+
|
25
|
+
# For inputs that support `<input autofocus=...>`
|
26
|
+
option :autofocus, default: -> { false }
|
27
|
+
|
28
|
+
# Some inputs (such as checkboxes and textfields)
|
29
|
+
# can be submitted continously by submitting their <form>
|
30
|
+
# via javascript.
|
31
|
+
option :async_submit, default: -> { false }
|
32
|
+
|
33
|
+
# See `Formatic::Wrapper`
|
34
|
+
option :label, default: -> { true }
|
35
|
+
option :label_for_id, optional: true
|
36
|
+
option :readonly, as: :readonly, default: -> { false }
|
37
|
+
option :required, optional: true
|
38
|
+
option :prevent_submit_on_enter, default: -> { false }
|
39
|
+
|
40
|
+
def wrapper
|
41
|
+
@wrapper ||= ::Formatic::Wrapper.new(
|
42
|
+
f:,
|
43
|
+
attribute_name:,
|
44
|
+
label:,
|
45
|
+
required:,
|
46
|
+
prevent_submit_on_enter:,
|
47
|
+
label_for_id:,
|
48
|
+
class: wrapper_class
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def value
|
53
|
+
return manual_value if manual_value != :_fetch_from_record
|
54
|
+
|
55
|
+
f.object.public_send(attribute_name) if f.object.respond_to?(attribute_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# ---------------------------
|
59
|
+
# ActiveModel and Rails slugs
|
60
|
+
# ---------------------------
|
61
|
+
|
62
|
+
# Name of the URL param for this record.
|
63
|
+
def param_key
|
64
|
+
f.object.model_name.param_key
|
65
|
+
end
|
66
|
+
|
67
|
+
# # Name of the URL param for this input.
|
68
|
+
def input_name
|
69
|
+
"#{param_key}[#{attribute_name}]"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# Multiple checkboxes for an Array of values in one attribute.
|
5
|
+
class Checklist < ::Formatic::Base
|
6
|
+
# Alternative 1:
|
7
|
+
# Raw options that are passed on to the Rails collection_check_boxes.check_box.
|
8
|
+
option :options, optional: true
|
9
|
+
|
10
|
+
# Alternative 2:
|
11
|
+
# ActiveRecord records used to populate the checkbox list.
|
12
|
+
option :records, optional: true
|
13
|
+
|
14
|
+
# Alternative 3:
|
15
|
+
# Keys to lookup in i18n translations.
|
16
|
+
option :keys, optional: true
|
17
|
+
|
18
|
+
option :include_current, optional: true
|
19
|
+
|
20
|
+
renders_many :toggles, ::Formatic::Toggle
|
21
|
+
|
22
|
+
# This is highjacking the CSS definitions of another component, `Formatic::Toggle`.
|
23
|
+
# I'm not terribly pleased by this, but I think it's a good work-around.
|
24
|
+
erb_template <<~ERB
|
25
|
+
<%= render wrapper do |wrap| %>
|
26
|
+
|
27
|
+
<% wrap.with_input do %>
|
28
|
+
<div class="c-formatic-checklist s-formatic">
|
29
|
+
|
30
|
+
<% f.collection_check_boxes(attribute_name, choices, :last, :first) do |builder| %>
|
31
|
+
|
32
|
+
<%= content_tag :div,
|
33
|
+
builder.label { builder.check_box(class: manual_class) + content_tag(:i) + content_tag(:span, split_and_wrap(builder.object.first)) },
|
34
|
+
class: 'c-formatic-toggle' %>
|
35
|
+
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
</div>
|
39
|
+
<% end %>
|
40
|
+
<% end %>
|
41
|
+
ERB
|
42
|
+
|
43
|
+
def choices
|
44
|
+
::Formatic::Choices.call(
|
45
|
+
f:,
|
46
|
+
attribute_name:,
|
47
|
+
options:,
|
48
|
+
records:,
|
49
|
+
keys:,
|
50
|
+
include_current:,
|
51
|
+
include_blank: false
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def split_and_wrap(string)
|
56
|
+
parts = string.split(' ')
|
57
|
+
return parts.first if parts.size == 1
|
58
|
+
|
59
|
+
main_part = parts[0..-2].join(' ')
|
60
|
+
last_part = parts.last
|
61
|
+
|
62
|
+
::Formatic::SafeJoin.call(main_part, '<br/>'.html_safe, content_tag(:small, last_part))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'formatic/templates/date'
|
4
|
+
|
5
|
+
module Formatic
|
6
|
+
# Date/calendar
|
7
|
+
class Date < ::Formatic::Base
|
8
|
+
# Represents one element in the calendar.
|
9
|
+
class Day
|
10
|
+
extend Dry::Initializer
|
11
|
+
|
12
|
+
option :date
|
13
|
+
|
14
|
+
def classes
|
15
|
+
[
|
16
|
+
('is-today' if date.today?),
|
17
|
+
('is-saturday' if date.saturday?),
|
18
|
+
('is-sunday' if date.sunday?),
|
19
|
+
('is-holiday' if holiday?)
|
20
|
+
].join(' ')
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def holiday?
|
26
|
+
return false if date.saturday? || date.sunday?
|
27
|
+
|
28
|
+
::Holidays.on(date, :de_nw).present?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
option :discard_day, optional: true
|
33
|
+
|
34
|
+
erb_template(::Formatic::Templates::Date.call)
|
35
|
+
|
36
|
+
def css_classes
|
37
|
+
%i[c-formatic-date__input]
|
38
|
+
end
|
39
|
+
|
40
|
+
def options_for_day
|
41
|
+
options_for_select collection_for_day, day_value
|
42
|
+
end
|
43
|
+
|
44
|
+
def options_for_month
|
45
|
+
options_for_select collection_for_month, f.object.public_send(attribute_name)&.month
|
46
|
+
end
|
47
|
+
|
48
|
+
def options_for_year
|
49
|
+
options_for_select collection_for_year, f.object.public_send(attribute_name)&.year
|
50
|
+
end
|
51
|
+
|
52
|
+
def day_attribute_name
|
53
|
+
"#{f.object.model_name.param_key}[#{attribute_name}(3i)]"
|
54
|
+
end
|
55
|
+
|
56
|
+
def month_attribute_name
|
57
|
+
"#{f.object.model_name.param_key}[#{attribute_name}(2i)]"
|
58
|
+
end
|
59
|
+
|
60
|
+
def year_attribute_name
|
61
|
+
"#{f.object.model_name.param_key}[#{attribute_name}(1i)]"
|
62
|
+
end
|
63
|
+
|
64
|
+
def day_value
|
65
|
+
f.object.public_send(attribute_name)&.day
|
66
|
+
end
|
67
|
+
|
68
|
+
def day_input_id
|
69
|
+
"#{f.object.model_name.param_key}_#{attribute_name}_3i"
|
70
|
+
end
|
71
|
+
|
72
|
+
def month_input_id
|
73
|
+
"#{f.object.model_name.param_key}_#{attribute_name}_2i"
|
74
|
+
end
|
75
|
+
|
76
|
+
def year_input_id
|
77
|
+
"#{f.object.model_name.param_key}_#{attribute_name}_1i"
|
78
|
+
end
|
79
|
+
|
80
|
+
def calendar(now: Time.current)
|
81
|
+
from = 5.days.ago.to_date
|
82
|
+
till = now.beginning_of_month.advance(months: 2).end_of_month.to_date
|
83
|
+
|
84
|
+
(from..till).map do |date|
|
85
|
+
::Formatic::Date::Day.new(date:)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def collection_for_day
|
92
|
+
return (1..31) if wrapper.required?
|
93
|
+
|
94
|
+
[nil] + (1..31).to_a
|
95
|
+
end
|
96
|
+
|
97
|
+
def collection_for_month
|
98
|
+
result = (1..12).map { [l(::Date.new(1, _1), format: '%B %-m'), _1] }
|
99
|
+
result.prepend([nil, nil]) if wrapper.optional?
|
100
|
+
result
|
101
|
+
end
|
102
|
+
|
103
|
+
def collection_for_year
|
104
|
+
result = (30.years.ago.year..10.years.from_now.year)
|
105
|
+
return result if wrapper.required?
|
106
|
+
|
107
|
+
result.to_a.prepend nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'formatic/templates/select'
|
4
|
+
|
5
|
+
module Formatic
|
6
|
+
# Dropdown box
|
7
|
+
class Select < ::Formatic::Base
|
8
|
+
# Alternative 1:
|
9
|
+
# Raw options that are passed on to the Rails select_box_tag.
|
10
|
+
option :options, optional: true
|
11
|
+
|
12
|
+
# Alternative 2:
|
13
|
+
# ActiveRecord records used to populate the select box.
|
14
|
+
option :records, optional: true
|
15
|
+
|
16
|
+
# Alternative 3:
|
17
|
+
# Keys to lookup in i18n translations.
|
18
|
+
option :keys, optional: true
|
19
|
+
|
20
|
+
# Whether or not to show an empty (nil) option.
|
21
|
+
option :include_blank, default: -> { :guess }
|
22
|
+
|
23
|
+
option :include_current, optional: true
|
24
|
+
|
25
|
+
erb_template(::Formatic::Templates::Select.call)
|
26
|
+
|
27
|
+
def choices
|
28
|
+
::Formatic::Choices.call(
|
29
|
+
f:,
|
30
|
+
attribute_name:,
|
31
|
+
options:,
|
32
|
+
records:,
|
33
|
+
keys:,
|
34
|
+
include_current:,
|
35
|
+
include_blank: include_blank?
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def current_choice_name
|
40
|
+
choices.detect { _1.last == value }&.first
|
41
|
+
end
|
42
|
+
|
43
|
+
def include_blank?
|
44
|
+
return wrapper.optional? if @include_blank == :guess
|
45
|
+
|
46
|
+
!!@include_blank
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# Stepper input for integer values.
|
5
|
+
class Stepper < ::Formatic::Base
|
6
|
+
option :minimum, default: -> { 0 }
|
7
|
+
|
8
|
+
erb_template <<~ERB
|
9
|
+
<%= render wrapper do |wrap| %>
|
10
|
+
|
11
|
+
<% wrap.with_input do %>
|
12
|
+
<div class="c-formatic-stepper js-formatic-stepper">
|
13
|
+
|
14
|
+
<%= link_to '#', class: 'c-formatic-stepper__step js-formatic-stepper__decrement' do %>
|
15
|
+
−
|
16
|
+
<% end %>
|
17
|
+
<%= text_field_tag input_name,
|
18
|
+
value.to_i,
|
19
|
+
{ \
|
20
|
+
min: minimum,
|
21
|
+
inputmode: :numeric,
|
22
|
+
pattern: '-?[0-9]*',
|
23
|
+
class: 'c-formatic-stepper__number js-formatic-stepper__number',
|
24
|
+
placeholder: wrapper.placeholder,
|
25
|
+
} %>
|
26
|
+
<%= link_to '#', class: 'c-formatic-stepper__step js-formatic-stepper__increment' do %>
|
27
|
+
+
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
ERB
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# Text input for one-liners.
|
5
|
+
class String < ::Formatic::Base
|
6
|
+
option :terminal, default: -> { false }
|
7
|
+
|
8
|
+
erb_template <<~ERB
|
9
|
+
<%= render wrapper do |wrap| -%>
|
10
|
+
|
11
|
+
<% wrap.with_input do -%>
|
12
|
+
<div class="c-formatic-string s-formatic">
|
13
|
+
|
14
|
+
<% if readonly -%>
|
15
|
+
<div class="s-markdown">
|
16
|
+
<p>
|
17
|
+
<% if terminal? -%>
|
18
|
+
<tt><%= value -%></tt>
|
19
|
+
<% else -%>
|
20
|
+
<%= value -%>
|
21
|
+
<% end -%>
|
22
|
+
</p>
|
23
|
+
</div>
|
24
|
+
<% else -%>
|
25
|
+
<%= f.text_field(attribute_name, **input_options) -%>
|
26
|
+
<% end -%>
|
27
|
+
|
28
|
+
</div>
|
29
|
+
<% end -%>
|
30
|
+
<% end -%>
|
31
|
+
ERB
|
32
|
+
|
33
|
+
def input_options
|
34
|
+
result = {
|
35
|
+
placeholder: wrapper.placeholder,
|
36
|
+
autofocus:,
|
37
|
+
class: css_classes
|
38
|
+
}
|
39
|
+
|
40
|
+
(result[:value] = value) if value
|
41
|
+
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
def css_classes
|
46
|
+
classes = %i[c-formatic-string__input]
|
47
|
+
classes.push(:'is-terminal') if terminal?
|
48
|
+
classes
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def terminal?
|
54
|
+
!!@terminal
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# Text input for multi-line text.
|
5
|
+
class Textarea < ::Formatic::Base
|
6
|
+
renders_one :footer
|
7
|
+
|
8
|
+
erb_template <<~ERB
|
9
|
+
<%= render wrapper do |wrap| -%>
|
10
|
+
|
11
|
+
<% wrap.with_input do -%>
|
12
|
+
<div class="c-formatic-textarea s-formatic">
|
13
|
+
|
14
|
+
<% if readonly -%>
|
15
|
+
<div class="s-markdown">
|
16
|
+
<p>
|
17
|
+
<%= value -%>
|
18
|
+
</p>
|
19
|
+
</div>
|
20
|
+
<% else -%>
|
21
|
+
<%= f.text_area(attribute_name, **input_options) -%>
|
22
|
+
<% end -%>
|
23
|
+
|
24
|
+
</div>
|
25
|
+
<% end -%>
|
26
|
+
<% end -%>
|
27
|
+
ERB
|
28
|
+
|
29
|
+
def input_options
|
30
|
+
result = {
|
31
|
+
placeholder: wrapper.placeholder,
|
32
|
+
data: { '1p-ignore' => true },
|
33
|
+
autofocus:,
|
34
|
+
class: css_classes
|
35
|
+
}
|
36
|
+
|
37
|
+
(result[:value] = value) if value
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def css_classes
|
43
|
+
classes = %i[c-formatic-textarea__input js-formatic-textarea]
|
44
|
+
classes.push(:'is-autosubmit') if async_submit
|
45
|
+
classes
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Formatic
|
4
|
+
# Stylish checkbox for a boolean attribute.
|
5
|
+
class Toggle < ::Formatic::Base
|
6
|
+
erb_template <<~ERB
|
7
|
+
<%= render wrapper do |wrap| %>
|
8
|
+
|
9
|
+
<% wrap.with_input do %>
|
10
|
+
<div class="c-formatic-toggle s-formatic <%= 'js-formatic-toggle' if async_submit %>">
|
11
|
+
|
12
|
+
<% if readonly %>
|
13
|
+
<div class="s-markdown">
|
14
|
+
<p>
|
15
|
+
<%= value %>
|
16
|
+
</p>
|
17
|
+
</div>
|
18
|
+
<% else %>
|
19
|
+
<%= f.label attribute_name, nil, { for: dom_id } do |builder| %>
|
20
|
+
<%
|
21
|
+
f.check_box(attribute_name, { id: dom_id, class: css_classes }) +
|
22
|
+
content_tag(:i) +
|
23
|
+
content_tag(:div, human_attribute_name, class: 'c-formatic-toggle__label-caption-dummy') +
|
24
|
+
content_tag(:div, wrap.toggle_on, class: 'is-active') +
|
25
|
+
content_tag(:div, wrap.toggle_off, class: 'is-inactive')
|
26
|
+
%>
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
ERB
|
34
|
+
|
35
|
+
def css_classes
|
36
|
+
::Formatic::Css.call('c-formatic-toggle__input', manual_class)
|
37
|
+
end
|
38
|
+
|
39
|
+
# So that the wrapper <label> references our custom checkbox.
|
40
|
+
def label_for_id
|
41
|
+
dom_id
|
42
|
+
end
|
43
|
+
|
44
|
+
def human_attribute_name
|
45
|
+
return unless f.object
|
46
|
+
|
47
|
+
f.object.class.human_attribute_name(attribute_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# There can be multiple checkboxes with the same attribute name on the page.
|
51
|
+
# To couple a <label> to its checkbox, make the checkbox ID unequivocal.
|
52
|
+
def dom_id
|
53
|
+
# For predictability in tests, maybe use something like `Time.now.nsec`?
|
54
|
+
@dom_id ||= SecureRandom.hex(4)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|