playbook_ui 15.7.0.pre.rc.1 → 15.7.0.pre.rc.2

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +3 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +4 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +95 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_colors_rails.html.erb +43 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_colors_rails.md +1 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_control_rails.html.erb +11 -5
  8. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_control_rails.md +7 -1
  9. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background.jsx +54 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background.md +9 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_multi.jsx +80 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_multi.md +3 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +4 -1
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +3 -1
  15. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +2 -2
  16. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +57 -0
  17. data/app/pb_kits/playbook/pb_contact/_contact.tsx +51 -24
  18. data/app/pb_kits/playbook/pb_contact/contact.html.erb +53 -19
  19. data/app/pb_kits/playbook/pb_contact/contact.rb +11 -1
  20. data/app/pb_kits/playbook/pb_contact/contact.test.js +76 -0
  21. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled.html.erb +33 -0
  22. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled.jsx +46 -0
  23. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled_rails.md +2 -0
  24. data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled_react.md +2 -0
  25. data/app/pb_kits/playbook/pb_contact/docs/example.yml +2 -0
  26. data/app/pb_kits/playbook/pb_contact/docs/index.js +1 -0
  27. data/app/pb_kits/playbook/pb_dialog/_dialog.tsx +2 -1
  28. data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +1 -1
  29. data/app/pb_kits/playbook/pb_dialog/dialog.rb +1 -0
  30. data/app/pb_kits/playbook/pb_dialog/dialog.test.jsx +14 -0
  31. data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +5 -4
  32. data/app/pb_kits/playbook/pb_dialog/dialog_header.rb +2 -0
  33. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.html.erb +24 -0
  34. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.jsx +60 -0
  35. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.md +3 -0
  36. data/app/pb_kits/playbook/pb_dialog/docs/example.yml +2 -0
  37. data/app/pb_kits/playbook/pb_dialog/docs/index.js +2 -1
  38. data/dist/chunks/vendor.js +2 -2
  39. data/dist/menu.yml +1 -1
  40. data/lib/playbook/version.rb +1 -1
  41. metadata +15 -2
@@ -1,24 +1,23 @@
1
- <%= pb_content_tag do %>
2
- <%= pb_rails("body", props: {
3
- tag: "span",
4
- classname: "pb_contact_kit",
5
- color: "light",
6
- dark: object.dark
7
- }) do %>
8
- <% if contact_type == "email" %>
9
- <%= pb_rails("icon", props: {
10
- custom_icon: Playbook::Engine::root.join(envelope_path),
11
- fixed_width: true,
12
- dark: object.dark
13
- }) %>
1
+ <% if object.unstyled %>
2
+ <%= content_tag :span, class: object.classname, id: object.id, data: object.data, aria: object.aria, **object.html_options do %>
3
+ <% if icon_enabled %>
4
+ <% if contact_type == "email" %>
5
+ <%= pb_rails("icon", props: {
6
+ custom_icon: Playbook::Engine::root.join(envelope_path),
7
+ fixed_width: true,
8
+ dark: object.dark
9
+ }) %>
10
+ <% else %>
11
+ <%= pb_rails("icon", props: {
12
+ icon: object.contact_icon,
13
+ fixed_width: true,
14
+ dark: object.dark
15
+ }) %>
16
+ <% end %>
17
+ <%= " #{object.formatted_contact_value}" if object.contact_value %>
14
18
  <% else %>
15
- <%= pb_rails("icon", props: {
16
- icon: object.contact_icon,
17
- fixed_width: true,
18
- dark: object.dark
19
- }) %>
19
+ <%= object.formatted_contact_value if object.contact_value %>
20
20
  <% end %>
21
- <%= object.formatted_contact_value if object.contact_value %>
22
21
 
23
22
  <%= pb_rails("caption", props: {
24
23
  text: object.contact_detail,
@@ -27,4 +26,39 @@
27
26
  dark: object.dark
28
27
  }) if object.contact_detail %>
29
28
  <% end %>
