Skip to content

Commit

Permalink
feat(FormField, Input, NumericInput, TagInput, Picker, Textarea, Auto…
Browse files Browse the repository at this point in the history
…complete): add readonly state (#1497)
  • Loading branch information
JoannaSikora authored Jan 30, 2025
1 parent 009a88a commit 090918a
Show file tree
Hide file tree
Showing 29 changed files with 492 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ export const Examples = (): React.ReactElement => {
<AutoComplete options={options} placeholder={placeholderText} />
</form>
</StoryDescriptor>

<StoryDescriptor title="Read Only">
<AutoComplete
options={options}
value="Option 1"
placeholder={placeholderText}
readOnly
/>
</StoryDescriptor>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import * as React from 'react';

import { FloatingNode, FloatingPortal } from '@floating-ui/react';

import { useReadOnlyFormFieldContext } from '../../providers/ReadOnlyFormFieldProvider';
import noop from '../../utils/noop';
import { Input } from '../Input';
import { IPickerListItem, PickerList } from '../Picker';
import { DEFAULT_LIST_HEIGHT, MIN_LIST_HEIGHT } from '../Picker/constants';
import { useFloatingPicker } from '../Picker/hooks/useFloatingPicker';
import { usePickerItems } from '../Picker/hooks/usePickerItems';
import { ReadOnlyText } from '../ReadOnlyText';

import {
areAllOptionsStrings,
Expand Down Expand Up @@ -44,11 +46,14 @@ export const AutoComplete = React.forwardRef<
single,
alwaysShowAllOptions,
hideIfExactMatch = true,
noDataFallbackText = 'No data',
...inputProps
},
ref
) => {
const inputRef = React.useRef<HTMLInputElement>(null);
const { readOnly: readOnlyContext } = useReadOnlyFormFieldContext();
const computedReadOnly = readOnlyContext || readOnly;

React.useImperativeHandle(ref, () => inputRef.current!, []);

Expand Down Expand Up @@ -158,6 +163,15 @@ export const AutoComplete = React.forwardRef<
floatingStrategy,
});

if (computedReadOnly) {
return (
<ReadOnlyText
value={inputValue?.toString()}
noDataFallbackText={noDataFallbackText}
/>
);
}

return (
<div ref={setReference} {...getReferenceProps()}>
<Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ export interface AutoCompleteProps extends Omit<IInputProps, 'type'> {
single?: boolean;
/** If true, the option list will be hidden if there is only one option and it is an exact match to the input value. */
hideIfExactMatch?: boolean;
/**
* Set the text to display with read-only state when there is no data. Default to 'No data'
*/
noDataFallbackText?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@
}
}

&--read-only {
.checkbox {
&__input {
border-color: var(--border-basic-secondary);
background-color: var(--surface-primary-default);

&::after {
background-color: var(--content-basic-primary);
}
}

&__label,
&__input,
&__helper,
&__text {
cursor: default;
pointer-events: none;
}
}
}

