proscenium 0.19.0.beta4-x86_64-darwin → 0.19.0.beta6-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/proscenium/builder.rb +9 -13
  4. data/lib/proscenium/ext/proscenium +0 -0
  5. data/lib/proscenium/importer.rb +13 -13
  6. data/lib/proscenium/middleware/base.rb +5 -2
  7. data/lib/proscenium/middleware/engines.rb +5 -9
  8. data/lib/proscenium/middleware/esbuild.rb +13 -8
  9. data/lib/proscenium/middleware.rb +2 -4
  10. data/lib/proscenium/railtie.rb +15 -5
  11. data/lib/proscenium/react_componentable.rb +1 -1
  12. data/lib/proscenium/resolver.rb +3 -8
  13. data/lib/proscenium/side_load.rb +1 -1
  14. data/lib/proscenium/ui/flash/index.css +1 -0
  15. data/lib/proscenium/ui/flash/index.js +73 -0
  16. data/lib/proscenium/ui/flash.rb +15 -0
  17. data/lib/proscenium/ui/form/field_methods.rb +88 -0
  18. data/lib/proscenium/ui/form/fields/base.rb +188 -0
  19. data/lib/proscenium/ui/form/fields/checkbox/index.jsx +48 -0
  20. data/lib/proscenium/ui/form/fields/checkbox/index.module.css +9 -0
  21. data/lib/proscenium/ui/form/fields/checkbox/previews/basic.jsx +8 -0
  22. data/lib/proscenium/ui/form/fields/checkbox.rb +32 -0
  23. data/lib/proscenium/ui/form/fields/date.module.css +27 -0
  24. data/lib/proscenium/ui/form/fields/datetime.rb +15 -0
  25. data/lib/proscenium/ui/form/fields/hidden.rb +9 -0
  26. data/lib/proscenium/ui/form/fields/input/index.jsx +71 -0
  27. data/lib/proscenium/ui/form/fields/input/index.module.css +13 -0
  28. data/lib/proscenium/ui/form/fields/input/previews/basic.jsx +8 -0
  29. data/lib/proscenium/ui/form/fields/input.rb +14 -0
  30. data/lib/proscenium/ui/form/fields/radio_group.rb +173 -0
  31. data/lib/proscenium/ui/form/fields/radio_input/index.jsx +44 -0
  32. data/lib/proscenium/ui/form/fields/radio_input/index.module.css +13 -0
  33. data/lib/proscenium/ui/form/fields/radio_input/previews/basic.jsx +8 -0
  34. data/lib/proscenium/ui/form/fields/radio_input.rb +17 -0
  35. data/lib/proscenium/ui/form/fields/rich_textarea.css +23 -0
  36. data/lib/proscenium/ui/form/fields/rich_textarea.js +6 -0
  37. data/lib/proscenium/ui/form/fields/rich_textarea.rb +18 -0
  38. data/lib/proscenium/ui/form/fields/select.jsx +47 -0
  39. data/lib/proscenium/ui/form/fields/select.module.css +46 -0
  40. data/lib/proscenium/ui/form/fields/select.rb +300 -0
  41. data/lib/proscenium/ui/form/fields/tel.css +297 -0
  42. data/lib/proscenium/ui/form/fields/tel.js +83 -0
  43. data/lib/proscenium/ui/form/fields/tel.rb +54 -0
  44. data/lib/proscenium/ui/form/fields/textarea/index.jsx +50 -0
  45. data/lib/proscenium/ui/form/fields/textarea/index.module.css +13 -0
  46. data/lib/proscenium/ui/form/fields/textarea/previews/basic.jsx +8 -0
  47. data/lib/proscenium/ui/form/fields/textarea.rb +18 -0
  48. data/lib/proscenium/ui/form/translation.rb +71 -0
  49. data/lib/proscenium/ui/form.css +52 -0
  50. data/lib/proscenium/ui/form.rb +213 -0
  51. data/lib/proscenium/ui/props.css +7 -0
  52. data/lib/proscenium/ui/react-manager/index.jsx +1 -1
  53. data/lib/proscenium/ui/test.js +1 -1
  54. data/lib/proscenium/ui/ujs/index.js +1 -1
  55. data/lib/proscenium/ui.rb +3 -0
  56. data/lib/proscenium/utils.rb +33 -0
  57. data/lib/proscenium/version.rb +1 -1
  58. data/lib/proscenium/view_component.rb +0 -2
  59. data/lib/proscenium.rb +12 -2
  60. metadata +61 -10
  61. data/lib/proscenium/middleware/runtime.rb +0 -18
