playbook_ui 14.8.0 → 14.9.0.pre.alpha.PBNTR702stickyleftcolrails4806

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +2 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +61 -17
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.html.erb +1 -1
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.html.erb +1 -1
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_custom_cell_rails.html.erb +53 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_custom_cell_rails.md +5 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination.jsx +50 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination.md +1 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination_with_props.jsx +57 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pagination_with_props.md +5 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_pagination_mock_data.json +5600 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -0
  15. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +3 -1
  16. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +15 -0
  17. data/app/pb_kits/playbook/pb_background/_background.tsx +8 -2
  18. data/app/pb_kits/playbook/pb_button/_button.scss +6 -0
  19. data/app/pb_kits/playbook/pb_button/_button.tsx +1 -3
  20. data/app/pb_kits/playbook/pb_button/_button_mixins.scss +15 -0
  21. data/app/pb_kits/playbook/pb_button/button.rb +1 -1
  22. data/app/pb_kits/playbook/pb_button/docs/_button_default.html.erb +1 -0
  23. data/app/pb_kits/playbook/pb_button/docs/_button_default.jsx +8 -0
  24. data/app/pb_kits/playbook/pb_button/docs/_button_default.md +1 -1
  25. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +2 -2
  26. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +0 -4
  27. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_indeterminate.html.erb +84 -7
  28. data/app/pb_kits/playbook/pb_collapsible/_collapsible.tsx +3 -1
  29. data/app/pb_kits/playbook/pb_currency/_currency.tsx +7 -3
  30. data/app/pb_kits/playbook/pb_currency/currency.html.erb +2 -2
  31. data/app/pb_kits/playbook/pb_currency/currency.rb +17 -1
  32. data/app/pb_kits/playbook/pb_currency/currency.test.js +40 -3
  33. data/app/pb_kits/playbook/pb_currency/docs/_currency_negative.html.erb +4 -0
  34. data/app/pb_kits/playbook/pb_currency/docs/_currency_negative.jsx +16 -0
  35. data/app/pb_kits/playbook/pb_currency/docs/example.yml +2 -0
  36. data/app/pb_kits/playbook/pb_currency/docs/index.js +1 -0
  37. data/app/pb_kits/playbook/pb_drawer/_drawer.scss +1 -0
  38. data/app/pb_kits/playbook/pb_drawer/_drawer.tsx +159 -45
  39. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.jsx +31 -0
  40. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.md +6 -0
  41. data/app/pb_kits/playbook/pb_drawer/docs/example.yml +1 -1
  42. data/app/pb_kits/playbook/pb_drawer/docs/index.js +1 -0
  43. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +1 -0
  44. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +0 -1
  45. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +0 -4
  46. data/app/pb_kits/playbook/pb_dropdown/utilities/subComponentHelper.tsx +13 -2
  47. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +2 -2
  48. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb +1 -1
  49. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +63 -12
  50. data/app/pb_kits/playbook/pb_image/_image.tsx +3 -1
  51. data/app/pb_kits/playbook/pb_layout/_layout.tsx +6 -3
  52. data/app/pb_kits/playbook/pb_overlay/_overlay.tsx +3 -1
  53. data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +2 -2
  54. data/app/pb_kits/playbook/pb_progress_simple/_progress_simple.tsx +5 -3
  55. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.html.erb +3 -2
  56. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.rb +5 -0
  57. data/app/pb_kits/playbook/pb_section_separator/_section_separator.tsx +3 -1
  58. data/app/pb_kits/playbook/pb_selectable_card/docs/_selectable_card_default.html.erb +2 -1
  59. data/app/pb_kits/playbook/pb_table/_table.tsx +109 -25
  60. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns.html.erb +95 -0
  61. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns.jsx +87 -0
  62. data/app/pb_kits/playbook/pb_table/docs/_table_sticky_left_columns_react.md +2 -0
  63. data/app/pb_kits/playbook/pb_table/docs/index.js +1 -0
  64. data/app/pb_kits/playbook/pb_table/index.ts +100 -26
  65. data/app/pb_kits/playbook/pb_table/styles/_all.scss +2 -0
  66. data/app/pb_kits/playbook/pb_table/styles/_scroll.scss +4 -0
  67. data/app/pb_kits/playbook/pb_table/styles/_sticky_columns.scss +18 -0
  68. data/app/pb_kits/playbook/pb_table/table.html.erb +1 -1
  69. data/app/pb_kits/playbook/pb_table/table.rb +17 -2
  70. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.html.erb +47 -0
  71. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.jsx +59 -0
  72. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +3 -0
  73. data/app/pb_kits/playbook/pb_typeahead/index.ts +29 -3
  74. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +5 -2
  75. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -0
  76. data/app/pb_kits/playbook/tokens/_height.scss +19 -0
  77. data/app/pb_kits/playbook/tokens/exports/_height.module.scss +37 -0
  78. data/app/pb_kits/playbook/utilities/_height.scss +33 -0
  79. data/app/pb_kits/playbook/utilities/_max_width.scss +29 -9
  80. data/app/pb_kits/playbook/utilities/_min_width.scss +6 -2
  81. data/app/pb_kits/playbook/utilities/_width.scss +45 -0
  82. data/app/pb_kits/playbook/utilities/globalPropNames.mjs +1 -1
  83. data/app/pb_kits/playbook/utilities/globalProps.ts +37 -4
  84. data/dist/chunks/_typeahead-CCDoUmRR.js +22 -0
  85. data/dist/chunks/_weekday_stacked-CxjKLoMr.js +45 -0
  86. data/dist/chunks/{lib-BC6ESsxG.js → lib-CVPInSs5.js} +2 -2
  87. data/dist/chunks/{pb_form_validation-B_Z9rEbg.js → pb_form_validation-CDLJ5eAG.js} +1 -1
  88. data/dist/chunks/vendor.js +1 -1
  89. data/dist/menu.yml +321 -0
  90. data/dist/playbook-doc.js +1 -1
  91. data/dist/playbook-rails-react-bindings.js +1 -1
  92. data/dist/playbook-rails.js +1 -1
  93. data/dist/playbook.css +1 -1
  94. data/lib/playbook/classnames.rb +4 -0
  95. data/lib/playbook/forms/builder/typeahead_field.rb +13 -0
  96. data/lib/playbook/height.rb +29 -0
  97. data/lib/playbook/kit_base.rb +16 -1
  98. data/lib/playbook/max_height.rb +29 -0
  99. data/lib/playbook/min_height.rb +29 -0
  100. data/lib/playbook/spacing.rb +21 -0
  101. data/lib/playbook/version.rb +2 -2
  102. metadata +32 -9
  103. data/dist/chunks/_typeahead-D0PihN_3.js +0 -22
  104. data/dist/chunks/_weekday_stacked-uMIX8f-A.js +0 -45
