playbook_ui 16.4.0.pre.alpha.PLAY2718containerfalseborderradiusresponsiveonly15164 → 16.4.0.pre.alpha.PLAY2839singlefilterresultscomma15414

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props.html.erb +1 -31
  3. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props.jsx +0 -93
  4. data/app/pb_kits/playbook/pb_button/docs/_button_full_width_rails.md +19 -0
  5. data/app/pb_kits/playbook/pb_button/docs/_button_full_width_react.md +23 -0
  6. data/app/pb_kits/playbook/pb_circle_icon_button/_circle_icon_button.scss +5 -0
  7. data/app/pb_kits/playbook/pb_filter/Filter/ResultsCount.tsx +3 -1
  8. data/app/pb_kits/playbook/pb_filter/filter.rb +1 -1
  9. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +3 -1
  10. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.html.erb +109 -0
  11. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.jsx +127 -0
  12. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.md +1 -0
  13. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -0
  14. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
  15. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +3 -0
  16. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +27 -0
  17. data/app/pb_kits/playbook/pb_table/docs/_sections.yml +1 -0
  18. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_external_filter_rails.html.erb +45 -0
  19. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_external_filter_rails.md +39 -0
  20. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.md +2 -1
  21. data/app/pb_kits/playbook/pb_table/docs/example.yml +1 -0
  22. data/app/pb_kits/playbook/pb_table/table.html.erb +5 -2
  23. data/app/pb_kits/playbook/pb_table/table.rb +4 -0
  24. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.jsx +20 -8
  25. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.md +3 -0
  26. data/app/pb_kits/playbook/utilities/_hover.scss +6 -3
  27. data/dist/chunks/_typeahead-BNp_YiTh.js +1 -0
  28. data/dist/chunks/vendor.js +1 -1
  29. data/dist/playbook-rails-react-bindings.js +1 -1
  30. data/dist/playbook-rails.js +1 -1
  31. data/dist/playbook.css +1 -1
  32. data/lib/playbook/align_content.rb +17 -28
  33. data/lib/playbook/align_items.rb +17 -28
  34. data/lib/playbook/align_self.rb +17 -28
  35. data/lib/playbook/border_radius.rb +7 -10
  36. data/lib/playbook/bottom.rb +8 -12
  37. data/lib/playbook/classnames.rb +65 -49
  38. data/lib/playbook/cursor.rb +7 -10
  39. data/lib/playbook/display.rb +16 -22
  40. data/lib/playbook/flex.rb +17 -28
  41. data/lib/playbook/flex_direction.rb +17 -28
  42. data/lib/playbook/flex_grow.rb +17 -28
  43. data/lib/playbook/flex_shrink.rb +17 -28
  44. data/lib/playbook/flex_wrap.rb +17 -28
  45. data/lib/playbook/height.rb +7 -10
  46. data/lib/playbook/hover.rb +31 -27
  47. data/lib/playbook/justify_content.rb +17 -28
  48. data/lib/playbook/justify_self.rb +17 -28
  49. data/lib/playbook/kit_base.rb +33 -16
  50. data/lib/playbook/left.rb +8 -12
  51. data/lib/playbook/line_height.rb +7 -10
  52. data/lib/playbook/max_height.rb +7 -10
  53. data/lib/playbook/min_height.rb +7 -10
  54. data/lib/playbook/number_spacing.rb +7 -10
  55. data/lib/playbook/order.rb +17 -28
  56. data/lib/playbook/overflow.rb +13 -12
  57. data/lib/playbook/position.rb +7 -12
  58. data/lib/playbook/props.rb +24 -5
  59. data/lib/playbook/right.rb +8 -12
  60. data/lib/playbook/shadow.rb +7 -10
  61. data/lib/playbook/spacing.rb +100 -128
  62. data/lib/playbook/text_align.rb +17 -28
  63. data/lib/playbook/top.rb +8 -12
  64. data/lib/playbook/truncate.rb +7 -10
  65. data/lib/playbook/version.rb +1 -1
  66. data/lib/playbook/vertical_align.rb +17 -28
  67. data/lib/playbook/z_index.rb +17 -26
  68. metadata +11 -4
  69. data/app/pb_kits/playbook/pb_button/docs/_button_full_width.md +0 -1
  70. data/dist/chunks/_typeahead-Bh0RF1X-.js +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: deb30921ab6e5e0a134bf71decaef200f0a7e9393ab5ae6a926f6267604f80b9
