Adding Label component (#16203)

* Adding Label component

* doc fixes

* Adding label wrapping input
feature/default_network_editable
George Marshall 2 years ago committed by GitHub
parent 3d37ad3b6e
commit cda3e3e4c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ui/components/component-library/component-library-components.scss
  2. 95
      ui/components/component-library/label/README.mdx
  3. 1
      ui/components/component-library/label/index.js
  4. 73
      ui/components/component-library/label/label.js
  5. 11
      ui/components/component-library/label/label.scss
  6. 112
      ui/components/component-library/label/label.stories.js
  7. 67
      ui/components/component-library/label/label.test.js

@ -10,6 +10,7 @@
@import 'button-primary/button-primary'; @import 'button-primary/button-primary';
@import 'button-secondary/button-secondary'; @import 'button-secondary/button-secondary';
@import 'icon/icon'; @import 'icon/icon';
@import 'label/label';
@import 'tag/tag'; @import 'tag/tag';
@import 'text/text'; @import 'text/text';
@import 'text-field/text-field'; @import 'text-field/text-field';

@ -0,0 +1,95 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { Label } from './label';
# Label
The `Label` is a component used to label form inputs.
<Canvas>
<Story id="ui-components-component-library-label-label-stories-js--default-story" />
</Canvas>
## Props
The `Label` accepts all props below as well as all [Text](/docs/ui-components-component-library-text-text-stories-js--default-story#props) and [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props.
<ArgsTable of={Label} />
### Children
The `children` of the label can be text or a react node.
<Canvas>
<Story id="ui-components-component-library-label-label-stories-js--children" />
</Canvas>
```jsx
import { DISPLAY, ALIGN_ITEMS, FLEX_DIRECTION, SIZES, COLORS } from '../../../helpers/constants/design-system';
import { Icon, ICON_NAMES } from '../../ui/component-library/icon';
import { Label } from '../../ui/component-library/label';
import { TextFieldBase } from '../../ui/component-library/text-field-base'
<Label>Plain text</Label>
<Label display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.FLEX_START}>
Text and icon
<Icon
color={COLORS.ICON_ALTERNATIVE}
name={ICON_NAMES.INFO_FILLED}
size={SIZES.AUTO}
/>
</Label>
<Label
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
alignItems={ALIGN_ITEMS.FLEX_START}
>
Label that wraps an input
{/* TODO: replace with TextField component */}
<TextFieldBase placeholder="Click label to focus" />
</Label>
```
### Html For
Use the `htmlFor` prop to allow the `Label` to focus on an input with the same id when clicked. The cursor will also change to a `pointer` when the `htmlFor` has a value
<Canvas>
<Story id="ui-components-component-library-label-label-stories-js--html-for" />
</Canvas>
```jsx
import { TextFieldBase } from '../../ui/component-library/text-field-base';
import { Label } from '../../ui/component-library/label';
<Label htmlFor="add-network">Add network</Label>
<TextFieldBase id="add-network" placeholder="Enter network name" />
```
### Required
Use the `required` prop to add a required red asterisk next to the `children` of the `Label`. Note the required asterisk will always render after the `children`.
<Canvas>
<Story id="ui-components-component-library-label-label-stories-js--required" />
</Canvas>
```jsx
import { Label } from '../../ui/component-library/label';
<Label required>Label</Label>;
```
### Disabled
Use the `disabled` prop to set the `Label` in disabled state
<Canvas>
<Story id="ui-components-component-library-label-label-stories-js--disabled" />
</Canvas>
```jsx
import { Label } from '../../ui/component-library/label';
<Label disabled>Label</Label>;
```

@ -0,0 +1 @@
export { Label } from './label';

@ -0,0 +1,73 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {
COLORS,
FONT_WEIGHT,
TEXT,
DISPLAY,
ALIGN_ITEMS,
} from '../../../helpers/constants/design-system';
import { Text } from '../text';
export const Label = ({
htmlFor,
required,
disabled,
className,
children,
...props
}) => (
<Text
as="label"
disabled={disabled}
htmlFor={htmlFor}
className={classnames(
'mm-label',
{ 'mm-label--disabled': disabled },
{ 'mm-label--html-for': htmlFor && !disabled },
className,
)}
variant={TEXT.BODY_MD}
fontWeight={FONT_WEIGHT.BOLD}
display={DISPLAY.INLINE_FLEX}
alignItems={ALIGN_ITEMS.CENTER}
{...props}
>
{children}
{required && (
<Text
as="span"
className="mm-label__required-asterisk"
aria-hidden="true"
color={COLORS.ERROR_DEFAULT}
>
*
</Text>
)}
</Text>
);
Label.propTypes = {
/**
* The content of the label
*/
children: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
/**
* The id of the input associated with the label
*/
htmlFor: PropTypes.string,
/**
* If true the label will display as required
*/
required: PropTypes.bool,
/**
* Whether the label is disabled or not
*/
disabled: PropTypes.bool,
/**
* Additional classNames to be added to the label component
*/
className: PropTypes.string,
};

@ -0,0 +1,11 @@
.mm-label {
--label-opacity-disabled: 0.5; // TODO: replace with design token
&--html-for {
cursor: pointer;
}
&--disabled {
opacity: var(--label-opacity-disabled);
}
}

@ -0,0 +1,112 @@
import React, { useState } from 'react';
import {
DISPLAY,
FLEX_DIRECTION,
COLORS,
SIZES,
ALIGN_ITEMS,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box';
import { Icon, ICON_NAMES } from '../icon';
import { TextFieldBase } from '../text-field-base';
import { Label } from './label';
import README from './README.mdx';
export default {
title: 'Components/ComponentLibrary/Label',
id: __filename,
component: Label,
parameters: {
docs: {
page: README,
},
},
argTypes: {
htmlFor: {
control: 'text',
},
required: {
control: 'boolean',
},
disabled: {
control: 'boolean',
},
children: {
control: 'text',
},
className: {
control: 'text',
},
},
args: {
children: 'Label',
},
};
const Template = (args) => <Label {...args} />;
export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';
export const Children = (args) => (
<Box
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
gap={2}
>
<Label {...args}>Plain text</Label>
<Label {...args} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.FLEX_START}>
Text and icon
<Icon
color={COLORS.ICON_ALTERNATIVE}
name={ICON_NAMES.INFO_FILLED}
size={SIZES.AUTO}
/>
</Label>
<Label
{...args}
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
alignItems={ALIGN_ITEMS.FLEX_START}
>
Label that wraps an input
{/* TODO: replace with TextField component */}
<TextFieldBase placeholder="Click label to focus" />
</Label>
</Box>
);
export const HtmlFor = (args) => {
const [value, setValue] = useState('');
const handleOnChange = (e) => {
setValue(e.target.value);
};
return (
<Box display={DISPLAY.INLINE_FLEX} flexDirection={FLEX_DIRECTION.COLUMN}>
<Label {...args} />
<TextFieldBase
id="add-network"
value={value}
onChange={handleOnChange}
placeholder="Enter network name"
/>
</Box>
);
};
HtmlFor.args = {
children: 'Network name',
htmlFor: 'add-network',
};
export const Required = Template.bind({});
Required.args = {
required: true,
};
export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
};

@ -0,0 +1,67 @@
/* eslint-disable jest/require-top-level-describe */
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { Icon, ICON_NAMES } from '../icon';
import { TextFieldBase } from '../text-field-base';
import { Label } from './label';
describe('label', () => {
it('should render text inside the label', () => {
const { getByText } = render(<Label>label</Label>);
expect(getByText('label')).toBeDefined();
});
it('should render text and react nodes as children', () => {
const { getByText, getByTestId } = render(
<Label>
label
<Icon name={ICON_NAMES.INFO_FILLED} data-testid="icon" />
</Label>,
);
expect(getByText('label')).toBeDefined();
expect(getByTestId('icon')).toBeDefined();
});
it('should be able to accept an htmlFor prop and focus an input of a given id', () => {
const { getByText, getByRole } = render(
<>
<Label htmlFor="input">label</Label>
<TextFieldBase id="input" />
</>,
);
const input = getByRole('textbox');
const label = getByText('label');
expect(label).toBeDefined();
expect(input).not.toHaveFocus();
fireEvent.click(label);
expect(input).toHaveFocus();
});
it('should render when wrapping an input and focus input when clicked without htmlFor', () => {
const { getByText, getByRole } = render(
<>
<Label>
Label text
<TextFieldBase />
</Label>
</>,
);
const input = getByRole('textbox');
const label = getByText('Label text');
expect(label).toBeDefined();
expect(input).not.toHaveFocus();
fireEvent.click(label);
expect(input).toHaveFocus();
});
it('should render with required asterisk', () => {
const { getByText } = render(<Label required>label</Label>);
expect(getByText('label')).toBeDefined();
expect(getByText('*')).toBeDefined();
});
it('should render with disabled state and have disabled class', () => {
const { getByText } = render(<Label disabled>label</Label>);
expect(getByText('label')).toHaveClass('mm-label--disabled');
});
it('should render with additional className', () => {
const { getByText } = render(<Label className="test-class">label</Label>);
expect(getByText('label')).toHaveClass('test-class');
});
});
Loading…
Cancel
Save