playbook_ui 16.5.0.pre.alpha.PLAY2893datepickerlabelclicktoggle15576 → 16.5.0.pre.alpha.RTEPOC15682
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_advanced_table/table_header.html.erb +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +33 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/_tiptap_styles.scss +9 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.html.erb +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/kit.schema.json +14 -7
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +303 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +45 -0
- data/app/pb_kits/playbook/pb_select/select.rb +2 -2
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +4 -4
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_createable.html.erb +29 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_createable.md +1 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_typeahead/kit.schema.json +4 -2
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -1
- data/dist/chunks/{_typeahead-B_Avup-T.js → _typeahead-BYUXg9ZT.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/menu.yml +0 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +9 -4
- data/app/pb_kits/playbook/utilities/globalPropNames.mjs +0 -58
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '092a215d37d2e1e61ec273d8e0fb5dd90fdf2e8756f34856743051da963a342f'
|
|
4
|
+
data.tar.gz: 9eb1b908eb0575b20f79633081b593538ae371bbc25f962d3d96b59c799f98b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 752e655ecec4026fe1b19fcfee31dcb3bf595153c64b3e66b297ed0c8bc385cd22de29e09dbd109fd70a6336124903f0f3812e8477cb52657a94a78b3bf3de3b
|
|
7
|
+
data.tar.gz: e118779c805e4969b93c9e7eb5dd8fc3f58434447f818481e6873a3e3d2aa1bccdf5ce5c35d2328ade18691b12b0a1658e17bceb0388da8e38453ef3f23405ca
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<% header_row.each_with_index do |cell, cell_index| %>
|
|
8
8
|
<% header_component = object.header_component_info(cell, cell_index, row_index) %>
|
|
9
9
|
<%= pb_rails(header_component[:name], props: header_component[:props]) do %>
|
|
10
|
-
<%= pb_rails("flex", props: { align: "center", justify:
|
|
10
|
+
<%= pb_rails("flex", props: { align: "center", justify: object.header_flex_justify(cell, cell_index, row_index), text_align: object.header_flex_text_align(cell) }) do %>
|
|
11
11
|
<% if cell_index.zero? && row_index === header_rows.size - 1 %>
|
|
12
12
|
<% if object.selectable_rows && object.enable_toggle_expansion != "none" %>
|
|
13
13
|
<%= pb_rails("flex/flex_item", props: { padding_right: "xs" }) do %>
|
|
@@ -192,6 +192,19 @@ module Playbook
|
|
|
192
192
|
{ name: component_name, props: component_props }
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
+
# Flex justify for header cells: column_styling header_alignment when present (otherwisedefault by column/row index)
|
|
196
|
+
def header_flex_justify(cell, cell_index, row_index)
|
|
197
|
+
ha = cell[:header_alignment]
|
|
198
|
+
return header_alignment_to_justify(ha) if ha.present?
|
|
199
|
+
|
|
200
|
+
default_header_flex_justify(cell_index, row_index)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Flex text_align from column_styling header_alignment (default is end)
|
|
204
|
+
def header_flex_text_align(cell)
|
|
205
|
+
(cell[:header_alignment].presence || "end").to_s
|
|
206
|
+
end
|
|
207
|
+
|
|
195
208
|
private
|
|
196
209
|
|
|
197
210
|
# Find the original column definition for a cell
|
|
@@ -351,6 +364,26 @@ module Playbook
|
|
|
351
364
|
row[:children] || row["children"]
|
|
352
365
|
end
|
|
353
366
|
end
|
|
367
|
+
|
|
368
|
+
# 2 header alignment helper methods
|
|
369
|
+
def header_alignment_to_justify(header_alignment)
|
|
370
|
+
case header_alignment.to_s
|
|
371
|
+
when "left" then "start"
|
|
372
|
+
when "center" then "center"
|
|
373
|
+
when "right" then "end"
|
|
374
|
+
else "end"
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def default_header_flex_justify(cell_index, row_index)
|
|
379
|
+
if cell_index.zero?
|
|
380
|
+
"start"
|
|
381
|
+
elsif row_index == header_rows.size - 1
|
|
382
|
+
"end"
|
|
383
|
+
else
|
|
384
|
+
"center"
|
|
385
|
+
end
|
|
386
|
+
end
|
|
354
387
|
end
|
|
355
388
|
end
|
|
356
389
|
end
|
|
@@ -142,7 +142,7 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
142
142
|
// Determine if toolbar should be shown
|
|
143
143
|
const shouldShowToolbar = focus && advancedEditor ? showToolbarOnFocus : advancedEditorToolbar
|
|
144
144
|
|
|
145
|
-
const labelFor = advancedEditor ? fieldId : (id ? id : (inputOptions
|
|
145
|
+
const labelFor = advancedEditor ? fieldId : (id ? id : (inputOptions?.id ? `${inputOptions.id}_trix` : undefined))
|
|
146
146
|
|
|
147
147
|
return (
|
|
148
148
|
<div
|
|
@@ -73,6 +73,12 @@
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Active state for toolbar (Rails kit uses pb_button_kit pb_button_link; override link variant when active)
|
|
77
|
+
.toolbar button.pb_button_kit.is-active {
|
|
78
|
+
color: $primary;
|
|
79
|
+
background-color: $bg_light;
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
.pb_rich_text_editor_tiptap_toolbar_sticky {
|
|
77
83
|
position: sticky;
|
|
78
84
|
top: 0;
|
|
@@ -83,6 +89,9 @@
|
|
|
83
89
|
border: 1px solid $input_border_default;
|
|
84
90
|
overflow-x: auto;
|
|
85
91
|
&_block {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-wrap: wrap;
|
|
94
|
+
align-items: center;
|
|
86
95
|
gap: $space_xs;
|
|
87
96
|
}
|
|
88
97
|
.editor-dropdown-button {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= pb_rails("rich_text_editor", props: { input_options: { id: 'hidden_input_id', name: "hidden_input_name" }, value: "Add your text here. You can format your text, add links, quotes, and bullets." }) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TipTap (vanilla JS) — the Playbook **Rails** rich text editor. No React; same editor core as the React TipTap variant. Content is synced to a hidden input for Rails form submission. Use `pb_rails("rich_text_editor", props: { input_options: { id: "...", name: "..." }, value: "..." })`.
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"name": "RichTextEditor",
|
|
4
4
|
"description": "RichTextEditor component",
|
|
5
5
|
"platforms": [
|
|
6
|
-
"react"
|
|
6
|
+
"react",
|
|
7
|
+
"rails"
|
|
7
8
|
],
|
|
8
9
|
"props": {
|
|
9
10
|
"advancedEditor": {
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
"inputOptions": {
|
|
34
35
|
"type": "{ [key: string]: string | number | boolean | (() => void) }",
|
|
35
36
|
"platforms": [
|
|
36
|
-
"react"
|
|
37
|
+
"react",
|
|
38
|
+
"rails"
|
|
37
39
|
]
|
|
38
40
|
},
|
|
39
41
|
"inline": {
|
|
@@ -45,7 +47,8 @@
|
|
|
45
47
|
"label": {
|
|
46
48
|
"type": "string",
|
|
47
49
|
"platforms": [
|
|
48
|
-
"react"
|
|
50
|
+
"react",
|
|
51
|
+
"rails"
|
|
49
52
|
]
|
|
50
53
|
},
|
|
51
54
|
"extensions": {
|
|
@@ -69,7 +72,8 @@
|
|
|
69
72
|
"placeholder": {
|
|
70
73
|
"type": "string",
|
|
71
74
|
"platforms": [
|
|
72
|
-
"react"
|
|
75
|
+
"react",
|
|
76
|
+
"rails"
|
|
73
77
|
]
|
|
74
78
|
},
|
|
75
79
|
"inputHeight": {
|
|
@@ -97,8 +101,10 @@
|
|
|
97
101
|
"requiredIndicator": {
|
|
98
102
|
"type": "boolean",
|
|
99
103
|
"platforms": [
|
|
100
|
-
"react"
|
|
101
|
-
|
|
104
|
+
"react",
|
|
105
|
+
"rails"
|
|
106
|
+
],
|
|
107
|
+
"default": false
|
|
102
108
|
},
|
|
103
109
|
"simple": {
|
|
104
110
|
"type": "boolean",
|
|
@@ -121,7 +127,8 @@
|
|
|
121
127
|
"value": {
|
|
122
128
|
"type": "string",
|
|
123
129
|
"platforms": [
|
|
124
|
-
"react"
|
|
130
|
+
"react",
|
|
131
|
+
"rails"
|
|
125
132
|
]
|
|
126
133
|
},
|
|
127
134
|
"TrixEditor": {
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
<%# Import map: single CDN copy of TipTap/ProseMirror (avoids duplicate prosemirror-model). %>
|
|
2
|
+
<%# Sidecar ERB only — not render inline/file — so KitBase#pb_content_tag runs on the component. %>
|
|
3
|
+
<script type="importmap">
|
|
4
|
+
{
|
|
5
|
+
"imports": {
|
|
6
|
+
"@tiptap/core": "https://cdn.jsdelivr.net/npm/@tiptap/core@2.8.0/dist/index.js",
|
|
7
|
+
"@tiptap/starter-kit": "https://cdn.jsdelivr.net/npm/@tiptap/starter-kit@2.8.0/dist/index.js",
|
|
8
|
+
"@tiptap/extension-link": "https://cdn.jsdelivr.net/npm/@tiptap/extension-link@2.8.0/dist/index.js",
|
|
9
|
+
"@tiptap/extension-blockquote": "https://cdn.jsdelivr.net/npm/@tiptap/extension-blockquote@2.8.0/dist/index.js",
|
|
10
|
+
"@tiptap/extension-bold": "https://cdn.jsdelivr.net/npm/@tiptap/extension-bold@2.8.0/dist/index.js",
|
|
11
|
+
"@tiptap/extension-bullet-list": "https://cdn.jsdelivr.net/npm/@tiptap/extension-bullet-list@2.8.0/dist/index.js",
|
|
12
|
+
"@tiptap/extension-code": "https://cdn.jsdelivr.net/npm/@tiptap/extension-code@2.8.0/dist/index.js",
|
|
13
|
+
"@tiptap/extension-code-block": "https://cdn.jsdelivr.net/npm/@tiptap/extension-code-block@2.8.0/dist/index.js",
|
|
14
|
+
"@tiptap/extension-document": "https://cdn.jsdelivr.net/npm/@tiptap/extension-document@2.8.0/dist/index.js",
|
|
15
|
+
"@tiptap/extension-dropcursor": "https://cdn.jsdelivr.net/npm/@tiptap/extension-dropcursor@2.8.0/dist/index.js",
|
|
16
|
+
"@tiptap/extension-gapcursor": "https://cdn.jsdelivr.net/npm/@tiptap/extension-gapcursor@2.8.0/dist/index.js",
|
|
17
|
+
"@tiptap/extension-hard-break": "https://cdn.jsdelivr.net/npm/@tiptap/extension-hard-break@2.8.0/dist/index.js",
|
|
18
|
+
"@tiptap/extension-heading": "https://cdn.jsdelivr.net/npm/@tiptap/extension-heading@2.8.0/dist/index.js",
|
|
19
|
+
"@tiptap/extension-history": "https://cdn.jsdelivr.net/npm/@tiptap/extension-history@2.8.0/dist/index.js",
|
|
20
|
+
"@tiptap/extension-horizontal-rule": "https://cdn.jsdelivr.net/npm/@tiptap/extension-horizontal-rule@2.8.0/dist/index.js",
|
|
21
|
+
"@tiptap/extension-italic": "https://cdn.jsdelivr.net/npm/@tiptap/extension-italic@2.8.0/dist/index.js",
|
|
22
|
+
"@tiptap/extension-list-item": "https://cdn.jsdelivr.net/npm/@tiptap/extension-list-item@2.8.0/dist/index.js",
|
|
23
|
+
"@tiptap/extension-ordered-list": "https://cdn.jsdelivr.net/npm/@tiptap/extension-ordered-list@2.8.0/dist/index.js",
|
|
24
|
+
"@tiptap/extension-paragraph": "https://cdn.jsdelivr.net/npm/@tiptap/extension-paragraph@2.8.0/dist/index.js",
|
|
25
|
+
"@tiptap/extension-strike": "https://cdn.jsdelivr.net/npm/@tiptap/extension-strike@2.8.0/dist/index.js",
|
|
26
|
+
"@tiptap/extension-text": "https://cdn.jsdelivr.net/npm/@tiptap/extension-text@2.8.0/dist/index.js",
|
|
27
|
+
"@tiptap/extension-text-style": "https://cdn.jsdelivr.net/npm/@tiptap/extension-text-style@2.8.0/dist/index.js",
|
|
28
|
+
"@tiptap/pm/state": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/state/dist/index.js",
|
|
29
|
+
"@tiptap/pm/view": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/view/dist/index.js",
|
|
30
|
+
"@tiptap/pm/keymap": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/keymap/dist/index.js",
|
|
31
|
+
"@tiptap/pm/model": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/model/dist/index.js",
|
|
32
|
+
"@tiptap/pm/transform": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/transform/dist/index.js",
|
|
33
|
+
"@tiptap/pm/commands": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/commands/dist/index.js",
|
|
34
|
+
"@tiptap/pm/schema-list": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/schema-list/dist/index.js",
|
|
35
|
+
"@tiptap/pm/history": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/history/dist/index.js",
|
|
36
|
+
"@tiptap/pm/gapcursor": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/gapcursor/dist/index.js",
|
|
37
|
+
"@tiptap/pm/dropcursor": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/dropcursor/dist/index.js",
|
|
38
|
+
"@tiptap/pm/inputrules": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/inputrules/dist/index.js",
|
|
39
|
+
"@tiptap/pm/schema-basic": "https://cdn.jsdelivr.net/npm/@tiptap/pm@2.8.0/schema-basic/dist/index.js",
|
|
40
|
+
"prosemirror-model": "https://cdn.jsdelivr.net/npm/prosemirror-model@1.22.3/dist/index.js",
|
|
41
|
+
"prosemirror-state": "https://cdn.jsdelivr.net/npm/prosemirror-state@1.4.3/dist/index.js",
|
|
42
|
+
"prosemirror-view": "https://cdn.jsdelivr.net/npm/prosemirror-view@1.33.10/dist/index.js",
|
|
43
|
+
"prosemirror-transform": "https://cdn.jsdelivr.net/npm/prosemirror-transform@1.10.0/dist/index.js",
|
|
44
|
+
"prosemirror-commands": "https://cdn.jsdelivr.net/npm/prosemirror-commands@1.6.0/dist/index.js",
|
|
45
|
+
"prosemirror-keymap": "https://cdn.jsdelivr.net/npm/prosemirror-keymap@1.2.2/dist/index.js",
|
|
46
|
+
"prosemirror-history": "https://cdn.jsdelivr.net/npm/prosemirror-history@1.4.1/dist/index.js",
|
|
47
|
+
"prosemirror-gapcursor": "https://cdn.jsdelivr.net/npm/prosemirror-gapcursor@1.3.2/dist/index.js",
|
|
48
|
+
"prosemirror-dropcursor": "https://cdn.jsdelivr.net/npm/prosemirror-dropcursor@1.8.1/dist/index.js",
|
|
49
|
+
"prosemirror-inputrules": "https://cdn.jsdelivr.net/npm/prosemirror-inputrules@1.4.0/dist/index.js",
|
|
50
|
+
"prosemirror-schema-basic": "https://cdn.jsdelivr.net/npm/prosemirror-schema-basic@1.2.3/dist/index.js",
|
|
51
|
+
"prosemirror-schema-list": "https://cdn.jsdelivr.net/npm/prosemirror-schema-list@1.4.1/dist/index.js",
|
|
52
|
+
"linkifyjs": "https://cdn.jsdelivr.net/npm/linkifyjs@4.1.0/dist/linkify.es.js",
|
|
53
|
+
"orderedmap": "https://cdn.jsdelivr.net/npm/orderedmap@2.1.1/dist/index.js",
|
|
54
|
+
"w3c-keyname": "https://cdn.jsdelivr.net/npm/w3c-keyname@2.2.8/index.js",
|
|
55
|
+
"rope-sequence": "https://cdn.jsdelivr.net/npm/rope-sequence@1.3.4/dist/index.js"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
<%= pb_content_tag(:div, id: object.container_id, class: object.classname, data: { pb_rte_tiptap: true, input_id: object.input_id, initial_html: object.initial_html }) do %>
|
|
60
|
+
<div class="pb_rich_text_editor_kit">
|
|
61
|
+
<% if object.label.present? %>
|
|
62
|
+
<label for="<%= object.input_id %>">
|
|
63
|
+
<% if object.required_indicator %>
|
|
64
|
+
<%= pb_rails("caption", props: { color: "lighter", text: object.label, dark: object.dark }) %><span style="color: #DA0014;"> *</span>
|
|
65
|
+
<% else %>
|
|
66
|
+
<%= pb_rails("caption", props: { color: "lighter", text: object.label, dark: object.dark }) %>
|
|
67
|
+
<% end %>
|
|
68
|
+
</label>
|
|
69
|
+
<% end %>
|
|
70
|
+
<input type="hidden" name="<%= object.input_name %>" id="<%= object.input_id %>" value="" />
|
|
71
|
+
<div class="pb_rich_text_editor_advanced_container toolbar-active">
|
|
72
|
+
<div class="pb_background_kit pb_background_color_white toolbar" id="<%= object.toolbar_id %>">
|
|
73
|
+
<div class="toolbar_block">
|
|
74
|
+
<%= pb_rails("button", props: {
|
|
75
|
+
icon: "bold",
|
|
76
|
+
size: "sm",
|
|
77
|
+
variant: "link",
|
|
78
|
+
html_options: {
|
|
79
|
+
type: "button",
|
|
80
|
+
data: { action: "bold" },
|
|
81
|
+
title: "Bold",
|
|
82
|
+
role: "button",
|
|
83
|
+
tabindex: 0,
|
|
84
|
+
class: "toolbar_button"
|
|
85
|
+
}
|
|
86
|
+
}) %>
|
|
87
|
+
<%= pb_rails("button", props: {
|
|
88
|
+
icon: "italic",
|
|
89
|
+
size: "sm",
|
|
90
|
+
variant: "link",
|
|
91
|
+
html_options: {
|
|
92
|
+
type: "button",
|
|
93
|
+
data: { action: "italic" },
|
|
94
|
+
title: "Italic",
|
|
95
|
+
role: "button",
|
|
96
|
+
tabindex: 0,
|
|
97
|
+
class: "toolbar_button"
|
|
98
|
+
}
|
|
99
|
+
}) %>
|
|
100
|
+
<%= pb_rails("button", props: {
|
|
101
|
+
icon: "strikethrough",
|
|
102
|
+
size: "sm",
|
|
103
|
+
variant: "link",
|
|
104
|
+
html_options: {
|
|
105
|
+
type: "button",
|
|
106
|
+
data: { action: "strike" },
|
|
107
|
+
title: "Strikethrough",
|
|
108
|
+
role: "button",
|
|
109
|
+
tabindex: 0,
|
|
110
|
+
class: "toolbar_button"
|
|
111
|
+
}
|
|
112
|
+
}) %>
|
|
113
|
+
<%= pb_rails("button", props: {
|
|
114
|
+
icon: "h1",
|
|
115
|
+
size: "sm",
|
|
116
|
+
variant: "link",
|
|
117
|
+
html_options: {
|
|
118
|
+
type: "button",
|
|
119
|
+
data: { action: "heading", level: "1" },
|
|
120
|
+
title: "Heading 1",
|
|
121
|
+
role: "button",
|
|
122
|
+
tabindex: 0,
|
|
123
|
+
class: "toolbar_button"
|
|
124
|
+
}
|
|
125
|
+
}) %>
|
|
126
|
+
<%= pb_rails("button", props: {
|
|
127
|
+
icon: "h2",
|
|
128
|
+
size: "sm",
|
|
129
|
+
variant: "link",
|
|
130
|
+
html_options: {
|
|
131
|
+
type: "button",
|
|
132
|
+
data: { action: "heading", level: "2" },
|
|
133
|
+
title: "Heading 2",
|
|
134
|
+
role: "button",
|
|
135
|
+
tabindex: 0,
|
|
136
|
+
class: "toolbar_button"
|
|
137
|
+
}
|
|
138
|
+
}) %>
|
|
139
|
+
<%= pb_rails("button", props: {
|
|
140
|
+
icon: "list",
|
|
141
|
+
size: "sm",
|
|
142
|
+
variant: "link",
|
|
143
|
+
html_options: {
|
|
144
|
+
type: "button",
|
|
145
|
+
data: { action: "bulletList" },
|
|
146
|
+
title: "Bullet list",
|
|
147
|
+
role: "button",
|
|
148
|
+
tabindex: 0,
|
|
149
|
+
class: "toolbar_button"
|
|
150
|
+
}
|
|
151
|
+
}) %>
|
|
152
|
+
<%= pb_rails("button", props: {
|
|
153
|
+
icon: "list-ol",
|
|
154
|
+
size: "sm",
|
|
155
|
+
variant: "link",
|
|
156
|
+
html_options: {
|
|
157
|
+
type: "button",
|
|
158
|
+
data: { action: "orderedList" },
|
|
159
|
+
title: "Ordered list",
|
|
160
|
+
role: "button",
|
|
161
|
+
tabindex: 0,
|
|
162
|
+
class: "toolbar_button"
|
|
163
|
+
}
|
|
164
|
+
}) %>
|
|
165
|
+
<%= pb_rails("button", props: {
|
|
166
|
+
icon: "quote-left",
|
|
167
|
+
size: "sm",
|
|
168
|
+
variant: "link",
|
|
169
|
+
html_options: {
|
|
170
|
+
type: "button",
|
|
171
|
+
data: { action: "blockquote" },
|
|
172
|
+
title: "Quote",
|
|
173
|
+
role: "button",
|
|
174
|
+
tabindex: 0,
|
|
175
|
+
class: "toolbar_button"
|
|
176
|
+
}
|
|
177
|
+
}) %>
|
|
178
|
+
<%= pb_rails("button", props: {
|
|
179
|
+
icon: "code",
|
|
180
|
+
size: "sm",
|
|
181
|
+
variant: "link",
|
|
182
|
+
html_options: {
|
|
183
|
+
type: "button",
|
|
184
|
+
data: { action: "codeBlock" },
|
|
185
|
+
title: "Code block",
|
|
186
|
+
role: "button",
|
|
187
|
+
tabindex: 0,
|
|
188
|
+
class: "toolbar_button"
|
|
189
|
+
}
|
|
190
|
+
}) %>
|
|
191
|
+
<%= pb_rails("button", props: {
|
|
192
|
+
icon: "link",
|
|
193
|
+
size: "sm",
|
|
194
|
+
variant: "link",
|
|
195
|
+
html_options: {
|
|
196
|
+
type: "button",
|
|
197
|
+
data: { action: "link" },
|
|
198
|
+
title: "Link",
|
|
199
|
+
role: "button",
|
|
200
|
+
tabindex: 0,
|
|
201
|
+
class: "toolbar_button"
|
|
202
|
+
}
|
|
203
|
+
}) %>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="rte-editor-wrap">
|
|
207
|
+
<div id="<%= object.editor_node_id %>"></div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<% end %>
|
|
212
|
+
|
|
213
|
+
<script type="module">
|
|
214
|
+
(async function() {
|
|
215
|
+
const container = document.getElementById("<%= object.container_id %>");
|
|
216
|
+
if (!container) return;
|
|
217
|
+
const inputId = container.dataset.inputId;
|
|
218
|
+
let initialHtml = container.dataset.initialHtml || "<p></p>";
|
|
219
|
+
if (initialHtml && !initialHtml.trim().startsWith("<")) {
|
|
220
|
+
initialHtml = "<p>" + initialHtml + "</p>";
|
|
221
|
+
}
|
|
222
|
+
const hiddenInput = document.getElementById(inputId);
|
|
223
|
+
const editorNode = document.getElementById("<%= object.editor_node_id %>");
|
|
224
|
+
const toolbar = document.getElementById("<%= object.toolbar_id %>");
|
|
225
|
+
if (!editorNode || !hiddenInput || !toolbar) return;
|
|
226
|
+
|
|
227
|
+
function syncToHiddenInput(editor) {
|
|
228
|
+
if (editor && hiddenInput) {
|
|
229
|
+
hiddenInput.value = editor.getHTML();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const { Editor } = await import("@tiptap/core");
|
|
234
|
+
const { default: StarterKit } = await import("@tiptap/starter-kit");
|
|
235
|
+
const { default: Link } = await import("@tiptap/extension-link");
|
|
236
|
+
|
|
237
|
+
const editor = new Editor({
|
|
238
|
+
element: editorNode,
|
|
239
|
+
extensions: [
|
|
240
|
+
StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
|
|
241
|
+
Link.configure({ openOnClick: false, HTMLAttributes: { target: "_blank", rel: "noopener" } }),
|
|
242
|
+
],
|
|
243
|
+
content: initialHtml,
|
|
244
|
+
editable: true,
|
|
245
|
+
onUpdate: ({ editor: ed }) => syncToHiddenInput(ed),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
syncToHiddenInput(editor);
|
|
249
|
+
|
|
250
|
+
const actionToChain = {
|
|
251
|
+
bold: "toggleBold",
|
|
252
|
+
italic: "toggleItalic",
|
|
253
|
+
strike: "toggleStrike",
|
|
254
|
+
bulletList: "toggleBulletList",
|
|
255
|
+
orderedList: "toggleOrderedList",
|
|
256
|
+
blockquote: "toggleBlockquote",
|
|
257
|
+
codeBlock: "toggleCodeBlock",
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
function updateActiveStates() {
|
|
261
|
+
toolbar.querySelectorAll("button[data-action]").forEach((btn) => {
|
|
262
|
+
const action = btn.dataset.action;
|
|
263
|
+
const level = btn.dataset.level ? parseInt(btn.dataset.level, 10) : null;
|
|
264
|
+
let active = false;
|
|
265
|
+
if (action === "bold") active = editor.isActive("bold");
|
|
266
|
+
else if (action === "italic") active = editor.isActive("italic");
|
|
267
|
+
else if (action === "strike") active = editor.isActive("strike");
|
|
268
|
+
else if (action === "blockquote") active = editor.isActive("blockquote");
|
|
269
|
+
else if (action === "bulletList") active = editor.isActive("bulletList");
|
|
270
|
+
else if (action === "orderedList") active = editor.isActive("orderedList");
|
|
271
|
+
else if (action === "codeBlock") active = editor.isActive("codeBlock");
|
|
272
|
+
else if (action === "link") active = editor.isActive("link");
|
|
273
|
+
else if (action === "heading" && level != null) active = editor.isActive("heading", { level });
|
|
274
|
+
btn.classList.toggle("is-active", active);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
toolbar.addEventListener("click", (e) => {
|
|
279
|
+
const btn = e.target.closest("button[data-action]");
|
|
280
|
+
if (!btn) return;
|
|
281
|
+
e.preventDefault();
|
|
282
|
+
const action = btn.dataset.action;
|
|
283
|
+
const level = btn.dataset.level ? parseInt(btn.dataset.level, 10) : null;
|
|
284
|
+
|
|
285
|
+
if (action === "heading" && level) {
|
|
286
|
+
editor.chain().focus().toggleHeading({ level }).run();
|
|
287
|
+
} else if (action === "link") {
|
|
288
|
+
const url = window.prompt("URL:");
|
|
289
|
+
if (url) editor.chain().focus().setLink({ href: url }).run();
|
|
290
|
+
} else {
|
|
291
|
+
const chainMethod = actionToChain[action];
|
|
292
|
+
if (chainMethod && typeof editor.chain().focus()[chainMethod] === "function") {
|
|
293
|
+
editor.chain().focus()[chainMethod]().run();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
updateActiveStates();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
editor.on("selectionUpdate", updateActiveStates);
|
|
300
|
+
editor.on("transaction", updateActiveStates);
|
|
301
|
+
updateActiveStates();
|
|
302
|
+
})();
|
|
303
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Playbook
|
|
4
|
+
module PbRichTextEditor
|
|
5
|
+
# Rails rich text editor: TipTap (vanilla JS), no React. Content syncs to a hidden input for form submission.
|
|
6
|
+
class RichTextEditor < Playbook::KitBase
|
|
7
|
+
prop :value
|
|
8
|
+
prop :placeholder
|
|
9
|
+
prop :input_options, type: Playbook::Props::HashProp, default: {}
|
|
10
|
+
prop :label
|
|
11
|
+
prop :required_indicator, type: Playbook::Props::Boolean, default: false
|
|
12
|
+
|
|
13
|
+
def classname
|
|
14
|
+
generate_classname("pb_rich_text_editor_kit", "rte-container")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def input_id
|
|
18
|
+
input_options[:id].presence || (id.present? ? "#{id}-input" : "rich_text_editor-input")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def input_name
|
|
22
|
+
input_options[:name].presence || "content"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initial_html
|
|
26
|
+
raw = value.present? ? value.to_s.strip : ""
|
|
27
|
+
return "<p></p>" if raw.blank?
|
|
28
|
+
|
|
29
|
+
raw.start_with?("<") ? raw : "<p>#{raw}</p>"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def container_id
|
|
33
|
+
id.present? ? "rte-tiptap-#{id}" : "rte-tiptap-#{input_id.gsub(/[^a-z0-9_-]/i, '')}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def editor_node_id
|
|
37
|
+
"#{container_id}-editor"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def toolbar_id
|
|
41
|
+
"#{container_id}-toolbar"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -103,8 +103,8 @@ module Playbook
|
|
|
103
103
|
|
|
104
104
|
def data_attributes
|
|
105
105
|
data = attributes[:data] || {}
|
|
106
|
-
data
|
|
107
|
-
data
|
|
106
|
+
data["data-pb-select"] = true
|
|
107
|
+
data["data-validation-message"] = validation_message if validation_message.present?
|
|
108
108
|
data
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -516,18 +516,18 @@ const resolvedLoadOptions =
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
// Reset form submitted state when a selection is made (this is all for react rendered rails kit)
|
|
519
|
-
if (action === "select-option") {
|
|
519
|
+
if (action === "select-option" || action === "create-option") {
|
|
520
520
|
setFormSubmitted(false)
|
|
521
521
|
// Mark that user has made a selection to disable default value focus behavior
|
|
522
522
|
setHasUserSelected(true)
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
-
// If a value is selected and we're preserving input on blur, clear the input
|
|
526
|
-
if (action === "select-option" && preserveSearchInput) {
|
|
525
|
+
// If a value is selected/created and we're preserving input on blur, clear the input
|
|
526
|
+
if ((action === "select-option" || action === "create-option") && preserveSearchInput) {
|
|
527
527
|
setInputValue("")
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
-
if (action === "select-option") {
|
|
530
|
+
if (action === "select-option" || action === "create-option") {
|
|
531
531
|
if (selectProps.onMultiValueClick && option)
|
|
532
532
|
selectProps.onMultiValueClick(option)
|
|
533
533
|
const multiValueClearEvent = new CustomEvent(
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%
|
|
2
|
+
options = [
|
|
3
|
+
{ label: 'Orange', value: '#FFA500' },
|
|
4
|
+
{ label: 'Red', value: '#FF0000' },
|
|
5
|
+
{ label: 'Green', value: '#00FF00' },
|
|
6
|
+
{ label: 'Blue', value: '#0000FF' },
|
|
7
|
+
]
|
|
8
|
+
%>
|
|
9
|
+
|
|
10
|
+
<%= pb_rails("typeahead", props: {
|
|
11
|
+
id: "typeahead-creatable",
|
|
12
|
+
placeholder: "All Colors",
|
|
13
|
+
options: options,
|
|
14
|
+
label: "Colors",
|
|
15
|
+
name: :foo,
|
|
16
|
+
createable: true,
|
|
17
|
+
pills: true,
|
|
18
|
+
})
|
|
19
|
+
%>
|
|
20
|
+
|
|
21
|
+
<%= javascript_tag defer: "defer" do %>
|
|
22
|
+
document.addEventListener("pb-typeahead-kit-typeahead-creatable-result-option-select", function(event) {
|
|
23
|
+
console.log('Single Option selected')
|
|
24
|
+
console.dir(event.detail)
|
|
25
|
+
})
|
|
26
|
+
document.addEventListener("pb-typeahead-kit-typeahead-creatable-result-clear", function() {
|
|
27
|
+
console.log('All options cleared')
|
|
28
|
+
})
|
|
29
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
The `createable` prop allows users to create new options by typing a value that doesn't exist in the options list.
|
|
@@ -11,6 +11,7 @@ examples:
|
|
|
11
11
|
- typeahead_with_pills_async_users: With Pills (Async Data w/ Users)
|
|
12
12
|
- typeahead_inline: Inline
|
|
13
13
|
- typeahead_multi_kit: Multi Kit Options
|
|
14
|
+
- typeahead_createable: Createable
|
|
14
15
|
- typeahead_error_state: Error State
|
|
15
16
|
- typeahead_margin_bottom: Margin Bottom
|
|
16
17
|
- typeahead_with_pills_color: With Pills (Custom Color)
|
|
@@ -62,6 +62,8 @@ module Playbook
|
|
|
62
62
|
default: false
|
|
63
63
|
prop :required_indicator, type: Playbook::Props::Boolean,
|
|
64
64
|
default: false
|
|
65
|
+
prop :createable, type: Playbook::Props::Boolean,
|
|
66
|
+
default: false
|
|
65
67
|
def classname
|
|
66
68
|
default_margin_bottom = margin_bottom.present? ? "" : " mb_sm"
|
|
67
69
|
generate_classname("pb_typeahead_kit") + default_margin_bottom
|
|
@@ -83,7 +85,7 @@ module Playbook
|
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
def is_react?
|
|
86
|
-
pills || !is_multi || wrapped || input_display == "none"
|
|
88
|
+
pills || !is_multi || wrapped || input_display == "none" || createable
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def typeahead_react_options
|
|
@@ -115,6 +117,7 @@ module Playbook
|
|
|
115
117
|
clearOnContextChange: clear_on_context_change,
|
|
116
118
|
disabled: disabled,
|
|
117
119
|
preserveSearchInput: preserve_search_input,
|
|
120
|
+
createable: createable,
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
base_options[:getOptionLabel] = get_option_label if get_option_label.present?
|