proscenium-ui 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/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/lib/proscenium/ui/badge/index.css +75 -0
- data/lib/proscenium/ui/badge.rb +32 -0
- data/lib/proscenium/ui/breadcrumbs/computed_element.rb +71 -0
- data/lib/proscenium/ui/breadcrumbs/control.rb +103 -0
- data/lib/proscenium/ui/breadcrumbs/element.rb +16 -0
- data/lib/proscenium/ui/breadcrumbs/index.css +84 -0
- data/lib/proscenium/ui/breadcrumbs.rb +136 -0
- data/lib/proscenium/ui/combobox/index.css +162 -0
- data/lib/proscenium/ui/combobox/index.js +420 -0
- data/lib/proscenium/ui/combobox.rb +186 -0
- data/lib/proscenium/ui/component.rb +22 -0
- data/lib/proscenium/ui/flash/index.css +1 -0
- data/lib/proscenium/ui/flash/index.js +77 -0
- data/lib/proscenium/ui/flash.rb +15 -0
- data/lib/proscenium/ui/form/field_methods.rb +95 -0
- data/lib/proscenium/ui/form/fields/base.rb +189 -0
- data/lib/proscenium/ui/form/fields/checkbox/index.jsx +48 -0
- data/lib/proscenium/ui/form/fields/checkbox/index.module.css +9 -0
- data/lib/proscenium/ui/form/fields/checkbox/previews/basic.jsx +8 -0
- data/lib/proscenium/ui/form/fields/checkbox.rb +32 -0
- data/lib/proscenium/ui/form/fields/combobox.rb +117 -0
- data/lib/proscenium/ui/form/fields/date.module.css +27 -0
- data/lib/proscenium/ui/form/fields/datetime.rb +15 -0
- data/lib/proscenium/ui/form/fields/hidden.rb +9 -0
- data/lib/proscenium/ui/form/fields/input/index.jsx +71 -0
- data/lib/proscenium/ui/form/fields/input/index.module.css +13 -0
- data/lib/proscenium/ui/form/fields/input/previews/basic.jsx +8 -0
- data/lib/proscenium/ui/form/fields/input.rb +14 -0
- data/lib/proscenium/ui/form/fields/radio_group.rb +175 -0
- data/lib/proscenium/ui/form/fields/radio_input/index.jsx +44 -0
- data/lib/proscenium/ui/form/fields/radio_input/index.module.css +13 -0
- data/lib/proscenium/ui/form/fields/radio_input/previews/basic.jsx +8 -0
- data/lib/proscenium/ui/form/fields/radio_input.rb +17 -0
- data/lib/proscenium/ui/form/fields/rich_textarea.css +23 -0
- data/lib/proscenium/ui/form/fields/rich_textarea.js +6 -0
- data/lib/proscenium/ui/form/fields/rich_textarea.rb +18 -0
- data/lib/proscenium/ui/form/fields/select.jsx +47 -0
- data/lib/proscenium/ui/form/fields/select.module.css +46 -0
- data/lib/proscenium/ui/form/fields/select.rb +302 -0
- data/lib/proscenium/ui/form/fields/tel.css +297 -0
- data/lib/proscenium/ui/form/fields/tel.js +83 -0
- data/lib/proscenium/ui/form/fields/tel.rb +54 -0
- data/lib/proscenium/ui/form/fields/textarea/index.jsx +50 -0
- data/lib/proscenium/ui/form/fields/textarea/index.module.css +13 -0
- data/lib/proscenium/ui/form/fields/textarea/previews/basic.jsx +8 -0
- data/lib/proscenium/ui/form/fields/textarea.rb +18 -0
- data/lib/proscenium/ui/form/index.css +52 -0
- data/lib/proscenium/ui/form/translation.rb +71 -0
- data/lib/proscenium/ui/form.rb +197 -0
- data/lib/proscenium/ui/railtie.rb +23 -0
- data/lib/proscenium/ui/ujs/class.js +15 -0
- data/lib/proscenium/ui/ujs/data_confirm.js +23 -0
- data/lib/proscenium/ui/ujs/data_disable_with.js +68 -0
- data/lib/proscenium/ui/ujs/index.js +10 -0
- data/lib/proscenium/ui/version.rb +7 -0
- data/lib/proscenium/ui.rb +36 -0
- metadata +177 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proscenium::UI::Form::Fields
|
|
4
|
+
class Combobox < Base
|
|
5
|
+
register_element :pui_combobox_field
|
|
6
|
+
|
|
7
|
+
def initialize(attribute, model, form, type: nil, error: nil, **attributes)
|
|
8
|
+
super
|
|
9
|
+
|
|
10
|
+
@options_from_attributes = @attributes.delete(:options)
|
|
11
|
+
@src = @attributes.delete(:src)
|
|
12
|
+
@multiple = @attributes.delete(:multiple) || false
|
|
13
|
+
@placeholder = @attributes.delete(:placeholder)
|
|
14
|
+
@min_chars = @attributes.delete(:min_chars) || 0
|
|
15
|
+
@debounce_ms = @attributes.delete(:debounce) || 300
|
|
16
|
+
@disabled = @attributes.delete(:disabled) || false
|
|
17
|
+
@selected_options = @attributes.delete(:selected_options) || []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def view_template
|
|
21
|
+
field :pui_combobox_field do
|
|
22
|
+
label { |content| content }
|
|
23
|
+
render Proscenium::UI::Combobox.new(
|
|
24
|
+
name: resolved_field_name,
|
|
25
|
+
options: resolved_options,
|
|
26
|
+
src: @src,
|
|
27
|
+
multiple: @multiple,
|
|
28
|
+
placeholder: @placeholder,
|
|
29
|
+
value: resolved_value,
|
|
30
|
+
min_chars: @min_chars,
|
|
31
|
+
debounce: @debounce_ms,
|
|
32
|
+
disabled: @disabled,
|
|
33
|
+
selected_options: @selected_options
|
|
34
|
+
)
|
|
35
|
+
render_hint
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def render_hint
|
|
42
|
+
content = attributes.delete(:hint)
|
|
43
|
+
return if content == false
|
|
44
|
+
|
|
45
|
+
content ||= translate(:hints)
|
|
46
|
+
content.present? && div(part: :hint) { plain content }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def resolved_field_name
|
|
50
|
+
if @multiple
|
|
51
|
+
field_name(multiple: true)
|
|
52
|
+
else
|
|
53
|
+
field_name
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolved_value
|
|
58
|
+
val = value
|
|
59
|
+
case val
|
|
60
|
+
when Array then val.map(&:to_s)
|
|
61
|
+
when nil then nil
|
|
62
|
+
else val.to_s
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resolved_options
|
|
67
|
+
if @options_from_attributes
|
|
68
|
+
@options_from_attributes
|
|
69
|
+
elsif enum_attribute?
|
|
70
|
+
fetch_enum_collection.map do |opt|
|
|
71
|
+
[model_class.human_attribute_name("#{attribute.last}.#{opt}"), opt]
|
|
72
|
+
end
|
|
73
|
+
elsif association_attribute?
|
|
74
|
+
fetch_association_collection.map do |opt|
|
|
75
|
+
[opt.to_s, opt.id.to_s]
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
[]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def enum_attribute?
|
|
83
|
+
model_class.defined_enums.key?(attribute.last.to_s)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def association_attribute?
|
|
87
|
+
association_reflection.present?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def association_reflection
|
|
91
|
+
@association_reflection ||= model_class.try(:reflect_on_association, attribute.last)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def fetch_enum_collection
|
|
95
|
+
actual_model.defined_enums[attribute.last.to_s].keys
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def fetch_association_collection
|
|
99
|
+
reflection = association_reflection
|
|
100
|
+
relation = reflection.klass.all
|
|
101
|
+
|
|
102
|
+
if reflection.respond_to?(:scope) && reflection.scope
|
|
103
|
+
relation = if reflection.scope.parameters.any?
|
|
104
|
+
reflection.klass.instance_exec(actual_model, &reflection.scope)
|
|
105
|
+
else
|
|
106
|
+
reflection.klass.instance_exec(&reflection.scope)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
relation
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def model_class
|
|
114
|
+
@model_class ||= actual_model.class
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
@layer pui {
|
|
2
|
+
.field_wrapper {
|
|
3
|
+
@mixin fieldWrapper from url('/hue/lib/hue/mixins/form.mixin.css');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.inputs {
|
|
7
|
+
display: flex;
|
|
8
|
+
|
|
9
|
+
& div {
|
|
10
|
+
padding-right: 10px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
& label > span {
|
|
14
|
+
color: var(--gray6);
|
|
15
|
+
font-size: var(--12px);
|
|
16
|
+
margin: 0 0 0.2em 0.2em;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
> div {
|
|
20
|
+
width: 7em;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.hint {
|
|
25
|
+
@mixin fieldHint from url('/hue/lib/hue/mixins/field.mixin.css');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import dsx from '/hue/lib/hue/utils/dsx'
|
|
5
|
+
import { useFormError } from '../../hooks'
|
|
6
|
+
|
|
7
|
+
import styles from './index.module.css'
|
|
8
|
+
|
|
9
|
+
const Input = ({
|
|
10
|
+
label,
|
|
11
|
+
hint,
|
|
12
|
+
className,
|
|
13
|
+
inputClassName,
|
|
14
|
+
errorAttrName,
|
|
15
|
+
inputRef,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}) => {
|
|
19
|
+
const [error, hasError] = useFormError(errorAttrName || props.name)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className={clsx(styles.fieldWrapper, className)} {...dsx({ fieldError: hasError })}>
|
|
23
|
+
<label>
|
|
24
|
+
<span>
|
|
25
|
+
{label ? <span>{label}</span> : null}
|
|
26
|
+
{hasError ? <span>{error}</span> : null}
|
|
27
|
+
</span>
|
|
28
|
+
|
|
29
|
+
{children || <input className={inputClassName || styles.input} {...props} ref={inputRef} />}
|
|
30
|
+
</label>
|
|
31
|
+
|
|
32
|
+
{hint ? <div className={styles.hint}>{hint}</div> : null}
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
Input.displayName = 'Hue.Form.Fields.Input'
|
|
37
|
+
Input.propTypes = {
|
|
38
|
+
name: PropTypes.string.isRequired,
|
|
39
|
+
|
|
40
|
+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]),
|
|
41
|
+
|
|
42
|
+
// Input `type` attribute. Default: 'text'.
|
|
43
|
+
type: PropTypes.string,
|
|
44
|
+
|
|
45
|
+
// Custom class name. This will be appended to the default class.
|
|
46
|
+
className: PropTypes.string,
|
|
47
|
+
|
|
48
|
+
// Custom class name for the actual input element. This will replace the default class.
|
|
49
|
+
inputClassName: PropTypes.string,
|
|
50
|
+
|
|
51
|
+
// The name of the attribute to use for the error message. Default: 'props.name'.
|
|
52
|
+
errorAttrName: PropTypes.string,
|
|
53
|
+
|
|
54
|
+
children: PropTypes.node,
|
|
55
|
+
|
|
56
|
+
inputRef: PropTypes.oneOfType([
|
|
57
|
+
PropTypes.func,
|
|
58
|
+
PropTypes.shape({ current: PropTypes.instanceOf(Element) })
|
|
59
|
+
]),
|
|
60
|
+
|
|
61
|
+
id: PropTypes.string,
|
|
62
|
+
hint: PropTypes.string,
|
|
63
|
+
disabled: PropTypes.bool
|
|
64
|
+
|
|
65
|
+
// All remaining non-descript props will be forwarded to the <input> element.
|
|
66
|
+
}
|
|
67
|
+
Input.defaultProps = {
|
|
68
|
+
type: 'text'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default Input
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer pui {
|
|
2
|
+
.fieldWrapper {
|
|
3
|
+
@mixin fieldWrapper from url('/hue/lib/hue/mixins/form.mixin.css');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.input {
|
|
7
|
+
@mixin input from url('/hue/lib/hue/mixins/input.mixin.css');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.hint {
|
|
11
|
+
@mixin fieldHint from url('/hue/lib/hue/mixins/field.mixin.css');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proscenium::UI::Form::Fields
|
|
4
|
+
# Render a group of <radio> inputs for the given model attribute. It supports ActiveRecord
|
|
5
|
+
# associations and enums.
|
|
6
|
+
#
|
|
7
|
+
# ## Supported options
|
|
8
|
+
#
|
|
9
|
+
# - options [Array] a list of options where each will render a radio input. If this is given, the
|
|
10
|
+
# automatic detection of options will be disabled. A flat array of strings will be used for
|
|
11
|
+
# both the label and value. While an Array of nested two-level arrays will be used.
|
|
12
|
+
class RadioGroup < Base
|
|
13
|
+
register_element :pui_radio_group
|
|
14
|
+
|
|
15
|
+
def before_template
|
|
16
|
+
@options_from_attributes = attributes.delete(:options)
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def view_template
|
|
22
|
+
field :pui_radio_group do
|
|
23
|
+
label
|
|
24
|
+
|
|
25
|
+
div part: :radio_group_inputs do
|
|
26
|
+
options.each do |opt|
|
|
27
|
+
form.radio_input(*attribute, name: field_name, value: opt[:value], label: opt[:label],
|
|
28
|
+
checked: opt[:checked], **attributes)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
hint
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def field_name(*names, multiple: false)
|
|
39
|
+
names.prepend association_attribute? ? association_attribute : attribute.last
|
|
40
|
+
|
|
41
|
+
if nested?
|
|
42
|
+
if nested_attributes_association?
|
|
43
|
+
names.prepend "#{attribute.first}_attributes"
|
|
44
|
+
else
|
|
45
|
+
names.prepend attribute.first
|
|
46
|
+
end
|
|
47
|
+
elsif names.one? && names.first.is_a?(String)
|
|
48
|
+
return names.first
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
form.field_name(*names, multiple:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def options
|
|
55
|
+
if @options_from_attributes
|
|
56
|
+
@options_from_attributes.map do |x|
|
|
57
|
+
if x.is_a?(Array)
|
|
58
|
+
{ value: x.first, label: x.last, checked: checked?(x.first) }
|
|
59
|
+
else
|
|
60
|
+
{ value: x, label: x, checked: checked?(x) }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
elsif enum_attribute?
|
|
64
|
+
fetch_enum_collection.map do |x|
|
|
65
|
+
{
|
|
66
|
+
value: x,
|
|
67
|
+
label: model_class.human_attribute_name("#{attribute.last}.#{x}"),
|
|
68
|
+
checked: checked?(x)
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
elsif association_attribute?
|
|
72
|
+
fetch_association_collection.map do |x|
|
|
73
|
+
{
|
|
74
|
+
value: x.id,
|
|
75
|
+
label: x.to_s,
|
|
76
|
+
checked: checked?(x)
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def value
|
|
83
|
+
if actual_model.respond_to?(model_attribute)
|
|
84
|
+
actual_model.public_send(model_attribute)
|
|
85
|
+
else
|
|
86
|
+
''
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Is the given `option` the current value (checked)?
|
|
91
|
+
def checked?(option)
|
|
92
|
+
if !option.is_a?(String) && !option.is_a?(Integer) && association_attribute?
|
|
93
|
+
reflection = association_reflection
|
|
94
|
+
key = if reflection.respond_to?(:options) && reflection.options[:primary_key]
|
|
95
|
+
reflection.options[:primary_key]
|
|
96
|
+
else
|
|
97
|
+
option.class.primary_key.to_s
|
|
98
|
+
end
|
|
99
|
+
option.attributes[key] == value
|
|
100
|
+
else
|
|
101
|
+
option.to_s == value.to_s
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def model_attribute
|
|
106
|
+
@model_attribute ||= association_attribute? ? association_attribute : attribute.last
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def enum_attribute?
|
|
110
|
+
model_class.defined_enums.key?(attribute.last.to_s)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def association_attribute?
|
|
114
|
+
association_reflection.present?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def association_attribute
|
|
118
|
+
@association_attribute ||= begin
|
|
119
|
+
reflection = association_reflection
|
|
120
|
+
|
|
121
|
+
case reflection.macro
|
|
122
|
+
when :belongs_to
|
|
123
|
+
(reflection.respond_to?(:options) && reflection.options[:foreign_key]&.to_sym) ||
|
|
124
|
+
:"#{reflection.name}_id"
|
|
125
|
+
else
|
|
126
|
+
# Force the association to be preloaded for performance.
|
|
127
|
+
if actual_model.respond_to?(attribute.last)
|
|
128
|
+
target = actual_model.send(attribute.last)
|
|
129
|
+
target.to_a if target.respond_to?(:to_a)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
:"#{reflection.name.to_s.singularize}_ids"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def association_reflection
|
|
138
|
+
@association_reflection ||= model_class.try :reflect_on_association, attribute.last
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def fetch_association_collection
|
|
142
|
+
relation = association_reflection.klass.all
|
|
143
|
+
|
|
144
|
+
# association_reflection.macro == :has_many
|
|
145
|
+
|
|
146
|
+
if association_reflection.respond_to?(:scope) && association_reflection.scope
|
|
147
|
+
relation = if association_reflection.scope.parameters.any?
|
|
148
|
+
association_reflection.klass.instance_exec(actual_model,
|
|
149
|
+
&association_reflection.scope)
|
|
150
|
+
else
|
|
151
|
+
association_reflection.klass.instance_exec(&association_reflection.scope)
|
|
152
|
+
end
|
|
153
|
+
else
|
|
154
|
+
order = association_reflection.options[:order]
|
|
155
|
+
conditions = association_reflection.options[:conditions]
|
|
156
|
+
conditions = actual_model.instance_exec(&conditions) if conditions.respond_to?(:call)
|
|
157
|
+
|
|
158
|
+
if relation.respond_to?(:where) && conditions.present?
|
|
159
|
+
relation = relation.where(conditions)
|
|
160
|
+
end
|
|
161
|
+
relation = relation.order(order) if relation.respond_to?(:order)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
relation
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def fetch_enum_collection
|
|
168
|
+
actual_model.defined_enums[attribute.last.to_s].keys
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def model_class
|
|
172
|
+
@model_class ||= actual_model.class
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import dsx from '/hue/lib/hue/utils/dsx'
|
|
5
|
+
import { useFormError } from '../../hooks'
|
|
6
|
+
|
|
7
|
+
import styles from './index.module.css'
|
|
8
|
+
|
|
9
|
+
const Component = ({ label, hint, className, errorAttrName, ...props }) => {
|
|
10
|
+
const [error, hasError] = useFormError(errorAttrName || props.name)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className={clsx(styles.fieldWrapper, className)} {...dsx({ fieldError: hasError })}>
|
|
14
|
+
<label>
|
|
15
|
+
<input type="radio" {...props} />
|
|
16
|
+
|
|
17
|
+
<span>{label}</span>
|
|
18
|
+
</label>
|
|
19
|
+
|
|
20
|
+
{hasError ? <div className={styles.error}>{error}</div> : null}
|
|
21
|
+
{hint ? <div className={styles.hint}>{hint}</div> : null}
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Component.displayName = 'Hue.Form.Fields.RadioInput'
|
|
27
|
+
Component.propTypes = {
|
|
28
|
+
name: PropTypes.string.isRequired,
|
|
29
|
+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]).isRequired,
|
|
30
|
+
|
|
31
|
+
// Custom class name. This will be appended to the default class.
|
|
32
|
+
className: PropTypes.string,
|
|
33
|
+
|
|
34
|
+
// The name of the attribute to use for the error message. Default: 'props.name'.
|
|
35
|
+
errorAttrName: PropTypes.string,
|
|
36
|
+
|
|
37
|
+
id: PropTypes.string,
|
|
38
|
+
hint: PropTypes.string,
|
|
39
|
+
disabled: PropTypes.bool
|
|
40
|
+
|
|
41
|
+
// All remaining non-descript props will be forwarded to the <input> element.
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Component
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer pui {
|
|
2
|
+
.fieldWrapper {
|
|
3
|
+
@mixin radio from url('/hue/lib/hue/mixins/radio.mixin.css');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.hint {
|
|
7
|
+
@mixin fieldHint from url('/hue/lib/hue/mixins/field.mixin.css');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.error {
|
|
11
|
+
@mixin fieldError from url('/hue/lib/hue/mixins/field.mixin.css');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proscenium::UI::Form::Fields
|
|
4
|
+
class RadioInput < Base
|
|
5
|
+
def view_template
|
|
6
|
+
checked = attributes[:value].to_s == value.to_s
|
|
7
|
+
|
|
8
|
+
default = model.class.human_attribute_name("#{attribute.join('.')}.#{attributes[:value]}")
|
|
9
|
+
label_contents = attributes.delete(:label) || translate_label(default:)
|
|
10
|
+
|
|
11
|
+
label do |_|
|
|
12
|
+
input(name: field_name, type: :radio, checked:, **build_attributes)
|
|
13
|
+
span { label_contents }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import 'trix/dist/trix.css';
|
|
2
|
+
|
|
3
|
+
trix-editor {
|
|
4
|
+
@mixin textarea from url('/hue/lib/hue/mixins/textarea.mixin.css');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
trix-toolbar {
|
|
8
|
+
.trix-button-group {
|
|
9
|
+
@mixin button-group from url('/hue/lib/hue/mixins/button_group.mixin.css');
|
|
10
|
+
@mixin button-group__nospace from url('/hue/lib/hue/mixins/button_group.mixin.css');
|
|
11
|
+
border-color: var(--gray5);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.trix-button {
|
|
15
|
+
@mixin button from url('/hue/lib/hue/mixins/button.mixin.css');
|
|
16
|
+
@mixin button__secondary from url('/hue/lib/hue/mixins/button.mixin.css');
|
|
17
|
+
box-shadow: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-trix-button-group='file-tools'] {
|
|
21
|
+
display: none;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proscenium::UI::Form::Fields
|
|
4
|
+
class RichTextarea < Base
|
|
5
|
+
register_element :trix_editor
|
|
6
|
+
|
|
7
|
+
def view_template
|
|
8
|
+
value = attributes.delete(:value)
|
|
9
|
+
|
|
10
|
+
field do
|
|
11
|
+
label
|
|
12
|
+
trix_editor input: field_id
|
|
13
|
+
hint
|
|
14
|
+
form.hidden_field(*attribute, id: field_id, value: value&.to_trix_html)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import exact from 'prop-types-exact'
|
|
4
|
+
|
|
5
|
+
import SmartSelect, {
|
|
6
|
+
propTypes as smartSelectPropTypes
|
|
7
|
+
} from '/hue/app/components/lib/smart_select'
|
|
8
|
+
|
|
9
|
+
const Component = ({ inputName, ...props }) => {
|
|
10
|
+
const [selected, setSelected] = useState(() => {
|
|
11
|
+
return Array.isArray(props.initialSelectedItem)
|
|
12
|
+
? props.initialSelectedItem
|
|
13
|
+
: [props.initialSelectedItem]
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const onChange = useCallback(values => {
|
|
17
|
+
if (Array.isArray(values)) {
|
|
18
|
+
setSelected(values)
|
|
19
|
+
} else {
|
|
20
|
+
setSelected([values?.value])
|
|
21
|
+
}
|
|
22
|
+
}, [])
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<SmartSelect {...props} onChange={onChange} />
|
|
27
|
+
|
|
28
|
+
{selected.length === 0 && <input type="hidden" name={inputName} value="" />}
|
|
29
|
+
{selected.map((item, i) => (
|
|
30
|
+
<input
|
|
31
|
+
key={item?.value || i}
|
|
32
|
+
type="hidden"
|
|
33
|
+
name={inputName}
|
|
34
|
+
value={item?.value || item || ''}
|
|
35
|
+
/>
|
|
36
|
+
))}
|
|
37
|
+
</>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Component.displayName = 'Hue.Form.Fields.Select'
|
|
42
|
+
Component.propTypes = exact({
|
|
43
|
+
inputName: PropTypes.string.isRequired,
|
|
44
|
+
...smartSelectPropTypes
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export default Component
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
@layer pui {
|
|
2
|
+
.field {
|
|
3
|
+
@mixin fieldWrapper from url('/hue/lib/hue/mixins/form.mixin.css');
|
|
4
|
+
|
|
5
|
+
&[data-field-error] > div > span > span {
|
|
6
|
+
color: var(--input-error-color);
|
|
7
|
+
display: var(--fieldError-display);
|
|
8
|
+
|
|
9
|
+
&:first-child {
|
|
10
|
+
font-weight: 500;
|
|
11
|
+
|
|
12
|
+
&:after {
|
|
13
|
+
content: '\00a0';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.hint {
|
|
20
|
+
@mixin fieldHint from url('/hue/lib/hue/mixins/field.mixin.css');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.typeahead {
|
|
24
|
+
@mixin label from url('/hue/lib/hue/mixins/label.mixin.css');
|
|
25
|
+
|
|
26
|
+
> span:first-child {
|
|
27
|
+
margin: 0 0 0.2em 0.2em;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:has(:not(> span)) {
|
|
31
|
+
margin: 0 0 0.2em 0.2em;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.typeahead_input {
|
|
36
|
+
&:empty {
|
|
37
|
+
@mixin select from url('/hue/lib/hue/mixins/select.mixin.css');
|
|
38
|
+
@mixin field__disabled from url('/hue/lib/hue/mixins/field.mixin.css');
|
|
39
|
+
|
|
40
|
+
&:after {
|
|
41
|
+
content: 'loading...';
|
|
42
|
+
color: var(--input-disabled-color);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|