Skip to content

Commit 5249b43

Browse files
dacerondrejadamkudrna
authored andcommitted
Create InputGroup component (#430)
1 parent 6213b0a commit 5249b43

File tree

21 files changed

+725
-39
lines changed

21 files changed

+725
-39
lines changed

src/lib/components/Button/Button.jsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
77
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
88
import { transferProps } from '../_helpers/transferProps';
99
import { ButtonGroupContext } from '../ButtonGroup';
10+
import { InputGroupContext } from '../InputGroup/InputGroupContext';
1011
import getRootLabelVisibilityClassName from './helpers/getRootLabelVisibilityClassName';
1112
import getRootPriorityClassName from './helpers/getRootPriorityClassName';
1213
import styles from './Button.scss';
@@ -28,8 +29,14 @@ export const Button = React.forwardRef((props, ref) => {
2829
color,
2930
...restProps
3031
} = props;
32+
const buttonGroupContext = useContext(ButtonGroupContext);
33+
const inputGroupContext = useContext(InputGroupContext);
3134

32-
const context = useContext(ButtonGroupContext);
35+
if (buttonGroupContext && inputGroupContext) {
36+
throw new Error('Button cannot be placed both in `ButtonGroup` and `InputGroup`.');
37+
}
38+
39+
const primaryContext = buttonGroupContext ?? inputGroupContext;
3340

3441
return (
3542
/* No worries, `type` is always assigned correctly through props. */
@@ -39,20 +46,21 @@ export const Button = React.forwardRef((props, ref) => {
3946
className={classNames(
4047
styles.root,
4148
getRootPriorityClassName(
42-
resolveContextOrProp(context && context.priority, priority),
49+
resolveContextOrProp(buttonGroupContext && buttonGroupContext.priority, priority),
4350
styles,
4451
),
4552
getRootColorClassName(color, styles),
4653
getRootSizeClassName(
47-
resolveContextOrProp(context && context.size, size),
54+
resolveContextOrProp(primaryContext && primaryContext.size, size),
4855
styles,
4956
),
5057
getRootLabelVisibilityClassName(labelVisibility, styles),
51-
resolveContextOrProp(context && context.block, block) && styles.isRootBlock,
52-
context && styles.isRootGrouped,
58+
resolveContextOrProp(buttonGroupContext && buttonGroupContext.block, block) && styles.isRootBlock,
59+
buttonGroupContext && styles.isRootInButtonGroup,
60+
inputGroupContext && styles.isRootInInputGroup,
5361
feedbackIcon && styles.hasRootFeedback,
5462
)}
55-
disabled={resolveContextOrProp(context && context.disabled, disabled) || !!feedbackIcon}
63+
disabled={resolveContextOrProp(buttonGroupContext && buttonGroupContext.disabled, disabled) || !!feedbackIcon}
5664
id={id}
5765
ref={ref}
5866
>
@@ -171,8 +179,8 @@ Button.propTypes = {
171179
/**
172180
* Size of the button.
173181
*
174-
* Ignored if the component is rendered within `ButtonGroup` component
175-
* as the value is inherited in such case.
182+
* Ignored if the component is rendered within `ButtonGroup` or `InputGroup` component as the value is inherited in
183+
* such case.
176184
*/
177185
size: PropTypes.oneOf(['small', 'medium', 'large']),
178186
/**

src/lib/components/Button/_base.scss

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
color: transparent;
7777
}
7878

79-
.isRootGrouped {
79+
.isRootInButtonGroup,
80+
.isRootInInputGroup {
8081
z-index: map.get(settings.$group-z-indexes, button);
8182

8283
&:not(:first-child) {
@@ -96,8 +97,10 @@
9697
}
9798
}
9899

99-
.isRootGrouped .startCorner,
100-
.isRootGrouped .endCorner {
100+
.isRootInButtonGroup .startCorner,
101+
.isRootInInputGroup .startCorner,
102+
.isRootInButtonGroup .endCorner,
103+
.isRootInInputGroup .endCorner {
101104
z-index: map.get(settings.$group-z-indexes, button-overflowing-elements);
102105
}
103106

src/lib/components/Button/_priorities.scss

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@
135135
@include tools.button-color(flat, dark);
136136
}
137137

138-
.isRootPriorityFilled.isRootGrouped:not(:first-child)::before,
139-
.isRootPriorityFlat.isRootGrouped:not(:first-child)::before {
138+
.isRootPriorityFilled.isRootInButtonGroup:not(:first-child)::before,
139+
.isRootPriorityFlat.isRootInButtonGroup:not(:first-child)::before {
140140
content: "";
141141
position: absolute;
142142
top: calc(-1 * #{theme.$border-width});
@@ -147,18 +147,18 @@
147147
transform: translateX(calc(-0.5 * var(--rui-local-gap) - 50%));
148148
}
149149

150-
.isRootPriorityFilled.isRootGrouped:not(:first-child) {
150+
.isRootPriorityFilled.isRootInButtonGroup:not(:first-child) {
151151
--rui-local-gap: #{theme.$group-filled-gap};
152152
--rui-local-separator-width: #{theme.$group-filled-separator-width};
153153
--rui-local-separator-color: #{theme.$group-filled-separator-color};
154154
}
155155

156-
.isRootPriorityFlat.isRootGrouped:not(:first-child) {
156+
.isRootPriorityFlat.isRootInButtonGroup:not(:first-child) {
157157
--rui-local-gap: #{theme.$group-flat-gap};
158158
--rui-local-separator-width: #{theme.$group-flat-separator-width};
159159
--rui-local-separator-color: #{theme.$group-flat-separator-color};
160160
}
161161

162-
.isRootPriorityOutline.isRootGrouped:not(:first-child) {
162+
.isRootPriorityOutline.isRootInButtonGroup:not(:first-child) {
163163
--rui-local-gap: #{theme.$group-outline-gap};
164164
}

src/lib/components/ButtonGroup/__tests__/ButtonGroup.test.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@testing-library/react';
66
import { blockPropTest } from '../../../../../tests/propTests/blockPropTest';
77
import { childrenEmptyPropTest } from '../../../../../tests/propTests/childrenEmptyPropTest';
8+
import { idPropTest } from '../../../../../tests/propTests/idPropTest';
89
import { Button } from '../../Button';
910
import { ButtonGroup } from '../ButtonGroup';
1011

@@ -16,6 +17,11 @@ describe('rendering', () => {
1617
it.each([
1718
...blockPropTest,
1819
...childrenEmptyPropTest,
20+
...idPropTest,
21+
[
22+
{},
23+
(rootElement) => expect(within(rootElement).getByRole('button')).toHaveClass('isRootInButtonGroup'),
24+
],
1925
[
2026
{ children: <Button label="label text" /> },
2127
(rootElement) => expect(within(rootElement).getByText('label text')),

src/lib/components/FormLayout/README.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
ToolbarItem,
2828
FormLayout,
2929
FormLayoutCustomField,
30+
InputGroup,
3031
} from '../..'
3132

3233
## Basic Usage
@@ -441,6 +442,10 @@ This is a demo of all components supported by FormLayout.
441442
options={options}
442443
value={fruit}
443444
/>
445+
<InputGroup label="Promo code">
446+
<TextField label="Promo code" />
447+
<Button label="Submit" />
448+
</InputGroup>
444449
</FormLayout>
445450
</div>
446451
)

src/lib/components/FormLayout/__tests__/FormLayout.test.jsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import {
44
within,
55
} from '@testing-library/react';
66
import { childrenEmptyPropTest } from '../../../../../tests/propTests/childrenEmptyPropTest';
7+
import { idPropTest } from '../../../../../tests/propTests/idPropTest';
8+
import { Button } from '../../Button';
79
import { CheckboxField } from '../../CheckboxField';
10+
import { InputGroup } from '../../InputGroup';
811
import { Radio } from '../../Radio';
912
import { SelectField } from '../../SelectField';
1013
import { TextArea } from '../../TextArea';
@@ -14,7 +17,7 @@ import { FormLayout } from '../FormLayout';
1417
import { FormLayoutCustomField } from '../FormLayoutCustomField';
1518

1619
const defaultProps = {
17-
children: <FormLayoutCustomField id="nested-id">content</FormLayoutCustomField>,
20+
children: <FormLayoutCustomField id="custom-field-id">content</FormLayoutCustomField>,
1821
};
1922

2023
describe('rendering', () => {
@@ -36,18 +39,31 @@ describe('rendering', () => {
3639
(rootElement) => expect(rootElement).not.toHaveClass('isRootAutoWidth'),
3740
],
3841
...childrenEmptyPropTest,
42+
...idPropTest,
3943
[
4044
{ children: <FormLayoutCustomField>other content text</FormLayoutCustomField> },
4145
(rootElement) => expect(within(rootElement).getByText('other content text')),
4246
],
4347
[
4448
{ children: <CheckboxField label="label" /> },
45-
(rootElement) => expect(within(rootElement).getByRole('checkbox')),
49+
(rootElement) => expect(within(rootElement).getByRole('checkbox').closest('label')).toHaveClass('isRootInFormLayout'),
50+
],
51+
[
52+
{
53+
children: (
54+
<InputGroup id="input-group-id" label="input group">
55+
<TextField key="text field" label="text field" />
56+
<Button key="button" label="button" />
57+
</InputGroup>
58+
),
59+
},
60+
(rootElement) => expect(within(rootElement).getByTestId('input-group-id')).toHaveClass('isRootInFormLayout'),
4661
],
4762
[
4863
{
4964
children: (
5065
<Radio
66+
id="radio-id"
5167
label="label"
5268
options={[{
5369
label: 'label',
@@ -56,7 +72,7 @@ describe('rendering', () => {
5672
/>
5773
),
5874
},
59-
(rootElement) => expect(within(rootElement).getByRole('radio')),
75+
(rootElement) => expect(within(rootElement).getByTestId('radio-id')).toHaveClass('isRootInFormLayout'),
6076
],
6177
[
6278
{
@@ -70,32 +86,32 @@ describe('rendering', () => {
7086
/>
7187
),
7288
},
73-
(rootElement) => expect(within(rootElement).getByRole('combobox')),
89+
(rootElement) => expect(within(rootElement).getByRole('combobox').closest('label')).toHaveClass('isRootInFormLayout'),
7490
],
7591
[
7692
{ children: <TextArea label="label" /> },
77-
(rootElement) => expect(within(rootElement).getByRole('textbox')),
93+
(rootElement) => expect(within(rootElement).getByRole('textbox').closest('label')).toHaveClass('isRootInFormLayout'),
7894
],
7995
[
8096
{ children: <TextField label="label" /> },
81-
(rootElement) => expect(within(rootElement).getByRole('textbox')),
97+
(rootElement) => expect(within(rootElement).getByRole('textbox').closest('label')).toHaveClass('isRootInFormLayout'),
8298
],
8399
[
84100
{ children: <Toggle label="label" /> },
85-
(rootElement) => expect(within(rootElement).getByRole('checkbox')),
101+
(rootElement) => expect(within(rootElement).getByRole('checkbox').closest('label')).toHaveClass('isRootInFormLayout'),
86102
],
87103
[
88104
{ fieldLayout: 'horizontal' },
89105
(rootElement) => {
90106
expect(rootElement).toHaveClass('isRootFieldLayoutHorizontal');
91-
expect(within(rootElement).getByTestId('nested-id')).toHaveClass('isRootLayoutHorizontal');
107+
expect(within(rootElement).getByTestId('custom-field-id')).toHaveClass('isRootLayoutHorizontal');
92108
},
93109
],
94110
[
95111
{ fieldLayout: 'vertical' },
96112
(rootElement) => {
97113
expect(rootElement).toHaveClass('isRootFieldLayoutVertical');
98-
expect(within(rootElement).getByTestId('nested-id')).toHaveClass('isRootLayoutVertical');
114+
expect(within(rootElement).getByTestId('custom-field-id')).toHaveClass('isRootLayoutVertical');
99115
},
100116
],
101117
[

0 commit comments

Comments
 (0)