Skip to content

Commit

Permalink
feat(FormRow): add component (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
ogonkov authored Mar 30, 2023
1 parent dec12c7 commit d8b5c59
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/src/components/FormRow @ogonkov
51 changes: 51 additions & 0 deletions src/components/FormRow/FormRow.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@use '../variables';

.#{variables.$ns}form-row {
--gc-form-row-label-width: 172px;
--gc-form-row-field-height: 28px;

margin-bottom: 20px;
align-items: flex-start;
display: flex;

&__left {
display: flex;
flex-flow: row;
min-height: var(--gc-form-row-field-height);
box-sizing: border-box;
flex-shrink: 0;
width: var(--gc-form-row-label-width);
padding-right: 8px;
}

&__field-name {
align-self: center;
}

&__field-name-text {
word-break: break-word;
}

&__required-mark {
line-height: 0;
color: var(--yc-color-text-danger);
font-size: inherit;
}

&__help-popover {
display: inline-flex;
vertical-align: middle;
align-items: center;
}

&__right {
flex: 1 1 auto;
min-width: 0;
}

&__field-description {
margin: 10px 0 0;
color: var(--yc-color-text-secondary);
word-break: break-word;
}
}
55 changes: 55 additions & 0 deletions src/components/FormRow/FormRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {FC} from 'react';
import {block} from '../utils/cn';

import {FormRowFieldDescription} from './FormRowFieldDescription';
import i18n from './i18n';
import {FormRowProps} from './types';

import './FormRow.scss';

const b = block('form-row');

const FormRowComponent: FC<FormRowProps> = ({
className,
label,
labelHelpPopover,
fieldId,
required = false,
children,
}) => {
const LabelComponent = fieldId ? 'label' : 'span';

return (
<div className={b(null, [className])}>
<div className={b('left')}>
<LabelComponent className={b('field-name')} htmlFor={fieldId ? fieldId : undefined}>
<span className={b('field-name-text')}>{label}</span>

{required ? (
<>
&nbsp;
<sup
className={b('required-mark')}
aria-label={i18n('label_required-field')}
>
*
</sup>
</>
) : null}

{labelHelpPopover ? (
<>
&nbsp;
<span className={b('help-popover')}>{labelHelpPopover}</span>
</>
) : null}
</LabelComponent>
</div>
<div className={b('right')}>{children}</div>
</div>
);
};

FormRowComponent.displayName = 'FormRow';

export const FormRow = Object.assign(FormRowComponent, {FieldDescription: FormRowFieldDescription});
19 changes: 19 additions & 0 deletions src/components/FormRow/FormRowFieldDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, {FC} from 'react';
import {block} from '../utils/cn';
import type {FormRowFieldDescriptionProps} from './types';

const b = block('form-row');

export const FormRowFieldDescription: FC<FormRowFieldDescriptionProps> = ({
children,
className,
...elementProps
}) => {
return (
<p {...elementProps} className={b('field-description', [className])}>
{children}
</p>
);
};

FormRowFieldDescription.displayName = 'FormRow.FieldDescription';
23 changes: 23 additions & 0 deletions src/components/FormRow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# FormRow

Base component to place form input and label.

## Usage

```tsx
import type {FC} from 'react';
import {TextInput} from '@gravity-ui/uikit';
import {FormRow} from '@gravity-ui/components';

const nameFieldId = 'form-field-name';

const Form: FC = () => {
return (
<>
<FormRow label={'Name'} fieldId={nameFieldId}>
<TextInput id={nameFieldId} name={'name'} />
</FormRow>
</>
);
};
```
87 changes: 87 additions & 0 deletions src/components/FormRow/__stories__/FormRow.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import {ComponentMeta, ComponentStory} from '@storybook/react';
import {HelpPopover, TextInput} from '@gravity-ui/uikit';
import {FormRow} from '../FormRow';

const fieldId = 'form-row-input-id';
const fieldDescriptionId = `${fieldId}-description`;

