playbook_ui 16.3.0.pre.alpha.PLAY2806emailcampaignmlsfix14704 → 16.3.0.pre.alpha.PLAY2810toastaddmargin14797

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx +17 -4
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +3 -1
  4. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +21 -9
  5. data/app/pb_kits/playbook/pb_advanced_table/Utilities/ExpansionControlHelpers.tsx +25 -1
  6. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +5 -1
  7. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +74 -1
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.jsx +50 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.md +1 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  12. data/app/pb_kits/playbook/pb_button/_button_mixins.scss +6 -1
  13. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_default.html.erb +18 -9
  14. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_default.jsx +24 -5
  15. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +3 -0
  16. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +1 -0
  17. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_closing_options_rails.html.erb +16 -0
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_closing_options_rails.md +1 -0
  19. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +1 -0
  20. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +4 -0
  21. data/app/pb_kits/playbook/pb_dropdown/index.js +27 -9
  22. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +19 -14
  23. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.scss +4 -0
  24. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +3 -0
  25. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin.html.erb +46 -0
  26. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin.jsx +42 -0
  27. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin_rails.md +1 -0
  28. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin_react.md +1 -0
  29. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
  30. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +2 -1
  31. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +7 -1
  32. data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +1 -22
  33. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +36 -100
  34. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +1 -0
  35. data/app/pb_kits/playbook/pb_table/_table.tsx +24 -21
  36. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props.jsx +152 -0
  37. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props.md +17 -0
  38. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props_rails.html.erb +121 -0
  39. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props_rails.md +17 -0
  40. data/app/pb_kits/playbook/pb_table/docs/example.yml +2 -0
  41. data/app/pb_kits/playbook/pb_table/docs/index.js +1 -0
  42. data/app/pb_kits/playbook/pb_table/table.html.erb +12 -11
  43. data/app/pb_kits/playbook/pb_table/table.rb +4 -0
  44. data/app/pb_kits/playbook/pb_table/table.test.js +33 -0
  45. data/app/pb_kits/playbook/pb_textarea/_textarea.scss +4 -1
  46. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +105 -3
  47. data/dist/chunks/_typeahead-DdGKR1rQ.js +1 -0
  48. data/dist/chunks/componentRegistry-DRSp5D_e.js +1 -0
  49. data/dist/chunks/vendor.js +3 -3
  50. data/dist/menu.yml +2 -1
  51. data/dist/playbook-rails-react-bindings.js +1 -1
  52. data/dist/playbook-rails.js +1 -1
  53. data/dist/playbook.css +1 -1
  54. data/lib/playbook/pb_forms_helper.rb +3 -0
  55. data/lib/playbook/version.rb +1 -1
  56. metadata +16 -4
  57. data/dist/chunks/_typeahead-DTLY_FOh.js +0 -1
  58. data/dist/chunks/componentRegistry-DzmmLR2x.js +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21eabdec1f5807a6d35212883575c9e25065fd44d57c0ef8cfe17b4845672d21
4
- data.tar.gz: 53316f98acf06d9a788eeb64b1329da468649f84180b137d4fa66cb58029a127
3
+ metadata.gz: 92ffb892cecf1e7e9873fa6950e907f9829962c52f8fca4bb3e5a35c5a4e40d8
4
+ data.tar.gz: c2e2c9822a171abe38ed7007c503ae94a06b21e110e4f4a2caf35a08c03a8506
5
5
  SHA512:
6
- metadata.gz: 709b75991c3ff2999a7ca26645a4af6e2b5c75f9551cb2e194a5f624024055bf9cfaa985a80fea610c90d0d324c0559c806fbe6e471669d24b2fd92fdc2611b2
7
- data.tar.gz: 5ddbf26149a38a594a590a59f1cd9627fb9d8d3dc01ade802ccfd0360f20c01f4f4e3cd8b340014ac01331a139ae09d754e458a923db3b661d1886594ae80686
6
+ metadata.gz: abe894c96f125a3501d169f44b75bebcc34d6abc8061f7f7acf16c18ecf116617c4a8274d26866a7b34fff03b13d3c35b2863c313c0b44e31a0bac360fa53ab6
7
+ data.tar.gz: 7047570ca08af14817e4ae7229eb47dc281e53fbe7eafb10a526d2ec5e9df4b9a2a397b40f367742b1d77869411dce8c151751711ff7212a6cb38d3f3f476977
@@ -11,6 +11,7 @@ import Icon from "../../pb_icon/_icon"
11
11
  import Checkbox from "../../pb_checkbox/_checkbox"
