playbook_ui 16.7.0.pre.rc.0 → 16.7.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_grouped_headers_composition.jsx +15 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_infinite_scroll.jsx +2 -2
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_inline_row_loading.jsx +2 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows.jsx +2 -2
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_mock_data_inline_loading_empty_children.json +41 -0
  7. data/app/pb_kits/playbook/pb_bread_crumbs/docs/_bread_crumbs_default.jsx +5 -5
  8. data/app/pb_kits/playbook/pb_button/_button.scss +1 -2
  9. data/app/pb_kits/playbook/pb_dialog/index.js +6 -1
  10. data/app/pb_kits/playbook/pb_draggable/index.js +6 -1
  11. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_external_control.jsx +22 -1
  12. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_page_change.jsx +22 -2
  13. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +68 -0
  14. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_show_placeholder.html.erb +5 -0
  15. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_show_placeholder.jsx +14 -0
  16. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_show_placeholder.md +3 -0
  17. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
  18. data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
  19. data/app/pb_kits/playbook/pb_phone_number_input/kit.schema.json +8 -0
  20. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
  21. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +79 -2
  22. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_templates.jsx +76 -1
  23. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_react_hook.jsx +2 -2
  24. data/dist/chunks/{_typeahead-DO5eU-VL.js → _typeahead-Cl5cZ7Hz.js} +1 -1
  25. data/dist/chunks/vendor.js +1 -1
  26. data/dist/playbook-rails-react-bindings.js +1 -1
  27. data/dist/playbook-rails.js +1 -1
  28. data/dist/playbook.css +1 -1
  29. data/lib/playbook/version.rb +1 -1
  30. metadata +7 -8
  31. data/app/pb_kits/playbook/pb_advanced_table/docs/_mock_data_inline_loading.js +0 -200
  32. data/app/pb_kits/playbook/pb_advanced_table/docs/_mock_data_inline_loading_empty_children.js +0 -42
  33. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_grouped_headers_composition_mock_data.json +0 -98
  34. data/app/pb_kits/playbook/pb_pagination/docs/data.js +0 -23
  35. data/app/pb_kits/playbook/pb_rich_text_editor/docs/templates.js +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1df511d9cea220056ca66936b64ebebb36c51334bcfaa7fc8f3a77d97625b05d
4
- data.tar.gz: a34dd71d6896b5cd3d2c2c46dcece4c046072b584d7879b6ee8ddc48378eaf1d
3
+ metadata.gz: 84a662254cd2533c99982e9c3d92cf898c52aa151e267258ea4a8e5007856b44
4
+ data.tar.gz: ff974535354bd21c7aef9810593b5898968080539f9d7fc673acacffcb96aad0
5
5
  SHA512:
6
- metadata.gz: f8cd336c91eb7498940880a19bafc7d49b16c36e4a777e547dc26dcb59e9d6b9bf527ef1763563a313fe7f4b2801b447d7f2b0b540baf830a6fa62589d34d034
7
- data.tar.gz: 4f9dcad612095338451775807e0dda91a34f834f5d7153dfa08d016b56cbe53fbd24114f39a18eb19b449cc49530f053d252e75b774f5ab2cf417664a424e125
6
+ metadata.gz: 5bba8f8aa8b742c61dcfdf739552e920fea3c73afb647c904891737a38240c13a598c8a6a3036f73d1d85db56555b3db56bc2b418dbd0cdce2ec5067fb27d25a
7
+ data.tar.gz: 2716f834102e665c6975a1a80db09c6ae84a1b558a74027903f5ce7602f1e5ab2d0f1e6fb481c37199fe6cf6804cb9b79d01de8cd867c8742e071c04bfddb690
@@ -9,7 +9,21 @@ import ListItem from "../../pb_list/_list_item"
9
9
  import PbReactPopover from "../../pb_popover/_popover"
10
10
  import SectionSeparator from "../../pb_section_separator/_section_separator"
11
11
  import StarRating from "../../pb_star_rating/_star_rating"
