playbook_ui 13.30.0 → 13.31.0.pre.alpha.PBNTR342navtabbing3231

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.html.erb +59 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.md +6 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +4 -4
  6. data/app/pb_kits/playbook/pb_button/_button.scss +3 -3
  7. data/app/pb_kits/playbook/pb_button/_button_mixins.scss +3 -2
  8. data/app/pb_kits/playbook/pb_caption/_caption_mixin.scss +1 -1
  9. data/app/pb_kits/playbook/pb_card/_card.tsx +4 -3
  10. data/app/pb_kits/playbook/pb_collapsible/index.js +6 -1
  11. data/app/pb_kits/playbook/pb_dashboard/commonSettings.js +1 -1
  12. data/app/pb_kits/playbook/pb_dashboard/pbChartsDarkTheme.ts +1 -1
  13. data/app/pb_kits/playbook/pb_dashboard/pbChartsLightTheme.ts +1 -1
  14. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_date_time_stacked_default_swift.md +33 -0
  15. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_date_time_stacked_props_swift.md +18 -0
  16. data/app/pb_kits/playbook/pb_date_time_stacked/docs/example.yml +6 -1
  17. data/app/pb_kits/playbook/pb_date_year_stacked/docs/_date_year_stacked_default.jsx +4 -1
  18. data/app/pb_kits/playbook/pb_draggable/_draggable.scss +2 -4
  19. data/app/pb_kits/playbook/pb_draggable/context/index.tsx +84 -50
  20. data/app/pb_kits/playbook/pb_draggable/context/types.ts +31 -0
  21. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.jsx +15 -19
  22. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.md +5 -3
  23. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_multiple_containers.jsx +5 -1
  24. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_cards.jsx +11 -4
  25. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_cards.md +8 -2
  26. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list.jsx +2 -2
  27. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list.md +5 -1
  28. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_selectable_list.jsx +6 -4
  29. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_selectable_list.md +5 -0
  30. data/app/pb_kits/playbook/pb_draggable/draggable.test.jsx +142 -15
  31. data/app/pb_kits/playbook/pb_draggable/subcomponents/DraggableItem.tsx +6 -5
  32. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +18 -0
  33. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +228 -218
  34. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_error.html.erb +9 -0
  35. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_error.jsx +34 -0
  36. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +2 -0
  37. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -1
  38. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +16 -12
  39. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
  40. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +17 -2
  41. data/app/pb_kits/playbook/pb_dropdown/index.js +33 -4
  42. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +9 -3
  43. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_custom_icon.html.erb +32 -0
  44. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_custom_icon.jsx +48 -0
  45. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
  46. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +1 -0
  47. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +16 -11
  48. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +72 -0
  49. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +9 -0
  50. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +1 -1
  51. data/app/pb_kits/playbook/pb_icon/_icon.tsx +37 -6
  52. data/app/pb_kits/playbook/pb_icon/docs/_icon_default.html.erb +1 -1
  53. data/app/pb_kits/playbook/pb_icon/docs/_icon_default.jsx +2 -3
  54. data/app/pb_kits/playbook/pb_icon/icon.rb +21 -1
  55. data/app/pb_kits/playbook/pb_icon/icon_aliases.json +39 -0
  56. data/app/pb_kits/playbook/pb_list/_list.tsx +4 -4
  57. data/app/pb_kits/playbook/pb_list/_list_item.tsx +7 -3
  58. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav.html.erb +1 -1
  59. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav.jsx +1 -0
  60. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav.md +3 -1
  61. data/app/pb_kits/playbook/pb_nav/docs/_tab_nav.html.erb +12 -0
  62. data/app/pb_kits/playbook/pb_nav/docs/_tab_nav.html.md +0 -0
  63. data/app/pb_kits/playbook/pb_nav/docs/example.yml +1 -0
  64. data/app/pb_kits/playbook/pb_nav/index.js +43 -0
  65. data/app/pb_kits/playbook/pb_nav/item.html.erb +1 -1
  66. data/app/pb_kits/playbook/pb_nav/item.rb +1 -0
  67. data/app/pb_kits/playbook/pb_nav/nav.rb +9 -0
  68. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_default.html.erb +24 -0
  69. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_default.md +4 -4
  70. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_multi_directional.html.erb +11 -0
  71. data/app/pb_kits/playbook/pb_overlay/docs/example.yml +4 -0
  72. data/app/pb_kits/playbook/pb_overlay/overlay.html.erb +27 -0
  73. data/app/pb_kits/playbook/pb_overlay/overlay.rb +110 -0
  74. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +8 -23
  75. data/app/pb_kits/playbook/pb_selectable_list/_item.tsx +7 -3
  76. data/app/pb_kits/playbook/pb_selectable_list/_selectable_list.tsx +3 -3
  77. data/app/pb_kits/playbook/pb_table/table_header.html.erb +16 -2
  78. data/app/pb_kits/playbook/pb_timeline/_item.tsx +11 -10
  79. data/app/pb_kits/playbook/pb_timeline/_timeline.tsx +8 -6
  80. data/app/pb_kits/playbook/playbook-rails.js +3 -0
  81. data/app/pb_kits/playbook/tokens/_titles.scss +4 -4
  82. data/app/pb_kits/playbook/tokens/_typography.scss +10 -10
  83. data/app/pb_kits/playbook/utilities/globalProps.ts +1 -0
  84. data/dist/menu.yml +566 -472
  85. data/dist/playbook-rails.js +7 -7
  86. data/dist/reset.css +1 -1
  87. data/lib/playbook/version.rb +2 -2
  88. metadata +24 -5