@@ -0,0 +1,297 @@
1
+ @layer proscenium-ui {
2
+ pui-tel-field {
3
+ --flag-ad: -24px 0;
4
+ --flag-ae: -48px 0;
5
+ --flag-af: -72px 0;
6
+ --flag-ag: -96px 0;
7
+ --flag-ai: -120px 0;
8
+ --flag-al: -144px 0;
9
+ --flag-am: -168px 0;
10
+ --flag-an: -192px 0;
11
+ --flag-ao: -216px 0;
12
+ --flag-ar: -240px 0;
13
+ --flag-as: -264px 0;
14
+ --flag-at: -288px 0;
15
+ --flag-au: -312px 0;
16
+ --flag-aw: -336px 0;
17
+ --flag-ax: -360px 0;
18
+ --flag-az: 0 -24px;
19
+ --flag-ba: -24px -24px;
20
+ --flag-bb: -48px -24px;
21
+ --flag-bd: -72px -24px;
22
+ --flag-be: -96px -24px;
23
+ --flag-bf: -120px -24px;
24
+ --flag-bg: -144px -24px;
25
+ --flag-bh: -168px -24px;
26
+ --flag-bi: -192px -24px;
27
+ --flag-bj: -216px -24px;
28
+ --flag-bl: -240px -24px;
29
+ --flag-bm: -264px -24px;
30
+ --flag-bn: -288px -24px;
31
+ --flag-bo: -312px -24px;
32
+ --flag-br: -336px -24px;
33
+ --flag-bs: -360px -24px;
34
+ --flag-bt: 0 -48px;
35
+ --flag-bw: -24px -48px;
36
+ --flag-by: -48px -48px;
37
+ --flag-bz: -72px -48px;
38
+ --flag-ca: -96px -48px;
39
+ --flag-cd: -120px -48px;
40
+ --flag-cf: -144px -48px;
41
+ --flag-cg: -168px -48px;
42
+ --flag-ch: -192px -48px;
43
+ --flag-ci: -216px -48px;
44
+ --flag-ck: -240px -48px;
45
+ --flag-cl: -264px -48px;
46
+ --flag-cm: -288px -48px;
47
+ --flag-cn: -312px -48px;
48
+ --flag-co: -336px -48px;
49
+ --flag-cr: -360px -48px;
50
+ --flag-cu: 0 -72px;
51
+ --flag-cv: -24px -72px;
52
+ --flag-cw: -48px -72px;
53
+ --flag-cy: -72px -72px;
54
+ --flag-cz: -96px -72px;
55
+ --flag-de: -120px -72px;
56
+ --flag-dj: -144px -72px;
57
+ --flag-dk: -168px -72px;
58
+ --flag-dm: -192px -72px;
59
+ --flag-do: -216px -72px;
60
+ --flag-dz: -240px -72px;
61
+ --flag-ec: -264px -72px;
62
+ --flag-ee: -288px -72px;
63
+ --flag-eg: -312px -72px;
64
+ --flag-eh: -336px -72px;
65
+ --flag-er: -360px -72px;
66
+ --flag-es: 0 -96px;
67
+ --flag-et: -24px -96px;
68
+ --flag-fi: -48px -96px;
69
+ --flag-fj: -72px -96px;
70
+ --flag-fk: -96px -96px;
71
+ --flag-fm: -120px -96px;
72
+ --flag-fo: -144px -96px;
73
+ --flag-fr: -168px -96px;
74
+ --flag-ga: -192px -96px;
75
+ --flag-gb: -216px -96px;
76
+ --flag-gd: -240px -96px;
77
+ --flag-ge: -264px -96px;
78
+ --flag-gg: -288px -96px;
79
+ --flag-gh: -312px -96px;
80
+ --flag-gi: -336px -96px;
81
+ --flag-gl: -360px -96px;
82
+ --flag-gm: 0 -120px;
83
+ --flag-gn: -24px -120px;
84
+ --flag-gq: -48px -120px;
85
+ --flag-gr: -72px -120px;
86
+ --flag-gt: -96px -120px;
87
+ --flag-gu: -120px -120px;
88
+ --flag-gw: -144px -120px;
89
+ --flag-gy: -168px -120px;
90
+ --flag-hk: -192px -120px;
91
+ --flag-hn: -216px -120px;
92
+ --flag-hr: -240px -120px;
93
+ --flag-ht: -264px -120px;
94
+ --flag-hu: -288px -120px;
95
+ --flag-id: -312px -120px;
96
+ --flag-ie: -336px -120px;
97
+ --flag-il: -360px -120px;
98
+ --flag-im: 0 -144px;
99
+ --flag-in: -24px -144px;
100
+ --flag-iq: -48px -144px;
101
+ --flag-ir: -72px -144px;
102
+ --flag-is: -96px -144px;
103
+ --flag-it: -120px -144px;
104
+ --flag-je: -144px -144px;
105
+ --flag-jm: -168px -144px;
106
+ --flag-jo: -192px -144px;
107
+ --flag-jp: -216px -144px;
108
+ --flag-ke: -240px -144px;
109
+ --flag-kg: -264px -144px;
110
+ --flag-kh: -288px -144px;
111
+ --flag-ki: -312px -144px;
112
+ --flag-km: -336px -144px;
113
+ --flag-kn: -360px -144px;
114
+ --flag-kp: 0 -168px;
115
+ --flag-kr: -24px -168px;
116
+ --flag-kw: -48px -168px;
117
+ --flag-ky: -72px -168px;
118
+ --flag-kz: -96px -168px;
119
+ --flag-la: -120px -168px;
120
+ --flag-lb: -144px -168px;
121
+ --flag-lc: -168px -168px;
122
+ --flag-li: -192px -168px;
123
+ --flag-lk: -216px -168px;
124
+ --flag-lr: -240px -168px;
125
+ --flag-ls: -264px -168px;
126
+ --flag-lt: -288px -168px;
127
+ --flag-lu: -312px -168px;
128
+ --flag-lv: -336px -168px;
129
+ --flag-ly: -360px -168px;
130
+ --flag-ma: 0 -192px;
131
+ --flag-mc: -24px -192px;
132
+ --flag-md: -48px -192px;
133
+ --flag-me: -72px -192px;
134
+ --flag-mf: -96px -192px;
135
+ --flag-mg: -120px -192px;
136
+ --flag-mh: -144px -192px;
137
+ --flag-mk: -168px -192px;
138
+ --flag-ml: -192px -192px;
139
+ --flag-mm: -216px -192px;
140
+ --flag-mn: -240px -192px;
141
+ --flag-mo: -264px -192px;
142
+ --flag-mp: -288px -192px;
143
+ --flag-mq: -312px -192px;
144
+ --flag-mr: -336px -192px;
145
+ --flag-ms: -360px -192px;
146
+ --flag-mt: 0 -216px;
147
+ --flag-mu: -24px -216px;
148
+ --flag-mv: -48px -216px;
149
+ --flag-mw: -72px -216px;
150
+ --flag-mx: -96px -216px;
151
+ --flag-my: -120px -216px;
152
+ --flag-mz: -144px -216px;
153
+ --flag-na: -168px -216px;
154
+ --flag-nc: -192px -216px;
155
+ --flag-ne: -216px -216px;
156
+ --flag-nf: -240px -216px;
157
+ --flag-ng: -264px -216px;
158
+ --flag-ni: -288px -216px;
159
+ --flag-nl: -312px -216px;
160
+ --flag-no: -336px -216px;
161
+ --flag-np: -360px -216px;
162
+ --flag-nr: 0 -240px;
163
+ --flag-nu: -24px -240px;
164
+ --flag-nz: -48px -240px;
165
+ --flag-om: -72px -240px;
166
+ --flag-pa: -96px -240px;
167
+ --flag-pe: -120px -240px;
168
+ --flag-pf: -144px -240px;
169
+ --flag-pg: -168px -240px;
170
+ --flag-ph: -192px -240px;
171
+ --flag-pk: -216px -240px;
172
+ --flag-pl: -240px -240px;
173
+ --flag-pn: -264px -240px;
174
+ --flag-pr: -288px -240px;
175
+ --flag-ps: -312px -240px;
176
+ --flag-pt: -336px -240px;
177
+ --flag-pw: -360px -240px;
178
+ --flag-py: 0 -264px;
179
+ --flag-qa: -24px -264px;
180
+ --flag-ro: -48px -264px;
181
+ --flag-rs: -72px -264px;
182
+ --flag-ru: -96px -264px;
183
+ --flag-rw: -120px -264px;
184
+ --flag-sa: -144px -264px;
185
+ --flag-sb: -168px -264px;
186
+ --flag-sc: -192px -264px;
187
+ --flag-sd: -216px -264px;
188
+ --flag-se: -240px -264px;
189
+ --flag-sg: -264px -264px;
190
+ --flag-sh: -288px -264px;
191
+ --flag-si: -312px -264px;
192
+ --flag-sk: -336px -264px;
193
+ --flag-sl: -360px -264px;
194
+ --flag-sm: 0 -288px;
195
+ --flag-sn: -24px -288px;
196
+ --flag-so: -48px -288px;
197
+ --flag-sr: -72px -288px;
198
+ --flag-ss: -96px -288px;
199
+ --flag-st: -120px -288px;
200
+ --flag-sv: -144px -288px;
201
+ --flag-sy: -168px -288px;
202
+ --flag-sz: -192px -288px;
203
+ --flag-tc: -216px -288px;
204
+ --flag-td: -240px -288px;
205
+ --flag-tg: -264px -288px;
206
+ --flag-th: -288px -288px;
207
+ --flag-tj: -312px -288px;
208
+ --flag-tk: -336px -288px;
209
+ --flag-tl: -360px -288px;
210
+ --flag-tm: 0 -312px;
211
+ --flag-tn: -24px -312px;
212
+ --flag-to: -48px -312px;
213
+ --flag-tr: -72px -312px;
214
+ --flag-tt: -96px -312px;
215
+ --flag-tv: -120px -312px;
216
+ --flag-tw: -144px -312px;
217
+ --flag-tz: -168px -312px;
218
+ --flag-ua: -192px -312px;
219
+ --flag-ug: -216px -312px;
220
+ --flag-us: -240px -312px;
221
+ --flag-uy: -264px -312px;
222
+ --flag-uz: -288px -312px;
223
+ --flag-va: -312px -312px;
224
+ --flag-vc: -336px -312px;
225
+ --flag-ve: -360px -312px;
226
+ --flag-vg: 0 -336px;
227
+ --flag-vi: -24px -336px;
228
+ --flag-vn: -48px -336px;
229
+ --flag-vu: -72px -336px;
230
+ --flag-wf: -96px -336px;
231
+ --flag-ws: -120px -336px;
232
+ --flag-ye: -144px -336px;
233
+ --flag-yt: -168px -336px;
234
+ --flag-za: -192px -336px;
235
+ --flag-zm: -216px -336px;
236
+ --flag-zw: -240px -336px;
237
+
238
+ input {
239
+ border: none;
240
+ background-color: var(--pui-input-background-color);
241
+ color: var(--pui-input-color);
242
+ }
243
+
244
+ [part="inputs"] {
245
+ border: var(--pui-input-border);
246
+ border-radius: var(--pui-input-border-radius);
247
+ display: inline-flex;
248
+ }
249
+
250
+ [part="country"] {
251
+ background-color: var(--pui-input-background-color);
252
+ display: flex;
253
+ align-items: center;
254
+ position: relative;
255
+ vertical-align: sub;
256
+ padding-left: 4px;
257
+ padding-right: 6px;
258
+
259
+ &::before {
260
+ content: "";
261
+ background: url("/proscenium/images/flags.png") no-repeat center;
262
+ background-position: var(--flag-position, 0 0);
263
+ width: 24px;
264
+ height: 24px;
265
+ }
266
+
267
+ &::after {
268
+ content: "\005E";
269
+ width: 1em;
270
+ height: 1em;
271
+ border-left: 1px solid var(--pui-input-accent-color);
272
+ transform: rotate(180deg);
273
+ text-align: center;
274
+ }
275
+
276
+ select {
277
+ position: absolute;
278
+ left: 0;
279
+ top: 0;
280
+ width: 100%;
281
+ height: 100%;
282
+ background-color: transparent;
283
+ border: none;
284
+ color: transparent;
285
+ box-shadow: none;
286
+ cursor: pointer;
287
+ appearance: none;
288
+ white-space: nowrap;
289
+
290
+ &:disabled {
291
+ opacity: 0.5;
292
+ cursor: default;
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
@@ -0,0 +1,83 @@
1
+ import { Maskito } from "https://esm.sh/@maskito/core@2.2.0";
2
+ import { maskitoPhoneOptionsGenerator } from "https://esm.sh/@maskito/phone@2.2.0";
3
+ import parsePhoneNumber from "https://esm.sh/libphonenumber-js@1.10.60/min";
4
+ import metadata from "https://esm.sh/libphonenumber-js@1.10.60/min/metadata";
5
+
6
+ class TelField extends HTMLElement {
7
+ connectedCallback() {
8
+ this.$country = this.querySelector("[part='country']");
9
+ this.$select = this.querySelector("select");
10
+ this.$input = this.querySelector("input");
11
+
12
+ this.#initMask();
13
+ this.#setFlag(this.$select.querySelector("option:checked").value);
14
+
15
+ this.$select.addEventListener("change", this);
16
+ }
17
+
18
+ handleEvent(event) {
19
+ event.type === "change" && this.#onSelectChange(event);
20
+ }
21
+
22
+ #onSelectChange = ({ target }) => {
23
+ this.#setFlag(target.value);
24
+ this.#resetMask();
25
+ };
26
+
27
+ #setFlag(country) {
28
+ this.$country.style.setProperty(
29
+ "--flag-position",
30
+ `var(--flag-${country.toLowerCase()})`
31
+ );
32
+ }
33
+
34
+ #initMask() {
35
+ this.mask = new Maskito(
36
+ this.$input,
37
+ maskitoPhoneOptionsGenerator({
38
+ countryIsoCode: this.$select.value,
39
+ metadata,
40
+ })
41
+ );
42
+
43
+ if (this.$input.value !== "") {
44
+ const phone = parsePhoneNumber(this.$input.value, this.$select.value, {
45
+ extract: false,
46
+ });
47
+ this.$input.value = phone.format("INTERNATIONAL");
48
+
49
+ if (phone.country) {
50
+ this.$select.value = phone.country;
51
+ this.#setFlag(phone.country);
52
+ }
53
+ }
54
+ }
55
+
56
+ #resetMask() {
57
+ this.mask.destroy();
58
+ this.mask = new Maskito(
59
+ this.$input,
60
+ maskitoPhoneOptionsGenerator({
61
+ countryIsoCode: this.$select.value,
62
+ strict: true,
63
+ metadata,
64
+ })
65
+ );
66
+
67
+ // Update country prefix
68
+ if (this.$input.value !== "") {
69
+ let phone = parsePhoneNumber(this.$input.value, this.$select.value, {
70
+ extract: false,
71
+ });
72
+ phone = parsePhoneNumber(phone.nationalNumber, this.$select.value);
73
+ this.$input.value = phone.format("INTERNATIONAL");
74
+ }
75
+ }
76
+
77
+ disconnectedCallback() {
78
+ this.mask.destroy();
79
+ this.$select.removeEventListener("change", this);
80
+ }
81
+ }
82
+
83
+ customElements.define("pui-tel-field", TelField);
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'phonelib'
4
+ require 'countries/iso3166'
5
+
6
+ module Proscenium::UI::Form::Fields
7
+ class Tel < Base
8
+ DEFAULT_COUNTRY = 'US'
9
+
10
+ sideload_assets js: { type: 'module' }
11
+
12
+ register_element :pui_tel_field
13
+
14
+ def initialize(attribute, model, form, type: nil, error: nil, **attributes) # rubocop:disable Metrics/ParameterLists
15
+ super
16
+
17
+ @default_country = @attributes.delete(:default_country)&.to_s&.upcase || DEFAULT_COUNTRY
18
+ end
19
+
20
+ def view_template
21
+ field :pui_tel_field do
22
+ label for: field_id
23
+
24
+ div part: :inputs do
25
+ div part: :country do
26
+ select do
27
+ countries.each do |name, code|
28
+ option(value: code, selected: code == country) { name }
29
+ end
30
+ end
31
+ end
32
+
33
+ input(name: field_name, type: 'text', part: :number, id: field_id, **build_attributes)
34
+ end
35
+
36
+ hint
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def country
43
+ @country ||= if value.blank?
44
+ @default_country
45
+ else
46
+ Phonelib.parse(value, @default_country).country || @default_country
47
+ end
48
+ end
49
+
50
+ def countries
51
+ ISO3166::Country.all_names_with_codes.to_h
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
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 Textarea = ({ label, hint, className, inputClassName, 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
+ <span>
16
+ {label ? <span>{label}</span> : null}
17
+ {hasError ? <span>{error}</span> : null}
18
+ </span>
19
+
20
+ <textarea className={inputClassName || styles.input} {...props} />
21
+ </label>
22
+
23
+ {hint ? <div className={styles.hint}>{hint}</div> : null}
24
+ </div>
25
+ )
26
+ }
27
+
28
+ Textarea.displayName = 'Hue.Form.Fields.Textarea'
29
+ Textarea.propTypes = {
30
+ name: PropTypes.string.isRequired,
31
+
32
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]),
33
+
34
+ // Custom class name. This will be appended to the default class.
35
+ className: PropTypes.string,
36
+
37
+ // Custom class name for the actual textarea element. This will replace the default class.
38
+ inputClassName: PropTypes.string,
39
+
40
+ // The name of the attribute to use for the error message. Default: 'props.name'.
41
+ errorAttrName: PropTypes.string,
42
+
43
+ id: PropTypes.string,
44
+ hint: PropTypes.string,
45
+ disabled: PropTypes.bool
46
+
47
+ // All remaining non-descript props will be forwarded to the <input> element.
48
+ }
49
+
50
+ export default Textarea
@@ -0,0 +1,13 @@
1
+ @layer hue-component {
2
+ .fieldWrapper {
3
+ @mixin fieldWrapper from url('/hue/lib/hue/mixins/form.mixin.css');
4
+ }
5
+
6
+ .input {
7
+ @mixin textarea from url('/hue/lib/hue/mixins/textarea.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 Textarea from '../'
2
+
3
+ const Component = () => {
4
+ return <Textarea name="name" label="Name" hint="Some hint about the input" />
5
+ }
6
+ Component.displayName = 'Hue.Form.Fields.Textarea.Previews.Basic'
7
+
8
+ export default Component
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI::Form::Fields
4
+ class Textarea < Base
5
+ register_element :pui_textarea
6
+
7
+ def view_template
8
+ field :pui_textarea do
9
+ label do
10
+ attrs = build_attributes
11
+ value = attrs.delete(:value)
12
+ textarea(name: field_name, **attrs) { value }
13
+ end
14
+ hint
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::UI::Form::Translation
4
+ # Lookup translations for the given namespace using I18n, based on model name, and attribute name.
5
+ #
6
+ # Lookup priority with nested attributes:
7
+ #
8
+ # form.{namespace}.{model}/{attribute.first}.{attribute.last}
9
+ # form.{namespace}.{attribute.first}.{attribute.last}
10
+ # form.{namespace}.{model}.{attribute.last}
11
+ # form.{namespace}.defaults.{attribute.last}
12
+ # {default}
13
+ #
14
+ # Lookup priority without nested attributes:
15
+ #
16
+ # form.{namespace}.{model}.{attribute}
17
+ # form.{namespace}.defaults.{attribute}
18
+ # {default}
19
+ #
20
+ # Namespace is used for :labels and :hints.
21
+ #
22
+ # Model is the actual object name, for a @user object you'll have :user.
23
+ # Attribute is the attribute itself, :name for example, or [:user, :name] if nested.
24
+ #
25
+ # If :postfix is given, it will be appended to the end of each lookup entry. So with a :postfix of
26
+ # 'stuff', '_stuff' will be appended:
27
+ #
28
+ # form.{namespace}.{model}.{attribute}_{postfix}
29
+ #
30
+ def translate(namespace, attribute, postfix: nil, default: '')
31
+ lookups = []
32
+ postfix = "_#{postfix}" if postfix
33
+ model_key = model.model_name.i18n_key
34
+
35
+ if attribute.is_a?(Array) && attribute.length > 1
36
+ joined_attrs = attribute.join('.')
37
+ lookups << :"#{model_key}/#{joined_attrs}#{postfix}"
38
+ lookups << :"#{joined_attrs}#{postfix}"
39
+ lookups << :"defaults.#{attribute.last}#{postfix}"
40
+ else
41
+ attribute = attribute.first if attribute.is_a?(Array)
42
+ lookups << :"#{model_key}.#{attribute}#{postfix}"
43
+ lookups << :"defaults.#{attribute}#{postfix}"
44
+ end
45
+
46
+ lookups << default
47
+
48
+ I18n.t(lookups.shift, scope: :"#{i18n_scope}.#{namespace}",
49
+ default: lookups).presence
50
+ end
51
+
52
+ def translate_label(attribute, default: nil, postfix: nil)
53
+ unless default
54
+ model = @model.class
55
+
56
+ if @model.class.respond_to?(:reflect_on_association) && attribute.count > 1
57
+ model = @model.class.reflect_on_association(attribute.first).klass
58
+ end
59
+
60
+ default = model.human_attribute_name(attribute.last)
61
+ end
62
+
63
+ translate :labels, attribute, default:, postfix:
64
+ end
65
+
66
+ private
67
+
68
+ def i18n_scope
69
+ 'form'
70
+ end
71
+ end
@@ -0,0 +1,52 @@
1
+ @import "/proscenium/props.css";
2
+
3
+ pui-field {
4
+ display: block;
5
+
6
+ [part="error"] {
7
+ &::before {
8
+ content: " ";
9
+ }
10
+
11
+ color: red;
12
+ }
13
+
14
+ [part="hint"] {
15
+ font-size: 0.9em;
16
+ margin-top: 0.2em;
17
+ }
18
+
19
+ input {
20
+ border: var(--pui-input-border);
21
+ border-radius: var(--pui-input-border-radius);
22
+ background-color: var(--pui-input-background-color);
23
+ }
24
+ }
25
+
26
+ pui-checkbox {
27
+ display: block;
28
+
29
+ label {
30
+ display: flex;
31
+ gap: 0.3rem;
32
+ }
33
+ }
34
+
35
+ pui-radio-group {
36
+ [part="radio_group_inputs"] {
37
+ display: flex;
38
+ gap: 1em;
39
+
40
+ > label > input {
41
+ margin-right: 0.3em;
42
+ }
43
+ }
44
+ }
45
+
46
+ pui-textarea {
47
+ display: block;
48
+
49
+ textarea {
50
+ display: block;
51
+ }
52
+ }