4
- data.tar.gz: b5ea71e20d11bffda695d920587c509123a5f31f59c7833a784a6d380a5112f7
3
+ metadata.gz: 115aff089c8f7a57d0223d7ba79a99a9d3bfafdd06c96f3947ef3dac58a81859
4
+ data.tar.gz: 789a51c4ad1191ef16dfd8a6c75c93c010f234796618981289812473b544091b
5
5
  SHA512:
6
- metadata.gz: 9eebe12c7de1c4073455e4bb2aea103157aa92c7a27f75bb11d75bd85914b414076700d3468c062e80dea2966cc62c68779398c44eb64ff791ff399904ab05c1
7
- data.tar.gz: c3051d4e9ef1e226b78c3d486fec6cb2c972c2a0e076d4d68a8e1a74246b36e6496e90e64d0e554e6ad1ce681c705abcb41b644ca0bf0dc6312a3511c3f8d9a8
6
+ metadata.gz: 6c1ee782cb55a7acf867b302c861e69d90ac74941fd21ab0122d983cb12905d737e59272e7ce282e191b103a911209f8525a79ed12b45a480ceebf0779fce417
7
+ data.tar.gz: a39f67a9251ec17e4eb4a2aa7e6419334afaaf4e1cc06fe5d8aeb427f417b03ad4b4856e4aa2846de065d670c5736e0814d8ff8be4ec7d242e69b790b8d1be8a
@@ -30,34 +30,4 @@
30
30
  }
31
31
  ] %>
32
32
 
33
- <%= pb_rails("caption", props: { text: "Default Advanced Table Table Props Doc Example" }) %>
34
- <%= pb_rails("advanced_table", props: { id: "table_props_table", table_data: @table_data, column_definitions: column_definitions, table_props: { vertical_border: true, container: false }}) %>
35
-
36
- <%= pb_rails("caption", props: { text: "Advanced Table with just container: false" }) %>
37
- <%= pb_rails("advanced_table", props: { id: "table_props_table_2", table_data: @table_data, column_definitions: column_definitions, table_props: { container: false }}) %>
38
-
39
- <%= pb_rails("caption", props: { text: "Typical set up: Card and Flex around Filter, SectionSeparator, and AdvancedTable with container: false" }) %>
40
- <%= pb_rails("card", props: { padding: "none", margin_bottom: "xl" }) do %>
41
- <%= pb_rails("flex", props: { align: "stretch", orientation: "column", gap: "none" }) do %>
42
- <%= pb_rails("filter", props: {
43
- background: false,
44
- max_height: (@table_data.to_a.size < 10) ? "50vh" : "none",
45
- min_width: "xs",
46
- popover_props: { width: "350px" },
47
- id: "rails-table-props-filter-with-data",
48
- template: "single",
49
- results: @table_data.to_a.size,
50
- }) do %>
51
- <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
52
- <%= form.number_field :new_leads_eq, props: { label: "New Leads" } %>
53
- <%= form.text_field :conversion_rate_eq, props: { label: "Conversion Rate" } %>
54
- <%= form.actions do |action| %>
55
- <%= action.submit props: { text: "Filter" } %>
56
- <%= action.button props: { type: "reset", text: "Defaults", variant: "secondary" } %>
57
- <% end %>
58
- <% end %>
59
- <% end %>
60
- <%= pb_rails("section_separator") %>
61
- <%= pb_rails("advanced_table", props: { id: "rails_template_advanced_table", table_data: @table_data, column_definitions: column_definitions, table_props: { container: false } }) %>
62
- <% end %>
63
- <% end %>
33
+ <%= pb_rails("advanced_table", props: { id: "table_props_table", table_data: @table_data, column_definitions: column_definitions, table_props: { vertical_border: true, container: false }}) %>
@@ -1,13 +1,6 @@
1
1
  import React from "react"
2
2
  import AdvancedTable from '../../pb_advanced_table/_advanced_table'
3
3
  import MOCK_DATA from "./advanced_table_mock_data.json"