29
+ <% else %>
30
+ <%= pb_content_tag do %>
31
+ <%= pb_rails("body", props: {
32
+ tag: "span",
33
+ classname: "pb_contact_kit",
34
+ color: "light",
35
+ dark: object.dark
36
+ }) do %>
37
+ <% if icon_enabled %>
38
+ <% if contact_type == "email" %>
39
+ <%= pb_rails("icon", props: {
40
+ custom_icon: Playbook::Engine::root.join(envelope_path),
41
+ fixed_width: true,
42
+ dark: object.dark
43
+ }) %>
44
+ <% else %>
45
+ <%= pb_rails("icon", props: {
46
+ icon: object.contact_icon,
47
+ fixed_width: true,
48
+ dark: object.dark
49
+ }) %>
50
+ <% end %>
51
+ <%= " #{object.formatted_contact_value}" if object.contact_value %>
52
+ <% else %>
53
+ <%= object.formatted_contact_value if object.contact_value %>
54
+ <% end %>
55
+
56
+ <%= pb_rails("caption", props: {
57
+ text: object.contact_detail,
58
+ tag: 'span',
59
+ size: 'xs',
60
+ dark: object.dark
61
+ }) if object.contact_detail %>
62
+ <% end %>
63
+ <% end %>
30
64
  <% end %>
@@ -8,6 +8,8 @@ module Playbook
8
8
  prop :contact_type
9
9
  prop :contact_value
10
10
  prop :contact_detail
11
+ prop :icon_enabled, type: Playbook::Props::Boolean, default: true
12
+ prop :unstyled, type: Playbook::Props::Boolean, default: false
11
13
 
12
14
  def classname
13
15
  generate_classname("pb_contact_kit")
@@ -44,7 +46,15 @@ module Playbook
44
46
  elsif contact_type == "international"
45
47
  contact_value
46
48
  else
47
- number_to_phone(formatted_value, area_code: true)
49
+ # Check if number has leading 1 (US country code)
50
+ # Format like "+1 (212) 555-1234"
51
+ intl_code = ""
52
+ cleaned_number = formatted_value
53
+ if cleaned_number.length == 11 && cleaned_number.start_with?("1")
54
+ intl_code = "+1 "
55
+ cleaned_number = cleaned_number.sub(/^1/, "")
56
+ end
57
+ "#{intl_code}#{number_to_phone(cleaned_number, area_code: true)}"
48
58
  end
49
59
  end
50
60
 
@@ -149,3 +149,79 @@ test('international contact type preserves original format', () => {
149
149
  const kit = screen.getByTestId('test-international-format')
150
150
  expect(kit).toHaveTextContent('+44 20 7946 0958')
151
151
  })
