playbook_ui 16.2.0.pre.rc.1 → 16.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.
- 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_dropdown/_dropdown.tsx +46 -11
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +10 -4
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
- data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
- data/app/pb_kits/playbook/pb_dropdown/index.js +125 -73
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +5 -3
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
- 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_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 +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +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 +19 -6
- data/dist/chunks/_typeahead-CWA5wlah.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: 57e7920a7b47285ec3e254c7c67f5eda6d1742c4b51455bff71452d431bbaa36
|
|
4
|
+
data.tar.gz: 6894b82d417daccc631daed96d00ee1462bf76944958c4ec2226f9a0fb588463
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b48cd0af7cc499e37ef1f2cc702c06f26b5865095b362da32f23e80eeb8aef75bae49c79e29b38d0c8bbe2790564896ebb92496196d639ae351d0579f57ca16
|
|
7
|
+
data.tar.gz: 5a57afcd4dfa17ef354d30b48c8d8a501e30e86283e343723fa63fee9395c115288b47d8a9ee68848d233e5ea0a96ae81cceb91bf0c21dde21e20608c851a002
|
|
@@ -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'
|
|
@@ -119,6 +119,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
119
119
|
|
|
120
120
|
const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
|
|
121
121
|
|
|
122
|
+
// Use a suffix for the trigger ID to avoid conflict with the outer div's id
|
|
123
|
+
const sanitizeForId = (str: string) =>
|
|
124
|
+
str.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
125
|
+
const selectId = id
|
|
126
|
+
? `${id}_trigger`
|
|
127
|
+
: label
|
|
128
|
+
? sanitizeForId(label)
|
|
129
|
+
: undefined;
|
|
130
|
+
const errorId = error ? `${selectId}-error` : undefined;
|
|
131
|
+
|
|
122
132
|
const [filterItem, setFilterItem] = useState("");
|
|
123
133
|
const initialSelected = useMemo(() => {
|
|
124
134
|
// Handle quickpick variant with string defaultValue (e.g., "This Month")
|
|
@@ -151,9 +161,19 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
151
161
|
|
|
152
162
|
const dropdownRef = useRef(null);
|
|
153
163
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
154
|
-
const inputWrapperRef = useRef(null);
|
|
164
|
+
const inputWrapperRef = useRef<HTMLDivElement | null>(null);
|
|
155
165
|
const dropdownContainerRef = useRef(null);
|
|
156
166
|
|
|
167
|
+
const handleLabelClick = (e: React.MouseEvent) => {
|
|
168
|
+
e.stopPropagation();
|
|
169
|
+
if (selectId) {
|
|
170
|
+
const trigger = document.getElementById(selectId);
|
|
171
|
+
if (trigger) trigger.focus();
|
|
172
|
+
}
|
|
173
|
+
setIsInputFocused(true);
|
|
174
|
+
toggleDropdown();
|
|
175
|
+
};
|
|
176
|
+
|
|
157
177
|
const selectedArray = Array.isArray(selected)
|
|
158
178
|
? selected
|
|
159
179
|
: selected && Object.keys(selected).length
|
|
@@ -411,9 +431,12 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
411
431
|
autocomplete,
|
|
412
432
|
clearable,
|
|
413
433
|
dropdownContainerRef,
|
|
414
|
-
|
|
434
|
+
error,
|
|
435
|
+
errorId,
|
|
415
436
|
filterItem,
|
|
437
|
+
filteredOptions,
|
|
416
438
|
focusedOptionIndex,
|
|
439
|
+
label,
|
|
417
440
|
formPillProps,
|
|
418
441
|
handleBackspace,
|
|
419
442
|
handleChange,
|
|
@@ -423,6 +446,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
423
446
|
inputWrapperRef,
|
|
424
447
|
isDropDownClosed,
|
|
425
448
|
isInputFocused,
|
|
449
|
+
selectId,
|
|
426
450
|
multiSelect,
|
|
427
451
|
onSelect,
|
|
428
452
|
optionsWithBlankSelection,
|
|
@@ -434,13 +458,20 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
434
458
|
toggleDropdown
|
|
435
459
|
}}
|
|
436
460
|
>
|
|
437
|
-
{label &&
|
|
438
|
-
<
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
461
|
+
{label && (
|
|
462
|
+
<label
|
|
463
|
+
data-dropdown="pb-dropdown-label"
|
|
464
|
+
htmlFor={selectId}
|
|
465
|
+
onClick={handleLabelClick}
|
|
466
|
+
>
|
|
467
|
+
<Caption
|
|
468
|
+
className="pb_dropdown_kit_label"
|
|
469
|
+
dark={dark}
|
|
470
|
+
marginBottom="xs"
|
|
471
|
+
text={label}
|
|
472
|
+
/>
|
|
473
|
+
</label>
|
|
474
|
+
)}
|
|
444
475
|
<div className={`dropdown_wrapper ${error ? 'error' : ''}`}
|
|
445
476
|
onBlur={() => {
|
|
446
477
|
// Debounce to delay the execution to prevent jumpiness in Focus state
|
|
@@ -473,12 +504,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
473
504
|
</>
|
|
474
505
|
)}
|
|
475
506
|
|
|
476
|
-
{error &&
|
|
507
|
+
{error && (
|
|
477
508
|
<Body
|
|
509
|
+
aria={{ atomic: "true", live: "polite" }}
|
|
510
|
+
dark={dark}
|
|
511
|
+
htmlOptions={{ role: "alert" }}
|
|
512
|
+
id={errorId}
|
|
478
513
|
status="negative"
|
|
479
514
|
text={error}
|
|
480
515
|
/>
|
|
481
|
-
}
|
|
516
|
+
)}
|
|
482
517
|
</div>
|
|
483
518
|
</DropdownContext.Provider>
|
|
484
519
|
</div>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
<%
|
|
1
|
+
<%
|
|
2
2
|
options = [
|
|
3
3
|
{ label: 'United States', value: 'unitedStates', id: 'us' },
|
|
4
4
|
{ label: 'Canada', value: 'canada', id: 'ca' },
|
|
5
5
|
{ label: 'Pakistan', value: 'pakistan', id: 'pk' },
|
|
6
6
|
]
|
|
7
|
-
|
|
8
7
|
%>
|
|
9
8
|
|
|
10
|
-
<%= pb_rails("dropdown", props: {
|
|
9
|
+
<%= pb_rails("dropdown", props: {
|
|
10
|
+
id: "select_a_country",
|
|
11
|
+
label: "Select a Country",
|
|
12
|
+
options: options
|
|
13
|
+
}) %>
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
The top-level Dropdown component optionally accepts any string through a `label` prop to produce a label above your trigger element.
|
|
1
|
+
The top-level Dropdown component optionally accepts any string through a `label` prop to produce a label above your trigger element.
|
|
2
|
+
|
|
3
|
+
Add an `id` to wire the label to the trigger so that clicking the label will move focus directly to the input, and open the drop-down.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<%= pb_content_tag do %>
|
|
2
2
|
<% if object.label.present? %>
|
|
3
|
-
|
|
3
|
+
<label for="<%= object.select_id %>" data-dropdown="pb-dropdown-label">
|
|
4
|
+
<%= pb_rails("caption", props: { text: object.label, margin_bottom: "xs", classname: "pb_dropdown_kit_label", dark: object.dark }) %>
|
|
5
|
+
</label>
|
|
4
6
|
<% end %>
|
|
5
7
|
<div class="dropdown_wrapper<%= error_class %>" style="position: relative">
|
|
6
8
|
<input
|
|
@@ -16,9 +18,11 @@
|
|
|
16
18
|
<% end %>
|
|
17
19
|
<% if content.present? %>
|
|
18
20
|
<%= content.presence %>
|
|
19
|
-
|
|
21
|
+
<% if object.error.present? %>
|
|
22
|
+
<%= pb_rails("body", props: { status: "negative", text: object.error, id: object.error_id, aria: { atomic: "true", live: "polite" }, html_options: { role: "alert" }, dark: object.dark }) %>
|
|
23
|
+
<% end %>
|
|
20
24
|
<% else %>
|
|
21
|
-
<%= pb_rails("dropdown/dropdown_trigger", props:{ autocomplete: object.autocomplete, multi_select:object.multi_select, placeholder: object.placeholder }) %>
|
|
25
|
+
<%= pb_rails("dropdown/dropdown_trigger", props: { autocomplete: object.autocomplete, multi_select: object.multi_select, placeholder: object.placeholder, select_id: object.select_id, label: object.label, error_id: object.error_id, error: object.error }) %>
|
|
22
26
|
<%= pb_rails("dropdown/dropdown_container", props: { searchbar: object.searchbar, constrain_height: object.constrain_height }) do %>
|
|
23
27
|
<% if options_with_blank.present? %>
|
|
24
28
|
<% options_with_blank.each do |option| %>
|
|
@@ -27,7 +31,9 @@
|
|
|
27
31
|
<% end %>
|
|
28
32
|
<% end %>
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
<% if object.error.present? %>
|
|
35
|
+
<%= pb_rails("body", props: { status: "negative", text: object.error, id: object.error_id, aria: { atomic: "true", live: "polite" }, html_options: { role: "alert" }, dark: object.dark }) %>
|
|
36
|
+
<% end %>
|
|
31
37
|
<% end %>
|
|
32
38
|
</div>
|
|
33
39
|
<% end %>
|
|
@@ -10,6 +10,7 @@ module Playbook
|
|
|
10
10
|
prop :label, type: Playbook::Props::String
|
|
11
11
|
prop :name, type: Playbook::Props::String
|
|
12
12
|
prop :error, type: Playbook::Props::String
|
|
13
|
+
prop :id, type: Playbook::Props::String
|
|
13
14
|
prop :required, type: Playbook::Props::Boolean,
|
|
14
15
|
default: false
|
|
15
16
|
prop :default_value
|
|
@@ -68,6 +69,14 @@ module Playbook
|
|
|
68
69
|
generate_classname("pb_dropdown", variant, separators_class)
|
|
69
70
|
end
|
|
70
71
|
|
|
72
|
+
def select_id
|
|
73
|
+
id.presence || (label.present? ? label.downcase.gsub(/\s+/, "_").gsub(/[^a-z0-9_]/, "") : nil)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def error_id
|
|
77
|
+
error.present? ? "#{select_id || 'dropdown_trigger'}-error" : nil
|
|
78
|
+
end
|
|
79
|
+
|
|
71
80
|
private
|
|
72
81
|
|
|
73
82
|
def error_class
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<%= pb_content_tag do %>
|
|
2
2
|
<% if content.present? %>
|
|
3
|
-
<div style="display: inline-block" tabindex="0" data-dropdown-custom-trigger
|
|
3
|
+
<div style="display: inline-block" tabindex="0" data-dropdown="pb-dropdown-trigger" data-dropdown-custom-trigger aria-invalid="<%= object.error.present? %>"
|
|
4
|
+
<% if object.label.present? %> aria-label="<%= [object.label, object.default_display_placeholder].join(', ') %>"<% end %>
|
|
5
|
+
<% if object.select_id.present? %> id="<%= object.select_id %>"<% end %>
|
|
6
|
+
<% if object.error_id.present? %> aria-describedby="<%= object.error_id %>"<% end %>
|
|
7
|
+
>
|
|
4
8
|
<%= content.presence %>
|
|
5
9
|
</div>
|
|
6
10
|
<% else %>
|
|
@@ -9,10 +13,11 @@
|
|
|
9
13
|
border_radius:"lg",
|
|
10
14
|
classname: object.trigger_wrapper_classes,
|
|
11
15
|
cursor: object.autocomplete ? "text" : "pointer",
|
|
16
|
+
id: object.select_id,
|
|
12
17
|
justify: "between",
|
|
13
18
|
padding_x:"sm",
|
|
14
19
|
padding_y:"xs",
|
|
15
|
-
html_options: {tabindex:"0"}
|
|
20
|
+
html_options: { tabindex: "0", "aria-label": object.label.present? ? [object.label, object.default_display_placeholder].join(", ") : nil, "aria-describedby": object.error_id, "aria-invalid": object.error.present?, "data-dropdown": "pb-dropdown-trigger" }
|
|
16
21
|
}) do %>
|
|
17
22
|
<%= pb_rails("flex/flex_item", props: { fixed_size: object.multi_select ? "85%" : "" }) do %>
|
|
18
23
|
<%= pb_rails("flex", props: {align: "center"}) do %>
|
|
@@ -13,6 +13,10 @@ module Playbook
|
|
|
13
13
|
default: false
|
|
14
14
|
prop :multi_select, type: Playbook::Props::Boolean,
|
|
15
15
|
default: false
|
|
16
|
+
prop :select_id, type: Playbook::Props::String
|
|
17
|
+
prop :label, type: Playbook::Props::String
|
|
18
|
+
prop :error_id, type: Playbook::Props::String
|
|
19
|
+
prop :error, type: Playbook::Props::String
|
|
16
20
|
|
|
17
21
|
def data
|
|
18
22
|
Hash(prop(:data)).merge(dropdown_trigger: true, dropdown_placeholder: default_display_placeholder)
|