12
- import COMPOSITION_MOCK_DATA from "./advanced_table_grouped_headers_composition_mock_data.json"
12
+
13
+ const COMPOSITION_MOCK_DATA = [
14
+ { id: "1", year: "2015", newEnrollments: "12", scheduledMeetings: "40", attendanceRate: "62%", classCompletionRate: "28%" },
15
+ { id: "2", year: "2016", newEnrollments: "88", scheduledMeetings: "12", attendanceRate: "71%", classCompletionRate: "55%" },
16
+ { id: "3", year: "2017", newEnrollments: "34", scheduledMeetings: "67", attendanceRate: "58%", classCompletionRate: "41%" },
17
+ { id: "4", year: "2018", newEnrollments: "05", scheduledMeetings: "91", attendanceRate: "44%", classCompletionRate: "73%" },
18
+ { id: "5", year: "2019", newEnrollments: "61", scheduledMeetings: "19", attendanceRate: "83%", classCompletionRate: "36%" },
19
+ { id: "6", year: "2020", newEnrollments: "19", scheduledMeetings: "54", attendanceRate: "67%", classCompletionRate: "62%" },
20
+ { id: "7", year: "2021", newEnrollments: "73", scheduledMeetings: "08", attendanceRate: "52%", classCompletionRate: "49%" },
21
+ { id: "8", year: "2022", newEnrollments: "50", scheduledMeetings: "50", attendanceRate: "75%", classCompletionRate: "45%" },
22
+ { id: "9", year: "2023", newEnrollments: "95", scheduledMeetings: "03", attendanceRate: "69%", classCompletionRate: "81%" },
23
+ { id: "10", year: "2024", newEnrollments: "27", scheduledMeetings: "76", attendanceRate: "91%", classCompletionRate: "22%" },
24
+ { id: "11", year: "2025", newEnrollments: "41", scheduledMeetings: "33", attendanceRate: "48%", classCompletionRate: "94%" },
25
+ { id: "12", year: "2026", newEnrollments: "66", scheduledMeetings: "66", attendanceRate: "55%", classCompletionRate: "58%" },
26
+ ]
13
27
 
14
28
  const LEAF_COUNT = "newEnrollments"
15
29
  const LEAF_SCHEDULED = "scheduledMeetings"
@@ -1,6 +1,6 @@
1
1
  import React from "react"
2
2
  import AdvancedTable from '../_advanced_table'
3
- import MOCK_DATA from "./advanced_table_mock_data_infinite_scroll.json"
3
+ import INFINITE_SCROLL_MOCK_DATA from "./advanced_table_mock_data_infinite_scroll.json"
4
4
 