@@ -8,16 +8,22 @@ const DOWN_ARROW_SELECTOR = "#dropdown_open_icon";
8
8
  const UP_ARROW_SELECTOR = "#dropdown_close_icon";
9
9
  const OPTION_SELECTOR = "[data-dropdown-option-label]";
10
10
  const CUSTOM_DISPLAY_SELECTOR = "[data-dropdown-custom-trigger]";
11
+ const INPUT_FORM_VALIDATION = "#dropdown-form-validation";
11
12
 
12
13
  export default class PbDropdown extends PbEnhancedElement {
13
14
  static get selector() {
14
15
  return DROPDOWN_SELECTOR;
15
16
  }
16
17
 
18
+ get target() {
19
+ return this.element.parentNode.querySelector(CONTAINER_SELECTOR);
20
+ }
21
+
17
22
  connect() {
18
23
  this.keyboardHandler = new PbDropdownKeyboard(this);
19
24
  this.bindEventListeners();
20
25
  this.updateArrowDisplay(false);
26
+ this.handleFormValidation();
21
27
  }
22
28
 
23
29
  bindEventListeners() {
@@ -38,9 +44,13 @@ export default class PbDropdown extends PbEnhancedElement {
38
44
  handleOptionClick(event) {
39
45
  const option = event.target.closest(OPTION_SELECTOR);
40
46
  const hiddenInput = this.element.querySelector("#dropdown-selected-option");
47
+ const inputFormValidation = this.element.querySelector(INPUT_FORM_VALIDATION);
48
+
41
49
  if (option) {
42
50
  const value = option.dataset.dropdownOptionLabel;
43
51
  hiddenInput.value = JSON.parse(value).id;
52
+ inputFormValidation.value = JSON.parse(value).id;
53
+ this.clearFormValidation(inputFormValidation);
44
54
  this.onOptionSelected(value, option);
45
55
  }
46
56
  }
@@ -103,10 +113,6 @@ export default class PbDropdown extends PbEnhancedElement {
103
113
  selectedOption.classList.add("pb_dropdown_option_selected");
104
114
  }
105
115
 
106
- get target() {
107
- return this.element.parentNode.querySelector(CONTAINER_SELECTOR);
108
- }
109
-
110
116
  showElement(elem) {
111
117
  elem.classList.remove("close");
112
118
  elem.classList.add("open");
@@ -150,4 +156,27 @@ export default class PbDropdown extends PbEnhancedElement {
150
156
  upArrow.style.display = isOpen ? "inline-block" : "none";
151
157
  }
152
158
  }
159
+
160
+ handleFormValidation() {
161
+ const inputFormValidation = this.element.querySelector(INPUT_FORM_VALIDATION);
162
+
163
+ inputFormValidation.addEventListener("invalid", function (event) {
164
+ if (inputFormValidation.hasAttribute("required") && inputFormValidation.value === "") {
165
+ event.preventDefault();
166
+ inputFormValidation.closest(".dropdown_wrapper").classList.add("error");
167
+ }
168
+ }, true);
169
+ }
170
+
171
+ clearFormValidation(input) {
172
+ if (input.checkValidity()) {
173
+ const dropdownWrapperElement = input.closest(".dropdown_wrapper");
174
+ dropdownWrapperElement.classList.remove("error");
175
+
176
+ const errorLabelElement = dropdownWrapperElement.querySelector(".pb_body_kit_negative");
177
+ if (errorLabelElement) {
178
+ errorLabelElement.remove();
179
+ }
180
+ }
181
+ }
153
182
  }
@@ -23,6 +23,7 @@ type FixedConfirmationToastProps = {
23
23
  data?: string;
24
24
  horizontal?: "right" | "left" | "center";
25
25
  htmlOptions?: { [key: string]: string | number | boolean | (VoidCallback) };
26
+ icon?: string,
26
27
  id?: string;
27
28
  multiLine?: boolean;
28
29
  onClose?: VoidCallback;
@@ -41,6 +42,7 @@ const FixedConfirmationToast = (props: FixedConfirmationToastProps): React.React
41
42
  closeable = false,
42
43
  horizontal,
43
44
  htmlOptions = {},
45
+ icon,
44
46
  multiLine = false,
45
47
  onClose = () => undefined,
46
48
  open = true,
@@ -48,14 +50,18 @@ const FixedConfirmationToast = (props: FixedConfirmationToastProps): React.React
48
50
  text,
49
51
  vertical,
50
52
  } = props;
53
+
54
+ const returnedIcon = icon || iconMap[status]
55
+ const iconClass = icon ? "custom_icon" : ""
56
+
51
57
  const css = classnames(
52
58
  `pb_fixed_confirmation_toast_kit_${status}`,
53
59
  { _multi_line: multiLine },
54
60
  { [`positioned_toast ${vertical} ${horizontal}`]: vertical && horizontal },
61
+ `${iconClass}`,
55
62
  globalProps(props),
56
63
  className
57
64
  );
58
- const icon = iconMap[status];
59
65
 
60
66
  const htmlProps = buildHtmlProps(htmlOptions);
61
67
 
@@ -86,11 +92,11 @@ const FixedConfirmationToast = (props: FixedConfirmationToastProps): React.React
86
92
  onClick={handleClick}
87
93
  {...htmlProps}
88
94
  >
89
- {icon && (
95
+ {returnedIcon && (
90
96
  <Icon
91
97
  className="pb_icon"
92
98
  fixedWidth
93
- icon={icon}
99
+ icon={returnedIcon}
94
100
  />
95
101
  )}
96
102
 
@@ -0,0 +1,32 @@
1
+ <div>
2
+ <%= pb_rails("fixed_confirmation_toast", props: {
3
+ icon: "wrench",
4
+ text: "Fix before proceeding",
5
+ status: "error",
6
+ closeable: true,
7
+ margin_bottom: "md"
8
+ })%>
9
+ </div>
10
+ <div>
11
+ <%= pb_rails("fixed_confirmation_toast", props: {
12
+ icon: "star",
13
+ text: "Thank you for completing the form!",
14
+ status: "success",
15
+ margin_bottom: "md"
16
+ })%>
17
+ </div>
18
+ <div>
19
+ <%= pb_rails("fixed_confirmation_toast", props: {
20
+ icon: "file-pdf",
21
+ text: "Saved as PDF",
22
+ status: "neutral",
23
+ margin_bottom: "md"
24
+ })%>
25
+ </div>
26
+ <div>
27
+ <%= pb_rails("fixed_confirmation_toast", props: {
28
+ icon: "arrow-down",
29
+ text: "New Messages",
30
+ status: "tip"
31
+ })%>
32
+ </div>
@@ -0,0 +1,48 @@
1
+ import React from 'react'
2
+
3
+ import FixedConfirmationToast from '../_fixed_confirmation_toast'
4
+
5
+ const FixedConfirmationToastCustomIcon = (props) => {
6
+ return (
7
+ <div>
8
+ <div>
9
+ <FixedConfirmationToast
10
+ closeable
11
+ icon="wrench"
12
+ marginBottom="md"
13
+ status="error"
14
+ text="Fix before proceeding"
15
+ {...props}
16
+ />
17
+ </div>
18
+ <div>
19
+ <FixedConfirmationToast
20
+ icon="star"
21
+ marginBottom="md"
22
+ status="success"
23
+ text="Thank you for completing the form!"
24
+ {...props}
25
+ />
26
+ </div>
27
+ <div>
28
+ <FixedConfirmationToast
29
+ icon="file-pdf"
30
+ marginBottom="md"
31
+ status="neutral"
32
+ text="Saved as PDF"
33
+ {...props}
34
+ />
35
+ </div>
36
+ <div>
37
+ <FixedConfirmationToast
38
+ icon="arrow-down"
39
+ status="tip"
40
+ text="New Messages"
41
+ {...props}
42
+ />
43
+ </div>
44
+ </div>
45
+ )
46
+ }
47
+
48
+ export default FixedConfirmationToastCustomIcon
@@ -6,6 +6,7 @@ examples:
6
6
  - fixed_confirmation_toast_close: Click to Close
7
7
  - fixed_confirmation_toast_positions: Click to Show Positions
8
8
  - fixed_confirmation_toast_children: Children
9
+ - fixed_confirmation_toast_custom_icon: Custom Icon
9
10
 
10
11
  react:
11
12
  - fixed_confirmation_toast_default: Default
@@ -14,6 +15,7 @@ examples:
14
15
  - fixed_confirmation_toast_positions: Click to Show Positions
15
16
  - fixed_confirmation_toast_auto_close: Click to Show Auto Close
16
17
  - fixed_confirmation_toast_children: Children
18
+ - fixed_confirmation_toast_custom_icon: Custom Icon
17
19
 
18
20
  swift:
19
21
  - fixed_confirmation_toast_default_swift: Default
@@ -4,3 +4,4 @@ export { default as FixedConfirmationToastClose } from './_fixed_confirmation_to
4
4
  export { default as FixedConfirmationToastPositions } from './_fixed_confirmation_toast_positions.jsx'
5
5
  export { default as FixedConfirmationToastAutoClose } from './_fixed_confirmation_toast_auto_close.jsx'
6
6
  export { default as FixedConfirmationToastChildren } from './_fixed_confirmation_toast_children.jsx'
7
+ export { default as FixedConfirmationToastCustomIcon } from './_fixed_confirmation_toast_custom_icon.jsx'
@@ -20,6 +20,7 @@ module Playbook
20
20
  values: [nil, "top", "bottom"],
21
21
  default: nil
22
22
  prop :auto_close, type: Playbook::Props::Number
23
+ prop :icon, type: Playbook::Props::String
23
24
 
24
25
  def show_text?
25
26
  text.present?
@@ -42,20 +43,24 @@ module Playbook
42
43
  end
43
44
 
44
45
  def icon_value
45
- case status
46
- when "success"
47
- "check"
48
- when "error"
49
- "exclamation-triangle"
50
- when "neutral"
51
- "info-circle"
52
- when "tip"
53
- "info-circle"
54
- end
46
+ icon || case status
47
+ when "success"
48
+ "check"
49
+ when "error"
50
+ "exclamation-triangle"
51
+ when "neutral"
52
+ "info-circle"
53
+ when "tip"
54
+ "info-circle"
55
+ end
56
+ end
57
+
58
+ def icon_class
59
+ icon.present? ? " custom_icon" : ""
55
60
  end
56
61
 
57
62
  def classname
58
- generate_classname("pb_fixed_confirmation_toast_kit", status, multi_line_class) + close_class + position_class + auto_close_class
63
+ generate_classname("pb_fixed_confirmation_toast_kit", status, multi_line_class) + close_class + position_class + auto_close_class + icon_class
59
64
  end
60
65
  end
61
66
  end
@@ -0,0 +1,72 @@
1
+ import React from 'react'
2
+ import { render, waitFor } from '../utilities/test-utils'
3
+
4
+ import { FixedConfirmationToast } from '../'
5
+
6
+ beforeEach(() => {
7
+ // Silences error logs within the test suite.
8
+ jest.spyOn(console, 'error')
9
+ // eslint-disable-next-line
10
+ console.error.mockImplementation(() => {})
11
+ })
12
+
13
+ afterEach(() => {
14
+ // eslint-disable-next-line
15
+ console.error.mockRestore()
16
+ })
17
+
18
+ test('renders with default props', () => {
19
+ const { container } = render(<FixedConfirmationToast />);
20
+ expect(container.firstChild).toBeInTheDocument();
21
+ });
22
+
23
+ test('renders with text', () => {
24
+ const { getByText } = render(<FixedConfirmationToast text="Message Text" />);
25
+ expect(getByText('Message Text')).toBeInTheDocument();
26
+ });
27
+
28
+ test('does not render if open prop is false', () => {
29
+ const { container } = render(<FixedConfirmationToast open={false} />);
30
+ expect(container.firstChild).toBeNull();
31
+ });
32
+
33
+ test('closes after autoClose duration', async () => {
34
+ jest.useFakeTimers();
35
+ const onCloseMock = jest.fn();
36
+ render(
37
+ <FixedConfirmationToast
38
+ autoClose={1000}
39
+ onClose={onCloseMock}
40
+ open
41
+ />
42
+ );
43
+
44
+ jest.advanceTimersByTime(1000);
45
+ await waitFor(() => expect(onCloseMock).toHaveBeenCalled());
46
+ });
47
+
48
+ test('renders success status with icon', () => {
49
+ const { container } = render(<FixedConfirmationToast status="success" />);
50
+ expect(container.querySelector('.pb_fixed_confirmation_toast_kit_success')).toBeInTheDocument();
51
+ expect(container.querySelector('.pb_icon')).toBeInTheDocument();
52
+ });
53
+
54
+ test('renders custom icon when provided', () => {
55
+ const { container } = render(<FixedConfirmationToast icon="wrench" />);
56
+ expect(container.querySelector('.custom_icon')).toBeInTheDocument();
57
+ });
58
+
59
+ test('renders correctly with multiLine prop', () => {
60
+ const { container } = render(<FixedConfirmationToast multiLine />);
61
+ expect(container.querySelector('._multi_line')).toBeInTheDocument();
62
+ });
63
+
64
+ test('renders position when provided', () => {
65
+ const { container } = render(
66
+ <FixedConfirmationToast
67
+ horizontal="right"
68
+ vertical="bottom"
69
+ />
70
+ );
71
+ expect(container.querySelector('.positioned_toast')).toBeInTheDocument();
72
+ });
@@ -13,6 +13,14 @@
13
13
  ]
14
14
  %>
15
15
 
16
+ <%
17
+ example_dropdown_options = [
18
+ { label: 'United States', value: 'United States', id: 'us' },
19
+ { label: 'Canada', value: 'Canada', id: 'ca' },
20
+ { label: 'Pakistan', value: 'Pakistan', id: 'pk' },
21
+ ]
22
+ %>
23
+
16
24
  <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
17
25
  <%= form.text_field :example_text_field, props: { label: true, required: true } %>
18
26
  <%= form.phone_number_field :example_phone_number_field, props: { label: "Example phone field" } %>
@@ -22,6 +30,7 @@
22
30
  <%= form.password_field :example_password_field, props: { label: true, required: true } %>
23
31
  <%= form.url_field :example_url_field, props: { label: true, required: true } %>
24
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 } %>
25
34
  <%= form.select :example_select, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %>
26
35
  <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %>
27
36
  <%= form.check_box :example_checkbox, props: { text: "Example Checkbox", label: true, required: true } %>
@@ -55,7 +55,7 @@ $form_pill_colors: (
55
55
  height: fit-content;
56
56
  height: -moz-fit-content;
57
57
  .pb_form_pill_text, .pb_form_pill_close, .pb_form_pill_tag {
58
- font-size: 16px;
58
+ font-size: $font_base;
59
59
  font-weight: $regular;
60
60
  }
61
61
  .pb_form_pill_text, .pb_form_pill_tag {
@@ -3,6 +3,7 @@ import classnames from 'classnames'
3
3
  import { buildAriaProps, buildDataProps, buildHtmlProps } from '../utilities/props'
4
4
  import { GlobalProps, globalProps } from '../utilities/globalProps'
5
5
  import { isValidEmoji } from '../utilities/validEmojiChecker'
6
+ import aliasesJson from './icon_aliases.json'
6
7
 
7
8
  export type IconSizes = "lg"
8
9
  | "xs"
@@ -40,6 +41,19 @@ type IconProps = {
40
41
  spin?: boolean,
41
42
  } & GlobalProps
42
43
 
44
+ type AliasType = string | string[];
45
+
46
+ interface Aliases {
47
+ [key: string]: AliasType;
48
+ }
49
+
50
+ interface AliasesJson {
51
+ aliases: Aliases;
52
+ }
53
+
54
+ const aliases: AliasesJson = aliasesJson;
55
+
56
+
43
57
  const flipMap = {
44
58
  horizontal: 'fa-flip-horizontal',
45
59
  vertical: 'fa-flip-vertical',
@@ -52,6 +66,22 @@ declare global {
52
66
  var PB_ICONS: {[key: string]: React.FunctionComponent<any>}
53
67
  }
54
68
 
69
+ // Resolve alias function
70
+ const resolveAlias = (icon: string): string => {
71
+ const alias = aliases.aliases[icon];
72
+
73
+ if (alias) {
74
+ if (Array.isArray(alias)) {
75
+ return alias[0];
76
+ } else {
77
+ return alias;
78
+ }
79
+ }
80
+
81
+ return icon;
82
+ };
83
+
84
+
55
85
  const Icon = (props: IconProps) => {
56
86
  const {
57
87
  aria = {},
@@ -74,7 +104,8 @@ const Icon = (props: IconProps) => {
74
104
  spin = false,
75
105
  } = props
76
106
 
77
- let iconElement: ReactSVGElement | null = typeof(icon) === "object" ? icon : null
107
+ const resolvedIcon = resolveAlias(icon as string)
108
+ let iconElement: ReactSVGElement | null = typeof(resolvedIcon) === "object" ? resolvedIcon : null
78
109
 
79
110
  const faClasses = {
80
111
  'fa-border': border,
@@ -90,12 +121,12 @@ const Icon = (props: IconProps) => {
90
121
 
91
122
  if (!customIcon && !iconElement) {
92
123
  const PowerIcon: React.FunctionComponent<any> | undefined =
93
- window.PB_ICONS ? window.PB_ICONS[icon as string] : null
124
+ window.PB_ICONS ? window.PB_ICONS[resolvedIcon as string] : null
94
125
 
95
126
  if (PowerIcon) {
96
127
  iconElement = <PowerIcon /> as ReactSVGElement
97
128
  } else {
98
- faClasses[`fa-${icon}`] = icon as string
129
+ faClasses[`fa-${resolvedIcon}`] = resolvedIcon as string
99
130
  }
100
131
  }
101
132
 
@@ -115,7 +146,7 @@ const Icon = (props: IconProps) => {
115
146
  className
116
147
  )
117
148
 
118
- aria.label ? null : aria.label = `${icon} icon`
149
+ aria.label ? null : aria.label = `${resolvedIcon} icon`
119
150
  const ariaProps: {[key: string]: any} = buildAriaProps(aria)
120
151
  const dataProps: {[key: string]: any} = buildDataProps(data)
121
152
  const htmlProps = buildHtmlProps(htmlOptions)
@@ -137,7 +168,7 @@ const Icon = (props: IconProps) => {
137
168
  }
138
169
  </>
139
170
  )
140
- else if (isValidEmoji(icon as string))
171
+ else if (isValidEmoji(resolvedIcon as string))
141
172
  return (
142
173
  <>
143
174
  <span
@@ -146,7 +177,7 @@ const Icon = (props: IconProps) => {
146
177
  className={classesEmoji}
147
178
  id={id}
148
179
  >
149
- {icon}
180
+ {resolvedIcon}
150
181
  </span>
151
182
  </>
152
183
  )
@@ -1 +1 @@
1
- <%= pb_rails("icon", props: { icon: "user", fixed_width: true }) %>
1
+ <%= pb_rails("icon", props: { icon: "user", fixed_width: true }) %>
@@ -1,6 +1,5 @@
1
- import React from 'react'
2
-
3
- import Icon from '../_icon'
1
+ import React from "react"
2
+ import Icon from "../_icon"
4
3
 
5
4
  const IconDefault = (props) => {
6
5
  return (
@@ -3,6 +3,7 @@
3
3
  # rubocop:disable Style/HashLikeCase
4
4
 
5
5
  require "open-uri"
6
+ require "json"
6
7
 
7
8
  module Playbook
8
9
  module PbIcon
@@ -38,6 +39,8 @@ module Playbook
38
39
  prop :spin, type: Playbook::Props::Boolean,
39
40
  default: false
40
41
 
42
+ ALIASES = JSON.parse(File.read(Playbook::Engine.root.join("app/pb_kits/playbook/pb_icon/icon_aliases.json")))["aliases"].freeze
43
+
41
44
  def valid_emoji?
42
45
  emoji_regex = /\p{Emoji}/
43
46
  emoji_regex.match?(icon)
@@ -83,7 +86,8 @@ module Playbook
83
86
  return unless Rails.application.config.respond_to?(:icon_path)
84
87
 
85
88
  base_path = Rails.application.config.icon_path
86
- icon_path = Dir.glob(Rails.root.join(base_path, "**", "#{icon}.svg")).first
89
+ resolved_icon = resolve_alias(icon)
90
+ icon_path = Dir.glob(Rails.root.join(base_path, "**", "#{resolved_icon}.svg")).first
87
91
  icon_path if icon_path && File.exist?(icon_path)
88
92
  end
89
93
 
@@ -106,6 +110,22 @@ module Playbook
106
110
 
107
111
  private
108
112
 
113
+ def resolve_alias(icon)
114
+ aliases = ALIASES[icon]
115
+ return icon unless aliases
116
+
117
+ if aliases.is_a?(Array)
118
+ aliases.find { |alias_name| file_exists?(alias_name) } || icon
119
+ else
120
+ aliases
121
+ end
122
+ end
123
+
124
+ def file_exists?(alias_name)
125
+ base_path = Rails.application.config.icon_path
126
+ File.exist?(Dir.glob(Rails.root.join(base_path, "**", "#{alias_name}.svg")).first)
127
+ end
128
+
109
129
  def svg_size
110
130
  size.nil? ? "1x" : size
111
131
  end
@@ -0,0 +1,39 @@
1
+ {
2
+ "aliases": {
3
+ "arrow-alt-circle-right": "circle-right",
4
+ "angles-down": "angle-double-down",
5
+ "arrow-alt-down": "down",
6
+ "arrow-alt-up": "up",
7
+ "arrow-right-long": "long-arrow-right",
8
+ "arrow-to-bottom": "arrow-down-to-line",
9
+ "arrows-h": "arrows-left-right",
10
+ "calendar-days": "calendar-alt",
11
+ "circle-arrow-right": "arrow-circle-right",
12
+ "clock-rotate-left": "history",
13
+ "close": [
14
+ "times",
15
+ "xmark"
16
+ ],
17
+ "ellipsis-h": "ellipsis",
18
+ "exclamation-circle": "circle-exclamation",
19
+ "external-link": "arrow-up-right-from-square",
20
+ "file-lines": "file-alt",
21
+ "gear": "cog",
22
+ "home": "house",
23
+ "info-circle": "circle-info",
24
+ "map-o": "map",
25
+ "message": "comment-alt",
26
+ "minus-circle": "circle-minus",
27
+ "money": "money-bill",
28
+ "mouse-pointer": "arrow-pointer",
29
+ "nitro": "nitro-n",
30
+ "play-circle": "circle-play",
31
+ "plus-circle": "circle-plus",
32
+ "plus-square": "square-plus",
33
+ "powergon": "powergon-p",
34
+ "question-circle": "circle-question",
35
+ "roofing": "product-roofing",
36
+ "shelves": "inventory",
37
+ "th-list": "table-list"
38
+ }
39
+ }
@@ -10,7 +10,7 @@ type ListProps = {
10
10
  className?: string;
11
11
  children: React.ReactNode[] | React.ReactNode;
12
12
  dark?: boolean;
13
- draggable?: boolean;
13
+ enableDrag?: boolean;
14
14
  data?: Record<string, unknown>;
15
15
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
16
16
  id?: string;
@@ -32,7 +32,7 @@ const List = (props: ListProps) => {
32
32
  className,
33
33
  dark = false,
34
34
  data = {},
35
- draggable = false,
35
+ enableDrag = false,
36
36
  htmlOptions = {},
37
37
  id,
38
38
  layout = "",
@@ -54,7 +54,7 @@ const List = (props: ListProps) => {
54
54
  const childrenWithProps = React.Children.map(
55
55
  children,
56
56
  (child: React.ReactElement) => {
57
- return React.cloneElement(child, { text, variant, draggable });
57
+ return React.cloneElement(child, { text, variant, enableDrag });
58
58
  }
59
59
  );
60
60
  const ariaProps = buildAriaProps(aria);
@@ -74,7 +74,7 @@ const List = (props: ListProps) => {
74
74
  return (
75
75
  <>
76
76
  {
77
- draggable ? (
77
+ enableDrag ? (
78
78
  <Draggable.Container>
79
79
  <div className={classes}>
80
80
  {ordered ? (
@@ -11,6 +11,7 @@ type ListItemProps = {
11
11
  children: React.ReactNode[] | React.ReactNode,
12
12
  className?: string,
13
13
  data?: Record<string, unknown>,
14
+ dragId?: string,
14
15
  dragHandle?: boolean,
15
16
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
16
17
  id?: string,
@@ -23,7 +24,8 @@ const ListItem = (props: ListItemProps) => {
23
24
  children,
24
25
  className,
25
26
  data = {},
26
- draggable = false,
27
+ enableDrag,
28
+ dragId,
27
29
  dragHandle = true,
28
30
  htmlOptions = {},
29
31
  id,
@@ -42,8 +44,10 @@ const ListItem = (props: ListItemProps) => {
42
44
  return (
43
45
  <>
44
46
  {
45
- draggable ? (
46
- <Draggable.Item id={id}>
47
+ enableDrag ? (
48
+ <Draggable.Item
49
+ dragId={dragId}
50
+ >
47
51
  <li
48
52
  {...ariaProps}
49
53
  {...dataProps}
@@ -1,5 +1,5 @@
1
1
  <%= pb_rails("nav", props: { variant: "bold" }) do %>
2
- <%= pb_rails("nav/item", props: { text: "Overview", link: "#", collapsible: true, icon_left:"city" }) do %>
2
+ <%= pb_rails("nav/item", props: { text: "Overview", link: "#", collapsible: true, icon_left:"city", collapsed: false }) do %>
3
3
  <%= pb_rails("nav", props: { variant: "bold" }) do %>
4
4
  <%= pb_rails("nav/item", props: { text: "City", link: "#" }) %>
5
5
  <%= pb_rails("nav/item", props: { text: "People", link: "#" }) %>
@@ -9,6 +9,7 @@ const CollapsibleNav = (props) => {
9
9
  >
10
10
  <NavItem
11
11
  active
12
+ collapsed={false}
12
13
  collapsible
13
14
  iconLeft="city"
14
15
  link="#"
@@ -1 +1,3 @@
1
- The `collapsible` prop allows users to create a nested, collapsible nav. Pass `collapsible` to any NavItem and pass that navItem any number of NavItems as children to create a collapsible nav.
1
+ The `collapsible` prop allows users to create a nested, collapsible nav. Pass `collapsible` to any NavItem and pass that navItem any number of NavItems as children to create a collapsible nav.
2
+
3
+ The optional `collapsed` prop can also be used to set the default state for the collapsed nav on first render of the page. `collapsed` takes a boolean value that is set to true (meaning nav is collapsed) by default. Set it to false as shown here to have the nav open on first render.