&--selected {
&:hover {
.checkbox__input:checked + .checkbox__square {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export const States = (): React.ReactElement => (
<CheckboxComponent checked description="Disabled" disabled>
Checkbox label
</CheckboxComponent>
<CheckboxComponent checked description="ReadOnly" readOnly>
Checkbox label
</CheckboxComponent>
</StoryDescriptor>
<StoryDescriptor title="Unchecked">
<CheckboxComponent checked={false} description="Enabled">
Expand All @@ -46,6 +49,9 @@ export const States = (): React.ReactElement => (
<CheckboxComponent checked={false} description="Disabled" disabled>
Checkbox label
</CheckboxComponent>
<CheckboxComponent checked={false} description="ReadOnly" readOnly>
Checkbox label
</CheckboxComponent>
</StoryDescriptor>
<StoryDescriptor title="Indeterminate">
<CheckboxComponent
Expand All @@ -63,6 +69,14 @@ export const States = (): React.ReactElement => (
>
Checkbox label
</CheckboxComponent>
<CheckboxComponent
indeterminate={true}
checked={false}
description="ReadOnly"
readOnly
>
Checkbox label
</CheckboxComponent>
</StoryDescriptor>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';

import cx from 'clsx';

import { useReadOnlyFormFieldContext } from '../../providers/ReadOnlyFormFieldProvider';
import { FieldDescription } from '../FieldDescription';
import { Text } from '../Typography';

Expand All @@ -12,6 +13,10 @@ export interface CheckboxProps extends React.HTMLAttributes<HTMLInputElement> {
* Specify whether the checkbox should be disabled
*/
disabled?: boolean;
/**
* Specify whether the checkbox should be read only
*/
readOnly?: boolean;
/**
* Specify whether the checkbox should be checked
*/
Expand Down Expand Up @@ -41,12 +46,16 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
},
ref
) => {
const { readOnly } = useReadOnlyFormFieldContext();
const computedReadOnly = readOnly || restInputProps.readOnly;

return (
<div
className={cx(styles[baseClass], className, {
[styles[`${baseClass}--selected`]]: checked,
[styles[`${baseClass}--disabled`]]: disabled,
[styles[`${baseClass}--indeterminate`]]: indeterminate,
[styles[`${baseClass}--read-only`]]: computedReadOnly,
})}
>
<label className={styles[`${baseClass}__label`]}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
flex-direction: column;
}

&__wrapper--inline {
display: flex;
flex-flow: row nowrap;
&__wrapper {
&--inline {
display: flex;
flex-flow: row nowrap;
}
}

&__label {
Expand Down Expand Up @@ -67,6 +69,10 @@
line-height: 20px;
color: var(--content-basic-primary);
font-size: 14px;

&--read-only {
cursor: default;
}
}

&__label-right-node {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
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, InputPromo } 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';

const DEFAULT_PICKER_OPTIONS = [
{ key: 'one', name: 'Option one' },
{ key: 'groupA', name: 'Group A title header', groupHeader: true },
{ key: 'two', name: 'Option two' },
{ key: 'three', name: 'Option three' },
{ key: 'groupB', name: 'Group B title header', groupHeader: true },
{ key: 'four', name: 'Option four' },
{ key: 'five', name: 'Option five' },
{ key: 'six', name: 'Option six' },
{ key: 'seven', name: 'Option seven' },
{ key: 'eight', name: 'Option eight' },
{ key: 'nine', name: 'Option nine' },
];

export default {
title: 'Forms/FormField',
component: FormFieldComponent,
Expand Down Expand Up @@ -102,6 +125,97 @@ export const TextFieldWithError: StoryFn<FormFieldProps> = () => (
</FormFieldComponent>
);

const ReadOnlyTemplate: StoryFn<FormFieldProps> = (args) => (
<ReadOnlyFormFieldContextProvider readOnly={args.readOnly}>
<div>
<StoryDescriptor title="WithNoValue">
<FormFieldComponent {...args}>
<ExampleInput />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithInput">
<FormFieldComponent {...args}>
<ExampleInput value="test value" />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithPromoInput">
<FormFieldComponent {...args} boldLabel>
<ExampleInputPromo value="test value" />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithPicker">
<FormFieldComponent {...args}>
<Picker
onSelect={() => {}}
options={DEFAULT_PICKER_OPTIONS}
selected={DEFAULT_PICKER_OPTIONS.slice(0, 3)}
/>
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithPickerWithExampleWidthContstraints">
<div
style={{ width: '200px', padding: '10px', border: '1px solid gray' }}
>
<FormFieldComponent {...args}>
<Picker
onSelect={() => {}}
options={DEFAULT_PICKER_OPTIONS}
selected={DEFAULT_PICKER_OPTIONS.slice(0, 9)}
/>
</FormFieldComponent>
</div>
</StoryDescriptor>
<StoryDescriptor title="WithTagInput">
<FormFieldComponent {...args}>
<TagInput
tags={['tag1', 'tag2', 'tag3', 'tag4']}
onChange={() => {}}
/>
</FormFieldComponent>
</StoryDescriptor>

<StoryDescriptor title="WithNumericInput">
<FormFieldComponent {...args}>
<NumericInput value="456" onChange={() => {}} />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithTextarea">
<FormFieldComponent {...args}>
<Textarea value="Lorem Ipsum is simply dummy text of the printing and typesetting industry." />
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithAutocomplete">
<FormFieldComponent {...args}>
<AutoComplete
options={['option1', 'option2', 'option3']}
value="option2"
/>
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithCheckbox">
<FormFieldComponent {...args}>
<Checkbox checked description="Checkbox description">
Checkbox label
</Checkbox>
</FormFieldComponent>
</StoryDescriptor>
<StoryDescriptor title="WithRadioButton">
<FormFieldComponent {...args}>
<RadioButton checked description="RadioButton description">
RadioButton label
</RadioButton>
</FormFieldComponent>
</StoryDescriptor>
</div>
</ReadOnlyFormFieldContextProvider>
);

export const TextFieldReadOnly = ReadOnlyTemplate.bind({});
TextFieldReadOnly.args = {
labelText: 'My label',
readOnly: true,
};

export const BoldLabelWithPromoInput: StoryFn<FormFieldProps> = () => (
<FormFieldComponent labelText="Username" boldLabel>
<ExampleInputPromo />
Expand Down
Loading

0 comments on commit 090918a

Please sign in to comment.