poetry-ui 0.0.2 → 0.0.3
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/app/components/poetry/ui/date_time_field/component.html.erb +6 -0
- data/app/components/poetry/ui/date_time_field/component.rb +91 -0
- data/app/components/poetry/ui/date_time_field/preview.rb +43 -0
- data/app/components/poetry/ui/date_time_field/style.rb +30 -0
- data/app/components/poetry/ui/time_field/component.rb +2 -2
- data/app/helpers/poetry/ui/components_helper.rb +12 -0
- data/app/helpers/poetry/ui/form_builder.rb +57 -14
- data/config/component_descriptions.yml +1 -0
- data/config/component_registry.yml +164 -3
- data/lib/poetry/ui/version.rb +1 -1
- data/lib/poetry/ui.rb +3 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a79167a3bdfe8283f8e3d6517e9b1a253fabd02df41417c5cafa79ad3fa12ddf
|
|
4
|
+
data.tar.gz: adb7128dde7ad61162336a2126b436e558bcd6bd49217c7347f0f829a8cecacc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78e59128853d1a15f2041262b1f8d486aeb626b4a0bb5f84cc649dd5c2f9dfe064962be4533de4e0f14a0c56e073daf33fb594e7ebfaa601d49adfb043d5c86a
|
|
7
|
+
data.tar.gz: f847ad9c837ff9f46a89fef1d40548506f0e425ef9f95292d5145fe92f76cfb3736cd750a6790c94535337c6b2223932af1c7e6b9bab275dcf853b7bafb61fab
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<%# DateField's template verbatim: native input visible (and THE form
|
|
2
|
+
value) until the controller builds segments into the group. %>
|
|
3
|
+
<%= content_tag(:div, **root_attributes.to_attributes) do %>
|
|
4
|
+
<%= content_tag(:span, "", **Poetry::Core::HTML::Attributes.new(group_attributes).to_attributes) %>
|
|
5
|
+
<%= tag.input(**input_attributes.to_attributes) %>
|
|
6
|
+
<% end %>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# Segmented date-and-time editors.
|
|
6
|
+
module DateTimeField
|
|
7
|
+
# A segmented date-and-time editor: one control, one value. The form
|
|
8
|
+
# value is a native <input type=datetime-local> submitting local wall
|
|
9
|
+
# time as YYYY-MM-DDTHH:MM (HH:MM:SS with seconds:), and without JS
|
|
10
|
+
# that native input simply renders, so the field always works.
|
|
11
|
+
# Enhanced, the date segments and the time segments share one row in
|
|
12
|
+
# the locale's own order and separators; the locale decides 12- vs
|
|
13
|
+
# 24-hour editing (hour_cycle: pins it). No zone travels on the wire:
|
|
14
|
+
# the value is the wall time the user typed, for the app to place.
|
|
15
|
+
#
|
|
16
|
+
# A DateTimeField is a DateField specialized to a date and a time -
|
|
17
|
+
# segments share the date-field-* part vocabulary.
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# render Poetry::Ui::DateTimeField::Component.new(
|
|
21
|
+
# name: "event[starts_at]", label: "Starts", value: "2026-07-13T09:30"
|
|
22
|
+
# )
|
|
23
|
+
class Component < DateField::Component
|
|
24
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
25
|
+
AGENT_RULES = [
|
|
26
|
+
"Date-and-time entry is a DateTimeField (poetry_date_time_field / form.datetime_field) - " \
|
|
27
|
+
"never a DateField beside a TimeField, three selects, or a bare input type=datetime-local " \
|
|
28
|
+
"when the design system is in play; params[<name>] is YYYY-MM-DDTHH:MM (with :SS under " \
|
|
29
|
+
"seconds:), with or without JS.",
|
|
30
|
+
"No zone rides the wire: the value is the wall time the user typed on their own clock - the " \
|
|
31
|
+
"app places it (Time.zone.parse in the controller, or the model's zone).",
|
|
32
|
+
"12- vs 24-hour follows the user's locale automatically (the dayPeriod segment appears only " \
|
|
33
|
+
"under twelve-hour cycles); hour_cycle: pins it when a product must."
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
# EXTENDS DateField's root declaration (extend: true merges into
|
|
37
|
+
# the inherited element) - same date-field controller, two more
|
|
38
|
+
# values; group/input inherit untouched.
|
|
39
|
+
use_stimulus do
|
|
40
|
+
on :root, extend: true do
|
|
41
|
+
controller :date_field do
|
|
42
|
+
value :seconds, true, if: :seconds
|
|
43
|
+
value :hour_cycle, if: -> { hour_cycle.present? }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
option :seconds, :boolean, default: false,
|
|
49
|
+
doc: "Adds the seconds segment; the wire format becomes YYYY-MM-DDTHH:MM:SS."
|
|
50
|
+
option :hour_cycle, :string, doc: "Pins the hour cycle (h12/h23/h11/h24) instead of the locale's."
|
|
51
|
+
|
|
52
|
+
part "date-time-field", "Root - the controller and the enhanced/disabled surface ride " \
|
|
53
|
+
"here (segments inside share the date-field-* vocabulary)",
|
|
54
|
+
states: {
|
|
55
|
+
"data-enhanced" => "the controller connected and built segments (no JS = the " \
|
|
56
|
+
"native input, visible and styled)",
|
|
57
|
+
"data-disabled" => "disabled: is set",
|
|
58
|
+
"data-invalid" => "invalid: is set (the group wears the destructive ring)"
|
|
59
|
+
}
|
|
60
|
+
part "date-time-field-group", "The bordered segment row - hidden until enhancement, then " \
|
|
61
|
+
"the editing surface (cn-input chrome, focus-within ring)",
|
|
62
|
+
states: {
|
|
63
|
+
"data-disabled" => "disabled: is set (chrome dims, pointer events off)",
|
|
64
|
+
"data-invalid" => "invalid: is set (destructive border + ring)"
|
|
65
|
+
}
|
|
66
|
+
part "date-time-field-input", "The native <input type=datetime-local> - THE form value in " \
|
|
67
|
+
"both modes; tabindex -1 + aria-hidden once segments exist"
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def input_type
|
|
72
|
+
"datetime-local"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def slot_prefix
|
|
76
|
+
"date-time-field"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def iso(candidate)
|
|
80
|
+
candidate.respond_to?(:strftime) ? candidate.strftime("%FT%H:%M#{":%S" if seconds}") : candidate.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def placeholder_iso
|
|
84
|
+
return iso(placeholder_value) if placeholder_value.present?
|
|
85
|
+
|
|
86
|
+
"#{Date.current.strftime("%F")}T12:00#{":00" if seconds}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DateTimeField
|
|
6
|
+
# The DateTimeField preview: empty and valued fields, seconds, a
|
|
7
|
+
# pinned 24-hour cycle, disabled, invalid, and a pinned locale.
|
|
8
|
+
class Preview < Poetry::Core::Preview::Base
|
|
9
|
+
# Empty: date placeholders then hour/minute (plus AM/PM under
|
|
10
|
+
# twelve-hour locales) once enhanced; the styled native input before that.
|
|
11
|
+
def default
|
|
12
|
+
render_component(name: "event[starts_at]", label: "Starts")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def with_value
|
|
16
|
+
render_component(name: "event[starts_at]", value: "2026-07-13T13:05", label: "Starts")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def with_seconds
|
|
20
|
+
render_component(name: "job[run_at]", value: "2026-07-13T09:30:15", seconds: true, label: "Run at")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A pinned 24-hour cycle regardless of the page locale.
|
|
24
|
+
def twenty_four_hour
|
|
25
|
+
render_component(name: "shift[starts_at]", value: "2026-07-13T21:15", hour_cycle: "h23", label: "Shift start")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def disabled
|
|
29
|
+
render_component(name: "event[starts_at]", value: "2026-07-13T13:05", disabled: true, label: "Starts")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def invalid
|
|
33
|
+
render_component(name: "event[starts_at]", value: "2026-07-13T13:05", invalid: true, label: "Starts")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# locale: pins the order, separators, and cycle instead of following the page locale.
|
|
37
|
+
def localized
|
|
38
|
+
render_component(name: "termin[beginn]", value: "2026-07-13T14:30", locale: "de-DE", label: "Beginn")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DateTimeField
|
|
6
|
+
# DateField's style verbatim (one segment engine, two components) -
|
|
7
|
+
# including the group/date-field marker name, which the enhanced-
|
|
8
|
+
# input selector keys on. See date_field/style.rb for the rationale.
|
|
9
|
+
class Style < Poetry::Core::Style
|
|
10
|
+
base "group/date-field flex w-full flex-col"
|
|
11
|
+
|
|
12
|
+
element :group,
|
|
13
|
+
"cn-input hidden w-fit min-w-0 select-none items-center tabular-nums " \
|
|
14
|
+
"group-data-[enhanced]/date-field:flex " \
|
|
15
|
+
"focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 " \
|
|
16
|
+
"data-invalid:border-destructive data-invalid:ring-destructive/20 " \
|
|
17
|
+
"dark:data-invalid:ring-destructive/40 " \
|
|
18
|
+
"data-disabled:pointer-events-none data-disabled:opacity-50 " \
|
|
19
|
+
"[&_[data-slot=date-field-segment]]:rounded-sm " \
|
|
20
|
+
"[&_[data-slot=date-field-segment]]:px-0.5 " \
|
|
21
|
+
"[&_[data-slot=date-field-segment]]:outline-none " \
|
|
22
|
+
"[&_[data-slot=date-field-segment]:focus]:bg-accent " \
|
|
23
|
+
"[&_[data-slot=date-field-segment]:focus]:text-accent-foreground " \
|
|
24
|
+
"[&_[data-slot=date-field-segment][data-placeholder]]:text-muted-foreground " \
|
|
25
|
+
"[&_[data-slot=date-field-literal]]:text-muted-foreground"
|
|
26
|
+
element :input, "group-data-[enhanced]/date-field:sr-only"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -26,8 +26,8 @@ module Poetry
|
|
|
26
26
|
"Input or a pair of selects; params[<name>] is HH:MM (HH:MM:SS with seconds:).",
|
|
27
27
|
"12- vs 24-hour follows the user's locale automatically (the dayPeriod segment " \
|
|
28
28
|
"appears only under twelve-hour cycles); hour_cycle: pins it when a product must.",
|
|
29
|
-
"For a date AND a time,
|
|
30
|
-
"
|
|
29
|
+
"For a date AND a time, use a DateTimeField (poetry_date_time_field / form.datetime_field): " \
|
|
30
|
+
"one control, one datetime-local value."
|
|
31
31
|
].freeze
|
|
32
32
|
|
|
33
33
|
# EXTENDS DateField's root declaration (extend: true merges into
|
|
@@ -1110,6 +1110,18 @@ module Poetry
|
|
|
1110
1110
|
render(Poetry::Ui::TimeField::Component.new(**))
|
|
1111
1111
|
end
|
|
1112
1112
|
|
|
1113
|
+
# A segmented date-and-time editor over a native datetime-local
|
|
1114
|
+
# input: one control, one local wall-time value (no zone on the wire).
|
|
1115
|
+
#
|
|
1116
|
+
# @example
|
|
1117
|
+
# poetry_date_time_field(
|
|
1118
|
+
# name: "event[starts_at]", label: "Starts", value: "2026-07-13T09:30"
|
|
1119
|
+
# )
|
|
1120
|
+
# @see Poetry::Ui::DateTimeField::Component
|
|
1121
|
+
def poetry_date_time_field(**)
|
|
1122
|
+
render(Poetry::Ui::DateTimeField::Component.new(**))
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1113
1125
|
# A quantity within a known range (disk, seats, strength) - role
|
|
1114
1126
|
# "meter progressbar" (the two-token fallback); never indeterminate.
|
|
1115
1127
|
#
|
|
@@ -28,12 +28,18 @@ module Poetry
|
|
|
28
28
|
"f.association(:company) reflects the association - belongs_to renders a Combobox on " \
|
|
29
29
|
"the foreign key, has_many the select-all checkbox group on singular_ids.",
|
|
30
30
|
"Validations become attributes: presence -> aria-required (NEVER native required), " \
|
|
31
|
-
"length -> maxlength/minlength, numericality -> min/max/step."
|
|
31
|
+
"length -> maxlength/minlength, numericality -> min/max/step; f.input(required: true/false) " \
|
|
32
|
+
"overrides the presence inference (aria only).",
|
|
32
33
|
"Hints/placeholders resolve from poetry_form.* i18n (simple_form.* keys keep working " \
|
|
33
34
|
"as a fallback); pass hint:/placeholder: to override.",
|
|
34
35
|
"f.submit renders a poetry Button with the Rails i18n label; f.fieldset(legend:)/" \
|
|
35
36
|
"f.group lay out sections; boolean f.input renders the horizontal Field " \
|
|
36
|
-
"(switch: true -> the setting row)."
|
|
37
|
+
"(switch: true -> the setting row).",
|
|
38
|
+
"Apps on simple_form: add poetry-simple_form instead of rewriting views - " \
|
|
39
|
+
"Poetry::SimpleForm.activate! maps every simple_form type onto this builder (poetry-only " \
|
|
40
|
+
"controls via as: :switch/:slider/:otp/:sensitive/:tag_group/:date_picker/:calendar/" \
|
|
41
|
+
":combobox/:autocomplete/:native_select; poetry: options merge last). The bridge is the " \
|
|
42
|
+
"migration path, form_with(builder:) the end state."
|
|
37
43
|
].freeze
|
|
38
44
|
|
|
39
45
|
# One-line summaries of the builder methods, projected into the
|
|
@@ -64,7 +70,7 @@ module Poetry
|
|
|
64
70
|
{
|
|
65
71
|
"rules" => AGENT_RULES,
|
|
66
72
|
"methods" => METHOD_SUMMARIES,
|
|
67
|
-
"input_types" => (INPUT_DISPATCH.keys + TYPED_FIELDS + %i[boolean enum]).map(&:to_s)
|
|
73
|
+
"input_types" => (INPUT_DISPATCH.keys + TYPED_FIELDS + %i[boolean switch enum]).map(&:to_s)
|
|
68
74
|
}
|
|
69
75
|
end
|
|
70
76
|
|
|
@@ -251,6 +257,27 @@ module Poetry
|
|
|
251
257
|
end
|
|
252
258
|
end
|
|
253
259
|
|
|
260
|
+
# A Field-wrapped DateTimeField (one control, one datetime-local value; seconds:/hour_cycle: pass through).
|
|
261
|
+
#
|
|
262
|
+
# @param method [Symbol] the model attribute
|
|
263
|
+
# @param hint [String, nil] hint text
|
|
264
|
+
# @return [ActiveSupport::SafeBuffer]
|
|
265
|
+
def datetime_field(method, hint: nil, **options)
|
|
266
|
+
field_component = field_for(method, hint: hint)
|
|
267
|
+
describedby = field_component.control_attributes["aria-describedby"]
|
|
268
|
+
@template.render(field_component) do
|
|
269
|
+
@template.render DateTimeField::Component.new(
|
|
270
|
+
name: field_name(method),
|
|
271
|
+
value: object.public_send(method),
|
|
272
|
+
required: required?(method),
|
|
273
|
+
invalid: field_component.invalid?,
|
|
274
|
+
id: field_component.control_attributes["id"],
|
|
275
|
+
**(describedby ? { described_by: describedby } : {}),
|
|
276
|
+
**options.transform_keys(&:to_sym)
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
254
281
|
# form.file_input(:document) / form.file_input(:photos, variant: :dropzone,
|
|
255
282
|
# multiple: true) - a Field wrapping a FileInput; the native
|
|
256
283
|
# input is the form value, so ActiveStorage attaches as usual.
|
|
@@ -354,13 +381,14 @@ module Poetry
|
|
|
354
381
|
time: :time_field, file: :file_input, sensitive: :sensitive_input,
|
|
355
382
|
select: :poetry_select, combobox: :poetry_combobox,
|
|
356
383
|
radio_group: :radio_group, autocomplete: :autocomplete,
|
|
357
|
-
tag_group: :tag_group, date_picker: :date_picker, calendar: :calendar
|
|
384
|
+
tag_group: :tag_group, date_picker: :date_picker, calendar: :calendar,
|
|
385
|
+
slider: :slider, otp: :otp_field, native_select: :native_select, datetime: :datetime_field
|
|
358
386
|
}.freeze
|
|
359
387
|
# The as: values that answer with #field carrying a native input type.
|
|
360
388
|
TYPED_FIELDS = %i[email url tel].freeze
|
|
361
389
|
# The as: values whose dispatch method takes the collection as its
|
|
362
390
|
# second positional argument.
|
|
363
|
-
COLLECTION_ARGS = %i[select combobox radio_group autocomplete].freeze
|
|
391
|
+
COLLECTION_ARGS = %i[select combobox radio_group autocomplete native_select].freeze
|
|
364
392
|
|
|
365
393
|
# The inferred entrypoint: one call, everything derived from the
|
|
366
394
|
# model. The control type comes from as: when given, otherwise from
|
|
@@ -379,34 +407,38 @@ module Poetry
|
|
|
379
407
|
#
|
|
380
408
|
# @param method [Symbol] the model attribute
|
|
381
409
|
# @param as [Symbol, nil] explicit control type - an INPUT_DISPATCH
|
|
382
|
-
# key, :email/:url/:tel, :boolean, or :enum (overrides inference)
|
|
410
|
+
# key, :email/:url/:tel, :boolean, :switch, or :enum (overrides inference)
|
|
383
411
|
# @param collection [Enumerable, nil] choices for collection-shaped
|
|
384
|
-
# types (:select, :combobox, :radio_group, :autocomplete)
|
|
412
|
+
# types (:select, :combobox, :radio_group, :autocomplete, :native_select)
|
|
385
413
|
# @param hint [String, nil] hint text (overrides the i18n chain)
|
|
414
|
+
# @param required [Boolean, nil] overrides the presence-validator inference for this
|
|
415
|
+
# call (aria-required only, never the native attribute)
|
|
386
416
|
# @return [ActiveSupport::SafeBuffer] the rendered Field
|
|
387
|
-
def input(method, as: nil, collection: nil, hint: nil, **options)
|
|
417
|
+
def input(method, as: nil, collection: nil, hint: nil, required: nil, **options)
|
|
418
|
+
unless required.nil?
|
|
419
|
+
return with_required_override(method, required) do
|
|
420
|
+
input(method, as: as, collection: collection, hint: hint, **options)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
388
424
|
type = as || infer_input_type(method, collection: collection)
|
|
389
425
|
hint ||= form_i18n(:hints, method)
|
|
390
426
|
options[:placeholder] = form_i18n(:placeholders, method) if options[:placeholder].nil?
|
|
391
427
|
options.compact!
|
|
392
428
|
|
|
393
429
|
return boolean_input(method, hint: hint, **options) if type == :boolean
|
|
430
|
+
return boolean_input(method, hint: hint, switch: true, **options) if type == :switch
|
|
394
431
|
return enum_input(method, hint: hint, **options) if type == :enum
|
|
395
432
|
if TYPED_FIELDS.include?(type)
|
|
396
433
|
return field(method, hint: hint, type: type, **length_attributes(method),
|
|
397
434
|
**options)
|
|
398
435
|
end
|
|
399
436
|
|
|
400
|
-
if type == :datetime
|
|
401
|
-
raise ArgumentError, "f.input cannot infer :datetime (no composite control yet) - " \
|
|
402
|
-
"pass as: :date or as: :time explicitly"
|
|
403
|
-
end
|
|
404
|
-
|
|
405
437
|
options = length_attributes(method).merge(options) if %i[string password text].include?(type)
|
|
406
438
|
options = numeric_attributes(method).merge(options) if type == :number
|
|
407
439
|
dispatch = INPUT_DISPATCH.fetch(type) do
|
|
408
440
|
raise ArgumentError, "f.input does not know as: #{type.inspect} " \
|
|
409
|
-
"(one of #{INPUT_DISPATCH.keys.inspect}, :email, :url, :tel, :boolean, :enum)"
|
|
441
|
+
"(one of #{INPUT_DISPATCH.keys.inspect}, :email, :url, :tel, :boolean, :switch, :enum)"
|
|
410
442
|
end
|
|
411
443
|
args = COLLECTION_ARGS.include?(type) ? [method, collection] : [method]
|
|
412
444
|
send(dispatch, *args, hint: hint, **options)
|
|
@@ -879,6 +911,7 @@ module Poetry
|
|
|
879
911
|
# validators (:if/:unless) never claim required, and :on contexts
|
|
880
912
|
# must match the record's persistence.
|
|
881
913
|
def required?(method)
|
|
914
|
+
return @required_overrides[method] if @required_overrides&.key?(method)
|
|
882
915
|
return false unless object.class.respond_to?(:validators_on)
|
|
883
916
|
|
|
884
917
|
context = object.respond_to?(:persisted?) && object.persisted? ? :update : :create
|
|
@@ -891,6 +924,16 @@ module Poetry
|
|
|
891
924
|
end
|
|
892
925
|
end
|
|
893
926
|
|
|
927
|
+
# Pins required?(method) for the duration of one render (f.input's
|
|
928
|
+
# required: keyword) - every Field-wrapped path reads required? through
|
|
929
|
+
# field_for, so the override reaches aria-required and nothing else.
|
|
930
|
+
def with_required_override(method, value)
|
|
931
|
+
(@required_overrides ||= {})[method] = value
|
|
932
|
+
yield
|
|
933
|
+
ensure
|
|
934
|
+
@required_overrides&.delete(method)
|
|
935
|
+
end
|
|
936
|
+
|
|
894
937
|
# The Rails 8 respellings: ActionView 8
|
|
895
938
|
# aliases textarea/checkbox at ITS class body, so a subclass override
|
|
896
939
|
# of the old name never reaches them - define both, version-gated.
|
|
@@ -33,6 +33,7 @@ poetry/ui/context_menu: A menu of actions revealed by right-clicking an element.
|
|
|
33
33
|
poetry/ui/data_table: A table with sorting, row selection, and sticky headers.
|
|
34
34
|
poetry/ui/date_field: A segmented input for typing a date one part at a time.
|
|
35
35
|
poetry/ui/date_picker: A date field that opens a calendar popover for selection.
|
|
36
|
+
poetry/ui/date_time_field: A segmented input for typing a date and a time one part at a time.
|
|
36
37
|
poetry/ui/deferred: A region that lazily loads its content on visibility, with skeleton
|
|
37
38
|
and error states.
|
|
38
39
|
poetry/ui/dialog: A window overlaid on the page for content that requires attention.
|
|
@@ -3922,6 +3922,156 @@ components:
|
|
|
3922
3922
|
'on': input
|
|
3923
3923
|
- method: inputKeydown
|
|
3924
3924
|
'on': keydown
|
|
3925
|
+
poetry/ui/date_time_field:
|
|
3926
|
+
class_name: Poetry::Ui::DateTimeField::Component
|
|
3927
|
+
identifier: poetry--ui--date_time_field
|
|
3928
|
+
bem_block: poetry-ui-date_time_field
|
|
3929
|
+
styles: []
|
|
3930
|
+
options:
|
|
3931
|
+
- name: described_by
|
|
3932
|
+
type: string
|
|
3933
|
+
description: Ids for aria-describedby (hint or error text).
|
|
3934
|
+
- name: disabled
|
|
3935
|
+
type: boolean
|
|
3936
|
+
default: false
|
|
3937
|
+
description: Disables the field; the segment group dims and goes inert.
|
|
3938
|
+
- name: hour_cycle
|
|
3939
|
+
type: string
|
|
3940
|
+
description: Pins the hour cycle (h12/h23/h11/h24) instead of the locale's.
|
|
3941
|
+
- name: id
|
|
3942
|
+
type: string
|
|
3943
|
+
description: The native input's DOM id - the Field label target.
|
|
3944
|
+
- name: invalid
|
|
3945
|
+
type: boolean
|
|
3946
|
+
default: false
|
|
3947
|
+
description: Paints the destructive border/ring and sets aria-invalid.
|
|
3948
|
+
- name: label
|
|
3949
|
+
type: string
|
|
3950
|
+
description: Standalone accessible name; inside a form the Field label wires
|
|
3951
|
+
ids instead. Segments announce it themselves.
|
|
3952
|
+
- name: locale
|
|
3953
|
+
type: string
|
|
3954
|
+
description: Pins the field to a locale other than the page's.
|
|
3955
|
+
- name: max
|
|
3956
|
+
type:
|
|
3957
|
+
description: The latest allowed date (Date or ISO string) - rides native constraint
|
|
3958
|
+
validation.
|
|
3959
|
+
- name: min
|
|
3960
|
+
type:
|
|
3961
|
+
description: The earliest allowed date (Date or ISO string) - rides native constraint
|
|
3962
|
+
validation.
|
|
3963
|
+
- name: name
|
|
3964
|
+
type: string
|
|
3965
|
+
required: true
|
|
3966
|
+
description: The form field name - required; the value posts as ISO yyyy-mm-dd
|
|
3967
|
+
with or without JS.
|
|
3968
|
+
- name: placeholder_value
|
|
3969
|
+
type:
|
|
3970
|
+
description: What the first arrow press on an empty segment lands on; defaults
|
|
3971
|
+
to today.
|
|
3972
|
+
- name: readonly
|
|
3973
|
+
type: boolean
|
|
3974
|
+
default: false
|
|
3975
|
+
description: The value shows but cannot be edited.
|
|
3976
|
+
- name: required
|
|
3977
|
+
type: boolean
|
|
3978
|
+
default: false
|
|
3979
|
+
description: Marks the native input required.
|
|
3980
|
+
- name: seconds
|
|
3981
|
+
type: boolean
|
|
3982
|
+
default: false
|
|
3983
|
+
description: Adds the seconds segment; the wire format becomes YYYY-MM-DDTHH:MM:SS.
|
|
3984
|
+
- name: value
|
|
3985
|
+
type:
|
|
3986
|
+
description: Date, or an ISO yyyy-mm-dd string; nil renders empty.
|
|
3987
|
+
slots: []
|
|
3988
|
+
description: A segmented input for typing a date and a time one part at a time.
|
|
3989
|
+
elements:
|
|
3990
|
+
- group
|
|
3991
|
+
- input
|
|
3992
|
+
capsule: c812f47a82a9
|
|
3993
|
+
controllers:
|
|
3994
|
+
- identifier: poetry--core--date-field
|
|
3995
|
+
targets:
|
|
3996
|
+
- group
|
|
3997
|
+
- input
|
|
3998
|
+
values:
|
|
3999
|
+
- hourCycle
|
|
4000
|
+
- labels
|
|
4001
|
+
- locale
|
|
4002
|
+
- placeholder
|
|
4003
|
+
- placeholders
|
|
4004
|
+
- seconds
|
|
4005
|
+
actions:
|
|
4006
|
+
- focusGap
|
|
4007
|
+
- settle
|
|
4008
|
+
events:
|
|
4009
|
+
- poetry:date-field:change
|
|
4010
|
+
agent_rules:
|
|
4011
|
+
- Date-and-time entry is a DateTimeField (poetry_date_time_field / form.datetime_field)
|
|
4012
|
+
- never a DateField beside a TimeField, three selects, or a bare input type=datetime-local
|
|
4013
|
+
when the design system is in play; params[<name>] is YYYY-MM-DDTHH:MM (with
|
|
4014
|
+
:SS under seconds:), with or without JS.
|
|
4015
|
+
- 'No zone rides the wire: the value is the wall time the user typed on their
|
|
4016
|
+
own clock - the app places it (Time.zone.parse in the controller, or the model''s
|
|
4017
|
+
zone).'
|
|
4018
|
+
- '12- vs 24-hour follows the user''s locale automatically (the dayPeriod segment
|
|
4019
|
+
appears only under twelve-hour cycles); hour_cycle: pins it when a product must.'
|
|
4020
|
+
parts:
|
|
4021
|
+
- name: date-time-field
|
|
4022
|
+
description: Root - the controller and the enhanced/disabled surface ride here
|
|
4023
|
+
(segments inside share the date-field-* vocabulary)
|
|
4024
|
+
states:
|
|
4025
|
+
- attr: data-enhanced
|
|
4026
|
+
condition: the controller connected and built segments (no JS = the native
|
|
4027
|
+
input, visible and styled)
|
|
4028
|
+
- attr: data-disabled
|
|
4029
|
+
condition: 'disabled: is set'
|
|
4030
|
+
- attr: data-invalid
|
|
4031
|
+
condition: 'invalid: is set (the group wears the destructive ring)'
|
|
4032
|
+
- name: date-time-field-group
|
|
4033
|
+
description: The bordered segment row - hidden until enhancement, then the editing
|
|
4034
|
+
surface (cn-input chrome, focus-within ring)
|
|
4035
|
+
states:
|
|
4036
|
+
- attr: data-disabled
|
|
4037
|
+
condition: 'disabled: is set (chrome dims, pointer events off)'
|
|
4038
|
+
- attr: data-invalid
|
|
4039
|
+
condition: 'invalid: is set (destructive border + ring)'
|
|
4040
|
+
- name: date-time-field-input
|
|
4041
|
+
description: The native <input type=datetime-local> - THE form value in both
|
|
4042
|
+
modes; tabindex -1 + aria-hidden once segments exist
|
|
4043
|
+
stimulus:
|
|
4044
|
+
- element: root
|
|
4045
|
+
controllers:
|
|
4046
|
+
- identifier: poetry--core--date-field
|
|
4047
|
+
registers: true
|
|
4048
|
+
values:
|
|
4049
|
+
- name: locale
|
|
4050
|
+
conditional: if
|
|
4051
|
+
- name: placeholder
|
|
4052
|
+
- name: labels
|
|
4053
|
+
- name: placeholders
|
|
4054
|
+
- identifier: poetry--core--date-field
|
|
4055
|
+
values:
|
|
4056
|
+
- name: seconds
|
|
4057
|
+
conditional: if seconds
|
|
4058
|
+
- name: hour_cycle
|
|
4059
|
+
conditional: if
|
|
4060
|
+
- element: group
|
|
4061
|
+
controllers:
|
|
4062
|
+
- identifier: poetry--core--date-field
|
|
4063
|
+
targets:
|
|
4064
|
+
- name: group
|
|
4065
|
+
actions:
|
|
4066
|
+
- method: focusGap
|
|
4067
|
+
'on': click
|
|
4068
|
+
- method: settle
|
|
4069
|
+
'on': focusout
|
|
4070
|
+
- element: input
|
|
4071
|
+
controllers:
|
|
4072
|
+
- identifier: poetry--core--date-field
|
|
4073
|
+
targets:
|
|
4074
|
+
- name: input
|
|
3925
4075
|
poetry/ui/deferred:
|
|
3926
4076
|
class_name: Poetry::Ui::Deferred::Component
|
|
3927
4077
|
identifier: poetry--ui--deferred
|
|
@@ -10672,8 +10822,8 @@ components:
|
|
|
10672
10822
|
Input or a pair of selects; params[<name>] is HH:MM (HH:MM:SS with seconds:).
|
|
10673
10823
|
- '12- vs 24-hour follows the user''s locale automatically (the dayPeriod segment
|
|
10674
10824
|
appears only under twelve-hour cycles); hour_cycle: pins it when a product must.'
|
|
10675
|
-
- For a date AND a time,
|
|
10676
|
-
|
|
10825
|
+
- 'For a date AND a time, use a DateTimeField (poetry_date_time_field / form.datetime_field):
|
|
10826
|
+
one control, one datetime-local value.'
|
|
10677
10827
|
parts:
|
|
10678
10828
|
- name: time-field
|
|
10679
10829
|
description: Root - the controller and the enhanced/disabled surface ride here
|
|
@@ -12093,6 +12243,7 @@ helper_args:
|
|
|
12093
12243
|
poetry_data_table: 0
|
|
12094
12244
|
poetry_date_field: 0
|
|
12095
12245
|
poetry_date_picker: 0
|
|
12246
|
+
poetry_date_time_field: 0
|
|
12096
12247
|
poetry_deferred: 0
|
|
12097
12248
|
poetry_dialog: 0
|
|
12098
12249
|
poetry_drawer: 0
|
|
@@ -12208,12 +12359,17 @@ form_builder:
|
|
|
12208
12359
|
- f.association(:company) reflects the association - belongs_to renders a Combobox
|
|
12209
12360
|
on the foreign key, has_many the select-all checkbox group on singular_ids.
|
|
12210
12361
|
- 'Validations become attributes: presence -> aria-required (NEVER native required),
|
|
12211
|
-
length -> maxlength/minlength, numericality -> min/max/step.
|
|
12362
|
+
length -> maxlength/minlength, numericality -> min/max/step; f.input(required:
|
|
12363
|
+
true/false) overrides the presence inference (aria only).'
|
|
12212
12364
|
- 'Hints/placeholders resolve from poetry_form.* i18n (simple_form.* keys keep working
|
|
12213
12365
|
as a fallback); pass hint:/placeholder: to override.'
|
|
12214
12366
|
- 'f.submit renders a poetry Button with the Rails i18n label; f.fieldset(legend:)/f.group
|
|
12215
12367
|
lay out sections; boolean f.input renders the horizontal Field (switch: true ->
|
|
12216
12368
|
the setting row).'
|
|
12369
|
+
- 'Apps on simple_form: add poetry-simple_form instead of rewriting views - Poetry::SimpleForm.activate!
|
|
12370
|
+
maps every simple_form type onto this builder (poetry-only controls via as: :switch/:slider/:otp/:sensitive/:tag_group/:date_picker/:calendar/:combobox/:autocomplete/:native_select;
|
|
12371
|
+
poetry: options merge last). The bridge is the migration path, form_with(builder:)
|
|
12372
|
+
the end state.'
|
|
12217
12373
|
methods:
|
|
12218
12374
|
input: 'the inferred entrypoint: type from as:/attachments/enums/column/name'
|
|
12219
12375
|
association: 'reflection-derived: belongs_to -> Combobox(fk), has_many -> checkbox
|
|
@@ -12250,8 +12406,13 @@ form_builder:
|
|
|
12250
12406
|
- tag_group
|
|
12251
12407
|
- date_picker
|
|
12252
12408
|
- calendar
|
|
12409
|
+
- slider
|
|
12410
|
+
- otp
|
|
12411
|
+
- native_select
|
|
12412
|
+
- datetime
|
|
12253
12413
|
- email
|
|
12254
12414
|
- url
|
|
12255
12415
|
- tel
|
|
12256
12416
|
- boolean
|
|
12417
|
+
- switch
|
|
12257
12418
|
- enum
|
data/lib/poetry/ui/version.rb
CHANGED
data/lib/poetry/ui.rb
CHANGED
|
@@ -45,6 +45,7 @@ module Poetry
|
|
|
45
45
|
"file_input" => %w[input icon], # input variant wears Input; the dropzone's upload glyph
|
|
46
46
|
"date_field" => %w[input], # the pre-enhancement native input wears Input's chrome
|
|
47
47
|
"time_field" => %w[date_field input], # DateField at hour granularity - one segment engine
|
|
48
|
+
"date_time_field" => %w[date_field input], # DateField with both runs - the same segment engine
|
|
48
49
|
"meter" => %w[progress], # wears Progress::Style chrome verbatim
|
|
49
50
|
"search_field" => %w[input input_group button icon], # composes their chrome
|
|
50
51
|
"tag_group" => %w[icon], # the remove glyph
|
|
@@ -60,7 +61,8 @@ module Poetry
|
|
|
60
61
|
# only the family it is composing in. The coverage gate fails on any
|
|
61
62
|
# new component until it is mapped here.
|
|
62
63
|
SKILL_FAMILIES = {
|
|
63
|
-
"forms" => %w[autocomplete button button_group calendar checkbox combobox date_field date_picker
|
|
64
|
+
"forms" => %w[autocomplete button button_group calendar checkbox combobox date_field date_picker date_time_field
|
|
65
|
+
field
|
|
64
66
|
field_group field_separator fieldset
|
|
65
67
|
file_input input input_group input_otp label native_select number_field
|
|
66
68
|
questionnaire radio_group search_field select sensitive_input slider switch textarea time_field
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: poetry-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Solt
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.0.
|
|
18
|
+
version: 0.0.3
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.0.
|
|
25
|
+
version: 0.0.3
|
|
26
26
|
description: 'Poetry''s components for the AI-native UI component library: accessible,
|
|
27
27
|
themeable, agent-legible ViewComponents built entirely on poetry-core''s public
|
|
28
28
|
DSL.'
|
|
@@ -141,6 +141,10 @@ files:
|
|
|
141
141
|
- app/components/poetry/ui/date_picker/component.rb
|
|
142
142
|
- app/components/poetry/ui/date_picker/preview.rb
|
|
143
143
|
- app/components/poetry/ui/date_picker/style.rb
|
|
144
|
+
- app/components/poetry/ui/date_time_field/component.html.erb
|
|
145
|
+
- app/components/poetry/ui/date_time_field/component.rb
|
|
146
|
+
- app/components/poetry/ui/date_time_field/preview.rb
|
|
147
|
+
- app/components/poetry/ui/date_time_field/style.rb
|
|
144
148
|
- app/components/poetry/ui/deferred/component.html.erb
|
|
145
149
|
- app/components/poetry/ui/deferred/component.rb
|
|
146
150
|
- app/components/poetry/ui/deferred/preview.rb
|