152
+
153
+ test('iconEnabled prop hides icon when false', () => {
154
+ render(
155
+ <>
156
+ <Contact
157
+ contactType="home"
158
+ contactValue="2125551234"
159
+ data={{ testid: 'test-with-icon' }}
160
+ iconEnabled
161
+ />
162
+ <Contact
163
+ contactType="home"
164
+ contactValue="2125551234"
165
+ data={{ testid: 'test-without-icon' }}
166
+ iconEnabled={false}
167
+ />
168
+ </>
169
+ )
170
+
171
+ // With icon enabled, should have icon
172
+ expect(screen.getByTestId('test-with-icon').querySelector('.pb_custom_icon')).toBeInTheDocument()
173
+
174
+ // Without icon, should not have icon
175
+ expect(screen.getByTestId('test-without-icon').querySelector('.pb_custom_icon')).not.toBeInTheDocument()
176
+
177
+ // But should still have the formatted phone number
178
+ expect(screen.getByTestId('test-without-icon')).toHaveTextContent('(212) 555-1234')
179
+ })
180
+
181
+ test('unstyled prop renders without Body wrapper', () => {
182
+ render(
183
+ <>
184
+ <Contact
185
+ contactType="home"
186
+ contactValue="2125551234"
187
+ data={{ testid: 'test-styled' }}
188
+ />
189
+ <Contact
190
+ contactType="home"
191
+ contactValue="2125551234"
192
+ data={{ testid: 'test-unstyled' }}
193
+ unstyled
194
+ />
195
+ </>
196
+ )
197
+
198
+ // Styled version should have Body wrapper with pb_contact_kit class
199
+ const styled = screen.getByTestId('test-styled')
200
+ const styledBody = styled.querySelector('span.pb_contact_kit')
201
+ expect(styledBody).toBeInTheDocument()
202
+ expect(styledBody).toHaveTextContent('(212) 555-1234')
203
+
204
+ // Unstyled version should be a span without Body wrapper
205
+ const unstyled = screen.getByTestId('test-unstyled')
206
+ expect(unstyled.tagName).toBe('SPAN')
207
+ expect(unstyled.querySelector('span.pb_contact_kit')).not.toBeInTheDocument()
208
+ expect(unstyled).toHaveTextContent('(212) 555-1234')
209
+ })
210
+
211
+ test('unstyled and iconEnabled work together', () => {
212
+ render(
213
+ <Contact
214
+ contactType="home"
215
+ contactValue="2125551234"
216
+ data={{ testid: 'test-unstyled-no-icon' }}
217
+ iconEnabled={false}
218
+ unstyled
219
+ />
220
+ )
221
+
222
+ const kit = screen.getByTestId('test-unstyled-no-icon')
223
+ expect(kit.tagName).toBe('SPAN')
224
+ expect(kit.querySelector('.pb_custom_icon')).not.toBeInTheDocument()
225
+ expect(kit.querySelector('.pb_body_kit')).not.toBeInTheDocument()
226
+ expect(kit).toHaveTextContent('(212) 555-1234')
227
+ })
@@ -0,0 +1,33 @@
1
+ <%= pb_rails("body", props: { color: "default" }) do %>
2
+ <%= pb_rails("contact", props: {
3
+ contact_value: "2125551234",
4
+ icon_enabled: false,
5
+ unstyled: true
6
+ }) %>
7
+ <% end %>
8
+
9
+ <%= pb_rails("body", props: { color: "light" }) do %>
10
+ <%= pb_rails("contact", props: {
11
+ contact_value: "12125551234",
12
+ icon_enabled: false,
13
+ unstyled: true
14
+ }) %>
15
+ <% end %>
16
+
17
+ <%= pb_rails("body", props: { color: "lighter" }) do %>
18
+ <%= pb_rails("contact", props: {
19
+ contact_value: "4155551234",
20
+ icon_enabled: false,
21
+ unstyled: true
22
+ }) %>
23
+ <% end %>
24
+
25
+ <%= pb_rails("body", props: { color: "default" }) do %>
26
+ <%= pb_rails("contact", props: {
27
+ contact_type: "extension",
28
+ contact_value: "1234",
29
+ icon_enabled: false,
30
+ unstyled: true
31
+ }) %>
32
+ <% end %>
33
+
@@ -0,0 +1,46 @@
1
+ import React from 'react'
2
+ import Contact from "../../pb_contact/_contact"
3
+ import Body from "../../pb_body/_body"
4
+
5
+ const ContactUnstyled = (props) => {
6
+ return (
7
+ <div>
8
+ <Body color="default">
9
+ <Contact
10
+ contactValue="2125551234"
11
+ iconEnabled={false}
12
+ unstyled
13
+ {...props}
14
+ />
15
+ </Body>
16
+ <Body color="light">
17
+ <Contact
18
+ contactValue="12125551234"
19
+ iconEnabled={false}
20
+ unstyled
21
+ {...props}
22
+ />
23
+ </Body>
24
+ <Body color="lighter">
25
+ <Contact
26
+ contactValue="4155551234"
27
+ iconEnabled={false}
28
+ unstyled
29
+ {...props}
30
+ />
31
+ </Body>
32
+ <Body color="default">
33
+ <Contact
34
+ contactType="extension"
35
+ contactValue="1234"
36
+ iconEnabled={false}
37
+ unstyled
38
+ {...props}
39
+ />
40
+ </Body>
41
+ </div>
42
+ )
43
+ }
44
+
45
+ export default ContactUnstyled
46
+
@@ -0,0 +1,2 @@
1
+ Use the Contact kit with `icon_enabled: false` and `unstyled: true` to display phone numbers with full typography control. When `unstyled: true`, the Contact kit renders just the formatted text without a Body wrapper, allowing you to wrap it in your own Typography kit to control the color and styling.
2
+
@@ -0,0 +1,2 @@
1
+ Use the Contact kit with `iconEnabled={false}` and `unstyled` to display phone numbers with full typography control. With `unstyled`, the Contact kit renders just the formatted text without a Body wrapper, allowing you to wrap it in your own Typography kit to control the color and styling.
2
+
@@ -3,11 +3,13 @@ examples:
3
3
  rails:
4
4
  - contact_default: Default
5
5
  - contact_with_detail: Detail Indicator
6
+ - contact_unstyled: Unstyled
6
7
 
7
8
 
8
9
  react:
9
10
  - contact_default: Default
10
11
  - contact_with_detail: Detail Indicator
12
+ - contact_unstyled: Unstyled
11
13
 
12
14
 
13
15
  swift:
@@ -1,2 +1,3 @@
1
1
  export { default as ContactDefault } from './_contact_default.jsx'
2
2
  export { default as ContactWithDetail } from './_contact_with_detail.jsx'
