playbook_ui 15.2.0.pre.rc.0 → 15.2.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +0 -2
  3. data/app/pb_kits/playbook/pb_date_picker/_date_picker.scss +4 -0
  4. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_stacked_alert.html.erb +16 -16
  5. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_stacked_alert.jsx +2 -1
  6. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_status.html.erb +31 -31
  7. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_status.jsx +4 -3
  8. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +11 -0
  9. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.tsx +2 -2
  10. data/app/pb_kits/playbook/pb_icon_stat_value/_icon_stat_value.scss +15 -21
  11. data/app/pb_kits/playbook/pb_icon_stat_value/_icon_stat_value.tsx +6 -5
  12. data/app/pb_kits/playbook/pb_icon_stat_value/icon_stat_value.html.erb +2 -0
  13. data/app/pb_kits/playbook/pb_icon_stat_value/icon_stat_value.rb +11 -3
  14. data/app/pb_kits/playbook/pb_icon_stat_value/icon_stat_value.test.js +9 -8
  15. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +14 -6
  16. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_default.html.erb +8 -4
  17. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_default.jsx +5 -0
  18. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_default.md +1 -0
  19. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +3 -1
  20. data/app/pb_kits/playbook/pb_text_input/text_input.rb +6 -0
  21. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +39 -1
  22. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +2 -0
  23. data/dist/chunks/{_line_graph-C9stNsP3.js → _line_graph-B-1uF3pN.js} +1 -1
  24. data/dist/chunks/{_typeahead-D3MtsWXG.js → _typeahead-C8eN5nhR.js} +1 -1
  25. data/dist/chunks/{_weekday_stacked-Bvc7R5vH.js → _weekday_stacked-1o7KVL87.js} +3 -3
  26. data/dist/chunks/vendor.js +1 -1
  27. data/dist/playbook-doc.js +2 -2
  28. data/dist/playbook-rails-react-bindings.js +1 -1
  29. data/dist/playbook-rails.js +1 -1
  30. data/dist/playbook.css +1 -1
  31. data/lib/playbook/version.rb +1 -1
  32. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13bfcc5673dfc00619bf8b3fb557c22f4c4609140237fc800757ed584a710a56
4
- data.tar.gz: dc055c2848c96f663adddd3e1a3452e63431cf8d26505ca57d17886bcebf2a86
3
+ metadata.gz: a2242bca91bdb8f1f2a355e2ad50f8f036980b6576243293c31a017ecfeb7fac
4
+ data.tar.gz: 22b06e68fbc840b549751cd72e6738cc7da7d15b0cab64e1fc47df77064375a4
5
5
  SHA512:
