Skip to content

Commit 4ddc0ef

Browse files
committed
Refactor icons padding affordance with CSS variables and calc()
- use inline styles to set the CSS variables dynamically based on props, which lets us reduce compressed overrides - store icon size affordances in variable obj
1 parent 5501d49 commit 4ddc0ef

7 files changed

Lines changed: 144 additions & 31 deletions

File tree

packages/eui/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ exports[`EuiFieldText is rendered 1`] = `
99
<eui-validatable-control>
1010
<input
1111
aria-label="aria-label"
12-
class="euiFieldText testClass1 testClass2 euiFieldText--withIcon emotion-euiFieldText-euiTestCss"
12+
class="euiFieldText testClass1 testClass2 emotion-euiFieldText-euiTestCss"
1313
data-test-subj="test subject string"
1414
id="1"
1515
name="elastic"
@@ -54,7 +54,7 @@ exports[`EuiFieldText props isInvalid is rendered 1`] = `
5454
isinvalid="true"
5555
>
5656
<input
57-
class="euiFieldText euiFormControlLayout--1icons emotion-euiFieldText"
57+
class="euiFieldText emotion-euiFieldText"
5858
type="text"
5959
value=""
6060
/>
@@ -69,7 +69,7 @@ exports[`EuiFieldText props isLoading is rendered 1`] = `
6969
>
7070
<eui-validatable-control>
7171
<input
72-
class="euiFieldText euiFormControlLayout--1icons euiFieldText-isLoading emotion-euiFieldText"
72+
class="euiFieldText euiFieldText-isLoading emotion-euiFieldText"
7373
type="text"
7474
value=""
7575
/>
Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +0,0 @@
1-
.euiFieldText {
2-
@include euiFormControlWithIcon($isIconOptional: true, $side: 'left');
3-
}
4-
5-
.euiFieldText--withIcon.euiFieldText--compressed {
6-
@include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true);
7-
}

packages/eui/src/components/form/field_text/field_text.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
* Side Public License, v 1.
77
*/
88

9-
import React, { InputHTMLAttributes, Ref, FunctionComponent } from 'react';
9+
import React, {
10+
InputHTMLAttributes,
11+
Ref,
12+
FunctionComponent,
13+
useMemo,
14+
} from 'react';
1015
import classNames from 'classnames';
1116

1217
import { useEuiMemoizedStyles } from '../../../services';
@@ -16,10 +21,7 @@ import {
1621
EuiFormControlLayoutProps,
1722
} from '../form_control_layout';
1823
import { EuiValidatableControl } from '../validatable_control';
19-
import {
20-
isRightSideIcon,
21-
getFormControlClassNameForIconCount,
22-
} from '../form_control_layout/_num_icons';
24+
import { getIconAffordanceStyles } from '../form_control_layout/_num_icons';
2325
import { useFormContext } from '../eui_form_context';
2426

2527
import { euiFieldTextStyles } from './field_text.styles';
@@ -70,6 +72,7 @@ export const EuiFieldText: FunctionComponent<EuiFieldTextProps> = (props) => {
7072
placeholder,
7173
value,
7274
className,
75+
style,
7376
icon,
7477
isInvalid,
7578
inputRef,
@@ -83,19 +86,8 @@ export const EuiFieldText: FunctionComponent<EuiFieldTextProps> = (props) => {
8386
...rest
8487
} = props;
8588

86-
const hasRightSideIcon = isRightSideIcon(icon);
87-
88-
const numIconsClass = controlOnly
89-
? false
90-
: getFormControlClassNameForIconCount({
91-
isInvalid,
92-
isLoading,
93-
icon: hasRightSideIcon,
94-
});
95-
96-
const classes = classNames('euiFieldText', className, numIconsClass, {
89+
const classes = classNames('euiFieldText', className, {
9790
...(!controlOnly && {
98-
'euiFieldText--withIcon': icon && !hasRightSideIcon,
9991
'euiFieldText--inGroup': prepend || append,
10092
}),
10193
'euiFieldText-isLoading': isLoading,
@@ -108,6 +100,12 @@ export const EuiFieldText: FunctionComponent<EuiFieldTextProps> = (props) => {
108100
fullWidth ? styles.fullWidth : styles.formWidth,
109101
];
110102

103+
const iconAffordanceStyles = useMemo(() => {
104+
return !controlOnly
105+
? getIconAffordanceStyles({ icon, isInvalid, isLoading })
106+
: undefined;
107+
}, [controlOnly, icon, isInvalid, isLoading]);
108+
111109
const control = (
112110
<EuiValidatableControl isInvalid={isInvalid}>
113111
<input
@@ -117,6 +115,7 @@ export const EuiFieldText: FunctionComponent<EuiFieldTextProps> = (props) => {
117115
placeholder={placeholder}
118116
className={classes}
119117
css={cssStyles}
118+
style={{ ...iconAffordanceStyles, ...style }}
120119
value={value}
121120
ref={inputRef}
122121
readOnly={readOnly}

packages/eui/src/components/form/form.styles.test.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ describe('euiFormVariables', () => {
5353
"controlPlaceholderText": "#646a77",
5454
"customControlBorderColor": "#f5f7fc",
5555
"customControlDisabledIconColor": "#cacfd8",
56+
"iconAffordance": "24px",
57+
"iconCompressedAffordance": "18px",
5658
"inputGroupBorder": "none",
5759
"inputGroupLabelBackground": "#e9edf3",
5860
"maxWidth": "400px",
@@ -89,7 +91,9 @@ describe('euiFormControlStyles', () => {
8991
}",
9092
"compressed": "
9193
block-size: 32px;
92-
padding: 8px;
94+
padding-block: 8px;
95+
padding-inline-start: calc(8px + (18px * var(--euiFormControlLeftIconsCount, 0)));
96+
padding-inline-end: calc(8px + (18px * var(--euiFormControlRightIconsCount, 0)));
9397
border-radius: 4px;
9498
",
9599
"disabled": "
@@ -203,7 +207,9 @@ describe('euiFormControlStyles', () => {
203207
",
204208
"uncompressed": "
205209
block-size: 40px;
206-
padding: 12px;
210+
padding-block: 12px;
211+
padding-inline-start: calc(12px + (24px * var(--euiFormControlLeftIconsCount, 0)));
212+
padding-inline-end: calc(12px + (24px * var(--euiFormControlRightIconsCount, 0)));
207213
border-radius: 6px;
208214
",
209215
}

packages/eui/src/components/form/form.styles.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export const euiFormVariables = (euiThemeContext: UseEuiTheme) => {
3939
controlCompressedPadding: euiTheme.size.s,
4040
controlBorderRadius: euiTheme.border.radius.medium,
4141
controlCompressedBorderRadius: euiTheme.border.radius.small,
42+
iconAffordance: mathWithUnits(euiTheme.size.base, (x) => x * 1.5),
43+
iconCompressedAffordance: mathWithUnits(euiTheme.size.m, (x) => x * 1.5),
4244
};
4345

4446
const colors = {
@@ -118,12 +120,28 @@ export const euiFormControlStyles = (euiThemeContext: UseEuiTheme) => {
118120
// Sizes
119121
uncompressed: `
120122
${logicalCSS('height', form.controlHeight)}
121-
padding: ${form.controlPadding};
123+
${logicalCSS('padding-vertical', form.controlPadding)}
124+
${logicalCSS(
125+
'padding-left',
126+
`calc(${form.controlPadding} + (${form.iconAffordance} * var(--euiFormControlLeftIconsCount, 0)))`
127+
)}
128+
${logicalCSS(
129+
'padding-right',
130+
`calc(${form.controlPadding} + (${form.iconAffordance} * var(--euiFormControlRightIconsCount, 0)))`
131+
)}
122132
border-radius: ${form.controlBorderRadius};
123133
`,
124134
compressed: `
125135
${logicalCSS('height', form.controlCompressedHeight)}
126-
padding: ${form.controlCompressedPadding};
136+
${logicalCSS('padding-vertical', form.controlCompressedPadding)}
137+
${logicalCSS(
138+
'padding-left',
139+
`calc(${form.controlCompressedPadding} + (${form.iconCompressedAffordance} * var(--euiFormControlLeftIconsCount, 0)))`
140+
)}
141+
${logicalCSS(
142+
'padding-right',
143+
`calc(${form.controlCompressedPadding} + (${form.iconCompressedAffordance} * var(--euiFormControlRightIconsCount, 0)))`
144+
)}
127145
border-radius: ${form.controlCompressedBorderRadius};
128146
`,
129147

packages/eui/src/components/form/form_control_layout/_num_icons.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,68 @@
99
import {
1010
getFormControlClassNameForIconCount,
1111
isRightSideIcon,
12+
getIconAffordanceStyles,
1213
} from './_num_icons';
1314

15+
describe('getIconAffordanceStyles', () => {
16+
const noIcons = {
17+
icon: undefined,
18+
clear: false,
19+
isLoading: false,
20+
isInvalid: false,
21+
isDropdown: false,
22+
};
23+
const allIcons = {
24+
icon: { type: 'search', side: 'right' as const },
25+
clear: true,
26+
isLoading: true,
27+
isInvalid: true,
28+
isDropdown: true,
29+
};
30+
31+
test('empty object', () => {
32+
const styles = getIconAffordanceStyles({});
33+
expect(styles).toEqual(undefined);
34+
});
35+
36+
test('false values', () => {
37+
const styles = getIconAffordanceStyles(noIcons);
38+
expect(styles).toEqual(undefined);
39+
});
40+
41+
test('all icons', () => {
42+
const styles = getIconAffordanceStyles(allIcons);
43+
expect(styles).toMatchInlineSnapshot(`
44+
Object {
45+
"--euiFormControlRightIconsCount": 5,
46+
}
47+
`);
48+
});
49+
50+
test('some icons', () => {
51+
const styles = getIconAffordanceStyles({
52+
isLoading: true,
53+
isInvalid: true,
54+
});
55+
expect(styles).toMatchInlineSnapshot(`
56+
Object {
57+
"--euiFormControlRightIconsCount": 2,
58+
}
59+
`);
60+
});
61+
62+
test('left icon', () => {
63+
const styles = getIconAffordanceStyles({
64+
icon: 'search',
65+
});
66+
expect(styles).toMatchInlineSnapshot(`
67+
Object {
68+
"--euiFormControlLeftIconsCount": 1,
69+
}
70+
`);
71+
});
72+
});
73+
1474
describe('getFormControlClassNameForIconCount', () => {
1575
it('should return undefined if object is empty', () => {
1676
const numberClass = getFormControlClassNameForIconCount({});

packages/eui/src/components/form/form_control_layout/_num_icons.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,40 @@ export const isRightSideIcon = (
5151
): boolean => {
5252
return !!icon && isIconShape(icon) && icon.side === 'right';
5353
};
54+
55+
export const getIconAffordanceStyles = ({
56+
icon,
57+
clear,
58+
isLoading,
59+
isInvalid,
60+
isDropdown,
61+
}: {
62+
icon?: EuiFormControlLayoutIconsProps['icon'];
63+
clear?: boolean;
64+
isLoading?: boolean;
65+
isInvalid?: boolean;
66+
isDropdown?: boolean;
67+
}) => {
68+
const cssVariables = {
69+
'--euiFormControlLeftIconsCount': 0,
70+
'--euiFormControlRightIconsCount': 0,
71+
};
72+
73+
if (icon) {
74+
if (isRightSideIcon(icon)) {
75+
cssVariables['--euiFormControlRightIconsCount']++;
76+
} else {
77+
cssVariables['--euiFormControlLeftIconsCount']++;
78+
}
79+
}
80+
81+
if (clear) cssVariables['--euiFormControlRightIconsCount']++;
82+
if (isLoading) cssVariables['--euiFormControlRightIconsCount']++;
83+
if (isInvalid) cssVariables['--euiFormControlRightIconsCount']++;
84+
if (isDropdown) cssVariables['--euiFormControlRightIconsCount']++;
85+
86+
const filtered = Object.entries(cssVariables).filter(
87+
([, count]) => count > 0
88+
);
89+
return filtered.length ? Object.fromEntries(filtered) : undefined;
90+
};

0 commit comments

Comments
 (0)