Skip to content

Commit

Permalink
Create InputGroup component (#430)
Browse files Browse the repository at this point in the history
  • Loading branch information
dacerondrej authored and adamkudrna committed May 3, 2023
1 parent 6213b0a commit 5249b43
Show file tree
Hide file tree
Showing 21 changed files with 725 additions and 39 deletions.
24 changes: 16 additions & 8 deletions src/lib/components/Button/Button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
import { transferProps } from '../_helpers/transferProps';
import { ButtonGroupContext } from '../ButtonGroup';
import { InputGroupContext } from '../InputGroup/InputGroupContext';
import getRootLabelVisibilityClassName from './helpers/getRootLabelVisibilityClassName';
import getRootPriorityClassName from './helpers/getRootPriorityClassName';
import styles from './Button.scss';
Expand All @@ -28,8 +29,14 @@ export const Button = React.forwardRef((props, ref) => {
color,
...restProps
} = props;
const buttonGroupContext = useContext(ButtonGroupContext);
const inputGroupContext = useContext(InputGroupContext);

const context = useContext(ButtonGroupContext);
if (buttonGroupContext && inputGroupContext) {
throw new Error('Button cannot be placed both in `ButtonGroup` and `InputGroup`.');
}

const primaryContext = buttonGroupContext ?? inputGroupContext;

return (
/* No worries, `type` is always assigned correctly through props. */
Expand All @@ -39,20 +46,21 @@ export const Button = React.forwardRef((props, ref) => {
className={classNames(
styles.root,
getRootPriorityClassName(
resolveContextOrProp(context && context.priority, priority),
resolveContextOrProp(buttonGroupContext && buttonGroupContext.priority, priority),
styles,
),
getRootColorClassName(color, styles),
getRootSizeClassName(
resolveContextOrProp(context && context.size, size),
resolveContextOrProp(primaryContext && primaryContext.size, size),
styles,
),
getRootLabelVisibilityClassName(labelVisibility, styles),
resolveContextOrProp(context && context.block, block) && styles.isRootBlock,
context && styles.isRootGrouped,
resolveContextOrProp(buttonGroupContext && buttonGroupContext.block, block) && styles.isRootBlock,
buttonGroupContext && styles.isRootInButtonGroup,
inputGroupContext && styles.isRootInInputGroup,
feedbackIcon && styles.hasRootFeedback,
)}
disabled={resolveContextOrProp(context && context.disabled, disabled) || !!feedbackIcon}
disabled={resolveContextOrProp(buttonGroupContext && buttonGroupContext.disabled, disabled) || !!feedbackIcon}
id={id}
ref={ref}
>
Expand Down Expand Up @@ -171,8 +179,8 @@ Button.propTypes = {
/**
* Size of the button.
*
* Ignored if the component is rendered within `ButtonGroup` component
* as the value is inherited in such case.
* Ignored if the component is rendered within `ButtonGroup` or `InputGroup` component as the value is inherited in
* such case.
*/
size: PropTypes.oneOf(['small', 'medium', 'large']),
/**
Expand Down
9 changes: 6 additions & 3 deletions src/lib/components/Button/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@
color: transparent;
}

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

&:not(:first-child) {
Expand All @@ -96,8 +97,10 @@
}
}

.isRootGrouped .startCorner,
.isRootGrouped .endCorner {
.isRootInButtonGroup .startCorner,
.isRootInInputGroup .startCorner,
.isRootInButtonGroup .endCorner,
.isRootInInputGroup .endCorner {
z-index: map.get(settings.$group-z-indexes, button-overflowing-elements);
}

Expand Down
10 changes: 5 additions & 5 deletions src/lib/components/Button/_priorities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@
@include tools.button-color(flat, dark);
}

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

.isRootPriorityFilled.isRootGrouped:not(:first-child) {
.isRootPriorityFilled.isRootInButtonGroup:not(:first-child) {
--rui-local-gap: #{theme.$group-filled-gap};
--rui-local-separator-width: #{theme.$group-filled-separator-width};
--rui-local-separator-color: #{theme.$group-filled-separator-color};
}

.isRootPriorityFlat.isRootGrouped:not(:first-child) {
.isRootPriorityFlat.isRootInButtonGroup:not(:first-child) {
--rui-local-gap: #{theme.$group-flat-gap};
--rui-local-separator-width: #{theme.$group-flat-separator-width};
--rui-local-separator-color: #{theme.$group-flat-separator-color};
}

.isRootPriorityOutline.isRootGrouped:not(:first-child) {
.isRootPriorityOutline.isRootInButtonGroup:not(:first-child) {
--rui-local-gap: #{theme.$group-outline-gap};
}
6 changes: 6 additions & 0 deletions src/lib/components/ButtonGroup/__tests__/ButtonGroup.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from '@testing-library/react';
import { blockPropTest } from '../../../../../tests/propTests/blockPropTest';
import { childrenEmptyPropTest } from '../../../../../tests/propTests/childrenEmptyPropTest';
import { idPropTest } from '../../../../../tests/propTests/idPropTest';
import { Button } from '../../Button';
import { ButtonGroup } from '../ButtonGroup';

Expand All @@ -16,6 +17,11 @@ describe('rendering', () => {
it.each([
...blockPropTest,
...childrenEmptyPropTest,
...idPropTest,
[
{},
(rootElement) => expect(within(rootElement).getByRole('button')).toHaveClass('isRootInButtonGroup'),
],
[
{ children: <Button label="label text" /> },
(rootElement) => expect(within(rootElement).getByText('label text')),
Expand Down
5 changes: 5 additions & 0 deletions src/lib/components/FormLayout/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ToolbarItem,
FormLayout,
FormLayoutCustomField,
InputGroup,
} from '../..'

## Basic Usage
Expand Down Expand Up @@ -441,6 +442,10 @@ This is a demo of all components supported by FormLayout.
options={options}
value={fruit}
/>
<InputGroup label="Promo code">
<TextField label="Promo code" />
<Button label="Submit" />
</InputGroup>
</FormLayout>
</div>
)
Expand Down
34 changes: 25 additions & 9 deletions src/lib/components/FormLayout/__tests__/FormLayout.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
within,
} from '@testing-library/react';
import { childrenEmptyPropTest } from '../../../../../tests/propTests/childrenEmptyPropTest';
import { idPropTest } from '../../../../../tests/propTests/idPropTest';
import { Button } from '../../Button';
import { CheckboxField } from '../../CheckboxField';
import { InputGroup } from '../../InputGroup';
import { Radio } from '../../Radio';
import { SelectField } from '../../SelectField';
import { TextArea } from '../../TextArea';
Expand All @@ -14,7 +17,7 @@ import { FormLayout } from '../FormLayout';
import { FormLayoutCustomField } from '../FormLayoutCustomField';

const defaultProps = {
children: <FormLayoutCustomField id="nested-id">content</FormLayoutCustomField>,
children: <FormLayoutCustomField id="custom-field-id">content</FormLayoutCustomField>,
};

describe('rendering', () => {
Expand All @@ -36,18 +39,31 @@ describe('rendering', () => {
(rootElement) => expect(rootElement).not.toHaveClass('isRootAutoWidth'),
],
...childrenEmptyPropTest,
...idPropTest,
[
{ children: <FormLayoutCustomField>other content text</FormLayoutCustomField> },
(rootElement) => expect(within(rootElement).getByText('other content text')),
],
[
{ children: <CheckboxField label="label" /> },
(rootElement) => expect(within(rootElement).getByRole('checkbox')),
(rootElement) => expect(within(rootElement).getByRole('checkbox').closest('label')).toHaveClass('isRootInFormLayout'),
],
[
{
children: (
<InputGroup id="input-group-id" label="input group">
<TextField key="text field" label="text field" />
<Button key="button" label="button" />
</InputGroup>
),
},
(rootElement) => expect(within(rootElement).getByTestId('input-group-id')).toHaveClass('isRootInFormLayout'),
],
[
{
children: (
<Radio
id="radio-id"
label="label"
options={[{
label: 'label',
Expand All @@ -56,7 +72,7 @@ describe('rendering', () => {
/>
),
},
(rootElement) => expect(within(rootElement).getByRole('radio')),
(rootElement) => expect(within(rootElement).getByTestId('radio-id')).toHaveClass('isRootInFormLayout'),
],
[
{
Expand All @@ -70,32 +86,32 @@ describe('rendering', () => {
/>
),
},
(rootElement) => expect(within(rootElement).getByRole('combobox')),
(rootElement) => expect(within(rootElement).getByRole('combobox').closest('label')).toHaveClass('isRootInFormLayout'),
],
[
{ children: <TextArea label="label" /> },
(rootElement) => expect(within(rootElement).getByRole('textbox')),
(rootElement) => expect(within(rootElement).getByRole('textbox').closest('label')).toHaveClass('isRootInFormLayout'),
],
[
{ children: <TextField label="label" /> },
(rootElement) => expect(within(rootElement).getByRole('textbox')),
(rootElement) => expect(within(rootElement).getByRole('textbox').closest('label')).toHaveClass('isRootInFormLayout'),
],
[
{ children: <Toggle label="label" /> },
(rootElement) => expect(within(rootElement).getByRole('checkbox')),
(rootElement) => expect(within(rootElement).getByRole('checkbox').closest('label')).toHaveClass('isRootInFormLayout'),
],
[
{ fieldLayout: 'horizontal' },
(rootElement) => {
expect(rootElement).toHaveClass('isRootFieldLayoutHorizontal');
expect(within(rootElement).getByTestId('nested-id')).toHaveClass('isRootLayoutHorizontal');
expect(within(rootElement).getByTestId('custom-field-id')).toHaveClass('isRootLayoutHorizontal');
},
],
[
{ fieldLayout: 'vertical' },
(rootElement) => {
expect(rootElement).toHaveClass('isRootFieldLayoutVertical');
expect(within(rootElement).getByTestId('nested-id')).toHaveClass('isRootLayoutVertical');
expect(within(rootElement).getByTestId('custom-field-id')).toHaveClass('isRootLayoutVertical');
},
],
[
Expand Down
Loading

0 comments on commit 5249b43

Please sign in to comment.