@@ -7,7 +7,7 @@ import {
7
7
  buildDataProps,
8
8
  buildHtmlProps,
9
9
  } from "../utilities/props";
10
- import { globalProps } from "../utilities/globalProps";
10
+ import { globalProps, globalInlineProps } from "../utilities/globalProps";
11
11
 
12
12
  import { DialogContext } from "../pb_dialog/_dialog_context";
13
13
 
@@ -15,20 +15,22 @@ type DrawerProps = {
15
15
  aria?: { [key: string]: string };
16
16
  behavior?: "floating" | "push";
17
17
  border?: "full" | "none" | "right" | "left";
18
- breakpoint?: "none" | "xs" | "sm" | "md" | "lg" | "xl";
18
+ openBreakpoint?: "none" | "xs" | "sm" | "md" | "lg" | "xl";
19
+ closeBreakpoint?: "none" | "xs" | "sm" | "md" | "lg" | "xl";
19
20
  children: React.ReactNode | React.ReactNode[] | string;
20
21
  className?: string;
21
22
  data?: { [key: string]: string };
22
23
  htmlOptions?: { [key: string]: string | number | boolean | (() => void) };
23
24
  id?: string;
24
25
  fullHeight?: boolean;
26
+ menuButtonID?: string;
25
27
  onClose?: () => void;
26
28
  opened: boolean;
27
29
  overlay: boolean;
28
30
  placement?: "left" | "right";
29
31
  size?: "xs" | "sm" | "md" | "lg" | "xl";
30
32
  text?: string;
31
- trigger?: string;
33
+ withinElement?: boolean;
32
34
  };
33
35
 
