Skip to content

Commit

Permalink
feat: add field-label component (#267)
Browse files Browse the repository at this point in the history
closes #4

Co-Authored-By: Andre Luiz Rabello <andre.luiz_r@hotmail.com>
  • Loading branch information
josokinas and rabelloo authored Feb 9, 2021
1 parent 03a0f58 commit 3e91a40
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 26 deletions.
19 changes: 19 additions & 0 deletions packages/core/src/components/field-label/field-label.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@use '../../helpers';

@mixin FieldLabel() {
.ods-field-label {
@include helpers.font('300-regular');

color: helpers.color('content-main');
display: flex;
flex-flow: column wrap;
padding-top: helpers.space(0.5);

+ .ods-input,
+ .ods-textarea,
> .ods-input,
> .ods-textarea {
margin-top: helpers.space(0.5);
}
}
}
3 changes: 3 additions & 0 deletions packages/core/src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
@forward './button/button';
@use './checkbox/checkbox';
@forward './checkbox/checkbox';
@use './field-label/field-label';
@forward './field-label/field-label';
@use './helper-text/helper-text';
@forward './helper-text/helper-text';
@use './icon/icon';
Expand All @@ -18,6 +20,7 @@
@mixin components() {
@include button.Button();
@include checkbox.Checkbox();
@include field-label.FieldLabel();
@include helper-text.HelperText();
@include icon.Icon();
@include input.Input();
Expand Down
90 changes: 90 additions & 0 deletions packages/react/src/components/field-label/field-label.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
FieldLabel,
FieldLabelProps,
HelperText,
Input,
Textarea,
} from '@onfido/castor-react';
import React from 'react';
import { Meta, omit, Story } from '../../../../../docs';

export default {
title: 'React/FieldLabel',
component: FieldLabel,
argTypes: {
...omit<FieldLabelProps>('className', 'style'),
children: { control: 'text' },
},
args: {
children: 'Label',
},
} as Meta<FieldLabelProps>;

export const Playground: Story<FieldLabelProps> = (props: FieldLabelProps) => (
<FieldLabel {...props} />
);

interface FieldLabelWithHelperTextProps extends FieldLabelProps {
label: string;
helperText: string;
}

export const WithHelperText = ({
label,
helperText,
...restFieldLabelProps
}: FieldLabelWithHelperTextProps) => (
<FieldLabel {...restFieldLabelProps}>
{label}
<HelperText>{helperText}</HelperText>
</FieldLabel>
);
WithHelperText.argTypes = omit<FieldLabelWithHelperTextProps>('children');
WithHelperText.args = {
label: 'Label',
helperText: 'Helper text',
};

interface FieldLabelWithInputProps extends FieldLabelProps {
id: string;
label: string;
}

export const WithInput = ({
id,
label,
...restFieldLabelProps
}: FieldLabelWithInputProps) => (
<>
<FieldLabel {...restFieldLabelProps} htmlFor={id}>
{label}
</FieldLabel>
<Input id={id} />
</>
);
WithInput.args = {
id: 'field-label-with-input',
label: 'Label',
};

interface FieldLabelWithTextareaProps extends FieldLabelProps {
id: string;
label: string;
}

export const WithTexarea = ({
id,
label,
...restFieldLabelProps
}: FieldLabelWithTextareaProps) => (
<>
<FieldLabel {...restFieldLabelProps} htmlFor={id}>
{label}
</FieldLabel>
<Textarea id={id} />
</>
);
WithTexarea.args = {
id: 'field-label-with-textarea',
label: 'Label',
};
18 changes: 18 additions & 0 deletions packages/react/src/components/field-label/field-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { c, classy } from '@onfido/castor';
import React from 'react';

/**
* Intended to be used within `Input` and `Textarea` components, providing a
* text label for the field.
*
* Can also be used alongside these two components, but should then be connected
* via the "htmlFor" prop.
*/
export const FieldLabel = ({
className,
...restProps
}: FieldLabelProps): JSX.Element => (
<label {...restProps} className={classy(c('field-label'), className)} />
);

export type FieldLabelProps = JSX.IntrinsicElements['label'];
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './button/button';
export * from './checkbox/checkbox';
export * from './field-label/field-label';
export * from './helper-text/helper-text';
export * from './icon/icon';
export * from './input/input';
Expand Down
24 changes: 23 additions & 1 deletion packages/react/src/components/input/input.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Input, InputProps } from '@onfido/castor-react';
import { HelperText, Input, InputProps } from '@onfido/castor-react';
import React from 'react';
import { Meta, omit, Story, storyOf } from '../../../../../docs';

Expand All @@ -7,6 +7,7 @@ export default {
component: Input,
argTypes: {
...omit<InputProps>('className', 'style'),
children: { control: 'text' },
placeholder: { control: 'text' },
invalid: {
table: { type: { summary: 'boolean' } },
Expand Down Expand Up @@ -37,3 +38,24 @@ export const Disabled = storyOf(Input, 'disabled', [true, false], {
labelProp: 'defaultValue',
});
Disabled.argTypes = omit<InputProps>('disabled');

interface InputWithHelperTextProps extends InputProps {
label: string;
helperText: string;
}

export const WithHelperText = ({
label,
helperText,
...restInputProps
}: InputWithHelperTextProps) => (
<Input {...restInputProps}>
{label}
<HelperText>{helperText}</HelperText>
</Input>
);
WithHelperText.argTypes = omit<InputWithHelperTextProps>('children');
WithHelperText.args = {
label: 'Label',
helperText: 'Helper text',
};
47 changes: 35 additions & 12 deletions packages/react/src/components/input/input.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
import { c, classy, InputProps as BaseProps, m } from '@onfido/castor';
import React from 'react';
import React, { useState } from 'react';
import { FieldLabelWrapper } from '../../internal';
import { withRef } from '../../utils';

const idPrefix = 'castor_input';
let idCount = 0;

export const Input = withRef(
(
{ type = 'text', invalid, className, ...restProps }: InputProps,
{
id: externalId,
type = 'text',
invalid,
children,
className,
...restProps
}: InputProps,
ref: InputProps['ref']
): JSX.Element => (
<input
{...restProps}
ref={ref}
type={type}
className={classy(c('input'), m({ invalid }), className)}
/>
)
): JSX.Element => {
const [autoId] = useState(() => `${idPrefix}_${++idCount}`);
const id = externalId || (children ? autoId : undefined);

return (
<FieldLabelWrapper id={id}>
{{
children,
element: (
<input
{...restProps}
ref={ref}
id={id}
type={type}
className={classy(c('input'), m({ invalid }), className)}
/>
),
}}
</FieldLabelWrapper>
);
}
);
Input.displayName = 'Input';

export type InputProps = BaseProps &
Omit<JSX.IntrinsicElements['input'], 'children'>;
export type InputProps = BaseProps & JSX.IntrinsicElements['input'];
24 changes: 23 additions & 1 deletion packages/react/src/components/textarea/textarea.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Textarea, TextareaProps } from '@onfido/castor-react';
import { HelperText, Textarea, TextareaProps } from '@onfido/castor-react';
import React from 'react';
import { Meta, omit, Story, storyOf } from '../../../../../docs';

Expand All @@ -15,6 +15,7 @@ export default {
component: Textarea,
argTypes: {
...omit<TextareaProps>('className', 'style'),
children: { control: 'text' },
placeholder: { control: 'text' },
resize: {
table: {
Expand Down Expand Up @@ -72,3 +73,24 @@ export const Disabled = storyOf(Textarea, 'disabled', [true, false], {
labelProp: 'defaultValue',
});
Disabled.argTypes = omit<TextareaProps>('disabled');

interface TextareaWithHelperTextProps extends TextareaProps {
label: string;
helperText: string;
}

export const WithHelperText = ({
label,
helperText,
...restTextareaProps
}: TextareaWithHelperTextProps) => (
<Textarea {...restTextareaProps}>
{label}
<HelperText>{helperText}</HelperText>
</Textarea>
);
WithHelperText.argTypes = omit<TextareaWithHelperTextProps>('children');
WithHelperText.args = {
label: 'Label',
helperText: 'Helper text',
};
42 changes: 30 additions & 12 deletions packages/react/src/components/textarea/textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
import { c, classy, m, TextareaProps as BaseProps } from '@onfido/castor';
import React from 'react';
import React, { useState } from 'react';
import { FieldLabelWrapper } from '../../internal';
import { withRef } from '../../utils';

const idPrefix = 'castor_textarea';
let idCount = 0;

export const Textarea = withRef(
(
{
id: externalId,
resize = 'vertical',
rows = 3,
invalid,
children,
className,
style,
...restProps
}: TextareaProps,
ref: TextareaProps['ref']
): JSX.Element => (
<textarea
{...restProps}
ref={ref}
rows={rows}
className={classy(c('textarea'), m({ invalid }), className)}
style={{ ...style, resize }}
/>
)
): JSX.Element => {
const [autoId] = useState(() => `${idPrefix}_${++idCount}`);
const id = externalId || (children ? autoId : undefined);

return (
<FieldLabelWrapper id={id}>
{{
children,
element: (
<textarea
{...restProps}
ref={ref}
id={id}
rows={rows}
className={classy(c('textarea'), m({ invalid }), className)}
style={{ ...style, resize }}
/>
),
}}
</FieldLabelWrapper>
);
}
);
Textarea.displayName = 'Textarea';

export type TextareaProps = BaseProps &
Omit<JSX.IntrinsicElements['textarea'], 'children'>;
export type TextareaProps = BaseProps & JSX.IntrinsicElements['textarea'];
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { FieldLabel } from '@onfido/castor-react';
import React from 'react';

/**
* Wrapper for `Input` and `Textarea` components, using `FieldLabel` when
* children is provided.
*/
export const FieldLabelWrapper = ({
id,
children: { element, children },
}: FieldLabelWrapperProps): JSX.Element => {
if (!children) return element;

return (
<FieldLabel htmlFor={id}>
<span>{children}</span>
{element}
</FieldLabel>
);
};

export interface FieldLabelWrapperProps {
id: InputElementProps['id'] | TextareaElementProps['id'];
children: {
children: InputElementProps['children'] | TextareaElementProps['children'];
element: JSX.Element;
};
}

type InputElementProps = JSX.IntrinsicElements['input'];
type TextareaElementProps = JSX.IntrinsicElements['textarea'];
1 change: 1 addition & 0 deletions packages/react/src/internal/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './field-label-wrapper/field-label-wrapper';
export * from './indicator-container/indicator-container';

0 comments on commit 3e91a40

Please sign in to comment.