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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/lib/proscenium/ui/badge/index.css +75 -0
  5. data/lib/proscenium/ui/badge.rb +32 -0
  6. data/lib/proscenium/ui/breadcrumbs/computed_element.rb +71 -0
  7. data/lib/proscenium/ui/breadcrumbs/control.rb +103 -0
  8. data/lib/proscenium/ui/breadcrumbs/element.rb +16 -0
  9. data/lib/proscenium/ui/breadcrumbs/index.css +84 -0
  10. data/lib/proscenium/ui/breadcrumbs.rb +136 -0
  11. data/lib/proscenium/ui/combobox/index.css +162 -0
  12. data/lib/proscenium/ui/combobox/index.js +420 -0
  13. data/lib/proscenium/ui/combobox.rb +186 -0
  14. data/lib/proscenium/ui/component.rb +22 -0
  15. data/lib/proscenium/ui/flash/index.css +1 -0
  16. data/lib/proscenium/ui/flash/index.js +77 -0
  17. data/lib/proscenium/ui/flash.rb +15 -0
  18. data/lib/proscenium/ui/form/field_methods.rb +95 -0
  19. data/lib/proscenium/ui/form/fields/base.rb +189 -0
  20. data/lib/proscenium/ui/form/fields/checkbox/index.jsx +48 -0
  21. data/lib/proscenium/ui/form/fields/checkbox/index.module.css +9 -0
  22. data/lib/proscenium/ui/form/fields/checkbox/previews/basic.jsx +8 -0
  23. data/lib/proscenium/ui/form/fields/checkbox.rb +32 -0
  24. data/lib/proscenium/ui/form/fields/combobox.rb +117 -0
  25. data/lib/proscenium/ui/form/fields/date.module.css +27 -0
  26. data/lib/proscenium/ui/form/fields/datetime.rb +15 -0
  27. data/lib/proscenium/ui/form/fields/hidden.rb +9 -0
  28. data/lib/proscenium/ui/form/fields/input/index.jsx +71 -0
  29. data/lib/proscenium/ui/form/fields/input/index.module.css +13 -0
  30. data/lib/proscenium/ui/form/fields/input/previews/basic.jsx +8 -0
  31. data/lib/proscenium/ui/form/fields/input.rb +14 -0
  32. data/lib/proscenium/ui/form/fields/radio_group.rb +175 -0
  33. data/lib/proscenium/ui/form/fields/radio_input/index.jsx +44 -0
  34. data/lib/proscenium/ui/form/fields/radio_input/index.module.css +13 -0
  35. data/lib/proscenium/ui/form/fields/radio_input/previews/basic.jsx +8 -0
  36. data/lib/proscenium/ui/form/fields/radio_input.rb +17 -0
  37. data/lib/proscenium/ui/form/fields/rich_textarea.css +23 -0
  38. data/lib/proscenium/ui/form/fields/rich_textarea.js +6 -0
  39. data/lib/proscenium/ui/form/fields/rich_textarea.rb +18 -0
  40. data/lib/proscenium/ui/form/fields/select.jsx +47 -0
  41. data/lib/proscenium/ui/form/fields/select.module.css +46 -0
  42. data/lib/proscenium/ui/form/fields/select.rb +302 -0
  43. data/lib/proscenium/ui/form/fields/tel.css +297 -0
  44. data/lib/proscenium/ui/form/fields/tel.js +83 -0
  45. data/lib/proscenium/ui/form/fields/tel.rb +54 -0
  46. data/lib/proscenium/ui/form/fields/textarea/index.jsx +50 -0
  47. data/lib/proscenium/ui/form/fields/textarea/index.module.css +13 -0
  48. data/lib/proscenium/ui/form/fields/textarea/previews/basic.jsx +8 -0
  49. data/lib/proscenium/ui/form/fields/textarea.rb +18 -0
  50. data/lib/proscenium/ui/form/index.css +52 -0
  51. data/lib/proscenium/ui/form/translation.rb +71 -0
  52. data/lib/proscenium/ui/form.rb +197 -0
  53. data/lib/proscenium/ui/railtie.rb +23 -0
  54. data/lib/proscenium/ui/ujs/class.js +15 -0
  55. data/lib/proscenium/ui/ujs/data_confirm.js +23 -0
  56. data/lib/proscenium/ui/ujs/data_disable_with.js +68 -0
  57. data/lib/proscenium/ui/ujs/index.js +10 -0
  58. data/lib/proscenium/ui/version.rb +7 -0
  59. data/lib/proscenium/ui.rb +36 -0
  60. 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI::Form::Fields
4
+ class Datetime < Input
5
+ def field_type
6
+ 'datetime-local'
7
+ end
8
+
9
+ private
10
+
11
+ def value
12
+ super&.strftime('%Y-%m-%dT%H:%M')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI::Form::Fields
4
+ class Hidden < Base
5
+ def view_template
6
+ input(name: field_name, type: :hidden, **build_attributes)
7
+ end
8
+ end
9
+ end
@@ -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,8 @@
1
+ import Input from '../'
2
+
3
+ const Component = () => {
4
+ return <Input name="name" label="Name" />
5
+ }
6
+ Component.displayName = 'Hue.Form.Fields.Input.Previews.Basic'
7
+
8
+ export default Component
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI::Form::Fields
4
+ class Input < Base
5
+ def view_template
6
+ field do
7
+ label do
8
+ input(name: field_name, type: field_type, **build_attributes)
9
+ end
10
+ hint
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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,8 @@
1
+ import RadioInput from '../'
2
+
3
+ const Component = () => {
4
+ return <RadioInput name="awesome" label="I am awesome?" value="awesome" />
5
+ }
6
+ Component.displayName = 'Hue.Form.Fields.RadioInput.Previews.Basic'
7
+
8
+ export default Component
@@ -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,6 @@
1
+ import "trix"
2
+
3
+ document.addEventListener("trix-file-accept", function (event) {
4
+ // Prevent attachment drag and drop
5
+ event.preventDefault()
6
+ })
@@ -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
+ }