playbook_ui 16.1.0.pre.alpha.play276813969 → 16.1.0.pre.alpha.testingrailsfix14159
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +12 -2
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +33 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.jsx +71 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.md +4 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
- data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
- data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
- data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +14 -5
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_default.md +1 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +6 -3
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_table/index.ts +29 -27
- data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +10 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.html.erb +3 -3
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.jsx +3 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.md +1 -0
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +25 -9
- data/app/pb_kits/playbook/pb_textarea/textarea.rb +7 -1
- data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +97 -11
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +5 -2
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.html.erb +6 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.jsx +16 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +3 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +47 -1
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +410 -323
- data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
- data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
- data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
- data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
- data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
- data/dist/chunks/lib-BwX82vim.js +29 -0
- data/dist/chunks/vendor.js +3 -3
- data/dist/menu.yml +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
- data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
- data/lib/playbook/forms/builder.rb +2 -2
- data/lib/playbook/version.rb +1 -1
- metadata +23 -6
- data/dist/chunks/_typeahead-C4YsbA48.js +0 -1
- data/dist/chunks/lib-DD34ZrWL.js +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d322d1484a0695b5c047cf03aa19f8af74cf5ad1728dc20a327eb2447c986d61
|
|
4
|
+
data.tar.gz: 042e05bf1e98557de7c10f400b547d52d806788e4e14e40fdf374934eed0a0d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a29ba74bbb1ceefdc7817720135660996607782e6ecbe661b112bd23d7d576dd8526de402247e20551c94acba07ee146c1522f58b3165368926a49af0c00c9e0
|
|
7
|
+
data.tar.gz: c10ac2431f78b6b2d88a0ae45f7543d501341449d1d79d62a2321a93e2594c03aa36ef678f4f2377667801d261fde114228b8ad09c06b3cd71b61206ff6b5f3b
|
|
@@ -66,8 +66,18 @@ const TableCellRenderer = ({
|
|
|
66
66
|
// Find the "owning" colDefinition by accessor. Needed for multi column logic
|
|
67
67
|
const colDef = findColumnDefByAccessor(columnDefinitions ?? [], column.id)
|
|
68
68
|
const cellAlignment = colDef?.columnStyling?.cellAlignment ?? "right"
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
|
|
70
|
+
// Support function-based styling for conditional rendering
|
|
71
|
+
const cellFontColorValue = colDef?.columnStyling?.fontColor
|
|
72
|
+
const cellFontColor = typeof cellFontColorValue === 'function'
|
|
73
|
+
? cellFontColorValue(row)
|
|
74
|
+
: cellFontColorValue
|
|
75
|
+
|
|
76
|
+
const cellBackgroundColorValue = colDef?.columnStyling?.cellBackgroundColor
|
|
77
|
+
const cellBackgroundColor = typeof cellBackgroundColorValue === 'function'
|
|
78
|
+
? cellBackgroundColorValue(row)
|
|
79
|
+
: cellBackgroundColorValue
|
|
80
|
+
|
|
71
81
|
const paddingValue = colDef?.columnStyling?.cellPadding ?? customRowStyle?.cellPadding
|
|
72
82
|
const paddingClass = paddingValue ? `p_${paddingValue}` : undefined
|
|
73
83
|
|
|
@@ -920,6 +920,39 @@ test("columnStyling.backgroundColor works as excpected", () => {
|
|
|
920
920
|
expect(firstEnrollmentCell).toHaveStyle({ backgroundColor: colors.error_subtle });
|
|
921
921
|
});
|
|
922
922
|
|
|
923
|
+
test("columnStyling.cellBackgroundColor works as expected with function", () => {
|
|
924
|
+
const styledColumnDefs = [
|
|
925
|
+
{
|
|
926
|
+
accessor: "year",
|
|
927
|
+
label: "Year",
|
|
928
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
accessor: "newEnrollments",
|
|
932
|
+
label: "New Enrollments",
|
|
933
|
+
columnStyling:{
|
|
934
|
+
cellBackgroundColor: (row) => row.original.newEnrollments > 15 ? colors.success_subtle : colors.error_subtle,
|
|
935
|
+
fontColor: (row) => row.original.newEnrollments > 15 ? colors.success : colors.error,
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
accessor: "scheduledMeetings",
|
|
940
|
+
label: "Scheduled Meetings",
|
|
941
|
+
},
|
|
942
|
+
];
|
|
943
|
+
|
|
944
|
+
render(
|
|
945
|
+
<AdvancedTable
|
|
946
|
+
columnDefinitions={styledColumnDefs}
|
|
947
|
+
data={{ testid: testId }}
|
|
948
|
+
tableData={MOCK_DATA}
|
|
949
|
+
/>
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
const firstEnrollmentCell = screen.getAllByText("20")[0].closest("td");
|
|
953
|
+
expect(firstEnrollmentCell).toHaveStyle({ backgroundColor: colors.success_subtle, color: colors.success });
|
|
954
|
+
});
|
|
955
|
+
|
|
923
956
|
test("columnStyling.headerBackgroundColor works as excpected", () => {
|
|
924
957
|
const styledColumnDefs = [
|
|
925
958
|
{
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import AdvancedTable from '../_advanced_table'
|
|
3
|
+
import colors from '../../tokens/exports/_colors.module.scss'
|
|
4
|
+
import MOCK_DATA from "./advanced_table_mock_data.json"
|
|
5
|
+
import Flex from '../../pb_flex/_flex'
|
|
6
|
+
import Title from '../../pb_title/_title'
|
|
7
|
+
import Body from '../../pb_body/_body'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const AdvancedTableColumnStylingBackgroundCustom = (props) => {
|
|
11
|
+
const columnDefinitions = [
|
|
12
|
+
{
|
|
13
|
+
accessor: "year",
|
|
14
|
+
label: "Year",
|
|
15
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
16
|
+
customRenderer: (row, value) => (
|
|
17
|
+
<Flex flexDirection="column">
|
|
18
|
+
<Title size={4}
|
|
19
|
+
text={value}
|
|
20
|
+
/>
|
|
21
|
+
<Body text="lorem ipsum" />
|
|
22
|
+
<Body text="lorem ipsum" />
|
|
23
|
+
</Flex>
|
|
24
|
+
),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
accessor: "newEnrollments",
|
|
28
|
+
label: "New Enrollments",
|
|
29
|
+
columnStyling:{
|
|
30
|
+
cellBackgroundColor: (row) => row.original.newEnrollments > 15 ? colors.success_subtle : colors.error_subtle,
|
|
31
|
+
fontColor: (row) => row.original.newEnrollments > 15 ? colors.success : colors.error,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
accessor: "scheduledMeetings",
|
|
36
|
+
label: "Scheduled Meetings",
|
|
37
|
+
columnStyling:{
|
|
38
|
+
cellBackgroundColor: (row) => row.original.scheduledMeetings >= 15 ? colors.info_subtle : colors.warning_subtle
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
accessor: "attendanceRate",
|
|
43
|
+
label: "Attendance Rate",
|
|
44
|
+
columnStyling:{cellBackgroundColor: colors.info, headerBackgroundColor: colors.info, fontColor: colors.white, headerFontColor: colors.white},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
accessor: "completedClasses",
|
|
48
|
+
label: "Completed Classes",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
accessor: "classCompletionRate",
|
|
52
|
+
label: "Class Completion Rate",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
accessor: "graduatedStudents",
|
|
56
|
+
label: "Graduated Students",
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
<AdvancedTable
|
|
63
|
+
columnDefinitions={columnDefinitions}
|
|
64
|
+
tableData={MOCK_DATA}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default AdvancedTableColumnStylingBackgroundCustom
|
|
@@ -78,6 +78,7 @@ examples:
|
|
|
78
78
|
- advanced_table_column_styling: Column Styling
|
|
79
79
|
- advanced_table_column_styling_column_headers: Column Styling with Multiple Headers
|
|
80
80
|
- advanced_table_column_styling_background: Column Styling Background Color
|
|
81
|
+
- advanced_table_column_styling_background_custom: Column Styling Background Color (Custom)
|
|
81
82
|
- advanced_table_column_styling_background_multi: Column Styling Background Color with Multiple Headers
|
|
82
83
|
- advanced_table_padding_control: Padding Control using Column Styling
|
|
83
84
|
- advanced_table_column_border_color: Column Group Border Color
|
|
@@ -47,4 +47,5 @@ export { default as AdvancedTableSortPerColumnForMultiColumn } from './_advanced
|
|
|
47
47
|
export { default as AdvancedTablePaddingControl } from './_advanced_table_padding_control.jsx'
|
|
48
48
|
export { default as AdvancedTablePaddingControlPerRow } from './_advanced_table_padding_control_per_row.jsx'
|
|
49
49
|
export { default as AdvancedTableColumnStylingBackground } from './_advanced_table_column_styling_background.jsx'
|
|
50
|
-
export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
|
|
50
|
+
export { default as AdvancedTableColumnStylingBackgroundMulti } from './_advanced_table_column_styling_background_multi.jsx'
|
|
51
|
+
export { default as AdvancedTableColumnStylingBackgroundCustom } from './_advanced_table_column_styling_background_custom.jsx'
|
|
@@ -4,6 +4,8 @@ import Icon from '../pb_icon/_icon'
|
|
|
4
4
|
import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
|
|
5
5
|
import classnames from 'classnames'
|
|
6
6
|
import { globalProps, GlobalProps } from '../utilities/globalProps'
|
|
7
|
+
import colors from '../tokens/exports/_colors.module.scss'
|
|
8
|
+
import spacing from '../tokens/exports/_spacing.module.scss'
|
|
7
9
|
|
|
8
10
|
type CheckboxProps = {
|
|
9
11
|
aria?: {[key: string]: string},
|
|
@@ -19,6 +21,7 @@ type CheckboxProps = {
|
|
|
19
21
|
indeterminate?: boolean,
|
|
20
22
|
name?: string,
|
|
21
23
|
onChange?: (event: React.FormEvent<HTMLInputElement>) => void,
|
|
24
|
+
requiredIndicator?: boolean,
|
|
22
25
|
tabIndex?: number,
|
|
23
26
|
text?: string,
|
|
24
27
|
value?: string,
|
|
@@ -39,6 +42,7 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
|
|
|
39
42
|
indeterminate = false,
|
|
40
43
|
name = '',
|
|
41
44
|
onChange = () => { void 0 },
|
|
45
|
+
requiredIndicator = false,
|
|
42
46
|
tabIndex,
|
|
43
47
|
text = '',
|
|
44
48
|
value = '',
|
|
@@ -124,7 +128,20 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
|
|
|
124
128
|
variant={null}
|
|
125
129
|
>
|
|
126
130
|
{text}
|
|
131
|
+
{requiredIndicator && (
|
|
132
|
+
<span
|
|
133
|
+
aria-hidden="true"
|
|
134
|
+
className="pb_required_indicator"
|
|
135
|
+
style={{
|
|
136
|
+
color: colors.error,
|
|
137
|
+
marginLeft: spacing.space_xs,
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{'*'}
|
|
141
|
+
</span>
|
|
142
|
+
)}
|
|
127
143
|
</Body>
|
|
144
|
+
|
|
128
145
|
</label>
|
|
129
146
|
)
|
|
130
147
|
})
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
<%= pb_rails("icon", props: { icon: "minus", classname: "indeterminate_icon hidden", fixed_width: true}) %>
|
|
6
6
|
</span>
|
|
7
7
|
<span class="pb_checkbox_label">
|
|
8
|
-
<%= pb_rails("body", props: { status: object.checkbox_label_status,
|
|
8
|
+
<%= pb_rails("body", props: { status: object.checkbox_label_status, dark: object.dark, margin_right: object.form_spacing ? "xs" : "" }) do %>
|
|
9
|
+
<%= object.text%>
|
|
10
|
+
<% if object.required_indicator %>
|
|
11
|
+
<span
|
|
12
|
+
class="pb_checkbox_required_indicator"
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
style="color: #DA0014;"
|
|
15
|
+
>*</span>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
9
18
|
</span>
|
|
10
19
|
<% end %>
|
|
@@ -23,6 +23,8 @@ module Playbook
|
|
|
23
23
|
prop :hidden_input, type: Playbook::Props::Boolean,
|
|
24
24
|
default: false
|
|
25
25
|
prop :hidden_value
|
|
26
|
+
prop :required_indicator, type: Playbook::Props::Boolean,
|
|
27
|
+
default: false
|
|
26
28
|
|
|
27
29
|
def classname
|
|
28
30
|
generate_classname("pb_checkbox_kit", checked_class) + error_class
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Checkbox from '../_checkbox'
|
|
3
|
+
|
|
4
|
+
const CheckboxRequiredIndicator = () => {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<Checkbox
|
|
8
|
+
name="checkbox-name"
|
|
9
|
+
requiredIndicator
|
|
10
|
+
text="Checkbox label"
|
|
11
|
+
value="check-box value"
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default CheckboxRequiredIndicator
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
The `requiredIndicator`/`required_indicator` prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
|
|
2
|
+
|
|
3
|
+
You can use `requiredIndicator`/`required_indicator` with any validation approach: HTML5 validation via the `required` prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the `required` prop.
|
|
@@ -8,6 +8,7 @@ examples:
|
|
|
8
8
|
- checkbox_indeterminate: Indeterminate Checkbox
|
|
9
9
|
- checkbox_disabled: Disabled Checkbox
|
|
10
10
|
- checkbox_form: Form and Hidden Input
|
|
11
|
+
# - checkbox_required_indicator: Required Indicator
|
|
11
12
|
|
|
12
13
|
react:
|
|
13
14
|
- checkbox_default: Default
|
|
@@ -17,6 +18,7 @@ examples:
|
|
|
17
18
|
- checkbox_error: Default w/ Error
|
|
18
19
|
- checkbox_indeterminate: Indeterminate Checkbox
|
|
19
20
|
- checkbox_disabled: Disabled Checkbox
|
|
21
|
+
# - checkbox_required_indicator: Required Indicator
|
|
20
22
|
|
|
21
23
|
swift:
|
|
22
24
|
- checkbox_default_swift: Default
|
|
@@ -5,3 +5,4 @@ export { default as CheckboxError } from './_checkbox_error.jsx'
|
|
|
5
5
|
export { default as CheckboxChecked } from './_checkbox_checked.jsx'
|
|
6
6
|
export { default as CheckboxIndeterminate } from './_checkbox_indeterminate.jsx'
|
|
7
7
|
export { default as CheckboxDisabled } from './_checkbox_disabled.jsx'
|
|
8
|
+
export { default as CheckboxRequiredIndicator } from './_checkbox_required_indicator.jsx'
|
|
@@ -40,7 +40,7 @@ type DatePickerProps = {
|
|
|
40
40
|
maxDate: string,
|
|
41
41
|
minDate: string,
|
|
42
42
|
name: string,
|
|
43
|
-
pickerId
|
|
43
|
+
pickerId: string,
|
|
44
44
|
placeholder?: string,
|
|
45
45
|
positionElement?: HTMLElement | null,
|
|
46
46
|
scrollContainer?: string,
|
|
@@ -196,6 +196,8 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
196
196
|
|
|
197
197
|
const angleDown = getAllIcons()["angleDown"].icon as unknown as { [key: string]: SVGElement }
|
|
198
198
|
|
|
199
|
+
const errorId = error ? `${pickerId}-error` : undefined
|
|
200
|
+
|
|
199
201
|
return (
|
|
200
202
|
<div
|
|
201
203
|
{...ariaProps}
|
|
@@ -211,14 +213,18 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
211
213
|
>
|
|
212
214
|
|
|
213
215
|
{!hideLabel && (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
<label htmlFor={pickerId}>
|
|
217
|
+
<Caption
|
|
218
|
+
className="pb_date_picker_kit_label"
|
|
219
|
+
text={label}
|
|
220
|
+
/>
|
|
221
|
+
</label>
|
|
218
222
|
)}
|
|
219
223
|
<>
|
|
220
224
|
<div className="date_picker_input_wrapper">
|
|
221
225
|
<input
|
|
226
|
+
aria-describedby={errorId}
|
|
227
|
+
aria-invalid={!!error}
|
|
222
228
|
autoComplete="off"
|
|
223
229
|
className="date_picker_input"
|
|
224
230
|
disabled={disableInput}
|
|
@@ -232,6 +238,9 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
232
238
|
|
|
233
239
|
{error &&
|
|
234
240
|
<Body
|
|
241
|
+
aria={{ atomic: "true", live: "polite" }}
|
|
242
|
+
htmlOptions={{ role: "alert" }}
|
|
243
|
+
id={errorId}
|
|
235
244
|
status="negative"
|
|
236
245
|
text={error}
|
|
237
246
|
variant={null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
`pickerId`/`picker_id` is a **required prop** to instantiate the Date Picker. The presence of `pickerId`/`picker_id` in your Date Picker also associates the label with the input, providing the ability to focus the Date Picker by clicking the label.
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<%= pb_form_with(scope: :example, url: "", method: :get, validate: true) do |form| %>
|
|
2
|
+
<%= form.typeahead :example_typeahead_field, props: { label: true, required: true, required_indicator: true } %>
|
|
2
3
|
<%= form.text_field :example_text_field, props: { label: true, required: true, required_indicator: true } %>
|
|
3
4
|
<%= form.text_field :example_text_field_2, props: { label: "Text Field Custom Label", required: true, required_indicator: true } %>
|
|
4
|
-
<%= form.
|
|
5
|
-
<%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
|
|
5
|
+
<%= form.phone_number_field :example_phone_number_field, props: { label: true, required: true, required_indicator: true } %>
|
|
6
6
|
<%= form.email_field :example_email_field, props: { label: true, required: true, required_indicator: true } %>
|
|
7
7
|
<%= form.number_field :example_number_field, props: { label: true, required: true, required_indicator: true } %>
|
|
8
8
|
<%= form.search_field :example_search_field, props: { label: true, required: true, required_indicator: true } %>
|
|
9
9
|
<%= form.password_field :example_password_field, props: { label: true, required: true, required_indicator: true } %>
|
|
10
10
|
<%= form.url_field :example_url_field, props: { label: true, required: true, required_indicator: true } %>
|
|
11
|
-
<%= form.
|
|
11
|
+
<%= form.text_area :example_text_area, props: { label: true, required: true, required_indicator: true } %>
|
|
12
|
+
<%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
|
|
13
|
+
<%# <%= form.check_box :example_checkbox, props: { label: true, text: "Checkbox Label", required: true, required_indicator: true } %>
|
|
14
|
+
<%= form.time_picker :example_time_picker_required_indicator, props: { label: true, required: true, required_indicator: true } %>
|
|
12
15
|
|
|
13
16
|
<%= form.actions do |action| %>
|
|
14
17
|
<%= action.submit %>
|
|
@@ -26,6 +26,10 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
26
26
|
const isPhoneNumberInput = field.closest('.pb_phone_number_input')
|
|
27
27
|
if (isPhoneNumberInput) return
|
|
28
28
|
|
|
29
|
+
// Skip TimePicker inputs - they handle their own validation
|
|
30
|
+
const isTimePickerInput = field.closest('.pb_time_picker')
|
|
31
|
+
if (isTimePickerInput) return
|
|
32
|
+
|
|
29
33
|
FIELD_EVENTS.forEach((e) => {
|
|
30
34
|
field.addEventListener(e, debounce((event) => {
|
|
31
35
|
this.validateFormField(event)
|
|
@@ -67,13 +71,16 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
67
71
|
|
|
68
72
|
// Check if this is a phone number input
|
|
69
73
|
const isPhoneNumberInput = kitElement.classList.contains('pb_phone_number_input')
|
|
74
|
+
|
|
75
|
+
// Check if this is a TimePicker input
|
|
76
|
+
const isTimePickerInput = kitElement.classList.contains('pb_time_picker')
|
|
70
77
|
|
|
71
78
|
// ensure clean error message state
|
|
72
79
|
this.clearError(target)
|
|
73
80
|
kitElement.classList.add('error')
|
|
74
81
|
|
|
75
|
-
// Only add error message if it's NOT a phone number input
|
|
76
|
-
if (!isPhoneNumberInput) {
|
|
82
|
+
// Only add error message if it's NOT a phone number input or TimePicker input
|
|
83
|
+
if (!isPhoneNumberInput && !isTimePickerInput) {
|
|
77
84
|
// set the error message element
|
|
78
85
|
const errorMessageContainer = this.errorMessageContainer
|
|
79
86
|
|
|
@@ -178,22 +178,40 @@ const Passphrase = (props: PassphraseProps): React.ReactElement => {
|
|
|
178
178
|
{...inputProps}
|
|
179
179
|
/>
|
|
180
180
|
<span
|
|
181
|
+
aria-label={
|
|
182
|
+
showPassphrase
|
|
183
|
+
? "Passphrase currently visible. Click icon to hide password"
|
|
184
|
+
: "Passphrase currently hidden. Click icon to reveal password"
|
|
185
|
+
}
|
|
186
|
+
aria-pressed={showPassphrase}
|
|
181
187
|
className="show-passphrase-icon"
|
|
182
188
|
onClick={toggleShowPassphrase}
|
|
189
|
+
onKeyDown={(e) => {
|
|
190
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
191
|
+
e.preventDefault()
|
|
192
|
+
toggleShowPassphrase(e as any)
|
|
193
|
+
}
|
|
194
|
+
}}
|
|
195
|
+
role="button"
|
|
196
|
+
tabIndex={0}
|
|
183
197
|
>
|
|
184
198
|
<Body
|
|
185
199
|
className={showPassphrase ? "hide-icon" : ""}
|
|
186
200
|
color="light"
|
|
187
201
|
dark={dark}
|
|
188
202
|
>
|
|
189
|
-
<Icon
|
|
203
|
+
<Icon
|
|
204
|
+
aria={{ label: "eye icon" }}
|
|
205
|
+
icon="eye-slash"
|
|
206
|
+
/>
|
|
190
207
|
</Body>
|
|
191
208
|
<Body
|
|
192
209
|
className={showPassphrase ? "" : "hide-icon"}
|
|
193
210
|
color="light"
|
|
194
211
|
dark={dark}
|
|
195
212
|
>
|
|
196
|
-
<Icon
|
|
213
|
+
<Icon
|
|
214
|
+
aria={{ label: "eye icon" }}
|
|
197
215
|
className="svg-inline--fa"
|
|
198
216
|
customIcon={eyeIcon.icon as unknown as { [key: string]: SVGElement }}
|
|
199
217
|
/>
|
|
@@ -94,25 +94,51 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
94
94
|
|
|
95
95
|
const htmlProps = buildHtmlProps(htmlOptions)
|
|
96
96
|
|
|
97
|
+
const fieldId = id ? (id as string) : null
|
|
98
|
+
const labelElementId = fieldId ? `${fieldId}-label` : null
|
|
99
|
+
|
|
97
100
|
const handleOnEditorReady = (editorInstance: Editor) => {
|
|
98
101
|
setEditor(editorInstance)
|
|
102
|
+
|
|
99
103
|
setTimeout(() => {
|
|
100
|
-
const oldId = editorInstance.element
|
|
101
|
-
if (oldId)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
const oldId = editorInstance.element?.getAttribute("input")
|
|
105
|
+
if (!oldId) return
|
|
106
|
+
|
|
107
|
+
const hiddenInput = document.getElementById(oldId) as HTMLElement | null
|
|
108
|
+
if (!hiddenInput) return
|
|
109
|
+
|
|
110
|
+
const hiddenInputId = (inputOptions.id as string) || oldId
|
|
111
|
+
|
|
112
|
+
if (hiddenInputId !== oldId) {
|
|
113
|
+
hiddenInput.id = hiddenInputId
|
|
114
|
+
editorInstance.element?.setAttribute("input", hiddenInputId)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (inputOptions.name) {
|
|
118
|
+
hiddenInput.setAttribute("name", inputOptions.name as string)
|
|
112
119
|
}
|
|
120
|
+
|
|
121
|
+
const editorDomId = (id as string) || `${hiddenInputId}_trix`
|
|
122
|
+
const trixLabelId = ((id as string) || hiddenInputId) + "-label"
|
|
123
|
+
|
|
124
|
+
if (label) {
|
|
125
|
+
editorInstance.element?.setAttribute("aria-labelledby", trixLabelId)
|
|
126
|
+
}
|
|
127
|
+
editorInstance.element!.id = editorDomId
|
|
113
128
|
})
|
|
114
129
|
}
|
|
115
130
|
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!advancedEditor || !fieldId || !labelElementId) return
|
|
133
|
+
|
|
134
|
+
const dom = advancedEditor.view?.dom as HTMLElement | undefined
|
|
135
|
+
if (!dom) return
|
|
136
|
+
|
|
137
|
+
dom.setAttribute("aria-labelledby", labelElementId)
|
|
138
|
+
dom.setAttribute("role", "textbox")
|
|
139
|
+
dom.setAttribute("aria-multiline", "true")
|
|
140
|
+
}, [advancedEditor, fieldId, labelElementId])
|
|
141
|
+
|
|
116
142
|
// DOM manipulation must wait for editor to be ready
|
|
117
143
|
if (editor && editor.element) {
|
|
118
144
|
const toolbarElement = editor.element.parentElement.querySelector('trix-toolbar') as HTMLElement,
|
|
@@ -214,6 +240,8 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
214
240
|
// Determine if toolbar should be shown
|
|
215
241
|
const shouldShowToolbar = focus && advancedEditor ? showToolbarOnFocus : advancedEditorToolbar
|
|
216
242
|
|
|
243
|
+
const labelFor = advancedEditor ? fieldId : (id ? id : (inputOptions.id ? `${inputOptions.id}_trix` : undefined))
|
|
244
|
+
|
|
217
245
|
return (
|
|
218
246
|
<div
|
|
219
247
|
{...ariaProps}
|
|
@@ -223,7 +251,14 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
223
251
|
ref={focus ? containerRef : undefined}
|
|
224
252
|
>
|
|
225
253
|
{label && (
|
|
226
|
-
<label
|
|
254
|
+
<label
|
|
255
|
+
{...(labelFor ? { htmlFor: labelFor, id: labelElementId } : {})}
|
|
256
|
+
onMouseDown={(e) => {
|
|
257
|
+
if (!advancedEditor || !fieldId) return
|
|
258
|
+
e.preventDefault()
|
|
259
|
+
advancedEditor.commands.focus()
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
227
262
|
{
|
|
228
263
|
requiredIndicator ? (
|
|
229
264
|
<Caption className="pb_text_input_kit_label"
|
|
@@ -246,9 +281,9 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
246
281
|
advancedEditor ? (
|
|
247
282
|
<div
|
|
248
283
|
className={classnames(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
284
|
+
"pb_rich_text_editor_advanced_container",
|
|
285
|
+
{ [`input_height_${inputHeight}`]: !!inputHeight,[`input_min_height_${inputMinHeight}`]: !!inputMinHeight ,["toolbar-active"]: shouldShowToolbar }
|
|
286
|
+
)}
|
|
252
287
|
>
|
|
253
288
|
{shouldShowToolbar && (
|
|
254
289
|
<EditorToolbar editor={advancedEditor}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import RichTextEditor from '../../pb_rich_text_editor/_rich_text_editor'
|
|
3
|
+
import { useEditor, EditorContent } from "@tiptap/react"
|
|
4
|
+
import StarterKit from "@tiptap/starter-kit"
|
|
5
|
+
import Link from '@tiptap/extension-link'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const RichTextEditorAdvancedLabel = (props) => {
|
|
9
|
+
|
|
10
|
+
const editor = useEditor({
|
|
11
|
+
extensions: [StarterKit, Link],
|
|
12
|
+
content: "Add your text here. You can format your text, add links, quotes, and bullets.",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const editorNoLabel = useEditor({
|
|
16
|
+
extensions: [StarterKit, Link],
|
|
17
|
+
content: "Add your text here. You can format your text, add links, quotes, and bullets.",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (!editor || !editorNoLabel) return null
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<RichTextEditor
|
|
25
|
+
advancedEditor={editor}
|
|
26
|
+
id={"advanced-example"}
|
|
27
|
+
label="Advanced Example Label"
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
<EditorContent editor={editor}/>
|
|
31
|
+
</RichTextEditor>
|
|
32
|
+
<br/>
|
|
33
|
+
<RichTextEditor
|
|
34
|
+
advancedEditor={editorNoLabel}
|
|
35
|
+
label="Advanced Example Label No ID"
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
<EditorContent editor={editorNoLabel}/>
|
|
39
|
+
</RichTextEditor>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default RichTextEditorAdvancedLabel
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
The optional `label` prop adds a visible label to the advanced editor. Passing in the `id` prop associates the `label` with the editor for accessibility, enabling screen reader support and label-based focus behavior.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import RichTextEditor from '../../pb_rich_text_editor/_rich_text_editor'
|
|
3
|
+
|
|
4
|
+
const RichTextEditorLabel = (props) => {
|
|
5
|
+
const [value, setValue] = useState(''),
|
|
6
|
+
handleOnChange = (html) => setValue(html)
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
<RichTextEditor
|
|
11
|
+
id="example"
|
|
12
|
+
label="Example Label"
|
|
13
|
+
onChange={handleOnChange}
|
|
14
|
+
value={value}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
<br/>
|
|
18
|
+
<RichTextEditor
|
|
19
|
+
label="Example Label No ID"
|
|
20
|
+
onChange={handleOnChange}
|
|
21
|
+
value={value}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default RichTextEditorLabel
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
The optional `label` prop adds a visible label to the editor. Passing in the `id` prop associates the `label` with the editor for accessibility, enabling screen reader support and label-based focus behavior.
|