Skip to content

Commit

Permalink
feat(FormField, Input): add read-only styles with fallback text
Browse files Browse the repository at this point in the history
  • Loading branch information
JoannaSikora committed Jan 24, 2025
1 parent 0934708 commit 67e2fb0
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Info } from '@livechat/design-system-icons';
import { Meta, StoryFn } from '@storybook/react';

import { ReadOnlyFormFieldContextProvider } from '../../providers/ReadOnlyFormFieldProvider';
import { StoryDescriptor } from '../../stories/components/StoryDescriptor';
import { AutoComplete } from '../AutoComplete';
import { Checkbox } from '../Checkbox';
import { Icon } from '../Icon';
import { Input } from '../Input';
import { IInputProps } from '../Input/types';
import { NumericInput } from '../NumericInput';
import { Picker } from '../Picker';
import { RadioButton } from '../RadioButton';
import { TagInput } from '../TagInput';
import { Textarea } from '../Textarea';

import { FormField as FormFieldComponent, FormFieldProps } from './FormField';

Expand Down Expand Up @@ -94,8 +103,58 @@ export const TextFieldWithError: StoryFn<FormFieldProps> = () => (
</FormFieldComponent>
);

export const TextFieldReadOnlyWithInput: StoryFn<FormFieldProps> = () => (
<FormFieldComponent labelText="Username" readonly>
<ExampleInput />
</FormFieldComponent>
const ReadOnlyTemplate: StoryFn<FormFieldProps> = (args) => (
<ReadOnlyFormFieldContextProvider readOnly={args.readOnly}>
<div>
<StoryDescriptor title="WithInput">
<FormFieldComponent {...args}>
<ExampleInput value="" />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithPicker">
<FormFieldComponent {...args}>
<Picker onSelect={() => {}} options={[]} />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithTagInput">
<FormFieldComponent {...args}>
<TagInput onChange={() => {}} />
</FormFieldComponent>
</StoryDescriptor>

{/* // Those also? */}
<StoryDescriptor title="WithNumericInput">
<FormFieldComponent {...args}>
<NumericInput value="456" onChange={() => {}} />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithCheckbox">
<FormFieldComponent {...args}>
<Checkbox />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithTextarea">
<FormFieldComponent {...args}>
<Textarea />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithRadio">
<FormFieldComponent {...args}>
<RadioButton />
<RadioButton />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithAutocomplete">
<FormFieldComponent {...args}>
<AutoComplete />
</FormFieldComponent>
</StoryDescriptor>
</div>
</ReadOnlyFormFieldContextProvider>
);

export const TextFieldReadOnly = ReadOnlyTemplate.bind({});
TextFieldReadOnly.args = {
labelText: 'My label',
readOnly: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface FormFieldProps {
/**
* Whether the form field is read-only
*/
readonly?: boolean;
readOnly?: boolean;
}

export const FormField: React.FC<React.PropsWithChildren<FormFieldProps>> = ({
Expand All @@ -64,7 +64,7 @@ export const FormField: React.FC<React.PropsWithChildren<FormFieldProps>> = ({
labelFor,
children,
labelRightNode,
readonly = false,
readOnly = false,
}) => {
const childrenRef = React.useRef<HTMLDivElement>(null);
const [labelHeight, setLabelHeight] = React.useState('auto');
Expand All @@ -88,7 +88,7 @@ export const FormField: React.FC<React.PropsWithChildren<FormFieldProps>> = ({
});

return (
<ReadOnlyFormFieldContextProvider readonly={readonly}>
<ReadOnlyFormFieldContextProvider readOnly={readOnly}>
<div className={mergedClassNames}>
{labelRightNode && inline && (
<React.Fragment>
Expand Down Expand Up @@ -133,7 +133,7 @@ export const FormField: React.FC<React.PropsWithChildren<FormFieldProps>> = ({
className={styles[`${baseClass}__label-left-node`]}
htmlFor={labelFor}
>
<Text as="span" size="sm" bold={readonly}>
<Text as="span" size="sm">
{labelText}
</Text>
</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ $base-class: 'input';
}

&--read-only {
// TODO styles
border: 0;
background: none;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/react-components/src/components/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export const States = (): React.ReactElement => (
<StoryDescriptor title="Disabled">
<Input disabled={true} placeholder={placeholderText} />
</StoryDescriptor>
<StoryDescriptor title="ReadOnly">
<Input readOnly value="My input test value" />
</StoryDescriptor>
</>
);

Expand Down Expand Up @@ -135,6 +138,13 @@ export const InputPromoStates = (): React.ReactElement => (
<StoryDescriptor title="Disabled">
<InputPromoComponent disabled={true} placeholder={placeholderText} />
</StoryDescriptor>
<StoryDescriptor title="ReadOnly">
<InputPromoComponent
readOnly
placeholder={placeholderText}
value="My test value"
/>
</StoryDescriptor>
</>
);

Expand Down
16 changes: 13 additions & 3 deletions packages/react-components/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ export const InputComponent = React.forwardRef<
mainClassName,
isPromo = false,
cropOnBlur = true,
noDataFallbackText = 'No data',
...inputProps
},
ref
) => {
const innerRef = React.useRef<HTMLInputElement>(null);
const { readonly } = useReadOnlyFormFieldContext();
const { readOnly } = useReadOnlyFormFieldContext();
const computedReadOnly = readOnly || inputProps.readOnly;

React.useImperativeHandle(ref, () => innerRef.current!, []);
const [isFocused, setIsFocused] = React.useState(false);
Expand All @@ -65,7 +67,7 @@ export const InputComponent = React.forwardRef<
[styles[`${baseClass}--focused`]]: isFocused,
[styles[`${baseClass}--error`]]: error,
[styles[`${baseClass}--crop`]]: cropOnBlur,
[styles[`${baseClass}--read-only`]]: readonly || inputProps.readOnly,
[styles[`${baseClass}--read-only`]]: computedReadOnly,
},
className
);
Expand All @@ -81,6 +83,14 @@ export const InputComponent = React.forwardRef<
innerRef.current?.focus();
};

if (computedReadOnly) {
return (
<Text noMargin semiBold>
{inputProps.value || noDataFallbackText}
</Text>
);
}

return (
<Text
as="div"
Expand All @@ -93,7 +103,7 @@ export const InputComponent = React.forwardRef<
{shouldRenderLeftIcon && renderIcon(icon, disabled)}
<input
{...inputProps}
readOnly={readonly}
readOnly={computedReadOnly}
ref={innerRef}
onFocus={(e) => {
setIsFocused(true);
Expand Down
4 changes: 4 additions & 0 deletions packages/react-components/src/components/Input/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export interface IInputGlobalProps
* Set to enable ellipsis
*/
cropOnBlur?: boolean;
/**
* Set the text to display with read-only state when there is no data. Default to 'No data'
*/
noDataFallbackText?: string;
}

export interface IInputProps extends IInputGlobalProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ import { ReactNode, createContext, useContext } from 'react';
import type { FC } from 'react';

interface ReadOnlyFormFieldContextValue {
readonly: boolean;
readOnly: boolean;
}

const ReadOnlyFormFieldContext = createContext<ReadOnlyFormFieldContextValue>({
readonly: false,
readOnly: false,
});

export interface ReadOnlyFormFieldContextProps {
readonly?: boolean;
readOnly?: boolean;
children?: ReactNode;
}

export const ReadOnlyFormFieldContextProvider: FC<
ReadOnlyFormFieldContextProps
> = ({ children, readonly = false }) => {
> = ({ children, readOnly = false }) => {
return (
<ReadOnlyFormFieldContext.Provider
value={{
readonly,
readOnly,
}}
>
{children}
Expand Down

0 comments on commit 67e2fb0

Please sign in to comment.