3
+ export { default as ContactUnstyled } from './_contact_unstyled.jsx'
@@ -51,6 +51,7 @@ const Dialog = (props: DialogProps): React.ReactElement => {
51
51
  cancelButton,
52
52
  confirmButton,
53
53
  className,
54
+ closeable,
54
55
  data = {},
55
56
  htmlOptions = {},
56
57
  id,
@@ -187,7 +188,7 @@ const Dialog = (props: DialogProps): React.ReactElement => {
187
188
  style={{ content: dynamicInlineProps }}
188
189
  >
189
190
  <>
190
- {title && !status ? <Dialog.Header>{title}</Dialog.Header> : null}
191
+ {title && !status ? <Dialog.Header closeable={closeable}>{title}</Dialog.Header> : null}
191
192
  {!status && text ? <Dialog.Body>{text}</Dialog.Body> : null}
192
193
  {status && (
193
194
  <Dialog.Body
@@ -14,7 +14,7 @@
14
14
  >
15
15
  <%= pb_content_tag(:dialog, role: "dialog", "aria-modal": "true", "aria-label": "Dialog") do %>
16
16
  <% if object.status === "" && object.title %>
17
- <%= pb_rails("dialog/dialog_header", props: { title: object.title, id: object.id }) %>
17
+ <%= pb_rails("dialog/dialog_header", props: { title: object.title, id: object.id, closeable: object.closeable }) %>
18
18
  <% end %>
19
19
  <% if object.status === "" && object.text %>
20
20
  <%= pb_rails("dialog/dialog_body", props: { text: object.text }) %>
@@ -23,6 +23,7 @@ module Playbook
23
23
  default: ""
24
24
  prop :custom_event_type, type: Playbook::Props::String,
25
25
  default: ""
26
+ prop :closeable, type: Playbook::Props::Boolean, default: true
26
27
 
27
28
  def classname
28
29
  generate_classname("pb_dialog pb_dialog_rails pb_dialog_#{size}_#{placement}")
@@ -129,3 +129,17 @@ test('renders loading dialog with disabled buttons', async () => {
129
129
 
130
130
  cleanup()
131
131
  })
132
+
133
+ test('renders dialog without close button when closeable is false', async () => {
134
+
135
+ const { queryByText, container } = render(<DialogTest closeable={false} />);
136
+
137
+ fireEvent.click(queryByText('Open Dialog'));
138
+
139
+ await waitFor(() => expect(queryByText("Header Title is the Title Prop")));
140
+
141
+ const closeBtn = container.querySelector('.pb_dialog_close_icon');
142
+ expect(closeBtn).not.toBeInTheDocument();
143
+
144
+ cleanup()
145
+ })
@@ -1,10 +1,11 @@
1
1
  <%= pb_content_tag(:div, class: object.sticky_header) do %>
2
2
  <%= pb_rails("flex", props: {classname:object.classname, spacing:"between", padding:"sm", align:"center"}) do %>
3
3
  <%= content.presence || object.title %>
4
-
5
- <button class="dialog-button-class pb_dialog_close_icon" type="button" data-close-dialog= <%= object.id %> aria-label="Close Dialog" >
6
- <%= pb_rails("icon", props: { custom_icon: Playbook::Engine::root.join(times_icon), aria: { hidden: true } }) %>
7
- </button>
4
+ <% if object.closeable %>
5
+ <button class="dialog-button-class pb_dialog_close_icon" type="button" data-close-dialog= <%= object.id %> aria-label="Close Dialog" >
6
+ <%= pb_rails("icon", props: { custom_icon: Playbook::Engine::root.join(times_icon), aria: { hidden: true } }) %>
7
+ </button>
8
+ <% end %>
8
9
  <% end %>
9
10
  <%= pb_rails("section_separator") %>
10
11
  <% end %>
@@ -4,6 +4,8 @@ module Playbook
4
4
  module PbDialog
5
5
  class DialogHeader < Playbook::KitBase
6
6
  prop :title
7
+ prop :closeable, type: Playbook::Props::Boolean,
8
+ default: true
7
9
 
8
10
  def classname
9
11
  generate_classname("dialog_header")
@@ -0,0 +1,24 @@
1
+ <%= pb_rails("flex", props:{wrap:true}) do %>
2
+ <%= pb_rails("button", props: { text: "Open Simple Dialog", data: {"open-dialog": "dialog-simple"}, margin_right:"md" }) %>
3
+ <%= pb_rails("button", props: { text: "Open Complex Dialog", data: {"open-dialog": "dialog-complex2"} }) %>
4
+ <% end %>
5
+
6
+ <%= pb_rails("dialog", props: {
7
+ id:"dialog-simple",
8
+ size: "sm",
9
+ title: "Header Title is the Title Prop",
10
+ text: "Hello Body Text, Nice to meet ya.",
11
+ cancel_button: "Cancel Button",
12
+ closeable: false,
13
+ confirm_button: "Okay",
14
+ confirm_button_id: "confirm-button-simple"
15
+ }) %>
16
+
17
+ <%= pb_rails("dialog", props: {
18
+ id:"dialog-complex2",
19
+ size: "sm"
20
+ }) do %>
21
+ <%= pb_rails("dialog/dialog_header", props: { id: "dialog-complex2", title:"Header Title inside Dialog Header", closeable: false } ) %>
22
+ <%= pb_rails("dialog/dialog_body", props:{text: "Hello Body Text, Nice to meet ya."}) %>
23
+ <%= pb_rails("dialog/dialog_footer", props: {cancel_button: "Cancel Button", confirm_button: "Okay", confirm_button_id:"confirm-complex2", id: "dialog-complex2"}) %>
24
+ <% end %>
@@ -0,0 +1,60 @@
1
+ import React, { useState } from 'react'
2
+ import Button from '../../pb_button/_button'
3
+ import Dialog from '../../pb_dialog/_dialog'
4
+
5
+
6
+ const DialogCloseable = () => {
7
+ // Simple example
8
+ const [isOpenSimple, setIsOpenSimple] = useState(false)
9
+ const closeSimple = () => setIsOpenSimple(false)
10
+ const openSimple = () => setIsOpenSimple(true)
11
+
12
+ // Complex example
13
+ const [isOpenComplex, setIsOpenComplex] = useState(false)
14
+ const closeComplex = () => setIsOpenComplex(false)
15
+ const openComplex = () => setIsOpenComplex(true)
16
+
17
+ return (
18
+ <>
19
+ <Button
20
+ marginRight='md'
21
+ onClick={openSimple}
22
+ >
23
+ {'Open Simple Dialog'}
24
+ </Button>
25
+ <Button onClick={openComplex}>{'Open Complex Dialog'}</Button>
26
+
27
+ <Dialog
28
+ cancelButton="Cancel Button"
29
+ closeable={false}
30
+ confirmButton="Okay"
31
+ onCancel={closeSimple}
32
+ onClose={closeSimple}
33
+ onConfirm={closeSimple}
34
+ opened={isOpenSimple}
35
+ size="sm"
36
+ text="Hello Body Text, Nice to meet ya."
37
+ title="Header Title is the Title Prop"
38
+ />
39
+ <Dialog
40
+ onClose={closeComplex}
41
+ opened={isOpenComplex}
42
+ size="sm"
43
+ >
44
+ <Dialog.Header closeable={false}>{'Header Title inside Dialog.Header'}</Dialog.Header>
45
+ <Dialog.Body>{'Hello Body Text, Nice to meet ya.'}</Dialog.Body>
46
+ <Dialog.Footer>
47
+ <Button onClick={closeComplex}>{'Okay'}</Button>
48
+ <Button
49
+ onClick={closeComplex}
50
+ variant="link"
51
+ >
52
+ {'Cancel Button'}
53
+ </Button>
54
+ </Dialog.Footer>
55
+ </Dialog>
56
+ </>
57
+ )
58
+ }
59
+
60
+ export default DialogCloseable
@@ -0,0 +1,3 @@
1
+ The `closeable` prop can be set to false to optionally render the Dialog header without the close “X” button. `closeable` is set to true by default.
2
+
3
+ This prop can be used with the simple as well as the complex version of the Dialog as can be seen here.
@@ -12,6 +12,7 @@ examples:
12
12
  - dialog_full_height_placement: Full Height Placement
13
13
  - dialog_loading: Loading
14
14
  - dialog_turbo_frames: Within Turbo Frames
15
+ - dialog_closeable: Close Button in Header
15
16
 
16
17
 
17
18
  react:
@@ -25,6 +26,7 @@ examples:
25
26
  - dialog_full_height: Full Height
26
27
  - dialog_full_height_placement: Full Height Placement
27
28
  - dialog_loading: Loading
29
+ - dialog_closeable: Close Button in Header
28
30
 
29
31
  swift:
30
32
  - dialog_default_swift: Simple
@@ -8,4 +8,5 @@ export { default as DialogStatus } from './_dialog_status.jsx'
8
8
  export { default as DialogStackedAlert } from './_dialog_stacked_alert.jsx'
9
9
  export { default as DialogFullHeight } from './_dialog_full_height.jsx'
10
10
  export { default as DialogFullHeightPlacement } from './_dialog_full_height_placement.jsx'
11
- export { default as DialogLoading } from './_dialog_loading.jsx'
11
+ export { default as DialogLoading } from './_dialog_loading.jsx'
12
+ export { default as DialogCloseable } from './_dialog_closeable.jsx'