34
36
  const Drawer = (props: DrawerProps): React.ReactElement => {
@@ -36,19 +38,21 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
36
38
  aria = {},
37
39
  behavior = "floating",
38
40
  border = "none",
39
- breakpoint = "none",
41
+ openBreakpoint = "none",
42
+ closeBreakpoint = "none",
40
43
  className,
41
44
  data = {},
42
45
  htmlOptions = {},
43
46
  id,
44
47
  size = "md",
45
48
  children,
46
- fullHeight = false,
49
+ fullHeight = true,
50
+ menuButtonID,
47
51
  opened,
48
52
  onClose,
49
53
  overlay = true,
50
54
  placement = "left",
51
- trigger,
55
+ withinElement = false,
52
56
  } = props;
53
57
  const ariaProps = buildAriaProps(aria);
54
58
  const dataProps = buildDataProps(data);
@@ -80,6 +84,7 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
80
84
  drawer_border_full: border === "full",
81
85
  drawer_border_right: border === "right",
82
86
  drawer_border_left: border === "left",
87
+ pb_drawer_within_element: withinElement,
83
88
  }
84
89
  )} ${globalPropsString}`,
85
90
  afterOpen: "pb_drawer_after_open",
@@ -100,10 +105,11 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
100
105
  };
101
106
 
102
107
  const classes = classnames(buildCss("pb_drawer_wrapper"), className);
103
-
108
+ const dynamicInlineProps = globalInlineProps(props)
109
+ const [menuButtonOpened, setMenuButtonOpened] = useState(false);
104
110
  const [triggerOpened, setTriggerOpened] = useState(false);
105
111
 
106
- const breakpointWidths: Record<DrawerProps["breakpoint"], number> = {
112
+ const breakpointWidths: Record<DrawerProps["openBreakpoint"], number> = {
107
113
  none: 0,
108
114
  xs: 575,
109
115
  sm: 768,
@@ -112,20 +118,30 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
112
118
  xl: 1400,
113
119
  };
114
120
 
115
- // State to manage opening the drawer based on breakpoint
116
- const [isBreakpointOpen, setIsBreakpointOpen] = useState(false);
121
+ const breakpointValues = {
122
+ none: 0,
123
+ xs: 575,
124
+ sm: 768,
125
+ md: 992,
126
+ lg: 1200,
127
+ xl: 1400,
128
+ }
129
+
130
+ const [isOpenBreakpointOpen, setIsOpenBreakpointOpen] = useState(false);
131
+ const [isUserClosed, setIsUserClosed] = useState(false);
117
132
 
118
133
  useEffect(() => {
119
- if (breakpoint === "none") return;
134
+ if (openBreakpoint === "none") return;
120
135
 
121
136
  const handleResize = () => {
122
137
  const width = window.innerWidth;
123
- const breakpointWidth = breakpointWidths[breakpoint];
138
+ const openBreakpointWidth = breakpointWidths[openBreakpoint];
124
139
 
125
- if (width <= breakpointWidth) {
126
- setIsBreakpointOpen(true);
140
+ if (width <= openBreakpointWidth) {
141
+ setIsOpenBreakpointOpen(true);
127
142
  } else {
128
- setIsBreakpointOpen(false);
143
+ setIsOpenBreakpointOpen(false);
144
+ setIsUserClosed(false); // Reset when the breakpoint condition changes
129
145
  }
130
146
  };
131
147
 
@@ -137,9 +153,53 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
137
153
  return () => {
138
154
  window.removeEventListener("resize", handleResize);
139
155
  };
140
- }, [breakpoint]);
156
+ }, [openBreakpoint]);
157
+
158
+ useEffect(() => {
159
+ if (closeBreakpoint === "none") return;
141
160
 
142
- const modalIsOpened = trigger ? triggerOpened : opened || isBreakpointOpen;
161
+ const handleResize = () => {
162
+ const width = window.innerWidth;
163
+ if (width >= breakpointValues[closeBreakpoint]) {
164
+ setIsOpenBreakpointOpen(true);
165
+ } else {
166
+ setIsOpenBreakpointOpen(false);
167
+ }
168
+ }
169
+
170
+ window.addEventListener("resize", handleResize);
171
+
172
+ handleResize();
173
+
174
+ return () => {
175
+ window.removeEventListener("resize", handleResize);
176
+ };
177
+
178
+ }, [closeBreakpoint]);
179
+
180
+ //hide menu button if breakpoint opens the drawer
181
+ useEffect(() => {
182
+ if (menuButtonID) {
183
+ const menuButton = document.getElementById(menuButtonID);
184
+ if (menuButton) {
185
+ if (isOpenBreakpointOpen) {
186
+ menuButton.style.display = 'none';
187
+ } else {
188
+ menuButton.style.display = '';
189
+ }
190
+ }
191
+ }
192
+ }, [menuButtonID, isOpenBreakpointOpen]);
193
+
194
+ // Reset isUserClosed when isBreakpointOpen changes
195
+ useEffect(() => {
196
+ if (isOpenBreakpointOpen) {
197
+ setIsUserClosed(false);
198
+ }
199
+ }, [isOpenBreakpointOpen]);
200
+
201
+ const modalIsOpened =
202
+ (isOpenBreakpointOpen && !isUserClosed) || menuButtonOpened || opened;
143
203
 
144
204
  const [animationState, setAnimationState] = useState("");
145
205
 
@@ -150,13 +210,15 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
150
210
  setAnimationState("beforeClose");
151
211
  setTimeout(() => {
152
212
  setAnimationState("");
153
- }, 200); // closeTimeoutMS
213
+ }, 200);
154
214
  }
155
215
  }, [modalIsOpened]);
156
216
 
157
217
  const isModalVisible = modalIsOpened || animationState === "beforeClose";
158
218
 
159
219
  useEffect(() => {
220
+ if (withinElement) return;
221
+
160
222
  const sizeMap: Record<DrawerProps["size"], string> = {
161
223
  xl: "365px",
162
224
  lg: "300px",
@@ -165,7 +227,6 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
165
227
  xs: "64px",
166
228
  };
167
229
  const body = document.querySelector("body");
168
-
169
230
  if (modalIsOpened && behavior === "push" && body) {
170
231
  if (placement === "left") {
171
232
  body.style.cssText = `margin-left: ${sizeMap[size]} !important; margin-right: '' !important;`;
@@ -181,45 +242,98 @@ const Drawer = (props: DrawerProps): React.ReactElement => {
181
242
  body.style.cssText = ""; // Clear the styles when modal is closed or behavior is not 'push'
182
243
  body.classList.remove("PBDrawer__Body--open");
183
244
  }
184
- }, [modalIsOpened, behavior, placement, size]);
245
+ }, [modalIsOpened, behavior, placement, size, withinElement]);
185
246
 
186
247
  const api = {
187
- onClose: trigger
188
- ? function () {
189
- setTriggerOpened(false);
190
- }
191
- : onClose,
248
+ onClose: () => {
249
+ if (menuButtonID) {
250
+ setMenuButtonOpened(false);
251
+ }
252
+ setIsUserClosed(true);
253
+ if (onClose) {
254
+ onClose();
255
+ }
256
+ },
192
257
  };
193
258
 
259
+ useEffect(() => {
260
+ if (menuButtonID) {
261
+ const menuButton = document.getElementById(menuButtonID);
262
+ if (menuButton) {
263
+ const handleMenuButtonClick = () => {
264
+ if (modalIsOpened) {
265
+ // Drawer is open, close it
266
+ setMenuButtonOpened(false);
267
+ setIsUserClosed(true);
268
+ } else {
269
+ // Drawer is closed, open it
270
+ setMenuButtonOpened(true);
271
+ setIsUserClosed(false);
272
+ }
273
+ };
274
+ menuButton.addEventListener("click", handleMenuButtonClick);
275
+ return () => {
276
+ menuButton.removeEventListener("click", handleMenuButtonClick);
277
+ };
278
+ }
279
+ }
280
+ }, [menuButtonID, modalIsOpened]);
281
+
194
282
  return (
195
283
  <DialogContext.Provider value={api}>
196
- <div
197
- {...ariaProps}
198
- {...dataProps}
199
- {...htmlProps}
200
- className={classes}
201
- >
284
+ {withinElement ? (
285
+ isModalVisible && (
286
+ <div
287
+ {...ariaProps}
288
+ {...dataProps}
289
+ {...htmlProps}
290
+ style={dynamicInlineProps}
291
+ className={classnames(drawerClassNames.base, {
292
+ [drawerClassNames.afterOpen]:
293
+ animationState === "afterOpen",
294
+ [drawerClassNames.beforeClose]:
295
+ animationState === "beforeClose",
296
+ })}
297
+ id={id}
298
+ onClick={(e) => e.stopPropagation()}
299
+ >
300
+ {children}
301
+ </div>
302
+ )
303
+ ) : (
304
+ <div
305
+ {...ariaProps}
306
+ {...dataProps}
307
+ {...htmlProps}
308
+ className={classes}
309
+ style={dynamicInlineProps}
310
+ >
202
311
  {isModalVisible && (
203
312
  <div
204
313
  className={classnames(overlayClassNames.base, {
205
- [overlayClassNames.afterOpen]: animationState === "afterOpen",
206
- [overlayClassNames.beforeClose]: animationState === "beforeClose",
207
- })}
314
+ [overlayClassNames.afterOpen]:
315
+ animationState === "afterOpen",
316
+ [overlayClassNames.beforeClose]:
317
+ animationState === "beforeClose",
318
+ })}
208
319
  id={id}
209
- onClick={overlay ? onClose : undefined}
320
+ onClick={overlay ? api.onClose : undefined}
210
321
  >
211
- <div
212
- className={classnames(drawerClassNames.base, {
213
- [drawerClassNames.afterOpen]: animationState === "afterOpen",
214
- [drawerClassNames.beforeClose]: animationState === "beforeClose",
215
- })}
216
- onClick={(e) => e.stopPropagation()}
217
- >
218
- {children}
219
- </div>
322
+ <div
323
+ className={classnames(drawerClassNames.base, {
324
+ [drawerClassNames.afterOpen]:
325
+ animationState === "afterOpen",
326
+ [drawerClassNames.beforeClose]:
327
+ animationState === "beforeClose",
328
+ })}
329
+ onClick={(e) => e.stopPropagation()}
330
+ >
331
+ {children}
332
+ </div>
220
333
  </div>
221
334
  )}
222
- </div>
335
+ </div>
336
+ )}
223
337
  </DialogContext.Provider>
224
338
  );
225
339
  };
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { Button, Drawer, Icon, Title } from "playbook-ui";
3
+
4
+ const DrawerMenu = () => {
5
+
6
+ return (
7
+ <>
8
+ <Button id="menuButton"
9
+ padding="sm"
10
+ >
11
+ <Icon icon="bars"
12
+ size="3x"
13
+ />
14
+ </Button>
15
+ <Drawer
16
+ behavior="push"
17
+ closeBreakpoint="md"
18
+ menuButtonID="menuButton"
19
+ overlay={false}
20
+ placement="left"
21
+ size="lg"
22
+ withinElement
23
+ >
24
+ <Title paddingBottom="md">A really neat menu</Title>
25
+ <Button text="This Button does nothing" />
26
+ </Drawer>
27
+ </>
28
+ );
29
+ };
30
+
31
+ export default DrawerMenu;
@@ -0,0 +1,6 @@
1
+ Our drawer kit can fulfill your responsive menu needs! Using the `closeBreakpoint` prop you can have the menu close on smaller screens like phones/tablets.
2
+
3
+ Set a menu button with the `menuButtonID` props. When the Drawer is open, the menu button will be hidden. But when your Brakpoint closes the drawer, you can toggle the Drawer open/close with your menu butotn.
4
+
5
+ Also use the `withinElement` props to have the Drawer open within a specific element, instead of the default behavior of it taking up the entire screen size.
6
+
@@ -6,7 +6,7 @@ examples:
6
6
 
7
7
  react:
8
8
  - drawer_default: Default
9
+ - drawer_menu: Menu Behavior
9
10
  - drawer_sizes: Sizes
10
11
  - drawer_overlay: Overlay
11
12
  - drawer_borders: Borders
12
- - drawer_breakpoints: Open on Breakpoints
@@ -3,3 +3,4 @@ export { default as DrawerSizes } from './_drawer_sizes.jsx'
3
3
  export { default as DrawerOverlay } from './_drawer_overlay.jsx'
4
4
  export { default as DrawerBorders } from './_drawer_borders.jsx'
5
5
  export { default as DrawerBreakpoints } from './_drawer_breakpoints.jsx'
6
+ export { default as DrawerMenu } from './_drawer_menu.jsx'
@@ -53,6 +53,7 @@
53
53
  }
54
54
 
55
55
  .pb_dropdown_container {
56
+ position: absolute;
56
57
  background-color: $white;
57
58
  overflow: hidden;
58
59
  box-shadow: $shadow_deep;
@@ -3,7 +3,6 @@
3
3
  class: object.classname,
4
4
  data: object.data,
5
5
  id: object.id,
6
- style: object.container_style,
7
6
  **combined_html_options) do %>
8
7
  <%= pb_rails("list", props: {ordered: false, borderless: false}) do %>
9
8
  <% if content.present? %>
@@ -7,10 +7,6 @@ module Playbook
7
7
  generate_classname("pb_dropdown_container", "close", separator: " ")
8
8
  end
9
9
 
10
- def container_style
11
- "position: absolute"
12
- end
13
-
14
10
  def data
15
11
  Hash(prop(:data)).merge(dropdown_container: true)
16
12
  end
@@ -18,9 +18,19 @@ export const separateChildComponents = (children: React.ReactChild[] | React.Rea
18
18
  const otherChildren: React.ReactChild[] = [];
19
19
 
20
20
  React.Children.forEach(children, (child) => {
21
- if (child && (child as ReactElement).type === DropdownTrigger) {
21
+ const element = child as ReactElement;
22
+ const childType = element?.type;
23
+ const childDisplayName = (childType as any)?.displayName || (childType as any)?.name;
24
+
25
+ if (
26
+ childType === DropdownTrigger ||
27
+ childDisplayName === "DropdownTrigger"
28
+ ) {
22
29
  trigger = child;
23
- } else if (child && (child as ReactElement).type === DropdownContainer) {
30
+ } else if (
31
+ childType === DropdownContainer ||
32
+ childDisplayName === "DropdownContainer"
33
+ ) {
24
34
  container = child;
25
35
  } else {
26
36
  otherChildren.push(child);
@@ -30,6 +40,7 @@ export const separateChildComponents = (children: React.ReactChild[] | React.Rea
30
40
  return { trigger, container, otherChildren };
31
41
  };
32
42
 
43
+
33
44
  export const prepareSubcomponents = ({
34
45
  children,
35
46
  hasTriggerSubcomponent,
@@ -23,7 +23,7 @@
23
23
  %>
24
24
 
25
25
  <%= pb_form_with(scope: :example, url: "", method: :get) do |form| %>
26
- <%= form.typeahead :example_user, props: { data: { typeahead_example1: true, user: {} }, placeholder: "Search for a user" } %>
26
+ <%= form.typeahead :example_typeahead, props: { data: { typeahead_example1: true, user: {} }, label: true, placeholder: "Search for a user" } %>
27
27
  <%= form.text_field :example_text_field, props: { label: true } %>
28
28
  <%= form.phone_number_field :example_phone_number_field, props: { label: "Example phone field" } %>
29
29
  <%= form.email_field :example_email_field, props: { label: true } %>
@@ -92,7 +92,7 @@
92
92
  const selectedUserData = JSON.parse(selectedUserJSON)
93
93
 
94
94
  // set the input field's value
95
- event.target.querySelector('input[name=example_user]').value = selectedUserData.login
95
+ event.target.querySelector('input[name=example_typeahead]').value = selectedUserData.login
96
96
 
97
97
  // log the selected option's dataset
98
98
  console.log('The selected user data:')
@@ -1,5 +1,5 @@
1
1
  <%= pb_form_with(scope: :example, url: "", method: :get, loading: true) do |form| %>
2
- <%= form.text_field :example_text_field, props: { label: true } %>
2
+ <%= form.text_field :example_text_field_loading, props: { label: true } %>
3
3
 
4
4
  <%= form.actions do |action| %>
5
5
  <%= action.submit %>
@@ -22,23 +22,74 @@
22
22
  %>
23
23
 
24
24
  <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
25
- <%= form.text_field :example_text_field, props: { label: true, required: true } %>
26
- <%= form.phone_number_field :example_phone_number_field, props: { label: "Example phone field" } %>
27
- <%= form.email_field :example_email_field, props: { label: true, required: true } %>
28
- <%= form.number_field :example_number_field, props: { label: true, required: true } %>
29
- <%= form.search_field :example_project_number, props: { label: true, required: true, validation: { pattern: "[0-9]{2}-[0-9]{5}", message: "Please enter a valid project number (example: 33-12345)." } } %>
30
- <%= form.password_field :example_password_field, props: { label: true, required: true } %>
31
- <%= form.url_field :example_url_field, props: { label: true, required: true } %>
32
- <%= form.text_area :example_text_area, props: { label: true, required: true } %>
33
- <%= form.dropdown_field :example_dropdown, props: { label: true, options: example_dropdown_options, required: true } %>
34
- <%= form.select :example_select, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %>
35
- <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %>
25
+ <%= form.typeahead :example_typeahead_validation, props: { data: { typeahead_example2: true, user: {} }, label: true, placeholder: "Search for a user", required: true, validation: { message: "Please select a user." } } %>
26
+ <%= form.text_field :example_text_field_validation, props: { label: true, required: true } %>
27
+ <%= form.phone_number_field :example_phone_number_field_validation, props: { label: "Example phone field" } %>
28
+ <%= form.email_field :example_email_field_validation, props: { label: true, required: true } %>
29
+ <%= form.number_field :example_number_field_validation, props: { label: true, required: true } %>
30
+ <%= form.search_field :example_project_number_validation, props: { label: true, required: true, validation: { pattern: "[0-9]{2}-[0-9]{5}", message: "Please enter a valid project number (example: 33-12345)." } } %>
31
+ <%= form.password_field :example_password_field_validation, props: { label: true, required: true } %>
32
+ <%= form.url_field :example_url_field_validation, props: { label: true, required: true } %>
33
+ <%= form.text_area :example_text_area_validation, props: { label: true, required: true } %>
34
+ <%= form.dropdown_field :example_dropdown_validation, props: { label: true, options: example_dropdown_options, required: true } %>
35
+ <%= form.select :example_select_validation, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %>
36
+ <%= form.collection_select :example_collection_select_validation, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %>
36
37
  <%= form.check_box :example_checkbox, props: { text: "Example Checkbox", label: true, required: true } %>
37
38
  <%= form.date_picker :example_date_picker_2, props: { label: true, required: true } %>
38
- <%= form.star_rating_field :example_star_rating, props: { variant: "interactive", label: true, required: true } %>
39
+ <%= form.star_rating_field :example_star_rating_validation, props: { variant: "interactive", label: true, required: true } %>
39
40
 
40
41
  <%= form.actions do |action| %>
41
42
  <%= action.submit %>
42
43
  <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %>
43
44
  <% end %>
44
45
  <% end %>
46
+
47
+ <!-- form.typeahead user results example template -->
48
+ <template data-typeahead-example-result-option>
49
+ <%= pb_rails("user", props: {
50
+ name: tag(:slot, name: "name"),
51
+ orientation: "horizontal",
52
+ align: "left",
53
+ avatar_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGP6zwAAAgcBApocMXEAAAAASUVORK5CYII=",
54
+ avatar: true
55
+ }) %>
56
+ </template>
57
+
58
+ <!-- form.typeahead JS example implementation -->
59
+ <%= javascript_tag defer: "defer" do %>
60
+ document.addEventListener("pb-typeahead-kit-search", function(event) {
61
+ if (!event.target.dataset || !event.target.dataset.typeaheadExample2) return
62
+
63
+ fetch(`https://api.github.com/search/users?q=${encodeURIComponent(event.detail.searchingFor)}`)
64
+ .then(response => response.json())
65
+ .then((result) => {
66
+ const resultOptionTemplate = document.querySelector("[data-typeahead-example-result-option]")
67
+
68
+ event.detail.setResults((result.items || []).map((user) => {
69
+ const wrapper = resultOptionTemplate.content.cloneNode(true)
70
+ wrapper.children[0].dataset.user = JSON.stringify(user)
71
+ wrapper.querySelector('slot[name="name"]').replaceWith(user.login)
72
+ wrapper.querySelector('img').dataset.src = user.avatar_url
73
+ return wrapper
74
+ }))
75
+ })
76
+ })
77
+
78
+
79
+ document.addEventListener("pb-typeahead-kit-result-option-selected", function(event) {
80
+ if (!event.target.dataset.typeaheadExample2) return
81
+
82
+ const selectedUserJSON = event.detail.selected.firstElementChild.dataset.user
83
+ const selectedUserData = JSON.parse(selectedUserJSON)
84
+
85
+ // set the input field's value
86
+ event.target.querySelector('input[name=example_typeahead_validation]').value = selectedUserData.login
87
+
88
+ // log the selected option's dataset
89
+ console.log('The selected user data:')
90
+ console.dir(selectedUserData)
91
+
92
+ // do even more with the data later - TBD
93
+ event.target.dataset.user = selectedUserJSON
94
+ })
95
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import classnames from 'classnames'
3
- import { GlobalProps, globalProps } from '../utilities/globalProps'
3
+ import { GlobalProps, globalProps, globalInlineProps } from '../utilities/globalProps'
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
 
