Skip to content

Commit d9c72cb

Browse files
authored
chore(select): add support for data attributes of Select component (#436)
* chore(select): enhance component with data attributes * Add data-ga and data-testid attributes to Select component * Add TS declaration file for Select compoent * Export Select component from netdata-ui library * chore(netdata table): remove redundant selectedValue from DropdownFilter * Import Select component as default * Remove reduntant selectedValue * Implicit return of component * chore(netdata table): Implicit return of row actions cell * fix(select): warning for innerProps Remove console warning for unsupported innerProps attribute on input component * chore(utils): add capitalizeFirstLetter util * test(utils): add test for capitalizeFirstLetter util * review(select): rename addDataAttrs to withDataAttrs * v2.8.18
1 parent 65bc8c9 commit d9c72cb

File tree

9 files changed

+197
-70
lines changed

9 files changed

+197
-70
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@netdata/netdata-ui",
3-
"version": "2.8.17",
3+
"version": "2.8.18",
44
"description": "netdata UI kit",
55
"main": "./lib/index.js",
66
"files": [

src/components/select/index.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { FC } from "react"
2+
import { Props } from "react-select"
3+
4+
export interface SelectProps extends Props {
5+
"data-ga"?: string;
6+
"data-testid"?: string;
7+
}
8+
9+
declare const Select: FC<SelectProps>
10+
11+
export { Select }
12+
13+
export default Select

src/components/select/index.js

+130-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,101 @@
1-
import ReactSelect from "react-select"
1+
import React from "react"
22
import styled from "styled-components"
3+
import ReactSelect, { components } from "react-select"
4+
import { capitalizeFirstLetter } from "src/utils"
5+
6+
const withDataAttrs =
7+
(Component, { ga, testId }, hasInnerProps = true) =>
8+
({ innerProps, ...rest }) => {
9+
const dataProps = {
10+
...(ga ? { "data-ga": ga } : {}),
11+
...(testId ? { "data-testid": testId } : {}),
12+
}
13+
const props = {
14+
...rest,
15+
...(hasInnerProps ? { innerProps: { ...innerProps, ...dataProps } } : { ...dataProps }),
16+
}
17+
return <Component {...props} />
18+
}
19+
20+
const makeDataAttrs = (ga, testId) => type => {
21+
const dataAttrs = {}
22+
23+
if (ga) {
24+
const gaParts = ga.split("::")
25+
gaParts[1] = `${gaParts[1]}-${type}`
26+
dataAttrs.ga = gaParts.join("::")
27+
}
28+
29+
if (testId) {
30+
dataAttrs.testId = `${testId}${capitalizeFirstLetter(type)}`
31+
}
32+
33+
return dataAttrs
34+
}
35+
36+
const makeCustomComponents = makeComponentDataAttrs => ({
37+
ClearIndicator: withDataAttrs(
38+
components.ClearIndicator,
39+
makeComponentDataAttrs("clearIndicator")
40+
),
41+
Control: withDataAttrs(components.Control, makeComponentDataAttrs("clearIndicator")),
42+
DropdownIndicator: withDataAttrs(
43+
components.DropdownIndicator,
44+
makeComponentDataAttrs("dropdownIndicator")
45+
),
46+
DownChevron: withDataAttrs(components.DownChevron, makeComponentDataAttrs("downChevron")),
47+
CrossIcon: withDataAttrs(components.CrossIcon, makeComponentDataAttrs("crossIcon")),
48+
Group: withDataAttrs(components.Group, makeComponentDataAttrs("group")),
49+
GroupHeading: withDataAttrs(components.GroupHeading, makeComponentDataAttrs("groupHeading")),
50+
IndicatorsContainer: withDataAttrs(
51+
components.IndicatorsContainer,
52+
makeComponentDataAttrs("indicatorsContainer")
53+
),
54+
IndicatorSeparator: withDataAttrs(
55+
components.IndicatorSeparator,
56+
makeComponentDataAttrs("indicatorSeparator")
57+
),
58+
Input: withDataAttrs(components.Input, makeComponentDataAttrs("input"), false),
59+
LoadingIndicator: withDataAttrs(
60+
components.LoadingIndicator,
61+
makeComponentDataAttrs("loadingIndicator")
62+
),
63+
Menu: withDataAttrs(components.Menu, makeComponentDataAttrs("menu")),
64+
MenuList: withDataAttrs(components.MenuList, makeComponentDataAttrs("menuList")),
65+
MenuPortal: withDataAttrs(components.MenuPortal, makeComponentDataAttrs("menuPortal")),
66+
LoadingMessage: withDataAttrs(
67+
components.LoadingMessage,
68+
makeComponentDataAttrs("loadingMessage")
69+
),
70+
NoOptionsMessage: withDataAttrs(
71+
components.NoOptionsMessage,
72+
makeComponentDataAttrs("noOptionsMessage")
73+
),
74+
MultiValue: withDataAttrs(components.MultiValue, makeComponentDataAttrs("multiValue")),
75+
MultiValueContainer: withDataAttrs(
76+
components.MultiValueContainer,
77+
makeComponentDataAttrs("multiValueContainer")
78+
),
79+
MultiValueLabel: withDataAttrs(
80+
components.MultiValueLabel,
81+
makeComponentDataAttrs("multiValueLabel")
82+
),
83+
MultiValueRemove: withDataAttrs(
84+
components.MultiValueRemove,
85+
makeComponentDataAttrs("multiValueRemove")
86+
),
87+
Option: withDataAttrs(components.Option, makeComponentDataAttrs("option")),
88+
Placeholder: withDataAttrs(components.Placeholder, makeComponentDataAttrs("placeholder")),
89+
SelectContainer: withDataAttrs(
90+
components.SelectContainer,
91+
makeComponentDataAttrs("selectContainer")
92+
),
93+
SingleValue: withDataAttrs(components.SingleValue, makeComponentDataAttrs("singleValue")),
94+
ValueContainer: withDataAttrs(
95+
components.ValueContainer,
96+
makeComponentDataAttrs("valueContainer")
97+
),
98+
})
399

4100
const makeCustomTheme = theme => selectTheme => {
5101
return {
@@ -41,26 +137,29 @@ const makeCustomStyles = (theme, { size, ...providedStyles } = {}) => ({
41137
borderColor: theme.colors.inputBorderHover,
42138
},
43139
}),
44-
input: (styles, state) =>({
140+
input: (styles, state) => ({
45141
...styles,
46142
color: state.isDisabled ? theme.colors.placeholder : theme.colors.textDescription,
47-
...(size === "tiny" ? {
48-
lineHeight: "18px",
49-
paddingBottom: 0,
50-
paddingTop: 0
51-
} : {}),
143+
...(size === "tiny"
144+
? {
145+
lineHeight: "18px",
146+
paddingBottom: 0,
147+
paddingTop: 0,
148+
}
149+
: {}),
52150
}),
53151
menu: styles => ({ ...styles, zIndex: 100 }),
54152
menuPortal: styles => ({ ...styles, zIndex: 9999 }),
55-
multiValue: (styles) => ({
153+
multiValue: styles => ({
56154
...styles,
57155
fontSize: size === "tiny" ? "12px" : "14px",
156+
flexDirection: "row-reverse",
58157
...(size === "tiny" ? { minHeight: 18 } : {}),
59158
}),
60159
multiValueLabel: (styles, state) => ({
61160
...styles,
62161
backgroundColor: theme.colors.disabled,
63-
borderRadius: "2px 0 0 2px",
162+
borderRadius: "0 2px 2px 0",
64163
color: state.isDisabled ? theme.colors.placeholder : theme.colors.textDescription,
65164
...(size === "tiny" ? { padding: "1px" } : {}),
66165
paddingRight: state.data.isDisabled ? "8px" : "",
@@ -70,13 +169,13 @@ const makeCustomStyles = (theme, { size, ...providedStyles } = {}) => ({
70169
...(state.data.isDisabled
71170
? { ...styles, display: "none" }
72171
: {
73-
...styles,
74-
borderRadius: "0 2px 2px 0",
75-
background: theme.colors.disabled,
76-
":hover": {
77-
background: theme.colors.borderSecondary,
78-
},
79-
}),
172+
...styles,
173+
borderRadius: "2px 0 0 2px",
174+
background: theme.colors.disabled,
175+
":hover": {
176+
background: theme.colors.tabsBorder,
177+
},
178+
}),
80179
}),
81180
option: (styles, state) => ({
82181
...styles,
@@ -86,33 +185,33 @@ const makeCustomStyles = (theme, { size, ...providedStyles } = {}) => ({
86185
placeholder: styles => ({
87186
...styles,
88187
color: theme.colors.placeholder,
89-
...(size === "tiny"
90-
? { fontSize: "12px", lineHeight: "18px" }
91-
: {})
188+
...(size === "tiny" ? { fontSize: "12px", lineHeight: "18px" } : {}),
92189
}),
93190
singleValue: (styles, state) => ({
94191
...styles,
95192
color: state.isDisabled ? theme.colors.placeholder : theme.colors.textDescription,
96-
fontSize: size === "tiny" ? "12px" : "14px"
193+
fontSize: size === "tiny" ? "12px" : "14px",
97194
}),
98195
...(size === "tiny"
99196
? {
100-
dropdownIndicator: styles => ({ ...styles, padding: "3px" }),
101-
clearIndicator: styles => ({ ...styles, padding: "3px" }),
102-
indicatorsContainer: styles => ({ ...styles, minHeight: 28 }),
103-
valueContainer: styles => ({
104-
...styles,
105-
minHeight: 28,
106-
padding: "1px 6px",
107-
}),
108-
}
197+
dropdownIndicator: styles => ({ ...styles, padding: "3px" }),
198+
clearIndicator: styles => ({ ...styles, padding: "3px" }),
199+
indicatorsContainer: styles => ({ ...styles, minHeight: 28 }),
200+
valueContainer: styles => ({
201+
...styles,
202+
minHeight: 28,
203+
padding: "1px 6px",
204+
}),
205+
}
109206
: {}),
110207
...providedStyles,
111208
})
112209

113-
// @TODO check react-select for rendering data attributes
114-
export const Select = styled(ReactSelect).attrs(props => ({
210+
const Select = styled(ReactSelect).attrs(({ "data-ga": ga, "data-testid": testId, ...props }) => ({
115211
...props,
212+
components: makeCustomComponents(makeDataAttrs(ga, testId)),
116213
theme: makeCustomTheme(props.theme),
117214
styles: makeCustomStyles(props.theme, props.styles),
118215
}))``
216+
217+
export default Select
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import React from "react"
2+
import Select from "src/components/select"
23

3-
import { Select } from "src/components/select"
4-
5-
const DropdownFilter = ({ onChange, value, options, isMulti, styles }) => {
6-
const selectedValue = value
7-
8-
return (
9-
<Select
10-
isMulti={isMulti}
11-
options={options}
12-
value={selectedValue}
13-
onChange={option => {
14-
onChange(option)
15-
}}
16-
styles={styles}
17-
/>
18-
)
19-
}
4+
const DropdownFilter = ({ onChange, value, options, isMulti, styles }) => (
5+
<Select
6+
isMulti={isMulti}
7+
options={options}
8+
value={value}
9+
onChange={option => {
10+
onChange(option)
11+
}}
12+
styles={styles}
13+
/>
14+
)
2015

2116
export default DropdownFilter

src/components/tableV2/features/useRowActions.js

+19-21
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,25 @@ export default (rowActions, { testPrefix } = {}) => {
6868
enableHiding: false,
6969
enableResizing: false,
7070
header: "Actions",
71-
cell: ({ row, table }) => {
72-
return (
73-
<Flex data-testid="action-cell" height="100%" gap={2}>
74-
{availableRowActions.map(
75-
({ id, handleAction, isDisabled, isVisible = true, dataGa, ...rest }) => (
76-
<Action
77-
{...rest}
78-
disabled={typeof isDisabled === "function" ? isDisabled(row.original) : isDisabled}
79-
visible={typeof isVisible === "function" ? isVisible(row.original) : isVisible}
80-
dataGa={typeof dataGa === "function" ? dataGa(row.original) : dataGa}
81-
key={id}
82-
id={id}
83-
handleAction={() => handleAction(row.original, table)}
84-
testPrefix={testPrefix}
85-
currentRow={row}
86-
/>
87-
)
88-
)}
89-
</Flex>
90-
)
91-
},
71+
cell: ({ row, table }) => (
72+
<Flex data-testid="action-cell" height="100%" gap={2}>
73+
{availableRowActions.map(
74+
({ id, handleAction, isDisabled, isVisible = true, dataGa, ...rest }) => (
75+
<Action
76+
{...rest}
77+
disabled={typeof isDisabled === "function" ? isDisabled(row.original) : isDisabled}
78+
visible={typeof isVisible === "function" ? isVisible(row.original) : isVisible}
79+
dataGa={typeof dataGa === "function" ? dataGa(row.original) : dataGa}
80+
key={id}
81+
id={id}
82+
handleAction={() => handleAction(row.original, table)}
83+
testPrefix={testPrefix}
84+
currentRow={row}
85+
/>
86+
)
87+
)}
88+
</Flex>
89+
),
9290
enableColumnFilter: false,
9391
enableSorting: false,
9492
meta: { stopPropagation: true },

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,5 @@ export { default as Modal } from "./components/modal"
117117
export { ConfirmationDialog } from "./components/confirmation-dialog"
118118

119119
export { NetdataTable } from "./components/tableV2"
120+
121+
export { default as Select } from "./components/select"

src/utils/capitalizeFirstLetter.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const capitalizeFirstLetter = (string, capitalizeOnlyFirst) => {
2+
const word = capitalizeOnlyFirst ? string.toLowerCase() : string
3+
return word.charAt(0).toUpperCase() + word.slice(1)
4+
}
5+
6+
export default capitalizeFirstLetter
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import capitalizeFirstLetter from "./capitalizeFirstLetter"
2+
3+
describe("capitalizeFirstLetter util", () => {
4+
test("should return string with capitalized first letter", () => {
5+
const result = capitalizeFirstLetter("hello")
6+
expect(result).toEqual("Hello")
7+
})
8+
9+
test("should return capitalized string with capitalized first letter only", () => {
10+
const result = capitalizeFirstLetter("HELLO", true)
11+
expect(result).toEqual("Hello")
12+
})
13+
})

src/utils/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { mergeRefs } from "./mergeRefs"
22
export * from "./assertions"
3+
export { default as capitalizeFirstLetter } from "./capitalizeFirstLetter"

0 commit comments

Comments
 (0)