12
12
 
13
13
  import AdvancedTableContext from "../Context/AdvancedTableContext"
14
+ import { getDescendantRowIds } from "../Utilities/ExpansionControlHelpers"
14
15
 
15
16
  interface CustomCellProps {
16
17
  getValue?: Getter<string>
@@ -31,13 +32,25 @@ export const CustomCell = ({
31
32
  selectableRows,
32
33
  customStyle = {},
33
34
  }: CustomCellProps & GlobalProps) => {
34
- const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows } = useContext(AdvancedTableContext);
35
+ const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows, cascadeCollapse } = useContext(AdvancedTableContext);
35
36
 
36
37
  const handleOnExpand = (row: Row<GenericObject>) => {
37
38
  onRowToggleClick && onRowToggleClick(row);
38
-
39
- if (!expandedControl) {
40
- setExpanded({ ...expanded, [row.id]: !row.getIsExpanded() });
39
+
40
+ const willBeExpanded = !row.getIsExpanded();
41
+ if (willBeExpanded) {
42
+ if (!expandedControl) {
43
+ setExpanded({ ...expanded, [row.id]: true });
44
+ }
45
+ } else {
46
+ if (cascadeCollapse) {
47
+ const idsToRemove = new Set([row.id, ...getDescendantRowIds(row)]);
48
+ const nextExpanded = { ...expanded };
49
+ idsToRemove.forEach((id) => delete nextExpanded[id]);
50
+ setExpanded(nextExpanded);
51
+ } else if (!expandedControl) {
52
+ setExpanded({ ...expanded, [row.id]: false });
53
+ }
41
54
  }
42
55
  };
43
56
 
@@ -63,6 +63,7 @@ export const TableHeaderCell = ({
63
63
  stickyLeftColumn,
64
64
  inlineRowLoading,
65
65
  isActionBarVisible,
66
+ cascadeCollapse,
66
67
  } = useContext(AdvancedTableContext);
67
68
 
68
69
  type justifyTypes = "none" | "center" | "start" | "end" | "between" | "around" | "evenly"
@@ -182,7 +183,8 @@ const isToggleExpansionEnabled =
182
183
  table.getRowModel(),
183
184
  expanded,
184
185
  undefined,
185
- depth
186
+ depth,
187
+ cascadeCollapse
186
188
  )
187
189
  setExpanded(updated)
188
190
  }
@@ -12,6 +12,7 @@ interface UseTableActionsProps {
12
12
  inlineRowLoading?: boolean;
13
13
  localPagination?: { pageIndex: number; pageSize: number };
14
14
  setLocalPagination?: (pagination: { pageIndex: number; pageSize: number }) => void;
15
+ cascadeCollapse?: boolean;
15
16
  }
16
17
 