5
5
  const AdvancedTableInfiniteScroll = (props) => {
6
6
  const columnDefinitions = [
@@ -39,7 +39,7 @@ const AdvancedTableInfiniteScroll = (props) => {
39
39
  <div>
40
40
  <AdvancedTable
41
41
  columnDefinitions={columnDefinitions}
42
- tableData={MOCK_DATA}
42
+ tableData={INFINITE_SCROLL_MOCK_DATA}
43
43
  virtualizedRows
44
44
  {...props}
45
45
  />
@@ -1,8 +1,8 @@
1
1
  import React from "react"
2
2
  import AdvancedTable from '../../pb_advanced_table/_advanced_table'
3
3
  import Caption from '../../pb_caption/_caption'
4
- import { MOCK_DATA_INLINE_LOADING } from "./_mock_data_inline_loading"
5
- import { MOCK_DATA_INLINE_LOADING_EMPTY_CHILDREN } from "./_mock_data_inline_loading_empty_children"
4
+ import MOCK_DATA_INLINE_LOADING from "./advanced_table_mock_data_inline_loading.json"
5
+ import MOCK_DATA_INLINE_LOADING_EMPTY_CHILDREN from "./advanced_table_mock_data_inline_loading_empty_children.json"
6
6
 
7
7
  const AdvancedTableInlineRowLoading = (props) => {
8
8
  const columnDefinitions = [
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from "react"
2
2
  import AdvancedTable from '../_advanced_table'
3
- import MOCK_DATA from "./advanced_table_mock_data_with_id.json"
3
+ import MOCK_DATA_WITH_ID from "./advanced_table_mock_data_with_id.json"
4
4
 
5
5
  const AdvancedTableRowPinning = (props) => {
6
6
  const columnDefinitions = [
@@ -43,7 +43,7 @@ const AdvancedTableRowPinning = (props) => {
43
43
  columnDefinitions={columnDefinitions}
44
44
  maxHeight="xs"
45
45
  pinnedRows={{value: pinnedRows, onChange: setPinnedRows}}
46
- tableData={MOCK_DATA}
46
+ tableData={MOCK_DATA_WITH_ID}
47
47
  tableProps={{sticky: true}}
48
48
  {...props}
49
49
  >
@@ -0,0 +1,41 @@
1
+ [
2
+ {
3
+ "year": "2021",
4
+ "quarter": null,
5
+ "month": null,
6
+ "day": null,
7
+ "newEnrollments": "20",
8
+ "scheduledMeetings": "10",
9
+ "attendanceRate": "51%",
10
+ "completedClasses": "3",
11
+ "classCompletionRate": "33%",
12
+ "graduatedStudents": "19",
13
+ "children": []
14
+ },
15
+ {
16
+ "year": "2022",
17
+ "quarter": null,
18
+ "month": null,
19
+ "day": null,
20
+ "newEnrollments": "25",
21
+ "scheduledMeetings": "17",
22
+ "attendanceRate": "75%",
23
+ "completedClasses": "5",
24
+ "classCompletionRate": "45%",
25
+ "graduatedStudents": "32",
26
+ "children": []
27
+ },
28
+ {
29
+ "year": "2023",
30
+ "quarter": null,
31
+ "month": null,
32
+ "day": null,
33
+ "newEnrollments": "10",
34
+ "scheduledMeetings": "15",
35
+ "attendanceRate": "65%",
36
+ "completedClasses": "4",
37
+ "classCompletionRate": "49%",
38
+ "graduatedStudents": "29",
39
+ "children": []
40
+ }
41
+ ]
@@ -4,7 +4,7 @@ import Icon from "../../pb_icon/_icon"
4
4
  import Title from "../../pb_title/_title"
5
5
  import BreadCrumbItem from '../_bread_crumb_item'
6
6
 
7
- const Link = (props) => <BreadCrumbItem {...props} />
7
+ const LinkSection = (props) => <BreadCrumbItem {...props} />
8
8
  const BreadCrumbsDefault = (props) => {
9
9
  return (
10
10
  <BreadCrumbs
@@ -35,7 +35,7 @@ const BreadCrumbsDefault = (props) => {
35
35
  size="1x"
36
36
  {...props}
37
37
  />
38
- <Link
38
+ <LinkSection
39
39
  {...props}
40
40
  href="/users"
41
41
  >
@@ -46,20 +46,20 @@ const BreadCrumbsDefault = (props) => {
46
46
  text="Users"
47
47
  {...props}
48
48
  />
49
- </Link>
49
+ </LinkSection>
50
50
  <Icon
51
51
  icon="user"
52
52
  size="1x"
53
53
  {...props}
54
54
  />
55
- <Link {...props}>
55
+ <LinkSection {...props}>
56
56
  <Title
57
57
  size="4"
58
58
  tag="span"
59
59
  text="User"
60
60
  {...props}
61
61
  />
62
- </Link>
62
+ </LinkSection>
63
63
  </BreadCrumbs>
64
64
  )
65
65
  }
@@ -124,8 +124,7 @@ $pb_button_icon_only_dimensions: (
124
124
  // Rails: .pb_button_icon_only; React: class or :has(...) when label is empty.
125
125
  // Rails uses a truly empty .pb_button_content; React wraps text in a child span (may be empty).
126
126
  &.pb_button_icon_only,
127
- &:has(.pb_button_content:empty),
128
- &:has(.pb_button_content > span:empty) {
127
+ &:has(.pb_button_content:empty) {
129
128
  aspect-ratio: 1;
130
129
  box-sizing: border-box;
131
130
  $pb_button_icon_only_default: map-get($pb_button_icon_only_dimensions, "md");
@@ -16,7 +16,12 @@ export default class PbDialog extends PbEnhancedElement {
16
16
  this.domContentLoadedHandler = () => this.setupDialog()
17
17
  this.turboFrameLoadHandler = () => this.setupDialog()
18
18
 
19
- window.addEventListener("DOMContentLoaded", this.domContentLoadedHandler)
19
+ // If DOM is already loaded, setup immediately; otherwise wait for DOMContentLoaded
20
+ if (document.readyState === "loading") {
21
+ window.addEventListener("DOMContentLoaded", this.domContentLoadedHandler)
22
+ } else {
23
+ this.setupDialog()
24
+ }
20
25
  window.addEventListener("turbo:frame-load", this.turboFrameLoadHandler)
21
26
 
22
27
  // Code for custom_event_type setup (can take multiple events in a string separated by commas)
@@ -22,7 +22,12 @@ export default class PbDraggable extends PbEnhancedElement {
22
22
  this.dragZoneType = "";
23
23
  this.dragZoneColor = "";
24
24
 
25
- document.addEventListener("DOMContentLoaded", () => this.bindEventListeners());
25
+ // If DOM is already loaded, bind immediately; otherwise wait for DOMContentLoaded
26
+ if (document.readyState === "loading") {
27
+ document.addEventListener("DOMContentLoaded", () => this.bindEventListeners());
28
+ } else {
29
+ this.bindEventListeners();
30
+ }
26
31
  }
27
32
 
28
33
  setState(newState) {
@@ -4,7 +4,28 @@ import Pagination from '../../pb_pagination/_pagination'
4
4
  import Select from '../../pb_select/_select'
5
5
  import Table from '../../pb_table/_table'
6
6
 
7
- import { data } from "./data";
7
+ const data = [
8
+ ["Value 1", "Value 2", "Value 3", "Value 4", "Value 5"],
9
+ ["Value 6", "Value 7", "Value 8", "Value 9", "Value 10"],
10
+ ["Value 11", "Value 12", "Value 13", "Value 14", "Value 15"],
11
+ ["Value 16", "Value 17", "Value 18", "Value 19", "Value 20"],
12
+ ["Value 21", "Value 22", "Value 23", "Value 24", "Value 25"],
13
+ ["Value 26", "Value 27", "Value 28", "Value 29", "Value 30"],
14
+ ["Value 31", "Value 32", "Value 33", "Value 34", "Value 35"],
15
+ ["Value 36", "Value 37", "Value 38", "Value 39", "Value 40"],
16
+ ["Value 41", "Value 42", "Value 43", "Value 44", "Value 45"],
17
+ ["Value 46", "Value 47", "Value 48", "Value 49", "Value 50"],
18
+ ["Value 51", "Value 52", "Value 53", "Value 54", "Value 55"],
19
+ ["Value 56", "Value 57", "Value 58", "Value 59", "Value 60"],
20
+ ["Value 61", "Value 62", "Value 63", "Value 64", "Value 65"],
21
+ ["Value 66", "Value 67", "Value 68", "Value 69", "Value 70"],
22
+ ["Value 71", "Value 72", "Value 73", "Value 74", "Value 75"],
23
+ ["Value 76", "Value 77", "Value 78", "Value 79", "Value 80"],
24
+ ["Value 81", "Value 82", "Value 83", "Value 84", "Value 85"],
25
+ ["Value 86", "Value 87", "Value 88", "Value 89", "Value 90"],
26
+ ["Value 91", "Value 92", "Value 93", "Value 94", "Value 95"],
27
+ ["Value 96", "Value 97", "Value 98", "Value 99", "Value 100"],
28
+ ];
8
29
 
9
30
  const PaginationExternalControl = (props) => {
10
31
  const [totalItems, setTotalItems] = useState(20);
@@ -2,8 +2,28 @@ import React, { useState } from "react";
2
2
  import Table from '../../pb_table/_table'
3
3
  import Pagination from '../../pb_pagination/_pagination'
4
4
 
5
-
6
- import { data } from "./data";
5
+ const data = [
6
+ ["Value 1", "Value 2", "Value 3", "Value 4", "Value 5"],
7
+ ["Value 6", "Value 7", "Value 8", "Value 9", "Value 10"],
8
+ ["Value 11", "Value 12", "Value 13", "Value 14", "Value 15"],
9
+ ["Value 16", "Value 17", "Value 18", "Value 19", "Value 20"],
10
+ ["Value 21", "Value 22", "Value 23", "Value 24", "Value 25"],
11
+ ["Value 26", "Value 27", "Value 28", "Value 29", "Value 30"],
12
+ ["Value 31", "Value 32", "Value 33", "Value 34", "Value 35"],
13
+ ["Value 36", "Value 37", "Value 38", "Value 39", "Value 40"],
14
+ ["Value 41", "Value 42", "Value 43", "Value 44", "Value 45"],
15
+ ["Value 46", "Value 47", "Value 48", "Value 49", "Value 50"],
16
+ ["Value 51", "Value 52", "Value 53", "Value 54", "Value 55"],
17
+ ["Value 56", "Value 57", "Value 58", "Value 59", "Value 60"],
18
+ ["Value 61", "Value 62", "Value 63", "Value 64", "Value 65"],
19
+ ["Value 66", "Value 67", "Value 68", "Value 69", "Value 70"],
20
+ ["Value 71", "Value 72", "Value 73", "Value 74", "Value 75"],
21
+ ["Value 76", "Value 77", "Value 78", "Value 79", "Value 80"],
22
+ ["Value 81", "Value 82", "Value 83", "Value 84", "Value 85"],
23
+ ["Value 86", "Value 87", "Value 88", "Value 89", "Value 90"],
24
+ ["Value 91", "Value 92", "Value 93", "Value 94", "Value 95"],
25
+ ["Value 96", "Value 97", "Value 98", "Value 99", "Value 100"],
26
+ ];
7
27
 
8
28
  const PaginationPageChange = (props) => {
9
29
 
@@ -41,6 +41,7 @@ type PhoneNumberInputProps = {
41
41
  formatAsYouType?: boolean,
42
42
  strictMode?: boolean,
43
43
  countrySearch?: boolean,
44
+ showPlaceholder?: boolean,
44
45
  }
45
46
 
46
47
  enum ValidationError {
@@ -98,8 +99,14 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
98
99
  formatAsYouType = false,
99
100
  strictMode = false,
100
101
  countrySearch = false,
102
+ showPlaceholder = false,
101
103
  } = props
102
104
 
105
+ const showPlaceholderRef = useRef(showPlaceholder)
106
+ showPlaceholderRef.current = showPlaceholder
107
+
108
+ const placeholderTemplateRef = useRef<string | null>(null)
109
+
103
110
  const ariaProps = buildAriaProps(aria)
104
111
  const dataProps = buildDataProps(data)
105
112
  const htmlProps = buildHtmlProps(htmlOptions)
@@ -186,6 +193,37 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
186
193
  return formattedNumber.replace(/\D/g, "")
187
194
  }
188
195
 
196
+ const readInputPlaceholder = (element: HTMLInputElement) => {
197
+ return element.getAttribute("placeholder") || element.placeholder || ""
198
+ }
199
+
200
+ const cachePlaceholderTemplate = (element: HTMLInputElement) => {
201
+ const placeholder = readInputPlaceholder(element)
202
+ if (placeholder) {
203
+ placeholderTemplateRef.current = placeholder
204
+ }
205
+ }
206
+
207
+ const hidePlaceholderIfFocusedAndEmpty = (element: HTMLInputElement) => {
208
+ if (document.activeElement === element && !element.value) {
209
+ element.setAttribute("placeholder", "")
210
+ return true
211
+ }
212
+ return false
213
+ }
214
+
215
+ const hidePlaceholderIfEmpty = (element: HTMLInputElement) => {
216
+ if (!element.value) {
217
+ element.setAttribute("placeholder", "")
218
+ }
219
+ }
220
+
221
+ const restorePlaceholderIfEmpty = (element: HTMLInputElement) => {
222
+ if (!element.value && placeholderTemplateRef.current) {
223
+ element.setAttribute("placeholder", placeholderTemplateRef.current)
224
+ }
225
+ }
226
+
189
227
  const showFormattedError = (reason = '') => {
190
228
  const countryName = itiRef.current.getSelectedCountryData().name
191
229
  const reasonText = reason.length > 0 ? ` (${reason})` : ''
@@ -476,6 +514,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
476
514
  countryOrder: preferredCountries,
477
515
  allowDropdown: !disabled,
478
516
  autoInsertDialCode: false,
517
+ autoPlaceholder: showPlaceholderRef.current ? "polite" : "off",
479
518
  initialCountry: initialCountry || fallbackCountry,
480
519
  onlyCountries,
481
520
  excludeCountries,
@@ -497,6 +536,22 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
497
536
  setSelectedData(phoneNumberData)
498
537
  onChange(phoneNumberData)
499
538
  validateErrors()
539
+
540
+ if (showPlaceholderRef.current) {
541
+ const syncPlaceholderState = () => {
542
+ const el = inputRef.current
543
+ if (!el) return
544
+
545
+ cachePlaceholderTemplate(el)
546
+ if (hidePlaceholderIfFocusedAndEmpty(el)) return
547
+ restorePlaceholderIfEmpty(el)
548
+ }
549
+
550
+ // Run immediately, then once on the next tick in case intl-tel-input
551
+ // updates the placeholder asynchronously after countrychange.
552
+ syncPlaceholderState()
553
+ setTimeout(syncPlaceholderState, 0)
554
+ }
500
555
  })
501
556
 
502
557
  inputRef.current.addEventListener("open:countrydropdown", () => setDropDownIsOpen(true))
@@ -538,7 +593,20 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
538
593
  id,
539
594
  label,
540
595
  name,
596
+ onFocus: () => {
597
+ if (!showPlaceholder) return
598
+ const el = inputRef.current
599
+ if (!el || el.value) return
600
+ cachePlaceholderTemplate(el)
601
+ hidePlaceholderIfEmpty(el)
602
+ },
541
603
  onBlur: () => {
604
+ if (showPlaceholder) {
605
+ const el = inputRef.current
606
+ if (el && !el.value) {
607
+ restorePlaceholderIfEmpty(el)
608
+ }
609
+ }
542
610
  hasBlurredRef.current = true
543
611
  setHasBlurred(true)
544
612
  validateErrors()
@@ -0,0 +1,5 @@
1
+ <%= pb_rails("phone_number_input", props: {
2
+ id: "phone_number_input_show_placeholder",
3
+ label: "Phone Number",
4
+ show_placeholder: true,
5
+ }) %>
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+ import PhoneNumberInput from '../../pb_phone_number_input/_phone_number_input'
3
+
4
+ const PhoneNumberInputShowPlaceholder = (props) => (
5
+ <>
6
+ <PhoneNumberInput
7
+ id='phone_number_input_show_placeholder'
8
+ label='Phone Number'
9
+ showPlaceholder
10
+ {...props} />
11
+ </>
12
+ )
13
+
14
+ export default PhoneNumberInputShowPlaceholder
@@ -0,0 +1,3 @@
1
+ The `showPlaceholder`/`show_placeholder` prop enables the country-specific example placeholder in the phone input.
2
+
3
+ By default, the Phone Number Input does not show a placeholder. When enabled, the placeholder appears while the field is empty and unfocused, hides on focus, and returns on blur if no value is entered.
@@ -2,6 +2,7 @@ examples:
2
2
 
3
3
  react:
4
4
  - phone_number_input_default: Default
5
+ - phone_number_input_show_placeholder: Show Placeholder
5
6
  - phone_number_input_preferred_countries: Preferred Countries
6
7
  - phone_number_input_initial_country: Initial Country
7
8
  - phone_number_input_only_countries: Only Countries
@@ -16,6 +17,7 @@ examples:
16
17
 
17
18
  rails:
18
19
  - phone_number_input_default: Default
20
+ - phone_number_input_show_placeholder: Show Placeholder
19
21
  - phone_number_input_preferred_countries: Preferred Countries
20
22
  - phone_number_input_initial_country: Initial Country
21
23
  - phone_number_input_only_countries: Only Countries
@@ -10,3 +10,4 @@ export { default as PhoneNumberInputFormat } from './_phone_number_input_format'
10
10
  export { default as PhoneNumberInputStrictMode } from './_phone_number_input_strict_mode'
11
11
  export { default as PhoneNumberInputCountrySearch } from './_phone_number_input_country_search'
12
12
  export { default as PhoneNumberInputRequiredIndicator } from './_phone_number_input_required_indicator.jsx'
13
+ export { default as PhoneNumberInputShowPlaceholder } from './_phone_number_input_show_placeholder'
@@ -147,6 +147,14 @@
147
147
  "rails"
148
148
  ],
149
149
  "default": false
150
+ },
151
+ "showPlaceholder": {
152
+ "type": "boolean",
153
+ "platforms": [
154
+ "react",
155
+ "rails"
156
+ ],
157
+ "default": false
150
158
  }
151
159
  },
152
160
  "globalProps": true,
@@ -35,6 +35,8 @@ module Playbook
35
35
  default: false
36
36
  prop :country_search, type: Playbook::Props::Boolean,
37
37
  default: false
38
+ prop :show_placeholder, type: Playbook::Props::Boolean,
39
+ default: false
38
40
 
39
41
  def classname
40
42
  generate_classname("pb_phone_number_input")
@@ -59,6 +61,7 @@ module Playbook
59
61
  requiredIndicator: required_indicator,
60
62
  value: value,
61
63
  countrySearch: country_search,
64
+ showPlaceholder: show_placeholder,
62
65
  }
63
66
  end
64
67
  end
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { render, screen, act, within } from "../utilities/test-utils";
2
+ import { render, screen, act, within, waitFor, fireEvent } from "../utilities/test-utils";
3
3
  import PhoneNumberInput from "./_phone_number_input";
4
4
 
5
5
  const testId = "phoneNumberInput";
@@ -188,4 +188,81 @@ test("does not render required indicator asterisk when requiredIndicator is fals
188
188
  const label = within(kit).getByText(/Phone Number/);
189
189
  expect(label).toBeInTheDocument();
190
190
  expect(kit).not.toHaveTextContent("*");
191
- });
191
+ });
192
+
193
+ test("has no intl-tel example placeholder by default (showPlaceholder false)", async () => {
194
+ const props = {
195
+ id: testId,
196
+ initialCountry: "us",
197
+ };
198
+ render(<PhoneNumberInput {...props} />);
199
+ const input = screen.getByRole("textbox");
200
+ await waitFor(() => {
201
+ expect(input.closest(".iti")).toBeTruthy();
202
+ });
203
+ expect(!input.getAttribute("placeholder") || input.getAttribute("placeholder") === "").toBe(true);
204
+ });
205
+
206
+ test("optionally shows example placeholder when showPlaceholder is true; hides on focus and returns on blur if empty", async () => {
207
+ const props = {
208
+ id: testId,
209
+ initialCountry: "us",
210
+ showPlaceholder: true,
211
+ };
212
+ render(<PhoneNumberInput {...props} />);
213
+ const input = screen.getByRole("textbox");
214
+
215
+ await waitFor(() => {
216
+ expect(input.closest(".iti")).toBeTruthy();
217
+ });
218
+ await waitFor(() => {
219
+ expect((input.getAttribute("placeholder") || "").length).toBeGreaterThan(0);
220
+ });
221
+
222
+ const whenIdle = input.getAttribute("placeholder");
223
+
224
+ act(() => {
225
+ fireEvent.focus(input);
226
+ });
227
+ expect(input.getAttribute("placeholder") || "").toBe("");
228
+
229
+ act(() => {
230
+ fireEvent.blur(input);
231
+ });
232
+ expect(input.getAttribute("placeholder")).toBe(whenIdle);
233
+ });
234
+
235
+ test("restores latest placeholder on blur after country change", async () => {
236
+ const props = {
237
+ id: testId,
238
+ initialCountry: "us",
239
+ showPlaceholder: true,
240
+ };
241
+
242
+ render(<PhoneNumberInput {...props} />);
243
+ const input = screen.getByRole("textbox");
244
+
245
+ await waitFor(() => {
246
+ expect(input.closest(".iti")).toBeTruthy();
247
+ });
248
+
249
+ // Simulate focus behavior
250
+ act(() => {
251
+ fireEvent.focus(input);
252
+ });
253
+ expect(input.getAttribute("placeholder") || "").toBe("");
254
+
255
+ // Simulate library updating placeholder due to country change while focused.
256
+ input.setAttribute("placeholder", "+93 123 456 7890");
257
+ act(() => {
258
+ input.dispatchEvent(new Event("countrychange", { bubbles: true }));
259
+ });
260
+
261
+ // Blur should restore the latest country placeholder.
262
+ act(() => {
263
+ fireEvent.blur(input);
264
+ });
265
+ await waitFor(() => {
266
+ expect(input.getAttribute("placeholder")).toBe("+93 123 456 7890");
267
+ });
268
+ });
@@ -5,7 +5,82 @@ import StarterKit from "@tiptap/starter-kit"
5
5
  import Link from '@tiptap/extension-link'
6
6
 
7
7
  import Select from '../../pb_select/_select'
8
- import { changelog, release } from './templates.js'
8
+
9
+ const release = `
10
+ <div>
11
+ <div>
12
+ <strong>Story Background</strong>
13
+ </div>
14
+ <div>
15
+ Follow the{" "}
16
+ <a href='https://github.com/powerhome/playbook/wiki/Release-Team-Guide'>
17
+ release process
18
+ </a>{" "}
19
+ to create a new version, create a gem, and package. Create a Ninja testing
20
+ plan, then update Nitro with the new version.
21
+ </div>
22
+ <div>
23
+ <br />
24
+ </div>
25
+ <div>
26
+ <strong>Timeline / Due Date</strong>
27
+ </div>
28
+ <div>
29
+ <em>Release End of business Thursday</em>
30
+ </div>
31
+ <div>
32
+ <em>Testing on Nitro End of business Friday</em>
33
+ </div>
34
+ <div>
35
+ <br />
36
+ </div>
37
+ <div>
38
+ <strong>Definition of done</strong>
39
+ </div>
40
+ <ol>
41
+ <li>Merge all PR’s</li>
42
+ <li>Update the final CHANGELOG</li>
43
+ <li>Version up and generate NPM, and RubyGem</li>
44
+ <li>Create next version branch and milestone</li>
45
+ <li>Update default branch and branch protection rules&nbsp;</li>
46
+ <li>Notify Everyone of new version</li>
47
+ <li>
48
+ Generate testing plan and pages to test for Ninjas (update runway
49
+ ticket)
50
+ </li>
51
+ <li>Update version on Nitro and get on Demo</li>
52
+ <li>Send Ninjas demo and runway ticket for testing</li>
53
+ <li>Ninja Approved + PR Approved</li>
54
+ </ol>
55
+ <div>
56
+ <br />
57
+ </div>
58
+ <div>
59
+ <strong>Stakeholders / Sign-off</strong>
60
+ </div>
61
+ <ul>
62
+ <li>Code Owners</li>
63
+ </ul>
64
+ <div>
65
+ <br />
66
+ <strong>Cadence</strong>
67
+ <br />
68
+ Jason, Jon, Stephen, Jasper, Brendan, Cole
69
+ </div>
70
+ </div>
71
+ `
72
+
73
+ const changelog = `
74
+ <div>
75
+ <strong>Changelog:<br></strong>
76
+ [INSERT LINK]<br><br>
77
+ You can test the normal spots of Playbook rails and react on
78
+ dev docs plus the following:
79
+ </div>
80
+ <div>
81
+ <br>
82
+ </div>
83
+ `
9
84
 
10
85
  const RichTextEditorAdvancedTemplates = (props) => {
11
86
 
@@ -17,7 +17,7 @@ const languages = [
17
17
  { label: 'PHP', value: '1995', category: 'Web Development' },
18
18
  ]
19
19
 
20
- const colors = [
20
+ const colorOptions = [
21
21
  { label: 'Orange', value: '#FFA500' },
22
22
  { label: 'Red', value: '#FF0000' },
23
23
  { label: 'Green', value: '#00FF00' },
@@ -51,7 +51,7 @@ const TypeaheadReactHook = (props) => {
51
51
  <Typeahead
52
52
  label="Colors"
53
53
  marginTop="lg"
54
- options={colors}
54
+ options={colorOptions}
55
55
  {...props}
56
56
  {...register('color')}
57
57
  />