Skip to content

Commit

Permalink
fix(react): indicator data- and aria- properties apply to label (#236)
Browse files Browse the repository at this point in the history
closes #230
  • Loading branch information
rabelloo authored Jan 29, 2021
1 parent 552adf9 commit ef83180
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 36 deletions.
5 changes: 1 addition & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

export default {
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!**/*.stor{y,ies}.tsx', // stories
'!**/components/**/*', // components
'!**/internal/**/*', // internal components
'**/*.ts',
'!**/dist/**/*', // distribution files
'!**/index.ts', // index files
],
Expand Down
41 changes: 25 additions & 16 deletions packages/react/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { c, CheckboxProps as BaseProps, classy, m } from '@onfido/castor';
import React from 'react';
import { IndicatorContainer } from '../../internal';
import { IndicatorContainer, splitContainerProps } from '../../internal';
import { withRef } from '../../utils';

export const Checkbox = withRef(
Expand All @@ -14,21 +14,30 @@ export const Checkbox = withRef(
...restProps
}: CheckboxProps,
ref: CheckboxProps['ref']
): JSX.Element => (
<IndicatorContainer bordered={bordered} className={className} style={style}>
{{
children,
input: (
<input
{...restProps}
ref={ref}
type="checkbox"
className={classy(c('checkbox'), m({ invalid }))}
/>
),
}}
</IndicatorContainer>
)
): JSX.Element => {
const [containerProps, inputProps] = splitContainerProps(restProps);

return (
<IndicatorContainer
{...containerProps}
bordered={bordered}
className={className}
style={style}
>
{{
children,
input: (
<input
{...inputProps}
ref={ref}
type="checkbox"
className={classy(c('checkbox'), m({ invalid }))}
/>
),
}}
</IndicatorContainer>
);
}
);
Checkbox.displayName = 'Checkbox';

Expand Down
41 changes: 25 additions & 16 deletions packages/react/src/components/radio/radio.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
import { c, classy, m, RadioProps as BaseProps } from '@onfido/castor';
import React from 'react';
import { IndicatorContainer } from '../../internal';
import { IndicatorContainer, splitContainerProps } from '../../internal';
import { withRef } from '../../utils';

export const Radio = withRef(
(
{ bordered, invalid, children, className, style, ...restProps }: RadioProps,
ref: RadioProps['ref']
): JSX.Element => (
<IndicatorContainer bordered={bordered} className={className} style={style}>
{{
children,
input: (
<input
{...restProps}
ref={ref}
type="radio"
className={classy(c('radio'), m({ invalid }))}
/>
),
}}
</IndicatorContainer>
)
): JSX.Element => {
const [containerProps, inputProps] = splitContainerProps(restProps);

return (
<IndicatorContainer
{...containerProps}
bordered={bordered}
className={className}
style={style}
>
{{
children,
input: (
<input
{...inputProps}
ref={ref}
type="radio"
className={classy(c('radio'), m({ invalid }))}
/>
),
}}
</IndicatorContainer>
);
}
);
Radio.displayName = 'Radio';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { c, classy, m } from '@onfido/castor';
import { IndicatorContainerProps as BaseProps } from '@onfido/castor/src/internal';
import React from 'react';

export * from './splitContainerProps';

/**
* Container for `Checkbox` and `Radio` input components, adding custom
* indicator within <label> element.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, it } from '@jest/globals';
import { splitContainerProps } from './splitContainerProps';

describe('splitContainerProps', () => {
it('should split props into [containerProps, inputProps]', () => {
const foo = {};
const props = { foo, bar: 1, 'data-test': 'bar' };

const [containerProps, inputProps] = splitContainerProps(props as any);

expect(containerProps).toStrictEqual({ 'data-test': 'bar' });
expect(inputProps).toStrictEqual({ foo, bar: 1 });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { partitionObject } from '../../utils';

/**
* Splits `Indicator` properties into `label` and `input` props.
*/
export const splitContainerProps = (
props: InputElementProps | LabelElementProps
) => partitionObject(props, ([key]) => containerProps.test(key)) as Tuple;

const containerProps = /^aria-|^data-/;

type InputElementProps = JSX.IntrinsicElements['input'];
type LabelElementProps = JSX.IntrinsicElements['label'];
type Tuple = [LabelElementProps, InputElementProps];
1 change: 1 addition & 0 deletions packages/react/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './partitionObject/partitionObject';
export * from './withRef/withRef';
13 changes: 13 additions & 0 deletions packages/react/src/utils/partitionObject/partitionObject.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, it } from '@jest/globals';
import { partitionObject } from './partitionObject';

describe('partitionObject', () => {
it('should split an object like [{ "does" }, { "does not" }] satisfy the predicate', () => {
const obj = { foo: true, bar: false };

const [truthy, falsy] = partitionObject(obj, ([, v]) => Boolean(v));

expect(truthy).toStrictEqual({ foo: true });
expect(falsy).toStrictEqual({ bar: false });
});
});
20 changes: 20 additions & 0 deletions packages/react/src/utils/partitionObject/partitionObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Partitions an `object` in two with entries that satisfy the `predicate` then
* those that don't, respectively.
*
* @param object Object to be partitioned.
* @param predicate Function that determines in where to put each entry.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const partitionObject = <T extends Record<string, any>>(
object: T,
predicate: (entry: [keyof T, T[keyof T]], index: number) => boolean
) =>
Object.entries(object).reduce(
(tuple, [key, value]: [keyof T, T[keyof T]], index) => {
const which = predicate([key, value], index) ? 0 : 1;
tuple[which][key] = value;
return tuple;
},
[{}, {}] as [Partial<T>, Partial<T>]
);

0 comments on commit ef83180

Please sign in to comment.