const argTypeReactNode = {
control: {type: null},
};

export default {
title: 'Components/FormRow',
component: FormRow,
args: {
label: 'Enter your name',
fieldId,
children: <TextInput id={fieldId} />,
},
argTypes: {
children: argTypeReactNode,
labelHelpPopover: argTypeReactNode,
},
} as ComponentMeta<typeof FormRow>;

const Template: ComponentStory<typeof FormRow> = (args) => <FormRow {...args} />;

export const Default = Template.bind({});

export const WithLongLabel = Template.bind({});
WithLongLabel.args = {
label: 'Very long label for text field to test how it will wrap label text in real life',
};

export const WithLongLabelWord = Template.bind({});
WithLongLabelWord.args = {
label: 'Antidisestablishmentarianism',
};

/** Story with corner case of label length and required mark. Mark should not be alone at the line */
export const WithCornerLabelLength = Template.bind({});
WithCornerLabelLength.args = {
required: true,
label: 'Antidisestablishmentariani',
};

export const WithFieldDescription = Template.bind({});
WithFieldDescription.args = {
children: (
<>
<TextInput id={fieldId} controlProps={{'aria-describedby': fieldDescriptionId}} />
<FormRow.FieldDescription id={fieldDescriptionId}>
Your name as it used in your foreign passport.
</FormRow.FieldDescription>
</>
),
};

export const WithFieldDescriptionAndLongLabel = Template.bind({});
WithFieldDescriptionAndLongLabel.storyName = 'With Field Description (Long Label)';
WithFieldDescriptionAndLongLabel.args = {
...WithFieldDescription.args,
...WithLongLabel.args,
};

export const RequiredField = Template.bind({});
RequiredField.args = {
required: true,
};

export const WithHelpPopover = Template.bind({});
WithHelpPopover.args = {
labelHelpPopover: (
<HelpPopover
content={'Your name as it used in your foreign passport.'}
placement={['top', 'bottom']}
/>
),
};

export const WithHelpPopoverAndLongLabel = Template.bind({});
WithHelpPopoverAndLongLabel.storyName = 'With Help Popover (Long Label)';
WithHelpPopoverAndLongLabel.args = {
...WithLongLabel.args,
...WithHelpPopover.args,
};
3 changes: 3 additions & 0 deletions src/components/FormRow/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label_required-field": "(this field is mandatory)"
}
7 changes: 7 additions & 0 deletions src/components/FormRow/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {registerKeyset} from '../../utils/registerKeyset';
import en from './en.json';
import ru from './ru.json';

const COMPONENT = 'FormRow';

export default registerKeyset({en, ru}, COMPONENT);
3 changes: 3 additions & 0 deletions src/components/FormRow/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label_required-field": "(это поле обязательно для заполнения)"
}
2 changes: 2 additions & 0 deletions src/components/FormRow/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {FormRow} from './FormRow';
export type {FormRowProps, FormRowFieldDescriptionProps} from './types';
19 changes: 19 additions & 0 deletions src/components/FormRow/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type {ReactNode, HTMLAttributes, PropsWithChildren} from 'react';

export interface FormRowProps {
className?: string;
/** Field label */
label?: ReactNode;
/** Slot for inserting `<HelpPopover/>` next to label text */
labelHelpPopover?: ReactNode;
/** Id of field to correctly associate it label */
fieldId?: string;
/** Display star next to required field */
required?: boolean;
/** Field component itself. `<FormRow.FieldDescription/>` could be used here
* next to field component itself */
children?: ReactNode;
}

export type FormRowFieldDescriptionProps = PropsWithChildren<{}> &
Pick<HTMLAttributes<HTMLParagraphElement>, 'id' | 'className'>;
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './AdaptiveTabs';
export * from './FormRow';
export * from './InfiniteScroll';

export {Lang, configure} from './utils/configure';

0 comments on commit d8b5c59

Please sign in to comment.