feat(form): readOnly flag (#166)
* feat: add readOnly flag on form * fix: resolve PR coments * fix: add flag disabled to fields that dont have effect on readonly attr
This commit is contained in:
parent
640e945959
commit
f502e6ca24
11 changed files with 2334 additions and 5635 deletions
7810
package-lock.json
generated
7810
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -14,9 +14,10 @@ export interface Props {
|
|||
control: FormControl;
|
||||
showValidationHints: boolean;
|
||||
showErrors?: boolean; // feature flag for showing validation errors
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const { control, showValidationHints, showErrors = false } = Astro.props;
|
||||
const { control, showValidationHints, showErrors = false, readOnly = false } = Astro.props;
|
||||
|
||||
const hasErrors: boolean | null = !!control.errors?.length;
|
||||
---
|
||||
|
@ -25,12 +26,12 @@ const hasErrors: boolean | null = !!control.errors?.length;
|
|||
<Label control={control} showValidationHints={showValidationHints}>
|
||||
{
|
||||
control.type === 'radio' ? (
|
||||
<RadioGroup control={control as Radio} />
|
||||
<RadioGroup control={control as Radio} readOnly={readOnly} />
|
||||
) :
|
||||
control.type === 'dropdown' ? (
|
||||
<DropdownControl control={control as Dropdown} />
|
||||
<DropdownControl control={control as Dropdown} readOnly={readOnly} />
|
||||
) : (
|
||||
<Input control={control} />
|
||||
<Input control={control} readOnly={readOnly} />
|
||||
)
|
||||
}
|
||||
{showErrors && <Errors errors={control.errors} />}
|
||||
|
|
|
@ -5,16 +5,17 @@ import Field from './Field.astro';
|
|||
export interface Props {
|
||||
group: FormGroup;
|
||||
showValidationHints: boolean;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const { group, showValidationHints } = Astro.props;
|
||||
const { group, showValidationHints, readOnly = false } = Astro.props;
|
||||
---
|
||||
|
||||
<fieldset id={group.name} name={group.name}>
|
||||
{group.name && <legend>{group.name}</legend>}
|
||||
{
|
||||
group?.controls?.map((control) => (
|
||||
<Field showValidationHints={showValidationHints} control={control} />
|
||||
<Field showValidationHints={showValidationHints} control={control} readOnly={readOnly} />
|
||||
))
|
||||
}
|
||||
</fieldset>
|
||||
|
|
|
@ -9,9 +9,10 @@ export interface Props {
|
|||
submitControl?: Submit;
|
||||
theme?: 'light' | 'dark';
|
||||
showValidationHints?: boolean;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const { submitControl, formGroups = [], theme, showValidationHints = false } = Astro.props;
|
||||
const { submitControl, formGroups = [], theme, showValidationHints = false, readOnly = false } = Astro.props;
|
||||
|
||||
const formTheme = theme ?? 'light';
|
||||
const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
|
||||
|
@ -26,10 +27,10 @@ const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
|
|||
{
|
||||
Array.isArray(formGroups)
|
||||
? formGroups?.map((group) => (
|
||||
<FieldSet showValidationHints={showValidationHints} group={group} />
|
||||
<FieldSet showValidationHints={showValidationHints} group={group} readOnly={readOnly}/>
|
||||
))
|
||||
: formGroups?.controls.map((control) => (
|
||||
<Field showValidationHints={showValidationHints} control={control} />
|
||||
<Field showValidationHints={showValidationHints} control={control} readOnly={readOnly}/>
|
||||
))
|
||||
}
|
||||
{
|
||||
|
|
|
@ -6,9 +6,10 @@ import type { Dropdown, ControlOption } from '@astro-reactive/common';
|
|||
|
||||
export interface Props {
|
||||
control: Dropdown;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const { control } = Astro.props;
|
||||
const { control, readOnly } = Astro.props;
|
||||
|
||||
const options = control.options.map((option: string | ControlOption) => {
|
||||
if (typeof option === 'string') {
|
||||
|
@ -21,7 +22,11 @@ const options = control.options.map((option: string | ControlOption) => {
|
|||
});
|
||||
---
|
||||
|
||||
<select name={control.name} id={control.name}>
|
||||
<select
|
||||
name={control.name}
|
||||
id={control.name}
|
||||
disabled={readOnly || null}
|
||||
>
|
||||
{
|
||||
control?.placeholder && (
|
||||
<option value="" disabled selected={!control?.value}>
|
||||
|
@ -39,4 +44,4 @@ const options = control.options.map((option: string | ControlOption) => {
|
|||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
|
|
|
@ -7,9 +7,10 @@ import type { FormControl } from '../../core/form-control';
|
|||
|
||||
export interface Props {
|
||||
control: FormControl;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const { control } = Astro.props;
|
||||
const { control, readOnly } = Astro.props;
|
||||
|
||||
const { validators = [] } = control;
|
||||
|
||||
|
@ -37,5 +38,7 @@ const validatorAttributes: Record<string, string> = validators?.reduce((prev, va
|
|||
data-label={control.label}
|
||||
data-label-position={control.labelPosition}
|
||||
data-validator-haserrors={hasErrors.toString()}
|
||||
readonly={readOnly || null}
|
||||
disabled={(readOnly || null) && control.type === 'checkbox'}
|
||||
{...validatorAttributes}
|
||||
/>
|
||||
|
|
|
@ -6,9 +6,10 @@ import type { Radio, ControlOption } from '@astro-reactive/common';
|
|||
|
||||
export interface Props {
|
||||
control: Radio;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const { control } = Astro.props;
|
||||
const { control, readOnly = false } = Astro.props;
|
||||
|
||||
const options = control.options.map((option: string | ControlOption) => {
|
||||
if (typeof option === 'string') {
|
||||
|
@ -29,6 +30,8 @@ const options = control.options.map((option: string | ControlOption) => {
|
|||
name={control.name}
|
||||
value={option.value}
|
||||
checked={option.value === control.value}
|
||||
readonly={readOnly || null}
|
||||
disabled={readOnly || null}
|
||||
/>{' '}
|
||||
{option.label}
|
||||
</div>
|
||||
|
|
|
@ -149,4 +149,27 @@ describe('Dropdown.astro test', () => {
|
|||
// assert
|
||||
expect(actualResult).to.contain(expectedResult);
|
||||
});
|
||||
|
||||
it('Should render disabled attribute if the prop readOnly is passed as true', async () => {
|
||||
// arrange
|
||||
const expectedOptions = 1;
|
||||
const element = /disabled/g;
|
||||
const props = {
|
||||
control: {
|
||||
label: 'FAKE LABEL',
|
||||
name: 'FAKE NAME',
|
||||
type: 'dropdown',
|
||||
options: ['one', 'two', 'three'],
|
||||
},
|
||||
readOnly: true,
|
||||
};
|
||||
|
||||
// act
|
||||
component = await getComponentOutput('./components/controls/Dropdown.astro', props);
|
||||
const actualResult = cleanString(component.html);
|
||||
const matches = actualResult.match(element) || [];
|
||||
|
||||
// assert
|
||||
expect(matches.length).to.equal(expectedOptions);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -79,4 +79,27 @@ describe('Field.astro test', () => {
|
|||
// assert
|
||||
expect(actualResult).to.contain(expectedResult);
|
||||
});
|
||||
|
||||
it('Should render field with readOnly attribute when readOnly is true', async () => {
|
||||
// arrange
|
||||
const expectedLabel = 'TestLabel';
|
||||
const expectedAttribute = 'readonly';
|
||||
const props = {
|
||||
control: {
|
||||
label: expectedLabel,
|
||||
name: 'username',
|
||||
labelPosition: 'left',
|
||||
validators: ['validator-required'],
|
||||
},
|
||||
readOnly: true,
|
||||
};
|
||||
|
||||
// act
|
||||
component = await getComponentOutput('./components/Field.astro', props);
|
||||
const actualResult = cleanString(component.html);
|
||||
|
||||
// assert
|
||||
expect(actualResult).to.contain(expectedLabel);
|
||||
expect(actualResult).to.contain(expectedAttribute);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -85,6 +85,29 @@ describe('Form.astro test', () => {
|
|||
const actualResult = cleanString(component.html);
|
||||
const matches = actualResult.match(element) || [];
|
||||
|
||||
// assert
|
||||
expect(matches.length).to.equal(expectedCount);
|
||||
});
|
||||
it('Should render readOnly fields if the flag is passed as true', async () => {
|
||||
// arrange
|
||||
const expectedCount = 3;
|
||||
const element = /readonly/g;
|
||||
const fakeFormGroup = {
|
||||
controls: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'fake-checkbox',
|
||||
label: 'FAKE CHECKBOX',
|
||||
},
|
||||
],
|
||||
};
|
||||
const props = { formGroups: Array(expectedCount).fill(fakeFormGroup), readOnly: true };
|
||||
component = await getComponentOutput('./components/Form.astro', props);
|
||||
|
||||
// act
|
||||
const actualResult = cleanString(component.html);
|
||||
const matches = actualResult.match(element) || [];
|
||||
|
||||
// assert
|
||||
expect(matches.length).to.equal(expectedCount);
|
||||
});
|
||||
|
|
|
@ -87,4 +87,52 @@ describe('RadioGroup.astro test', () => {
|
|||
// assert
|
||||
expect(actualResult).to.contain(expectedResult);
|
||||
});
|
||||
|
||||
it('Should render readonly attribute if the prop readOnly is passed as true', async () => {
|
||||
// arrange
|
||||
const expectedOptions = 3;
|
||||
const element = /readonly/g;
|
||||
const props = {
|
||||
control: {
|
||||
label: 'FAKE LABEL',
|
||||
name: 'FAKE NAME',
|
||||
type: 'radio',
|
||||
options: ['one', 'two', 'three'],
|
||||
},
|
||||
showValidationHints: true,
|
||||
readOnly: true,
|
||||
};
|
||||
|
||||
// act
|
||||
component = await getComponentOutput('./components/controls/RadioGroup.astro', props);
|
||||
const actualResult = cleanString(component.html);
|
||||
const matches = actualResult.match(element) || [];
|
||||
|
||||
// assert
|
||||
expect(matches.length).to.equal(expectedOptions);
|
||||
});
|
||||
|
||||
it('Should render disabled attribute if the prop readOnly is passed as true', async () => {
|
||||
// arrange
|
||||
const expectedOptions = 3;
|
||||
const element = /disabled/g;
|
||||
const props = {
|
||||
control: {
|
||||
label: 'FAKE LABEL',
|
||||
name: 'FAKE NAME',
|
||||
type: 'radio',
|
||||
options: ['one', 'two', 'three'],
|
||||
},
|
||||
showValidationHints: true,
|
||||
readOnly: true,
|
||||
};
|
||||
|
||||
// act
|
||||
component = await getComponentOutput('./components/controls/RadioGroup.astro', props);
|
||||
const actualResult = cleanString(component.html);
|
||||
const matches = actualResult.match(element) || [];
|
||||
|
||||
// assert
|
||||
expect(matches.length).to.equal(expectedOptions);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue