Skip to content

Commit

Permalink
feat(FormItem): add required prop (#6820)
Browse files Browse the repository at this point in the history
* feat(FormItem): add required prop

* CHORE: Update screenshots

* fix: a11y issue with required indicator

---------

Co-authored-by: GitHub Action <[email protected]>
  • Loading branch information
BlackySoul and actions-user authored Apr 19, 2024
1 parent 83f6fd2 commit 08d1dd0
Show file tree
Hide file tree
Showing 16 changed files with 87 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export const FormItemPlayground = (props: ComponentPlaygroundProps) => {
<Input key={0} aria-labelledby="bottom-id" placeholder="Введите ваше значение" />,
],
},
{
top: ['Сверху'],
children: [<Input key={0} placeholder="Введите ваше значение" />],
required: [true],
},
]}
>
{(props: FormItemProps) => <FormItem {...props} style={{ maxWidth: '300px' }} />}
Expand Down
5 changes: 5 additions & 0 deletions packages/vkui/src/components/FormItem/FormItem.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
white-space: normal;
}

.FormItemTop__label--required {
color: var(--vkui--color_text_negative);
margin-inline-start: 3px;
}

.FormItem__bottom {
color: var(--vkui--color_text_secondary);
padding-block-start: 8px;
Expand Down
52 changes: 33 additions & 19 deletions packages/vkui/src/components/FormItem/FormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import * as React from 'react';
import { classNames, hasReactNode, noop } from '@vkontakte/vkjs';
import { useAdaptivity } from '../../hooks/useAdaptivity';
import { useExternRef } from '../../hooks/useExternRef';
import { useObjectMemo } from '../../hooks/useObjectMemo';
import { HasComponent, HasRootRef } from '../../types';
import { Removable, RemovableProps } from '../Removable/Removable';
import { RootComponent } from '../RootComponent/RootComponent';
import { Footnote } from '../Typography/Footnote/Footnote';
import { FormItemTop } from './FormItemTop/FormItemTop';
import { FormItemTopAside } from './FormItemTop/FormItemTopAside';
import { FormItemTopLabel } from './FormItemTop/FormItemTopLabel';
import { FormItemContext } from './context';
import styles from './FormItem.module.css';

const sizeYClassNames = {
Expand Down Expand Up @@ -64,6 +66,10 @@ export interface FormItemProps
* @since 5.8.0
*/
noPadding?: boolean;
/**
* Помечает поле обязательным
*/
required?: boolean;
}

/**
Expand All @@ -84,6 +90,7 @@ export const FormItem = ({
bottomId,
noPadding,
topNode,
required = false,
...restProps
}: FormItemProps) => {
const rootEl = useExternRef(getRootRef);
Expand Down Expand Up @@ -113,6 +120,8 @@ export const FormItem = ({
</React.Fragment>
);

const context = useObjectMemo({ required, topMultiline });

return (
<RootComponent
{...restProps}
Expand All @@ -128,26 +137,31 @@ export const FormItem = ({
removable && classNames(styles['FormItem--removable'], 'vkuiInternalFormItem--removable'),
)}
>
{removable ? (
<Removable
align="start"
onRemove={(e) => {
if (rootEl?.current) {
onRemove(e, rootEl.current);
}
}}
removePlaceholder={removePlaceholder}
indent={removable === 'indent'}
>
<div
className={classNames(styles['FormItem__removable'], 'vkuiInternalFormItem__removable')}
<FormItemContext.Provider value={context}>
{removable ? (
<Removable
align="start"
onRemove={(e) => {
if (rootEl?.current) {
onRemove(e, rootEl.current);
}
}}
removePlaceholder={removePlaceholder}
indent={removable === 'indent'}
>
{wrappedChildren}
</div>
</Removable>
) : (
wrappedChildren
)}
<div
className={classNames(
styles['FormItem__removable'],
'vkuiInternalFormItem__removable',
)}
>
{wrappedChildren}
</div>
</Removable>
) : (
wrappedChildren
)}
</FormItemContext.Provider>
</RootComponent>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { classNames } from '@vkontakte/vkjs';
import { HasComponent, HasRootRef } from '../../../types';
import { Subhead } from '../../Typography/Subhead/Subhead';
import { FormItemContext } from '../context';
import styles from '../FormItem.module.css';

export interface FormItemTopLabelProps
Expand All @@ -10,6 +11,7 @@ export interface FormItemTopLabelProps
HasComponent {
/**
* Многострочный вывод заголовка.
* TODO [>=7]: удалить и вседа брать из контекста
*/
multiline?: boolean;
}
Expand All @@ -25,21 +27,28 @@ export const FormItemTopLabel = ({
children,
Component: componentProp,
htmlFor,
multiline = false,
multiline,
...restProps
}: FormItemTopLabelProps) => {
const component = componentProp || (htmlFor && 'label') || 'span';
const { required, topMultiline: multilineContext } = React.useContext(FormItemContext);

return (
<Subhead
className={classNames(
styles['FormItemTop__label'],
multiline && styles['FormItemTop__label--multiline'],
(multiline ?? multilineContext) && styles['FormItemTop__label--multiline'],
)}
Component={component}
htmlFor={htmlFor}
{...restProps}
>
{children}
{required && (
<span className={styles['FormItemTop__label--required']} aria-hidden>
*
</span>
)}
</Subhead>
);
};
Expand Down
2 changes: 2 additions & 0 deletions packages/vkui/src/components/FormItem/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const Example = () => {
email ? 'Электронная почта введена верно!' : 'Пожалуйста, введите электронную почту'
}
bottomId="email-type"
required
>
<Input
aria-labelledby="email-type"
Expand Down Expand Up @@ -198,6 +199,7 @@ const Example = () => {
htmlFor="purpose-of-the-trip-select-id"
bottom={purpose ? '' : 'Пожалуйста, укажите цель поездки'}
status={purpose ? 'valid' : 'error'}
required
>
<Select
id="purpose-of-the-trip-select-id"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/vkui/src/components/FormItem/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';

interface FormItemContextProps {
required?: boolean;
topMultiline?: boolean;
}

export const FormItemContext = React.createContext<FormItemContextProps>({
required: false,
topMultiline: false,
});

0 comments on commit 08d1dd0

Please sign in to comment.