17
18
  export function useTableActions({
@@ -22,7 +23,8 @@ export function useTableActions({
22
23
  onRowSelectionChange,
23
24
  inlineRowLoading = false,
24
25
  localPagination,
25
- setLocalPagination
26
+ setLocalPagination,
27
+ cascadeCollapse = false
26
28
  }: UseTableActionsProps) {
27
29
 
28
30
  // State to achieve 1 second delay before fetching more rows
@@ -32,15 +34,25 @@ export function useTableActions({
32
34
  // Handle expand/collapse
33
35
  const handleExpandOrCollapse = useCallback(async (row: Row<GenericObject>) => {
34
36
  if (onToggleExpansionClick) onToggleExpansionClick(row)
35
- const updatedExpandedState = await updateExpandAndCollapseState(
36
- table.getRowModel(),
37
- expanded,
38
- row?.parentId,
39
- undefined
40
- )
41
37
 
42
- setExpanded(updatedExpandedState)
43
- }, [expanded, setExpanded, onToggleExpansionClick, table]);
38
+ const anyTopLevelExpanded = table.getRowModel().rows.some((r: Row<GenericObject>) => r.getIsExpanded())
39
+ const isHeaderCollapseAll = row == null && anyTopLevelExpanded
40
+
41
+ if (cascadeCollapse && isHeaderCollapseAll) {
42
+ setExpanded({})
43
+ return
44
+ }
45
+
46
+ const updatedExpandedState = await updateExpandAndCollapseState(
47
+ table.getRowModel(),
48
+ expanded,
49
+ row?.parentId,
50
+ undefined,
51
+ cascadeCollapse
52
+ )
53
+
54
+ setExpanded(updatedExpandedState)
55
+ }, [expanded, setExpanded, onToggleExpansionClick, table, cascadeCollapse]);
44
56
 
45
57
  // Handle pagination
46
58
  const onPageChange = useCallback((page: number) => {
@@ -11,11 +11,21 @@ const filterExpandableRows = (expandedState: Record<string, boolean>) => {
11
11
  return expandedState
12
12
  }
13
13
 
14
+ export const getDescendantRowIds = (row: Row<GenericObject>): string[] => {
15
+ const ids: string[] = []
16
+ for (const sub of row.subRows || []) {
17
+ ids.push(sub.id)
18
+ ids.push(...getDescendantRowIds(sub))
19
+ }
20
+ return ids
21
+ }
22
+
14
23
  export const updateExpandAndCollapseState = (
15
24
  tableRows: RowModel<GenericObject>,
16
25
  expanded: Record<string, boolean>,
17
26
  targetParent?: string,
18
27
  targetDepth?: number,
28
+ cascadeCollapse?: boolean,
19
29
  ) => {
20
30
  const updateExpandedRows: Record<string, boolean> = {};
21
31
  const rows = targetDepth !== undefined ? tableRows.flatRows : tableRows.rows;
@@ -51,8 +61,22 @@ export const updateExpandAndCollapseState = (
51
61
  }
52
62
  }
53
63
 
54
- return filterExpandableRows({
64
+ const updatedExpandedState = filterExpandableRows({
55
65
  ...(expanded as ExpandedStateObject),
56
66
  ...updateExpandedRows,
57
67
  });
68
+
69
+ if (cascadeCollapse && !isExpandAction) {
70
+ const idsToRemove = new Set<string>();
71
+ for (const row of rowsToToggle) {
72
+ const shouldUpdate =
73
+ targetDepth === undefined ? true : row.depth === targetDepth;
74
+ if (shouldUpdate) {
75
+ getDescendantRowIds(row).forEach((id) => idsToRemove.add(id));
76
+ }
77
+ }
78
+ idsToRemove.forEach((id) => delete updatedExpandedState[id]);
79
+ }
80
+
81
+ return updatedExpandedState;
58
82
  };
@@ -31,6 +31,7 @@ type FullscreenControls = {
31
31
  type AdvancedTableProps = {
32
32
  aria?: { [key: string]: string }
33
33
  actions?: React.ReactNode[] | React.ReactNode
34
+ cascadeCollapse?: boolean
34
35
  children?: React.ReactNode | React.ReactNode[]
35
36
  className?: string
36
37
  columnDefinitions: GenericObject[]
@@ -80,6 +81,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
80
81
  const {
81
82
  aria = {},
82
83
  actions,
84
+ cascadeCollapse = false,
83
85
  children,
84
86
  className,
85
87
  columnDefinitions,
@@ -173,7 +175,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
173
175
  onRowSelectionChange,
174
176
  inlineRowLoading,
175
177
  localPagination,
176
- setLocalPagination
178
+ setLocalPagination,
179
+ cascadeCollapse
177
180
  });
178
181
 
179
182
  // Set table row count for loading state
@@ -339,6 +342,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
339
342
  >
340
343
  {renderFullscreenHeader()}
341
344
  <AdvancedTableProvider
345
+ cascadeCollapse={cascadeCollapse}
342
346
  columnDefinitions={columnDefinitions}
343
347
  columnGroupBorderColor={columnGroupBorderColor}
344
348
  columnVisibilityControl={columnVisibilityControl}
@@ -1015,4 +1015,77 @@ test("columnStyling.headerFontColor works as excpected", () => {
1015
1015
 
1016
1016
  const firstEnrollmentHeader = screen.getAllByText("New Enrollments")[0].closest("th");
1017
1017
  expect(firstEnrollmentHeader).toHaveStyle({ color: colors.white });
1018
- });
1018
+ });
1019
+
1020
+ test("cascadeCollapse=false (default) preserves existing behavior when parent is re-expanded", () => {
1021
+ render(
1022
+ <AdvancedTable
1023
+ columnDefinitions={columnDefinitions}
1024
+ data={{ testid: testId }}
1025
+ tableData={MOCK_DATA}
1026
+ />
1027
+ )
1028
+
1029
+ const kit = screen.getByTestId(testId)
1030
+ const getParentExpandButton = () => kit.querySelector("tbody tr .gray-icon.expand-toggle-icon")
1031
+ const parentButton = getParentExpandButton()
1032
+ expect(parentButton).toBeInTheDocument()
1033
+ parentButton.click()
1034
+ let subRow = kit.querySelector(".pb-bg-row-white.depth-sub-row-1")
1035
+ expect(subRow).toBeInTheDocument()
1036
+ getParentExpandButton().click()
1037
+ subRow = kit.querySelector(".pb-bg-row-white.depth-sub-row-1")
1038
+ expect(subRow).not.toBeInTheDocument()
1039
+ getParentExpandButton().click()
1040
+ subRow = kit.querySelector(".pb-bg-row-white.depth-sub-row-1")
1041
+ expect(subRow).toBeInTheDocument()
1042
+ })
1043
+
1044
+ test("cascadeCollapse=true collapses all descendants when parent is collapsed", () => {
1045
+ render(
1046
+ <AdvancedTable
1047
+ cascadeCollapse
1048
+ columnDefinitions={columnDefinitions}
1049
+ data={{ testid: testId }}
1050
+ tableData={MOCK_DATA}
1051
+ />
1052
+ )
1053
+
1054
+ const kit = screen.getByTestId(testId)
1055
+ const getParentExpandButton = () => kit.querySelector("tbody tr .gray-icon.expand-toggle-icon")
1056
+ const parentButton = getParentExpandButton()
1057
+ expect(parentButton).toBeInTheDocument()
1058
+ parentButton.click()
1059
+ expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
1060
+ getParentExpandButton().click()
1061
+ expect(kit.querySelector(".depth-sub-row-1")).not.toBeInTheDocument()
1062
+ getParentExpandButton().click()
1063
+ expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
1064
+ })
1065
+
1066
+ test("cascadeCollapse=true with header toggle all: collapse all then expand all shows only direct children", async () => {
1067
+ render(
1068
+ <AdvancedTable
1069
+ cascadeCollapse
1070
+ columnDefinitions={columnDefinitions}
1071
+ data={{ testid: testId }}
1072
+ tableData={MOCK_DATA}
1073
+ />
1074
+ )
1075
+
1076
+ const kit = screen.getByTestId(testId)
1077
+ const toggleAllButton = kit.querySelector(".gray-icon.toggle-all-icon")
1078
+ expect(toggleAllButton).toBeInTheDocument()
1079
+ toggleAllButton.click()
1080
+ await waitFor(() => {
1081
+ expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
1082
+ })
1083
+ toggleAllButton.click()
1084
+ await waitFor(() => {
1085
+ expect(kit.querySelector(".depth-sub-row-1")).not.toBeInTheDocument()
1086
+ })
1087
+ toggleAllButton.click()
1088
+ await waitFor(() => {
1089
+ expect(kit.querySelector(".depth-sub-row-1")).toBeInTheDocument()
1090
+ })
1091
+ })
@@ -0,0 +1,50 @@
1
+ import React from "react"
2
+ import AdvancedTable from '../../pb_advanced_table/_advanced_table'
3
+ import MOCK_DATA from "./advanced_table_mock_data.json"
4
+
5
+ const AdvancedTableCascadeCollapse = (props) => {
6
+ const columnDefinitions = [
7
+ {
8
+ accessor: "year",
9
+ label: "Year",
10
+ cellAccessors: ["quarter", "month", "day"],
11
+ },
12
+ {
13
+ accessor: "newEnrollments",
14
+ label: "New Enrollments",
15
+ },
16
+ {
17
+ accessor: "scheduledMeetings",
18
+ label: "Scheduled Meetings",
19
+ },
20
+ {
21
+ accessor: "attendanceRate",
22
+ label: "Attendance Rate",
23
+ },
24
+ {
25
+ accessor: "completedClasses",
26
+ label: "Completed Classes",
27
+ },
28
+ {
29
+ accessor: "classCompletionRate",
30
+ label: "Class Completion Rate",
31
+ },
32
+ {
33
+ accessor: "graduatedStudents",
34
+ label: "Graduated Students",
35
+ },
36
+ ]
37
+
38
+ return (
39
+ <div>
40
+ <AdvancedTable
41
+ cascadeCollapse
42
+ columnDefinitions={columnDefinitions}
43
+ tableData={MOCK_DATA}
44
+ {...props}
45
+ />
46
+ </div>
47
+ )
48
+ }
49
+
50
+ export default AdvancedTableCascadeCollapse
@@ -0,0 +1 @@
1
+ `cascadeCollapse` is an optional prop that is set to 'false' by default. If set to 'true', collapsing any parent row itself or by using the toggle exapansion buttons in any header or subheader row also collapses all descendants and clears their expansion state. Re-expanding then shows only direct children until the user expands deeper levels again.
@@ -42,6 +42,7 @@ examples:
42
42
  - advanced_table_expanded_control: Expanded Control
43
43
  - advanced_table_expand_by_depth: Expand by Depth
44
44
  - advanced_table_subrow_headers: SubRow Headers
45
+ - advanced_table_cascade_collapse: Cascade Collapse
45
46
  - advanced_table_collapsible_trail: Collapsible Trail
46
47
  - advanced_table_table_options: Table Options
47
48
  - advanced_table_table_props: Table Props
@@ -48,4 +48,5 @@ export { default as AdvancedTablePaddingControl } from './_advanced_table_paddin
48
48
  export { default as AdvancedTablePaddingControlPerRow } from './_advanced_table_padding_control_per_row.jsx'
49
49
  export { default as AdvancedTableColumnStylingBackground } from './_advanced_table_column_styling_background.jsx'
50
50
  export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
51
- export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
51
+ export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
52
+ export { default as AdvancedTableCascadeCollapse } from './_advanced_table_cascade_collapse.jsx'
@@ -57,7 +57,7 @@ $pb_button_border_width: 0px;
57
57
  }
58
58
 
59
59
  .loading-icon {
60
- position: absolute;
60
+ position: static;
61
61
  display: none;
62
62
  }
63
63
  .pb_button_content {
@@ -158,10 +158,15 @@ $pb_button_border_width: 0px;
158
158
  // Loading =====================
159
159
  @mixin pb_button_loading($loading: false) {
160
160
  @if $loading == true {
161
+ display: inline-grid;
162
+ place-items: center;
163
+
161
164
  .loading-icon {
165
+ grid-area: 1 / 1;
162
166
  display: block;
163
167
  }
164
168
  .pb_button_content {
169
+ grid-area: 1 / 1;
165
170
  visibility: hidden;
166
171
  }
167
172
  }
@@ -1,11 +1,20 @@
1
1
  <%= pb_rails("button", props: { text: "Open Dialog", data: {"open-dialog": "dialog-1"} }) %>
2
2
 
3
- <%= pb_rails("dialog", props: {
4
- id:"dialog-1",
5
- size: "sm",
6
- title: "Header Title is the Title Prop",
7
- text: "Hello Body Text, Nice to meet ya.",
8
- cancel_button: "Cancel Button",
9
- confirm_button: "Okay",
10
- confirm_button_id: "confirm-button-1"
11
- }) %>
3
+ <%= pb_rails("dialog", props: {
4
+ id:"dialog-1",
5
+ size: "md",
6
+ title: "Header Title is the Title Prop"
7
+ }) do %>
8
+ <%= pb_rails("dialog/dialog_body") do %>
9
+ <%= pb_rails("button", props: { aria: { label: "Loading" }, loading: true, margin_right: "lg", text: "Button Primary" }) %>
10
+ <div style="height: 800px; background-color: lightgray;"></div>
11
+ <%= pb_rails("button", props: { loading: true, text: "Loading..." }) %>
12
+ <% end %>
13
+
14
+ <%= pb_rails("dialog/dialog_footer") do %>
15
+ <%= pb_rails("flex", props: { spacing: "between", padding_x: "md", padding_bottom: "md", padding: "sm" }) do %>
16
+ <%= pb_rails("button", props: { loading: true, text: "Send My Issue" }) %>
17
+ <%= pb_rails("button", props: { text: "Back", variant: "link", data: {"close-dialog": "dialog-1"} }) %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
@@ -12,16 +12,35 @@ const DialogDefault = () => {
12
12
  <>
13
13
  <Button onClick={open}>{'Open Dialog'}</Button>
14
14
  <Dialog
15
- cancelButton="Cancel Button"
16
- confirmButton="Okay"
17
15
  onCancel={close}
18
16
  onClose={close}
19
17
  onConfirm={close}
20
18
  opened={isOpen}
21
- size="sm"
22
- text="Hello Body Text, Nice to meet ya."
19
+ size="md"
23
20
  title="Header Title is the Title Prop"
24
- />
21
+ >
22
+ <Dialog.Body>
23
+ <Button
24
+ aria={{ label: 'Loading' }}
25
+ loading
26
+ text="Button Primary"
27
+ />
28
+ <div style={{height: '800px', backgroundColor: 'lightgray'}} />
29
+ <Button
30
+ loading
31
+ text="Loading..."
32
+ />
33
+ </Dialog.Body>
34
+ <Dialog.Footer>
35
+ <Button
36
+ loading
37
+ text="Send My Issue"
38
+ />
39
+ <Button variant="link">
40
+ {"Back"}
41
+ </Button>
42
+ </Dialog.Footer>
43
+ </Dialog>
25
44
  </>
26
45
  )
27
46
  }
@@ -15,6 +15,9 @@
15
15
  .pb_dropdown_quickpick,
16
16
  .pb_dropdown_default_separators_hidden,
17
17
  .pb_dropdown_subtle_separators_hidden {
18
+ label {
19
+ display: block !important;
20
+ }
18
21
  .dropdown_wrapper {
19
22
  .dropdown_trigger_wrapper,
20
23
  .dropdown_trigger_wrapper_focus,
@@ -441,6 +441,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
441
441
  value={{
442
442
  activeStyle,
443
443
  autocomplete,
444
+ blankSelection,
444
445
  clearable,
445
446
  dropdownContainerRef,
446
447
  error,
@@ -0,0 +1,16 @@
1
+ <%
2
+ options = [
3
+ { label: "United States", value: "unitedStates", id: "us" },
4
+ { label: "Canada", value: "canada", id: "ca" },
5
+ { label: "Pakistan", value: "pakistan", id: "pk" },
6
+ ]
7
+ %>
8
+
9
+ <%= pb_rails("caption", props: { margin_bottom: "xs", text: "Any" }) %>
10
+ <%= pb_rails("dropdown", props: { options: options, close_on_click: "any", margin_bottom: "md" }) %>
11
+
12
+ <%= pb_rails("caption", props: { margin_bottom: "xs", text: "Outside" }) %>
13
+ <%= pb_rails("dropdown", props: { options: options, close_on_click: "outside", margin_bottom: "md" }) %>
14
+
15
+ <%= pb_rails("caption", props: { margin_bottom: "xs", text: "Inside" }) %>
16
+ <%= pb_rails("dropdown", props: { options: options, close_on_click: "inside" }) %>
@@ -0,0 +1 @@
1
+ The `close_on_click` prop allows you to control when the Dropdown closes in response to click interactions. The value `any` reflects the default behavior, where the dropdown will close after any click. Set it to `outside` to ensure interactive elements as dropdown options are able to be interacted with or modified. Set it to `inside` for a dropdown that only closes when the input or dropdown menu is clicked.
@@ -25,6 +25,7 @@ examples:
25
25
  - dropdown_separators_hidden: Separators Hidden
26
26
  - dropdown_with_clearable: Clearable
27
27
  - dropdown_with_constrain_height_rails: Constrain Height
28
+ - dropdown_closing_options_rails: Closing Options
28
29
  - dropdown_quickpick_rails: Quick Pick Variant
29
30
  - dropdown_quickpick_range_end_rails: Quick Pick Variant (Range Ends Today)
30
31
  - dropdown_quickpick_default_dates: Quick Pick Variant (Default Dates)
@@ -39,6 +39,9 @@ module Playbook
39
39
  default: ""
40
40
  prop :clearable, type: Playbook::Props::Boolean,
41
41
  default: true
42
+ prop :close_on_click, type: Playbook::Props::Enum,
43
+ values: %w[outside inside any],
44
+ default: "any"
42
45
  prop :start_date_id, type: Playbook::Props::String,
43
46
  default: "start_date_id"
44
47
  prop :start_date_name, type: Playbook::Props::String,
@@ -59,6 +62,7 @@ module Playbook
59
62
  pb_dropdown_multi_select: multi_select,
60
63
  pb_dropdown_variant: variant,
61
64
  pb_dropdown_clearable: clearable,
65
+ pb_dropdown_close_on_click: close_on_click,
62
66
  form_pill_props: form_pill_props.to_json,
63
67
  start_date_id: variant == "quickpick" ? start_date_id : nil,
64
68
  end_date_id: variant == "quickpick" ? end_date_id : nil,
@@ -35,6 +35,7 @@ export default class PbDropdown extends PbEnhancedElement {
35
35
 
36
36
  this.keyboardHandler = new PbDropdownKeyboard(this);
37
37
  this.isMultiSelect = this.element.dataset.pbDropdownMultiSelect === "true";
38
+ this.closeOnClick = this.element.dataset.pbDropdownCloseOnClick || "any";
38
39
  this.formPillProps = this.element.dataset.formPillProps
39
40
  ? JSON.parse(this.element.dataset.formPillProps)
40
41
  : {};
@@ -140,6 +141,12 @@ export default class PbDropdown extends PbEnhancedElement {
140
141
  trigger.focus();
141
142
  }
142
143
  }
144
+ if (
145
+ this.closeOnClick === "outside" &&
146
+ this.target?.contains(e.target)
147
+ ) {
148
+ return;
149
+ }
143
150
  this.toggleElement(this.target);
144
151
  }
145
152
  customTrigger.addEventListener("click", this.customTriggerClickHandler);
@@ -305,7 +312,13 @@ export default class PbDropdown extends PbEnhancedElement {
305
312
 
306
313
  handleDocumentClick(event) {
307
314
  if (event.target.closest(SEARCH_BAR_SELECTOR)) return;
308
- if (this.isClickOutside(event) && this.target.classList.contains("open")) {
315
+ const shouldCloseOnOutsideClick =
316
+ this.closeOnClick === "outside" || this.closeOnClick === "any";
317
+ if (
318
+ shouldCloseOnOutsideClick &&
319
+ this.isClickOutside(event) &&
320
+ this.target.classList.contains("open")
321
+ ) {
309
322
  this.hideElement(this.target);
310
323
  this.updateArrowDisplay(false);
311
324
  }
@@ -316,11 +329,12 @@ export default class PbDropdown extends PbEnhancedElement {
316
329
  if (label && this.element.contains(label)) return false;
317
330
  const customTrigger = this.element.querySelector(CUSTOM_DISPLAY_SELECTOR);
318
331
  if (customTrigger) {
319
- return !customTrigger.contains(event.target);
332
+ const clickInTrigger = customTrigger.contains(event.target);
333
+ const clickInContainer = this.target?.contains(event.target);
334
+ return !clickInTrigger && !clickInContainer;
320
335
  } else {
321
336
  const triggerElement = this.element.querySelector(TRIGGER_SELECTOR);
322
- const containerElement =
323
- this.element.parentNode.querySelector(CONTAINER_SELECTOR);
337
+ const containerElement = this.element.querySelector(CONTAINER_SELECTOR);
324
338
 
325
339
  const isOutsideTrigger = triggerElement
326
340
  ? !triggerElement.contains(event.target)
@@ -459,11 +473,15 @@ export default class PbDropdown extends PbEnhancedElement {
459
473
  }
460
474
 
461
475
  const customTrigger = this.element.querySelector(CUSTOM_DISPLAY_SELECTOR);
462
- if (customTrigger) {
463
- if (this.target.classList.contains("open")) {
464
- this.hideElement(this.target);
465
- this.updateArrowDisplay(false);
466
- }
476
+ const shouldCloseOnOptionSelect =
477
+ this.closeOnClick === "any" || this.closeOnClick === "inside";
478
+ if (
479
+ customTrigger &&
480
+ shouldCloseOnOptionSelect &&
481
+ this.target.classList.contains("open")
482
+ ) {
483
+ this.hideElement(this.target);
484
+ this.updateArrowDisplay(false);
467
485
  }
468
486
 
469
487
  const options = this.element.querySelectorAll(OPTION_SELECTOR);
@@ -44,6 +44,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
44
44
 
45
45
  const {
46
46
  autocomplete,
47
+ blankSelection,
47
48
  clearable,
48
49
  error,
49
50
  errorId,
@@ -57,6 +58,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
57
58
  isInputFocused,
58
59
  label: contextLabel,
59
60
  multiSelect,
61
+ optionsWithBlankSelection,
60
62
  selected,
61
63
  selectId,
62
64
  setIsInputFocused,
@@ -240,20 +242,22 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
240
242
  onClick: (e: Event) => {e.stopPropagation();handleWrapperClick()}
241
243
  }}
242
244
  key={`${isDropDownClosed ? "chevron-down" : "chevron-up"}`}
243
- >
244
- {
245
- clearable !== false && selectedArray.length > 0 && (
246
- <div onClick={(e)=>{e.stopPropagation();handleBackspace()}}>
247
- <Icon
248
- cursor="pointer"
249
- dark={dark}
250
- icon="times"
251
- paddingRight="xs"
252
- size="sm"
253
- />
254
- </div>
255
- )
256
- }
245
+ >
246
+ {(!blankSelection || selected?.value !== optionsWithBlankSelection?.[0]?.value) && (
247
+ <>
248
+ {clearable !== false && selectedArray.length > 0 && (
249
+ <div onClick={(e)=>{e.stopPropagation();handleBackspace()}}>
250
+ <Icon
251
+ cursor="pointer"
252
+ dark={dark}
253
+ icon="times"
254
+ paddingRight="xs"
255
+ size="sm"
256
+ />
257
+ </div>
258
+ )}
259
+ </>
260
+ )}
257
261
  <Icon
258
262
  cursor="pointer"
259
263
  dark={dark}
@@ -261,6 +265,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
261
265
  size="sm"
262
266
  />
263
267
  </Body>
268
+
264
269
  </FlexItem>
265
270
  </Flex>
266
271
  </>
@@ -71,6 +71,10 @@ $confirmation_toast_colors: (
71
71
  }
72
72
  }
73
73
 
74
+ .nav_margin_top {
75
+ margin-top: 73px;
76
+ }
77
+
74
78
  .pb_fixed_confirmation_toast_kit_neutral,
75
79
  .pb_fixed_confirmation_toast_kit_neutral_multi_line {
76
80
  background: $text_lt_light;