4
- import Button from "../../pb_button/_button"
5
- import Caption from "../../pb_caption/_caption"
6
- import Card from "../../pb_card/_card"
7
- import Filter from "../../pb_filter/_filter"
8
- import Flex from "../../pb_flex/_flex"
9
- import SectionSeparator from "../../pb_section_separator/_section_separator"
10
- import TextInput from "../../pb_text_input/_text_input"
11
4
 
12
5
  const AdvancedTableTableProps = (props) => {
13
6
  const columnDefinitions = [
@@ -47,100 +40,14 @@ const AdvancedTableTableProps = (props) => {
47
40
  verticalBorder: true
48
41
  }
49
42
 
50
- const filterPopoverProps = { width: "350px" }
51
-
52
- const filterForm = (closePopover) => (
53
- <form>
54
- <TextInput
55
- label="New Leads"
56
- name="new_leads_eq"
57
- placeholder="0"
58
- type="number"
59
- {...props}
60
- />
61
- <TextInput
62
- label="Conversion Rate"
63
- name="conversion_rate_eq"
64
- placeholder="e.g. 12%"
65
- {...props}
66
- />
67
- <Flex
68
- spacing="between"
69
- {...props}
70
- >
71
- <Button
72
- onClick={closePopover}
73
- text="Filter"
74
- {...props}
75
- />
76
- <Button
77
- text="Defaults"
78
- type="reset"
79
- variant="secondary"
80
- {...props}
81
- />
82
- </Flex>
83
- </form>
84
- )
85
-
86
43
  return (
87
44
  <div>
88
- <Caption
89
- text="Default Advanced Table Table Props Doc Example"
90
- />
91
45
  <AdvancedTable
92
46
  columnDefinitions={columnDefinitions}
93
- marginBottom="md"
94
47
  tableData={MOCK_DATA}
95
48
  tableProps={tableProps}
96
49
  {...props}
97
50
  />
98
- <Caption
99
- text="Advanced Table with just container: false"
100
- />
101
- <AdvancedTable
102
- columnDefinitions={columnDefinitions}
103
- marginBottom="md"
104
- tableData={MOCK_DATA}
105
- tableProps={{container: false}}
106
- {...props}
107
- />
108
-
109
- <Caption
110
- text="Typical set up: Card and Flex around Filter, SectionSeparator, and AdvancedTable with container: false"
111
- />
112
- <Card
113
- marginBottom="xl"
114
- padding="none"
115
- {...props}
116
- >
117
- <Flex
118
- align="stretch"
119
- gap="none"
120
- orientation="column"
121
- {...props}
122
- >
123
- <Filter
124
- background={false}
125
- id="react-table-props-filter-with-data"
126
- maxHeight={MOCK_DATA.length < 10 ? "50vh" : "none"}
127
- minWidth="xs"
128
- popoverProps={filterPopoverProps}
129
- results={MOCK_DATA.length}
130
- {...props}
131
- >
132
- {({ closePopover }) => filterForm(closePopover)}
133
- </Filter>
134
- <SectionSeparator {...props} />
135
- <AdvancedTable
136
- columnDefinitions={columnDefinitions}
137
- id="react_template_advanced_table"
138
- tableData={MOCK_DATA}
139
- tableProps={{container: false}}
140
- {...props}
141
- />
142
- </Flex>
143
- </Card>
144
51
  </div>
145
52
  )
146
53
  }
@@ -0,0 +1,19 @@
1
+ This button is used many times for mobile or other things like cards and sidebars.
2
+
3
+ ### Responsive `display` and `full_width`
4
+
5
+ `full_width` applies block styling that includes `display: flex` on the **same element** as the button. The **`display` global prop** also sets `display` (via utility classes, often with `!important`).
6
+
7
+ Putting **both** on one button means **two systems control `display` on one node**, which can cause wrong visibility (e.g. both a header and a full-width mobile button showing) or confusing cascade behavior.
8
+
9
+ **Recommended:** Put responsive `display` on a **parent** (e.g. `Flex`, `Card`, or a plain wrapper) and keep `full_width` only on the `Button` inside. The wrapper handles show/hide by breakpoint; the button only handles full-width layout.
10
+
11
+ ```erb
12
+ <%= pb_rails("flex", props: {
13
+ display: { xs: "flex", default: "none" },
14
+ orientation: "column",
15
+ width: "100%",
16
+ }) do %>
17
+ <%= pb_rails("button", props: { full_width: true, text: "Add" }) %>
18
+ <% end %>
19
+ ```
@@ -0,0 +1,23 @@
1
+ This button is used many times for mobile or other things like cards and sidebars.
2
+
3
+ ### Responsive `display` and `full_width`
4
+
5
+ `full_width` applies block styling that includes `display: flex` on the **same element** as the button. The **`display` global prop** also sets `display` (via utility classes, often with `!important`).
6
+
7
+ Putting **both** on one button means **two systems control `display` on one node**, which can cause wrong visibility (e.g. both a header and a full-width mobile button showing) or confusing cascade behavior.
8
+
9
+ **Recommended:** Put responsive `display` on a **parent** (e.g. `Flex`, `Card`, or a plain wrapper) and keep `fullWidth` only on the `Button` inside. The wrapper handles show/hide by breakpoint; the button only handles full-width layout.
10
+
11
+ ```jsx
12
+ import { Flex, Button } from "playbook-ui"
13
+
14
+ const Example = () => (
15
+ <Flex
16
+ display={{ xs: "flex", default: "none" }}
17
+ orientation="column"
18
+ width="100%"
19
+ >
20
+ <Button fullWidth text="Add" />
21
+ </Flex>
22
+ )
23
+ ```
@@ -43,6 +43,11 @@ $pb_button_styles: (
43
43
  @include pb_circle_icon_button;
44
44
  }
45
45
  }