6
6
  type ImageType = {
@@ -41,6 +41,7 @@ const Image = (props: ImageType): React.ReactElement => {
41
41
  globalProps(props),
42
42
  className
43
43
  )
44
+ const dynamicInlineProps = globalInlineProps(props)
44
45
  const dataProps = buildDataProps(data)
45
46
  const htmlProps = buildHtmlProps(htmlOptions)
46
47
 
@@ -56,6 +57,7 @@ const Image = (props: ImageType): React.ReactElement => {
56
57
  id={id}
57
58
  onError={onError}
58
59
  src={url}
60
+ style={dynamicInlineProps}
59
61
  />
60
62
  )
61
63
  }
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import classnames from 'classnames'
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
4
4
 
5
- import { globalProps } from '../utilities/globalProps'
5
+ import { GlobalProps, globalProps, globalInlineProps } from '../utilities/globalProps'
6
6
 
7
7
  type LayoutPropTypes = {
8
8
  aria?: {[key: string]: string},
@@ -19,7 +19,7 @@ type LayoutPropTypes = {
19
19
  variant?: "light" | "dark" | "gradient",
20
20
  transparent?: boolean,
21
21
  layout?: "sidebar" | "collection" | "kanban" | "content" | "masonry",
22
- }
22
+ } & GlobalProps
23
23
 