6
- metadata.gz: b51dd269610cb015f62bbdf1199adece46fa8591f488efe8edd2e6b18fdee4b4ac7b3653f136a435ef89d9f848fde895e9d3fb6ac2f3fc5d99ee5d04ceffaccf
7
- data.tar.gz: 5d53c3805872480202c03694deea22babe4395cf31b946f26c1ef30c4737b682fa8c9ecae15282caee6c95b7f4ba4c33afaa6b91039290e9188465811233867e
6
+ metadata.gz: bd36ae9541db3a90dfda476bd8397d0cb951afddb1399d0aa3c9a27593624bad47816b1c16628d8c377d435151c342b8a53caa9b92c36bf8e6bcf91fde2330c7
7
+ data.tar.gz: e807e6cebfa03f4d217315bde5ed604c5cc696987b05fcd4e98bb5a15336cfe3ddf0334b1914b68156bb77e2c20e259e2c1ac2dcdc5a46dce52bfccd84fa6a0d
@@ -146,8 +146,6 @@ export function useTableState({
146
146
 
147
147
  // Pagination configuration
148
148
  const paginationInitializer = useMemo(() => {
149
- if (!pagination) return {};
150
-
151
149
  return {
152
150
  getPaginationRowModel: getPaginationRowModel(),
153
151
  paginateExpandedRows: false,
@@ -19,6 +19,10 @@
19
19
  @import "./sass_partials/calendar_input_icon";
20
20
  }
21
21
 
22
+ label {
23
+ display: block !important;
24
+ }
25
+
22
26
  &:focus,
23
27
  :focus-within {
24
28
  div.cal_icon_wrapper,
@@ -3,12 +3,12 @@
3
3
  <%= pb_rails("button", props: { text: "Delete Status", data: {"open-dialog": "dialog-stacked-delete"}, margin_right: "md" }) %>
4
4
 
5
5
 
6
- <%= pb_rails("dialog", props: {
7
- id:"dialog-stacked-default",
6
+ <%= pb_rails("dialog", props: {
7
+ id:"dialog-stacked-default",
8
8
  status: "default",
9
- size: "sm",
10
- title: "Are you sure?",
11
- text: "Text explaining why there is an alert",
9
+ size: "sm",
10
+ title: "Are you sure?",
11
+ text: "Text explaining why there is an alert",
12
12
  }) do %>
13
13
  <%= pb_rails("dialog/dialog_footer") do %>
14
14
  <%= pb_rails("flex", props: { orientation: "column", padding_x:"md", padding: "sm" }) do %>
@@ -18,12 +18,12 @@
18
18
  <% end %>
19
19
  <% end %>
20
20
 
21
- <%= pb_rails("dialog", props: {
22
- id:"dialog-stacked-caution",
21
+ <%= pb_rails("dialog", props: {
22
+ id:"dialog-stacked-caution",
23
23
  status: "caution",
24
- size: "sm",
25
- title: "Are you sure?",
26
- text: "This is the action you will be taking",
24
+ size: "sm",
25
+ title: "Are you sure?",
26
+ text: "This is the action you will be taking",
27
27
  }) do %>
28
28
  <%= pb_rails("dialog/dialog_footer") do %>
29
29
  <%= pb_rails("flex", props: { orientation: "column", padding_x:"md", padding: "sm" }) do %>
@@ -33,16 +33,16 @@
33
33
  <% end %>
34
34
  <% end %>
35
35
 
36
- <%= pb_rails("dialog", props: {
37
- id:"dialog-stacked-delete",
36
+ <%= pb_rails("dialog", props: {
37
+ id:"dialog-stacked-delete",
38
38
  status: "delete",
39
- size: "sm",
40
- title: "Delete",
41
- text: "You are about to delete ...",
39
+ size: "sm",
40
+ title: "Delete",
41
+ text: "You are about to delete ...",
42
42
  }) do %>
43
43
  <%= pb_rails("dialog/dialog_footer") do %>
44
44
  <%= pb_rails("flex", props: { orientation: "column", padding_x:"md", padding: "sm" }) do %>
45
- <%= pb_rails("button", props: { text: "Yes, Action", full_width: true }) %>
45
+ <%= pb_rails("button", props: { text: "Yes, Action", variant: "danger", full_width: true }) %>
46
46
  <%= pb_rails("button", props: { text: "No, Cancel", variant: "secondary", full_width: true, margin_top: "sm", data: {"close-dialog": "dialog-stacked-delete" } }) %>
47
47
  <% end %>
48
48
  <% end %>
@@ -51,7 +51,7 @@ const DialogStackedAlert = () => {
51
51
  return (
52
52
  <div>
53
53
  <Flex
54
- rowGap="xs"
54
+ rowGap="xs"
55
55
  wrap
56
56
  >
57
57
  <Button
@@ -93,6 +93,7 @@ const DialogStackedAlert = () => {
93
93
  <Button
94
94
  fullWidth
95
95
  onClick={dialog.toggle}
96
+ variant= {dialog.status == "delete" ? "danger" : "primary"}
96
97
  >
97
98
  {dialog.buttonOneText}
98
99
  </Button>
@@ -8,12 +8,12 @@
8
8
  <%= pb_rails("button", props: { text: "Success Status", data: {"open-dialog": "dialog-status-success"}, margin_right: "md" }) %>
9
9
  <% end %>
10
10
 
11
- <%= pb_rails("dialog", props: {
12
- id:"dialog-status-default",
11
+ <%= pb_rails("dialog", props: {
12
+ id:"dialog-status-default",
13
13
  status: "default",
14
- size: "status_size",
15
- title: "Are you sure?",
16
- text: "Text explaining why there is an alert",
14
+ size: "status_size",
15
+ title: "Are you sure?",
16
+ text: "Text explaining why there is an alert",
17
17
  }) do %>
18
18
  <%= pb_rails("dialog/dialog_footer") do %>
19
19
  <%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
@@ -23,12 +23,12 @@
23
23
  <% end %>
24
24
  <% end %>
25
25
 
26
- <%= pb_rails("dialog", props: {
27
- id:"dialog-status-info",
26
+ <%= pb_rails("dialog", props: {
27
+ id:"dialog-status-info",
28
28
  status: "info",
29
- size: "status_size",
30
- title: "Information",
31
- text: "Text explaining why there is an alert",
29
+ size: "status_size",
30
+ title: "Information",
31
+ text: "Text explaining why there is an alert",
32
32
  }) do %>
33
33
  <%= pb_rails("dialog/dialog_footer") do %>
34
34
  <%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
@@ -37,12 +37,12 @@
37
37
  <% end %>
38
38
  <% end %>
39
39
 
40
- <%= pb_rails("dialog", props: {
41
- id:"dialog-status-caution",
40
+ <%= pb_rails("dialog", props: {
41
+ id:"dialog-status-caution",
42
42
  status: "caution",
43
- size: "status_size",
44
- title: "Are you Sure?",
45
- text: "This is the action you will be taking",
43
+ size: "status_size",
44
+ title: "Are you Sure?",
45
+ text: "This is the action you will be taking",
46
46
  }) do %>
47
47
  <%= pb_rails("dialog/dialog_footer") do %>
48
48
  <%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
@@ -52,27 +52,27 @@
52
52
  <% end %>
53
53
  <% end %>
54
54
 
55
- <%= pb_rails("dialog", props: {
56
- id:"dialog-status-delete",
55
+ <%= pb_rails("dialog", props: {
56
+ id:"dialog-status-delete",
57
57
  status: "delete",
58
- size: "status_size",
59
- title: "Delete",
60
- text: "You are about to delete ...",
58
+ size: "status_size",
59
+ title: "Delete",
60
+ text: "You are about to delete ...",
61
61
  }) do %>
62
62
  <%= pb_rails("dialog/dialog_footer") do %>
63
63
  <%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
64
- <%= pb_rails("button", props: { text: "Yes, Delete" }) %>
64
+ <%= pb_rails("button", props: { text: "Yes, Delete", variant: "danger" }) %>
65
65
  <%= pb_rails("button", props: { text: "No, Cancel", variant: "secondary", data: {"close-dialog": "dialog-status-delete" } }) %>
66
66
  <% end %>
67
67
  <% end %>
68
68
  <% end %>
69
69
 
70
- <%= pb_rails("dialog", props: {
71
- id:"dialog-status-error",
70
+ <%= pb_rails("dialog", props: {
71
+ id:"dialog-status-error",
72
72
  status: "error",
73
- size: "status_size",
74
- title: "Error Message",
75
- text: "Text explaining the error",
73
+ size: "status_size",
74
+ title: "Error Message",
75
+ text: "Text explaining the error",
76
76
  }) do %>
77
77
  <%= pb_rails("dialog/dialog_footer") do %>
78
78
  <%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
@@ -81,12 +81,12 @@
81
81
  <% end %>
82
82
  <% end %>
83
83
 
84
- <%= pb_rails("dialog", props: {
85
- id:"dialog-status-success",
84
+ <%= pb_rails("dialog", props: {
85
+ id:"dialog-status-success",
86
86
  status: "success",
87
- size: "status_size",
88
- title: "Success!",
89
- text: "Text explaining what is successful",
87
+ size: "status_size",
88
+ title: "Success!",
89
+ text: "Text explaining what is successful",
90
90
  }) do %>
91
91
  <%= pb_rails("dialog/dialog_footer") do %>
92
92
  <%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
@@ -83,8 +83,8 @@ const DialogStatus = () => {
83
83
 
84
84
  return (
85
85
  <div>
86
- <Flex
87
- rowGap="xs"
86
+ <Flex
87
+ rowGap="xs"
88
88
  wrap
89
89
  >
90
90
  <Button
@@ -117,7 +117,7 @@ const DialogStatus = () => {
117
117
  >
118
118
  {"Success Status"}
119
119
  </Button>
120
- <Button
120
+ <Button
121
121
  marginRight="md"
122
122
  onClick={toggleErrorAlert}
123
123
  >
@@ -152,6 +152,7 @@ const DialogStatus = () => {
152
152
  <Button
153
153
  onClick={dialog.toggle}
154
154
  paddingRight="xl"
155
+ variant={dialog.status == "delete" ? "danger" : "primary"}
155
156
  >
156
157
  {dialog.buttonOneText}
157
158
  </Button>
@@ -21,6 +21,15 @@
21
21
  ]
22
22
  %>
23
23
 
24
+ <%
25
+ example_typeahead_options = [
26
+ { label: 'Orange', value: '#FFA500' },
27
+ { label: 'Red', value: '#FF0000' },
28
+ { label: 'Green', value: '#00FF00' },
29
+ { label: 'Blue', value: '#0000FF' },
30
+ ]
31
+ %>
32
+
24
33
  <% treeData = [{
25
34
  label: "Power Home Remodeling",
26
35
  value: "Power Home Remodeling",
@@ -89,6 +98,8 @@
89
98
 
90
99
  <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
91
100
  <%= 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." } } %>
101
+ <%= form.typeahead :example_typeahead_validation_react, props: { options: example_typeahead_options, pills: true, label: "Example Typeahead (React Rendered)", placeholder: "Search for a user", required: true, validation: { message: "Please select a color." } } %>
102
+ <%= form.typeahead :example_typeahead_validation_react_2, props: { options: example_typeahead_options, pills: true, label: "Example Typeahead 2 (React Rendered)", placeholder: "Search for a user", required: true } %>
92
103
  <%= form.text_field :example_text_field_validation, props: { label: true, required: true } %>
93
104
  <%= form.phone_number_field :example_phone_number_field_validation, props: { label: "Example phone field", hidden_inputs: true } %>
94
105
  <%= form.email_field :example_email_field_validation, props: { label: true, required: true } %>
@@ -3,7 +3,7 @@ import React from 'react'
3
3
  import classnames from 'classnames'
4
4
 
5
5
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
6
- import { globalProps } from '../utilities/globalProps'
6
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
7
7
 
8
8
  import Icon from '../pb_icon/_icon'
9
9
 
@@ -26,7 +26,7 @@ type IconCircleProps = {
26
26
  | "orange"
27
27
  | "green"
28
28
  | "lighter",
29
- }
29
+ } & GlobalProps
30
30
 
31
31
  const IconCircle = (props: IconCircleProps) => {
32
32
  const {
@@ -3,51 +3,45 @@
3
3
  @import "../tokens/spacing";
4
4
  @import "../pb_icon_circle/icon_circle";
5
5
 
6
- [class^=pb_icon_stat_value_kit]{
6
+ .pb_icon_stat_value_kit_horizontal,
7
+ .pb_icon_stat_value_kit_vertical
8
+ {
7
9
  display: flex;
8
10
  align-items: baseline;
9
11
 
10
- &[class*=_vertical] {
12
+ &.pb_icon_stat_value_kit_vertical {
11
13
  flex-direction: column;
12
14
 
13
- &[class*=_center] {
15
+ &.text_align_center {
14
16
  align-items: center;
15
17
 
16
- [class^=pb_title],
17
- [class^=pb_body],
18
- [class^=pb_caption] {
18
+ .pb_title_kit,
19
+ .pb_body_kit,
20
+ .pb_caption_kit_md {
19
21
  text-align: center;
20
22
  }
21
23
  }
22
24
 
23
- &[class*=_right] {
25
+ &.text_align_right {
24
26
  align-items: flex-end;
25
27
 
26
- [class^=pb_title],
27
- [class^=pb_body],
28
- [class^=pb_caption] {
28
+ .pb_title_kit,
29
+ .pb_body_kit,
30
+ .pb_caption_kit_md {
29
31
  text-align: right;
30
32
  }
31
33
  }
32
-
33
- [class^=pb_icon_circle] {
34
- margin-bottom: $space-xs;
35
- }
36
34
  }
37
35
 
38
- &[class*=_horizontal] {
36
+ &.pb_icon_stat_value_kit_horizontal {
39
37
  align-items: center;
40
38
 
41
- &[class*=_center] {
39
+ &.text_align_center {
42
40
  justify-content: center;
43
41
  }
44
42
 
45
- &[class*=_right] {
43
+ &.text_align_right {
46
44
  justify-content: flex-end;
47
45
  }
48
-
49
- [class^=pb_icon_circle] {
50
- margin-right: $space-sm;
51
- }
52
46
  }
53
47
  }
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import classnames from 'classnames'
3
3
 
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
- import { globalProps } from '../utilities/globalProps'
5
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
6
6
 
7
7
  import Body from '../pb_body/_body'
8
8
  import Caption from '../pb_caption/_caption'
@@ -33,8 +33,7 @@ type IconStatValueProps = {
33
33
  | "yellow"
34
34
  | "orange"
35
35
  | "green"
36
- | "lighter",
37
- }
36
+ } & GlobalProps
38
37
 
39
38
  const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
40
39
  const {
@@ -50,13 +49,13 @@ const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
50
49
  text = '',
51
50
  unit = '',
52
51
  value = 0,
53
- variant = 'lighter',
52
+ variant = 'default',
54
53
  } = props
55
54
  const ariaProps = buildAriaProps(aria)
56
55
  const dataProps = buildDataProps(data)
57
56
  const htmlProps = buildHtmlProps(htmlOptions)
58
57
  const classes = classnames(
59
- buildCss('pb_icon_stat_value_kit', orientation, size, variant), globalProps(props),
58
+ buildCss('pb_icon_stat_value_kit', orientation), globalProps(props),
60
59
  className
61
60
  )
62
61
  const titleSize = function(size: "sm" | "md" | "lg") {
@@ -101,6 +100,8 @@ const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
101
100
  <IconCircle
102
101
  dark={dark}
103
102
  icon={icon}
103
+ marginBottom={orientation == 'vertical' ? 'xs' : undefined}
104
+ marginRight={orientation == 'horizontal' ? 'sm' : undefined}
104
105
  size={size}
105
106
  variant={variant}
106
107
  />
@@ -3,6 +3,8 @@
3
3
  <%= pb_rails("icon_circle", props: {
4
4
  dark: object.dark,
5
5
  icon: object.icon,
6
+ margin_right: object.icon_margin_right,
7
+ margin_bottom: object.icon_margin_bottom,
6
8
  size: object.size,
7
9
  variant: object.variant }) %>
8
10
 
@@ -9,8 +9,8 @@ module Playbook
9
9
  values: %w[sm md lg],
10
10
  default: "sm"
11
11
  prop :variant, type: Playbook::Props::Enum,
12
- values: %w[default royal blue purple teal red yellow green orange lighter],
13
- default: "lighter"
12
+ values: %w[default royal blue purple teal red yellow green orange],
13
+ default: "default"
14
14
 
15
15
  prop :orientation, type: Playbook::Props::Enum,
16
16
  values: %w[vertical horizontal],
@@ -25,7 +25,7 @@ module Playbook
25
25
  prop :value
26
26
 
27
27
  def classname
28
- generate_classname("pb_icon_stat_value_kit", orientation, size, variant)
28
+ generate_classname("pb_icon_stat_value_kit", orientation)
29
29
  end
30
30
 
31
31
  def value_string
@@ -41,6 +41,14 @@ module Playbook
41
41
  3
42
42
  end
43
43
  end
44
+
45
+ def icon_margin_right
46
+ orientation === "horizontal" && "sm"
47
+ end
48
+
49
+ def icon_margin_bottom
50
+ orientation === "vertical" && "xs"
51
+ end
44
52
  end
45
53
  end
46
54
  end
@@ -18,7 +18,7 @@ describe("IconStatValue Kit", () => {
18
18
  )
19
19
 
20
20
  const kit = screen.getByTestId(testId)
21
- expect(kit).toHaveClass("pb_icon_stat_value_kit_horizontal_sm_lighter")
21
+ expect(kit).toHaveClass("pb_icon_stat_value_kit_horizontal")
22
22
  })
23
23
 
24
24
  test("renders icon", () => {
@@ -99,9 +99,10 @@ describe("IconStatValue Kit", () => {
99
99
  value={64.18}
100
100
  />
101
101
  )
102
-
102
+ const size = sizeProp === "sm" ? "3" : sizeProp === "md" ? "2" : "1"
103
103
  const kit = screen.getByTestId(testId)
104
- expect(kit).toHaveClass(`pb_icon_stat_value_kit_horizontal_${sizeProp}_lighter`)
104
+ const title = kit.querySelector(".pb_title_kit")
105
+ expect(title).toHaveClass(`pb_title_${size}`)
105
106
 
106
107
  cleanup()
107
108
  })
@@ -115,8 +116,7 @@ describe("IconStatValue Kit", () => {
115
116
  "teal",
116
117
  "red",
117
118
  "yellow",
118
- "green",
119
- "lighter"].forEach(
119
+ "green"].forEach(
120
120
  (colorProp) => {
121
121
  render(
122
122
  <IconStatValue
@@ -128,9 +128,10 @@ describe("IconStatValue Kit", () => {
128
128
  variant={colorProp}
129
129
  />
130
130
  )
131
-
131
+
132
132
  const kit = screen.getByTestId(testId)
133
- expect(kit).toHaveClass(`pb_icon_stat_value_kit_horizontal_sm_${colorProp}`)
133
+ const iconCircle = kit.querySelector(`.pb_icon_circle_kit_size_sm_${colorProp}`)
134
+ expect(iconCircle).toBeInTheDocument()
134
135
 
135
136
  cleanup()
136
137
  })
@@ -149,7 +150,7 @@ describe("IconStatValue Kit", () => {
149
150
  )
150
151
 
151
152
  const kit = screen.getByTestId(testId)
152
- expect(kit).toHaveClass("pb_icon_stat_value_kit_vertical_sm_lighter")
153
+ expect(kit).toHaveClass("pb_icon_stat_value_kit_vertical")
153
154
  })
154
155
 
155
156
  })
@@ -140,10 +140,14 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
140
140
  formattedValue = value
141
141
  }
142
142
 
143
+ const errorId = error ? `${id}-error` : undefined
144
+
143
145
  const textInput = (
144
146
  childInput ? React.cloneElement(children, { className: "text_input" }) :
145
147
  (<input
146
148
  {...domSafeProps(props)}
149
+ aria-describedby={errorId}
150
+ aria-invalid={!!error}
147
151
  autoComplete={typeof autoComplete === "string" ? autoComplete : ( autoComplete ? undefined : "off" )}
148
152
  className="text_input"
149
153
  disabled={disabled}
@@ -202,16 +206,20 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
202
206
  {...htmlProps}
203
207
  className={css}
204
208
  >
205
- {label &&
206
- <Caption
207
- className="pb_text_input_kit_label"
208
- text={label}
209
- />
210
- }
209
+ {label && (
210
+ <label htmlFor={id}>
211
+ <Caption className="pb_text_input_kit_label"
212
+ text={label}
213
+ />
214
+ </label>
215
+ )}
211
216
  <div className={`${addOnCss} text_input_wrapper`}>
212
217
  {render}
213
218
 
214
219
  {error && <Body
220
+ aria={{ atomic: "true", live: "polite" }}
221
+ htmlOptions={{ role: "alert" }}
222
+ id={errorId}
215
223
  status="negative"
216
224
  text={error}
217
225
  variant={null}
@@ -9,23 +9,27 @@
9
9
 
10
10
  <%= pb_rails("text_input", props: {
11
11
  label: "Last Name",
12
- placeholder: "Enter last name"
12
+ placeholder: "Enter last name",
13
+ id: "last-name"
13
14
  }) %>
14
15
 
15
16
  <%= pb_rails("text_input", props: {
16
17
  label: "Phone Number",
17
18
  type: "phone",
18
- placeholder: "Enter phone number"
19
+ placeholder: "Enter phone number",
20
+ id: "phone"
19
21
  }) %>
20
22
 
21
23
  <%= pb_rails("text_input", props: {
22
24
  label: "Email Address",
23
25
  type: "email",
24
- placeholder: "Enter email address"
26
+ placeholder: "Enter email address",
27
+ id: "email"
25
28
  }) %>
26
29
 
27
30
  <%= pb_rails("text_input", props: {
28
31
  label: "Zip Code",
29
32
  type: "number",
30
- placeholder: "Enter zip code"
33
+ placeholder: "Enter zip code",
34
+ id: "zip"
31
35
  }) %>
@@ -38,6 +38,7 @@ const TextInputDefault = (props) => {
38
38
  {...props}
39
39
  />
40
40
  <TextInput
41
+ id="last-name"
41
42
  label="Last Name"
42
43
  name="lastName"
43
44
  onChange={handleOnChangeFormField}
@@ -46,6 +47,7 @@ const TextInputDefault = (props) => {
46
47
  {...props}
47
48
  />
48
49
  <TextInput
50
+ id="phone"
49
51
  label="Phone Number"
50
52
  name="phone"
51
53
  onChange={handleOnChangeFormField}
@@ -55,6 +57,7 @@ const TextInputDefault = (props) => {
55
57
  {...props}
56
58
  />
57
59
  <TextInput
60
+ id="email"
58
61
  label="Email Address"
59
62
  name="email"
60
63
  onChange={handleOnChangeFormField}
@@ -64,6 +67,7 @@ const TextInputDefault = (props) => {
64
67
  {...props}
65
68
  />
66
69
  <TextInput
70
+ id="zip"
67
71
  label="Zip Code"
68
72
  name="zip"
69
73
  onChange={handleOnChangeFormField}
@@ -84,6 +88,7 @@ const TextInputDefault = (props) => {
84
88
  <br />
85
89
 
86
90
  <TextInput
91
+ id="first-name"
87
92
  label="First Name"
88
93
  onChange={handleOnChangeFirstName}
89
94
  placeholder="Enter first name"
@@ -0,0 +1 @@
1
+ Add an `id` to your Text Input so that clicking the label will move focus directly to the input.
@@ -1,6 +1,8 @@
1
1
  <%= pb_content_tag(:div, id: nil ) do %>
2
2
  <% if object.label.present? %>
3
+ <label for="<%= object.input_options[:id] || object.id %>" >
3
4
  <%= pb_rails("caption", props: { text: object.label, dark: object.dark, classname: "pb_text_input_kit_label" }) %>
5
+ </label>
4
6
  <% end %>
5
7
  <%= content_tag(:div, class: "#{add_on_class} text_input_wrapper") do %>
6
8
  <% if content.present? %>
@@ -15,7 +17,7 @@
15
17
  <% else %>
16
18
  <%= input_tag %>
17
19
  <% end %>
18
- <%= pb_rails("body", props: {dark: object.dark, status: "negative", text: object.error}) if object.error %>
20
+ <%= pb_rails("body", props: {dark: object.dark, status: "negative", text: object.error, id: object.error_id, aria: { atomic: "true", live: "polite" }, html_options: { role: "alert" }}) if object.error %>
19
21
  <% end %>
20
22
  <% end %>
21
23
 
@@ -64,10 +64,16 @@ module Playbook
64
64
  "#{object.id}-sanitized" if id.present?
65
65
  end
66
66
 
67
+ def error_id
68
+ "#{id}-error" if error.present?
69
+ end
70
+
67
71
  private
68
72
 
69
73
  def all_input_options
70
74
  {
75
+ 'aria-describedby': error.present? ? error_id : nil,
76
+ 'aria-invalid': error.present?,
71
77
  autocomplete: autocomplete == true ? nil : (autocomplete.presence || "off"),
72
78
  class: "text_input #{input_options.dig(:classname) || ''}",
73
79
  data: validation_data,