46
+
47
+ .pb_button_kit.pb_button_loading svg.loading-icon {
48
+ position: absolute;
49
+ }
50
+
46
51
  :first-child {
47
52
  &.pb_button_kit.pb_button_link {
48
53
  @include pb_circle_icon_button_active;
@@ -5,6 +5,8 @@ import TitleCount from '../../pb_title_count/_title_count'
5
5
 
6
6
  const resultsText = (results: number): string => results == 1 ? 'Result' : 'Results'
7
7
 
8
+ const formatResultsCount = (n: number): string => n.toLocaleString()
9
+
8
10
  type ResultsCountProps = {
9
11
  dark?: boolean,
10
12
  results?: number | null,
@@ -31,7 +33,7 @@ const ResultsCount = ({ dark, results, title }: ResultsCountProps): React.ReactE
31
33
  className="filter-results"
32
34
  dark={dark}
33
35
  size="xs"
34
- text={`${results} ${resultsText(results)}`}
36
+ text={`${formatResultsCount(results)} ${resultsText(results)}`}
35
37
  />
36
38
  )
37
39
  }
@@ -29,7 +29,7 @@ module Playbook
29
29
  when nil
30
30
  nil
31
31
  else
32
- "#{results} Results"
32
+ "#{number_with_delimiter(results)} Results"
33
33
  end
34
34
  end
35
35
 
@@ -51,6 +51,7 @@ type MultiLevelSelectProps = {
51
51
  treeData?: { [key: string]: string }[] | any;
52
52
  onChange?: (event: { target: { name?: string; value: any } }) => void;
53
53
  onSelect?: (prop: { [key: string]: any }) => void;
54
+ placeholder?: string;
54
55
  selectedIds?: string[] | any;
55
56
  variant?: "multi" | "single";
56
57
  wrapped?: boolean;
@@ -100,6 +101,7 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
100
101
  treeData,
101
102
  onChange = () => null,
102
103
  onSelect = () => null,
104
+ placeholder: placeholderText = "Start typing...",
103
105
  selectedIds,
104
106
  variant = "multi",
105
107
  wrapped,
@@ -675,7 +677,7 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
675
677
  ? `${itemsSelectedLength()} ${
676
678
  itemsSelectedLength() === 1 ? "item" : "items"
677
679
  } selected`
678
- : "Start typing..."
680
+ : placeholderText
679
681
  }
680
682
  required={required}
681
683
  value={singleSelectedItem.value || filterItem}
@@ -0,0 +1,109 @@
1
+ <%
2
+ tree_base = [{
3
+ label: "Power Home Remodeling",
4
+ value: "powerHomeRemodeling",
5
+ id: "100",
6
+ expanded: true,
7
+ children: [
8
+ {
9
+ label: "People",
10
+ value: "people",
11
+ id: "101",
12
+ expanded: true,
13
+ children: [
14
+ {
15
+ label: "Talent Acquisition",
16
+ value: "talentAcquisition",
17
+ id: "102",
18
+ },
19
+ {
20
+ label: "Business Affairs",
21
+ value: "business Affairs",
22
+ id: "103",
23
+ children: [
24
+ {
25
+ label: "Initiatives",
26
+ value: "initiatives",
27
+ id: "104",
28
+ },
29
+ {
30
+ label: "Learning & Development",
31
+ value: "learningAndDevelopment",
32
+ id: "105",
33
+ },
34
+ ],
35
+ },
36
+ {
37
+ label: "People Experience",
38
+ value: "peopleExperience",
39
+ id: "106",
40
+ },
41
+ ],
42
+ },
43
+ {
44
+ label: "Contact Center",
45
+ value: "contactCenter",
46
+ id: "107",
47
+ children: [
48
+ {
49
+ label: "Appointment Management",
50
+ value: "appointmentManagement",
51
+ id: "108",
52
+ },
53
+ {
54
+ label: "Customer Service",
55
+ value: "customerService",
56
+ id: "109",
57
+ },
58
+ {
59
+ label: "Energy",
60
+ value: "energy",
61
+ id: "110",
62
+ },
63
+ ],
64
+ },
65
+ ],
66
+ }]
67
+
68
+ prefix_mls_ids = nil
69
+ prefix_mls_ids = ->(nodes, pfx) {
70
+ nodes.map do |n|
71
+ h = n.dup
72
+ h[:id] = "#{pfx}#{n[:id]}"
73
+ h[:children] = prefix_mls_ids.call(n[:children], pfx) if n[:children].present?
74
+ h
75
+ end
76
+ }
77
+
78
+ tree_multi = prefix_mls_ids.call(tree_base, "phm_")
79
+ tree_return_all = prefix_mls_ids.call(tree_base, "phr_")
80
+ tree_single = prefix_mls_ids.call(tree_base, "phs_")
81
+ %>
82
+
83
+ <%= pb_rails("multi_level_select", props: {
84
+ id: "multi-level-select-placeholder-multi-rails",
85
+ label: "Multi (default)",
86
+ margin_bottom: "sm",
87
+ name: "placeholder_multi",
88
+ tree_data: tree_multi,
89
+ placeholder: "Search or choose options…",
90
+ }) %>
91
+
92
+ <%= pb_rails("multi_level_select", props: {
93
+ id: "multi-level-select-placeholder-return-all-rails",
94
+ label: "Multi (return all selected)",
95
+ margin_bottom: "sm",
96
+ name: "placeholder_return_all",
97
+ placeholder: "Departments...",
98
+ return_all_selected: true,
99
+ tree_data: tree_return_all,
100
+ }) %>
101
+
102
+ <%= pb_rails("multi_level_select", props: {
103
+ id: "multi-level-select-placeholder-single-rails",
104
+ label: "Single",
105
+ name: "placeholder_single",
106
+ placeholder: "Select one option…",
107
+ tree_data: tree_single,
108
+ variant: "single",
109
+ }) %>
@@ -0,0 +1,127 @@
1
+ import React from "react";
2
+
3
+ import MultiLevelSelect from "../_multi_level_select";
4
+
5
+ const treeTemplate = [
6
+ {
7
+ label: "Power Home Remodeling",
8
+ value: "powerHomeRemodeling",
9
+ id: "powerhome1",
10
+ expanded: true,
11
+ children: [
12
+ {
13
+ label: "People",
14
+ value: "people",
15
+ id: "people1",
16
+ expanded: true,
17
+ children: [
18
+ {
19
+ label: "Talent Acquisition",
20
+ value: "talentAcquisition",
21
+ id: "talent1",
22
+ },
23
+ {
24
+ label: "Business Affairs",
25
+ value: "businessAffairs",
26
+ id: "business1",
27
+ children: [
28
+ {
29
+ label: "Initiatives",
30
+ value: "initiatives",
31
+ id: "initiative1",
32
+ },
33
+ {
34
+ label: "Learning & Development",
35
+ value: "learningAndDevelopment",
36
+ id: "development1",
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ label: "People Experience",
42
+ value: "peopleExperience",
43
+ id: "experience1",
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ label: "Contact Center",
49
+ value: "contactCenter",
50
+ id: "contact1",
51
+ children: [
52
+ {
53
+ label: "Appointment Management",
54
+ value: "appointmentManagement",
55
+ id: "appointment1",
56
+ },
57
+ {
58
+ label: "Customer Service",
59
+ value: "customerService",
60
+ id: "customer1",
61
+ },
62
+ {
63
+ label: "Energy",
64
+ value: "energy",
65
+ id: "energy1",
66
+ },
67
+ ],
68
+ },
69
+ ],
70
+ },
71
+ ];
72
+
73
+ function prefixTreeIds(nodes, prefix) {
74
+ return nodes.map((node) => ({
75
+ ...node,
76
+ id: `${prefix}${node.id}`,
77
+ children:
78
+ node.children && node.children.length > 0
79
+ ? prefixTreeIds(node.children, prefix)
80
+ : node.children,
81
+ }));
82
+ }
83
+
84
+ const treeDataMulti = prefixTreeIds(treeTemplate, "phm_");
85
+ const treeDataReturnAll = prefixTreeIds(treeTemplate, "phr_");
86
+ const treeDataSingle = prefixTreeIds(treeTemplate, "phs_");
87
+
88
+ const MultiLevelSelectPlaceholder = () => (
89
+ <>
90
+ <MultiLevelSelect
91
+ id="multi-level-select-placeholder-multi"
92
+ label="Multi (default)"
93
+ marginBottom="sm"
94
+ name="placeholder_multi"
95
+ onSelect={(selectedNodes) =>
96
+ console.log("Multi — default", selectedNodes)
97
+ }
98
+ placeholder="Search or choose options…"
99
+ treeData={treeDataMulti}
100
+ />
101
+ <MultiLevelSelect
102
+ id="multi-level-select-placeholder-return-all"
103
+ label="Multi (return all selected)"
104
+ marginBottom="sm"
105
+ name="placeholder_return_all"
106
+ onSelect={(selectedNodes) =>
107
+ console.log("Multi — return all selected", selectedNodes)
108
+ }
109
+ placeholder="Departments..."
110
+ returnAllSelected
111
+ treeData={treeDataReturnAll}
112
+ />
113
+ <MultiLevelSelect
114
+ id="multi-level-select-placeholder-single"
115
+ label="Single"
116
+ name="placeholder_single"
117
+ onSelect={(selectedNodes) =>
118
+ console.log("Single", selectedNodes)
119
+ }
120
+ placeholder="Select one option…"
121
+ treeData={treeDataSingle}
122
+ variant="single"
123
+ />
124
+ </>
125
+ );
126
+
127
+ export default MultiLevelSelectPlaceholder;
@@ -0,0 +1 @@
1
+ Use the `placeholder` prop to customize the initial text shown in the input when nothing is selected. The default is `Start typing...`.
@@ -18,6 +18,7 @@ examples:
18
18
  - multi_level_select_disabled_options_parent: Disabled Parent Option (Return All Selected)
19
19
  - multi_level_select_single_disabled: Disabled Options (Single Select)
20
20
  - multi_level_select_required_indicator: Required Indicator
21
+ - multi_level_select_placeholder: Placeholder
21
22
 
22
23
  react:
23
24
  - multi_level_select_default: Default
@@ -40,3 +41,4 @@ examples:
40
41
  - multi_level_select_single_disabled: Disabled Options (Single Select)
41
42
  - multi_level_select_required_indicator: Required Indicator
42
43
  - multi_level_select_react_reset_key: Reset with Key (React)
44
+ - multi_level_select_placeholder: Placeholder
@@ -18,3 +18,4 @@ export { default as MultiLevelSelectLabel } from "./_multi_level_select_label.js
18
18
  export { default as MultiLevelSelectSingleDisabled } from "./_multi_level_select_single_disabled.jsx"
19
19
  export { default as MultiLevelSelectRequiredIndicator } from "./_multi_level_select_required_indicator.jsx"
20
20
  export { default as MultiLevelSelectReactResetKey } from "./_multi_level_select_react_reset_key.jsx"
21
+ export { default as MultiLevelSelectPlaceholder } from "./_multi_level_select_placeholder.jsx"
@@ -32,6 +32,8 @@ module Playbook
32
32
  default: ""
33
33
  prop :label, type: Playbook::Props::String,
34
34
  default: ""
35
+ prop :placeholder, type: Playbook::Props::String,
36
+ default: "Start typing..."
35
37
  prop :required_indicator, type: Playbook::Props::Boolean,
36
38
  default: false
37
39
  prop :show_checked_children, type: Playbook::Props::Boolean,
@@ -50,6 +52,7 @@ module Playbook
50
52
  inputDisplay: input_display,
51
53
  name: name,
52
54
  label: label,
55
+ placeholder: placeholder,
53
56
  treeData: tree_data,
54
57
  required: required,
55
58
  requiredIndicator: required_indicator,
@@ -173,6 +173,33 @@ describe('MultiLevelSelect', () => {
173
173
  expect(label).toHaveTextContent("Select Location")
174
174
  expect(label).not.toHaveTextContent("*")
175
175
  })
176
+
177
+ test('should use default placeholder when none is passed', () => {
178
+ render(
179
+ <MultiLevelSelect
180
+ data={{ testid: testId }}
181
+ id="mls-placeholder-default"
182
+ treeData={treeData}
183
+ />
184
+ )
185
+ expect(
186
+ screen.getByPlaceholderText('Start typing...')
187
+ ).toBeInTheDocument()
188
+ })
189
+
190
+ test('should use custom placeholder when passed', () => {
191
+ render(
192
+ <MultiLevelSelect
193
+ data={{ testid: testId }}
194
+ id="mls-placeholder-custom"
195
+ placeholder="Choose items…"
196
+ treeData={treeData}
197
+ />
198
+ )
199
+ expect(
200
+ screen.getByPlaceholderText('Choose items…')
201
+ ).toBeInTheDocument()
202
+ })
176
203
  })
177
204
 
178
205
  describe('MultiLevelSelect multi variant', () => {
@@ -55,6 +55,7 @@ sections:
55
55
  - table_with_clickable_rows
56
56
  - table_with_selectable_rows
57
57
  - table_with_filter_variant
58
+ - table_with_filter_variant_external_filter_rails
58
59
  - table_with_filter_variant_with_pagination
59
60
  - table_disable_hover
60
61
 
@@ -0,0 +1,45 @@
1
+ <%# External filter: capture any filter markup and pass it via the filter prop.
2
+ Use your own helper (e.g. a search/filter form) or pb_rails("filter") as shown here. %>
3
+ <% users = [
4
+ { name: "Alex", role: "Engineer" },
5
+ { name: "Sam", role: "Designer" },
6
+ { name: "Jordan", role: "Manager" },
7
+ ] %>
8
+
9
+ <% filter_output = capture do %>
10
+ <%= pb_rails("filter", props: {
11
+ id: "external-filter-demo",
12
+ template: "single",
13
+ results: 3,
14
+ background: false,
15
+ sort_menu: [
16
+ { item: "Name", link: "#", active: true, direction: "asc" },
17
+ { item: "Role", link: "#", active: false },
18
+ ],
19
+ }) do %>
20
+ <%= pb_rails("text_input", props: { label: "Name", placeholder: "Search by name" }) %>
21
+ <%= pb_rails("text_input", props: { label: "Role", placeholder: "e.g. Engineer, Designer" }) %>
22
+ <%= pb_rails("button", props: { text: "Apply" }) %>
23
+ <% end %>
24
+ <% end %>
25
+
26
+ <%= pb_rails("table", props: {
27
+ variant: "with_filter",
28
+ title: "Table with External Filter",
29
+ filter: filter_output,
30
+ }) do %>
31
+ <%= pb_rails("table/table_head") do %>
32
+ <%= pb_rails("table/table_row") do %>
33
+ <%= pb_rails("table/table_header", props: { text: "Name" }) %>
34
+ <%= pb_rails("table/table_header", props: { text: "Role" }) %>
35
+ <% end %>
36
+ <% end %>
37
+ <%= pb_rails("table/table_body") do %>
38
+ <% users.each do |user| %>
39
+ <%= pb_rails("table/table_row") do %>
40
+ <%= pb_rails("table/table_cell") { user[:name] } %>
41
+ <%= pb_rails("table/table_cell") { user[:role] } %>
42
+ <% end %>
43
+ <% end %>
44
+ <% end %>
45
+ <% end %>