24
24
  type LayoutSideProps = {
25
25
  children: React.ReactNode[] | React.ReactNode,
@@ -159,6 +159,8 @@ const Layout = (props: LayoutPropTypes) => {
159
159
  const filteredProps = {...props}
160
160
  delete filteredProps?.position
161
161
 
162
+ const dynamicInlineProps = globalInlineProps(props)
163
+
162
164
  return (
163
165
  <div
164
166
  {...ariaProps}
@@ -171,7 +173,8 @@ const Layout = (props: LayoutPropTypes) => {
171
173
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
172
174
  //@ts-ignore
173
175
  globalProps(filteredProps)
174
- )}
176
+ )}
177
+ style={dynamicInlineProps}
175
178
  >
176
179
  {subComponentTags('Side')}
177
180
  {nonSideChildren}
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import classnames from 'classnames'
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
4
- import { globalProps } from '../utilities/globalProps'
4
+ import { globalProps, globalInlineProps } from '../utilities/globalProps'
5
5
  import OverlayPercentage from './subcomponents/_overlay_percentage'
6
6
  import OverlayToken from './subcomponents/_overlay_token'
7
7
 
@@ -39,6 +39,7 @@ const Overlay = (props: OverlayProps) => {
39
39
  const dataProps = buildDataProps(data)
40
40
  const classes = classnames(buildCss('pb_overlay'), globalProps(props), className)
41
41
  const htmlProps = buildHtmlProps(htmlOptions)
42
+ const dynamicInlineProps = globalInlineProps(props)
42
43
 
43
44
  const getPosition = () => {
44
45
  return Object.keys(layout)[0]
@@ -57,6 +58,7 @@ const Overlay = (props: OverlayProps) => {
57
58
  {...htmlProps}
58
59
  className={classes}
59
60
  id={id}
61
+ style={dynamicInlineProps}
60
62
  >
61
63
  {isSizePercentage ?
62
64
  OverlayPercentage({
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from "react";
2
2
  import classnames from 'classnames'
3
- import { globalProps } from '../utilities/globalProps'
3
+ import { GlobalProps, globalProps } from '../utilities/globalProps'
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import Icon from '../pb_icon/_icon';
6
6
 
@@ -14,7 +14,7 @@ type PaginationProps = {
14
14
  onChange?: (pageNumber: number) => void;
15
15
  range?: number;
16
16
  total?: number;
17
- };
17
+ } & GlobalProps;
18
18
 
19
19
  const Pagination = ( props: PaginationProps) => {
20
20
  const {