playbook_ui 18.1.0.pre.rc.0 → 18.1.0.pre.rc.1
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/pb_kits/playbook/pb_form/docs/_form_model_values.html.erb +43 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_model_values.md +22 -0
- data/app/pb_kits/playbook/pb_form/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +12 -1
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +2 -3
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +26 -0
- data/app/pb_kits/playbook/pb_multi_level_select/tree_helpers.js +10 -4
- data/dist/ai/all-schemas.json +166 -153
- data/dist/ai/forms.json +101 -99
- data/dist/ai/index.json +2 -2
- data/dist/ai/kits/button.schema.json +4 -3
- data/dist/ai/kits/checkbox.schema.json +7 -6
- data/dist/ai/kits/date_picker.schema.json +12 -10
- data/dist/ai/kits/dropdown.schema.json +14 -13
- data/dist/ai/kits/multi_level_select.schema.json +13 -12
- data/dist/ai/kits/phone_number_input.schema.json +18 -17
- data/dist/ai/kits/select.schema.json +18 -17
- data/dist/ai/kits/star_rating.schema.json +10 -9
- data/dist/ai/kits/text_input.schema.json +35 -34
- data/dist/ai/kits/textarea.schema.json +10 -9
- data/dist/ai/kits/time_picker.schema.json +12 -11
- data/dist/ai/kits/typeahead.schema.json +13 -12
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/lib/playbook/changelog_generator.rb +22 -1
- data/lib/playbook/forms/builder/attribute_defaults.rb +118 -0
- data/lib/playbook/forms/builder/collection_select_field.rb +3 -0
- data/lib/playbook/forms/builder/date_picker_field.rb +22 -7
- data/lib/playbook/forms/builder/dropdown_field.rb +12 -0
- data/lib/playbook/forms/builder/form_field_builder.rb +2 -0
- data/lib/playbook/forms/builder/intl_telephone_field.rb +8 -0
- data/lib/playbook/forms/builder/multi_level_select_field.rb +10 -0
- data/lib/playbook/forms/builder/phone_number_field.rb +8 -0
- data/lib/playbook/forms/builder/select_field.rb +3 -0
- data/lib/playbook/forms/builder/star_rating_field.rb +7 -0
- data/lib/playbook/forms/builder/time_picker_field.rb +8 -0
- data/lib/playbook/forms/builder/typeahead_field.rb +15 -0
- data/lib/playbook/forms/builder.rb +3 -0
- data/lib/playbook/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8885b14507b1ffa09091188f028bc63097b1d187eae55a40673ac13a0a8d67c1
|
|
4
|
+
data.tar.gz: b5266d626ee2c9c069c5e18f7bf9b55c7e62d04f394645c1babca4bf7f1f0bf0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 641e3a2789ab3416e5f5f594c0c33fe31510e55750c4d1432b9acfa35a9f5f50529e1eb1663244c4faefbd353ebf15731f470c457fe365954125bd18c7e55183
|
|
7
|
+
data.tar.gz: 90781fad9169b4c1a23c078ec59a38448562d6cee63811282cbddb011a9269ac177bf5435472d2164aeebe2c36139e0d3763cf52539a03aee3865c54c409fa46
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<%
|
|
2
|
+
load Playbook.kit_path("form", "docs", "_sample_data.ruby").to_s
|
|
3
|
+
|
|
4
|
+
dropdown_options = [
|
|
5
|
+
{ label: "Open", value: "open", id: "status-open" },
|
|
6
|
+
{ label: "Closed", value: "closed", id: "status-closed" },
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
example_model = Class.new do
|
|
10
|
+
include ActiveModel::Model
|
|
11
|
+
attr_accessor :name, :spots, :starts_at, :ends_at, :status, :phone
|
|
12
|
+
|
|
13
|
+
def self.name
|
|
14
|
+
"Example"
|
|
15
|
+
end
|
|
16
|
+
end.new(
|
|
17
|
+
name: "Gary 🎉",
|
|
18
|
+
spots: 42,
|
|
19
|
+
starts_at: Time.utc(2026, 8, 22, 19, 37, 0),
|
|
20
|
+
ends_at: Time.utc(2026, 8, 20, 23, 59, 0),
|
|
21
|
+
status: "open",
|
|
22
|
+
phone: "+15551234567"
|
|
23
|
+
)
|
|
24
|
+
example_model.errors.add(:name, "cannot contain emoji")
|
|
25
|
+
example_model.errors.add(:spots, "must be 20 or fewer")
|
|
26
|
+
example_model.errors.add(:starts_at, "must be before ends at")
|
|
27
|
+
%>
|
|
28
|
+
|
|
29
|
+
<%= pb_rails("card", props: PlaybookFormDocsSampleData.demo_form_card_props) do %>
|
|
30
|
+
<%= pb_form_with(model: example_model, method: :get, url: "", html: { onsubmit: "return false;" }) do |form| %>
|
|
31
|
+
<%= form.text_field :name, props: { label: true } %>
|
|
32
|
+
<%= form.number_field :spots, props: { label: true } %>
|
|
33
|
+
<%= form.date_picker :starts_at, props: { label: true } %>
|
|
34
|
+
<%= form.date_picker :ends_at, props: { label: true } %>
|
|
35
|
+
<%= form.dropdown_field :status, props: { label: true, options: dropdown_options } %>
|
|
36
|
+
<%= form.phone_number_field :phone, props: { label: true } %>
|
|
37
|
+
|
|
38
|
+
<%= form.actions do |action| %>
|
|
39
|
+
<%= action.submit %>
|
|
40
|
+
<%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% end %>
|
|
43
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
When the form is bound to a model (`model:` / `options: { model: ... }`), builder fields read attribute values and validation errors automatically — similar to Rails `text_field`. You usually do not need `default_date`, `value`, `default_value`, `selected_ids`, or `error`:
|
|
2
|
+
|
|
3
|
+
```erb
|
|
4
|
+
<%= form.date_picker :starts_at, props: { label: true } %>
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
This example is an edit form after a failed validation: values come from the model, and errors appear under invalid attributes. Explicit props still win when you pass them.
|
|
8
|
+
|
|
9
|
+
| Field | Auto value | Auto error |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `date_picker` | `default_date` (wall-clock `YYYY-MM-DDT00:00:00`) | yes |
|
|
12
|
+
| `time_picker` | `default_time` (HH:MM) | yes |
|
|
13
|
+
| `dropdown_field` | `default_value` (matched via `options`) | yes |
|
|
14
|
+
| `star_rating_field` | `default_value` | n/a |
|
|
15
|
+
| `multi_level_select` | `selected_ids` | yes |
|
|
16
|
+
| `phone_number_field` / `intl_telephone` | `value` | yes |
|
|
17
|
+
| `typeahead` | `default_options` only if already option-shaped | yes |
|
|
18
|
+
| text / textarea / select / `collection_select` | via Rails | yes |
|
|
19
|
+
|
|
20
|
+
**Notes**
|
|
21
|
+
- Dropdown needs `options` so a stored id/value can resolve to an option hash; unmatched ids are left unset.
|
|
22
|
+
- Typeahead only auto-binds when the attribute is `{ label, value }` (or an array of those). A bare database id still needs explicit `default_options`.
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
// selected_ids / tree_data ids may be strings or numbers; includes() is strict.
|
|
2
|
+
export const selectedIdsInclude = (
|
|
3
|
+
selectedIds: Array<string | number> | null | undefined,
|
|
4
|
+
id: string | number
|
|
5
|
+
): boolean => {
|
|
6
|
+
if (!selectedIds || !selectedIds.length) return false
|
|
7
|
+
|
|
8
|
+
const target = String(id)
|
|
9
|
+
return selectedIds.some((selectedId) => String(selectedId) === target)
|
|
10
|
+
};
|
|
11
|
+
|
|
1
12
|
// Function is going over formattedData and returning all objects that match the
|
|
2
13
|
// ID of the clicked item from the dropdown
|
|
3
14
|
export const filterFormattedDataById = (
|
|
@@ -160,7 +171,7 @@ export const getExpandedItems = (
|
|
|
160
171
|
|
|
161
172
|
// Only expand based on selected items if showCheckedChildren is true
|
|
162
173
|
if (showCheckedChildren) {
|
|
163
|
-
if (selectedIds
|
|
174
|
+
if (selectedIdsInclude(selectedIds, item.id)) {
|
|
164
175
|
expandedItems.push(...itemAncestors.map((ancestor: any) => ancestor.id));
|
|
165
176
|
}
|
|
166
177
|
if (Array.isArray(item.children)) {
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
getDefaultCheckedItems,
|
|
33
33
|
recursiveCheckParent,
|
|
34
34
|
getExpandedItems,
|
|
35
|
+
selectedIdsInclude,
|
|
35
36
|
} from "./_helper_functions";
|
|
36
37
|
|
|
37
38
|
import { DialogContext } from "../pb_dialog/_dialog_context";
|
|
@@ -248,9 +249,7 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
|
|
|
248
249
|
const isDisabled =
|
|
249
250
|
item.disabled || (parentDisabled && !returnAllSelected);
|
|
250
251
|
|
|
251
|
-
const explicitlySelected =
|
|
252
|
-
selectedIds && selectedIds?.length && selectedIds.includes(item.id)
|
|
253
|
-
);
|
|
252
|
+
const explicitlySelected = selectedIdsInclude(selectedIds, item.id);
|
|
254
253
|
|
|
255
254
|
const checked =
|
|
256
255
|
!isDisabled &&
|
|
@@ -546,6 +546,32 @@ describe('MultiLevelSelect tree_helpers', () => {
|
|
|
546
546
|
expect(formatted[0].children[1].checked).toBe(false)
|
|
547
547
|
})
|
|
548
548
|
|
|
549
|
+
test('matches selected ids when selected_ids are numbers and tree ids are strings', () => {
|
|
550
|
+
const numericTree = [
|
|
551
|
+
{
|
|
552
|
+
id: '101',
|
|
553
|
+
label: 'Parent',
|
|
554
|
+
children: [{ id: '102', label: 'Child' }],
|
|
555
|
+
},
|
|
556
|
+
]
|
|
557
|
+
const formatted = addCheckedAndParentProperty(cloneTree(numericTree), [102])
|
|
558
|
+
expect(formatted[0].children[0].checked).toBe(true)
|
|
559
|
+
expect(formatted[0].checked).toBe(false)
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
test('matches selected ids when selected_ids are strings and tree ids are numbers', () => {
|
|
563
|
+
const numericTree = [
|
|
564
|
+
{
|
|
565
|
+
id: 101,
|
|
566
|
+
label: 'Parent',
|
|
567
|
+
children: [{ id: 102, label: 'Child' }],
|
|
568
|
+
},
|
|
569
|
+
]
|
|
570
|
+
const formatted = addCheckedAndParentProperty(cloneTree(numericTree), ['102'])
|
|
571
|
+
expect(formatted[0].children[0].checked).toBe(true)
|
|
572
|
+
expect(formatted[0].checked).toBe(false)
|
|
573
|
+
})
|
|
574
|
+
|
|
549
575
|
test('cascades checked state to children in default multi mode', () => {
|
|
550
576
|
const formatted = addCheckedAndParentProperty(cloneTree(helperTree), ['parent'])
|
|
551
577
|
expect(formatted[0].checked).toBe(true)
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export const cloneTree = (value) => JSON.parse(JSON.stringify(value))
|
|
2
2
|
|
|
3
|
+
// selected_ids / tree_data ids may be strings or numbers; includes() is strict.
|
|
4
|
+
export const selectedIdsInclude = (selectedIds, id) => {
|
|
5
|
+
if (!selectedIds || !selectedIds.length) return false
|
|
6
|
+
|
|
7
|
+
const target = String(id)
|
|
8
|
+
return selectedIds.some((selectedId) => String(selectedId) === target)
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
export const filterFormattedDataById = (formattedData, id) => {
|
|
4
12
|
const matched = []
|
|
5
13
|
const recursiveSearch = (data, term) => {
|
|
@@ -138,7 +146,7 @@ export const getExpandedItems = (
|
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
if (showCheckedChildren) {
|
|
141
|
-
if (selectedIds
|
|
149
|
+
if (selectedIdsInclude(selectedIds, item.id)) {
|
|
142
150
|
expandedItems.push(...itemAncestors.map((ancestor) => ancestor.id))
|
|
143
151
|
}
|
|
144
152
|
if (Array.isArray(item.children)) {
|
|
@@ -193,9 +201,7 @@ export const addCheckedAndParentProperty = (
|
|
|
193
201
|
const isDisabled =
|
|
194
202
|
item.disabled || (parentDisabled && !returnAllSelected)
|
|
195
203
|
|
|
196
|
-
const explicitlySelected =
|
|
197
|
-
selectedIds && selectedIds.length && selectedIds.includes(item.id)
|
|
198
|
-
)
|
|
204
|
+
const explicitlySelected = selectedIdsInclude(selectedIds, item.id)
|
|
199
205
|
|
|
200
206
|
const checked =
|
|
201
207
|
!isDisabled &&
|