Skip to content

Commit

Permalink
[@mantine/core] Add InlineInput component to remove duplicated code f…
Browse files Browse the repository at this point in the history
…rom Checkbox, Switch and Radio components
  • Loading branch information
rtivital committed Oct 24, 2022
1 parent 2b3c731 commit 3f3f5d9
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 256 deletions.
35 changes: 0 additions & 35 deletions src/mantine-core/src/Checkbox/Checkbox.styles.ts
Expand Up @@ -45,21 +45,6 @@ export default createStyles(
const errorColor = theme.fn.variant({ variant: 'filled', color: 'red' }).background;

return {
description: {
marginTop: `calc(${theme.spacing.xs}px / 2)`,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

error: {
marginTop: `calc(${theme.spacing.xs}px / 2)`,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

label: {
cursor: theme.cursorType,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

icon: {
ref: getRef('icon'),
color: indeterminate ? 'inherit' : theme.white,
Expand All @@ -83,33 +68,13 @@ export default createStyles(
},
},

root: {},

body: {
display: 'flex',
},

inner: {
position: 'relative',
width: _size,
height: _size,
order: labelPosition === 'left' ? 2 : 1,
},

labelWrapper: {
...theme.fn.fontStyles(),
WebkitTapHighlightColor: 'transparent',
fontSize: theme.fn.size({ size, sizes: theme.fontSizes }),
lineHeight: `${_size}px`,
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
cursor: theme.cursorType,
order: labelPosition === 'left' ? 1 : 2,

'& label[data-disabled]': {
color: theme.colorScheme === 'dark' ? theme.colors.dark[3] : theme.colors.gray[5],
},
},

input: {
...theme.fn.focusStyles(),
appearance: 'none',
Expand Down
10 changes: 1 addition & 9 deletions src/mantine-core/src/Checkbox/Checkbox.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
import { queryByTestId, render, screen } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import {
checkAccessibility,
itSupportsSystemProps,
Expand Down Expand Up @@ -34,14 +34,6 @@ describe('@mantine/core/Checkbox', () => {
providerName: 'Checkbox',
});

it('renders label based on label prop', () => {
const { container: withLabel, getByText } = render(<Checkbox label="test-label" />);
const { container: withoutLabel } = render(<Checkbox />);
expect(queryByTestId(withLabel, 'label')).toBeInTheDocument();
expect(queryByTestId(withoutLabel, 'label')).toBeNull();
expect(getByText('test-label')).toBeInTheDocument();
});

it('sets disabled attribute on input based on disabled prop', () => {
render(<Checkbox disabled />);
expect(screen.getByRole('checkbox')).toBeDisabled();
Expand Down
73 changes: 30 additions & 43 deletions src/mantine-core/src/Checkbox/Checkbox.tsx
Expand Up @@ -10,14 +10,13 @@ import {
} from '@mantine/styles';
import { ForwardRefWithStaticComponents } from '@mantine/utils';
import { useId } from '@mantine/hooks';
import { Box } from '../Box';
import { CheckboxIcon } from './CheckboxIcon';
import { CheckboxGroup } from './CheckboxGroup/CheckboxGroup';
import { useCheckboxGroupContext } from './CheckboxGroup.context';
import { InlineInput, InlineInputStylesNames } from '../InlineInput';
import useStyles, { CheckboxStylesParams } from './Checkbox.styles';
import { Input } from '../Input';

export type CheckboxStylesNames = Selectors<typeof useStyles>;
export type CheckboxStylesNames = Selectors<typeof useStyles> | InlineInputStylesNames;

export interface CheckboxProps
extends DefaultProps<CheckboxStylesNames, CheckboxStylesParams>,
Expand Down Expand Up @@ -101,7 +100,7 @@ export const Checkbox: CheckboxComponent = forwardRef<HTMLInputElement, Checkbox
const ctx = useCheckboxGroupContext();
const uuid = useId(id);
const { systemStyles, rest } = extractSystemStyles(others);
const { classes, cx } = useStyles(
const { classes } = useStyles(
{
size: ctx?.size || size,
radius,
Expand All @@ -122,51 +121,39 @@ export const Checkbox: CheckboxComponent = forwardRef<HTMLInputElement, Checkbox
: {};

return (
<Box
className={cx(classes.root, className)}
<InlineInput
className={className}
sx={sx}
style={style}
id={uuid}
size={ctx?.size || size}
labelPosition={labelPosition}
label={label}
description={description}
error={error}
disabled={disabled}
__staticSelector="Checkbox"
classNames={classNames}
styles={styles}
unstyled={unstyled}
{...systemStyles}
{...wrapperProps}
>
<div className={cx(classes.body)}>
<div className={classes.inner}>
<input
id={uuid}
ref={ref}
type="checkbox"
className={classes.input}
checked={checked}
disabled={disabled}
{...rest}
{...contextProps}
/>

<Icon indeterminate={indeterminate} className={classes.icon} />
</div>

<div className={classes.labelWrapper}>
{label && (
<label
className={classes.label}
data-disabled={disabled || undefined}
htmlFor={uuid}
data-testid="label"
>
{label}
</label>
)}

{description && (
<Input.Description className={classes.description}>{description}</Input.Description>
)}

{error && error !== 'boolean' && (
<Input.Error className={classes.error}>{error}</Input.Error>
)}
</div>
<div className={classes.inner}>
<input
id={uuid}
ref={ref}
type="checkbox"
className={classes.input}
checked={checked}
disabled={disabled}
{...rest}
{...contextProps}
/>

<Icon indeterminate={indeterminate} className={classes.icon} />
</div>
</Box>
</InlineInput>
);
}
) as any;
Expand Down
Empty file.
51 changes: 51 additions & 0 deletions src/mantine-core/src/InlineInput/InlineInput.styles.ts
@@ -0,0 +1,51 @@
import { createStyles, MantineSize } from '@mantine/styles';

const sizes = {
xs: 16,
sm: 20,
md: 24,
lg: 30,
xl: 36,
};

export interface InlineInputStylesParams {
size: MantineSize;
labelPosition: 'left' | 'right';
}

export default createStyles((theme, { labelPosition, size }: InlineInputStylesParams) => ({
root: {},

body: {
display: 'inline-flex',
},

labelWrapper: {
...theme.fn.fontStyles(),
WebkitTapHighlightColor: 'transparent',
fontSize: theme.fn.size({ size, sizes: theme.fontSizes }),
lineHeight: `${theme.fn.size({ size, sizes })}px`,
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
cursor: theme.cursorType,
order: labelPosition === 'left' ? 1 : 2,
},

description: {
marginTop: `calc(${theme.spacing.xs}px / 2)`,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

error: {
marginTop: `calc(${theme.spacing.xs}px / 2)`,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

label: {
cursor: theme.cursorType,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,

'&[data-disabled]': {
color: theme.colorScheme === 'dark' ? theme.colors.dark[3] : theme.colors.gray[5],
},
},
}));
68 changes: 68 additions & 0 deletions src/mantine-core/src/InlineInput/InlineInput.tsx
@@ -0,0 +1,68 @@
import React from 'react';
import { DefaultProps, MantineSize, Selectors } from '@mantine/styles';
import { Box } from '../Box';
import { Input } from '../Input';
import useStyles from './InlineInput.styles';

export type InlineInputStylesNames = Selectors<typeof useStyles>;

export interface InlineInputProps
extends DefaultProps<InlineInputStylesNames>,
React.ComponentPropsWithoutRef<'div'> {
__staticSelector: string;
label: React.ReactNode;
description: React.ReactNode;
id: string;
disabled: boolean;
error: React.ReactNode;
size: MantineSize;
labelPosition: 'left' | 'right';
}

export function InlineInput({
__staticSelector,
className,
classNames,
styles,
unstyled,
children,
label,
description,
id,
disabled,
error,
size,
labelPosition,
...others
}: InlineInputProps) {
const { classes, cx } = useStyles(
{ size, labelPosition },
{ name: __staticSelector, styles, classNames, unstyled }
);

return (
<Box className={cx(classes.root, className)} {...others}>
<div className={cx(classes.body)}>
{children}

<div className={classes.labelWrapper}>
{label && (
<label className={classes.label} data-disabled={disabled || undefined} htmlFor={id}>
{label}
</label>
)}

{description && (
<Input.Description className={classes.description}>{description}</Input.Description>
)}

{error && error !== 'boolean' && (
<Input.Error className={classes.error}>{error}</Input.Error>
)}
</div>
</div>
</Box>
);
}

InlineInput.displayName = '@mantine/core/InlineInput';
2 changes: 2 additions & 0 deletions src/mantine-core/src/InlineInput/index.ts
@@ -0,0 +1,2 @@
export { InlineInput } from './InlineInput';
export type { InlineInputProps, InlineInputStylesNames } from './InlineInput';
34 changes: 0 additions & 34 deletions src/mantine-core/src/Radio/Radio.styles.ts
Expand Up @@ -30,27 +30,6 @@ export default createStyles(
const errorColor = theme.fn.variant({ variant: 'filled', color: 'red' }).background;

return {
root: {},

description: {
marginTop: `calc(${theme.spacing.xs}px / 2)`,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

error: {
marginTop: `calc(${theme.spacing.xs}px / 2)`,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

label: {
cursor: theme.cursorType,
[labelPosition === 'left' ? 'paddingRight' : 'paddingLeft']: theme.spacing.sm,
},

body: {
display: 'flex',
},

inner: {
order: labelPosition === 'left' ? 2 : 1,
position: 'relative',
Expand Down Expand Up @@ -115,19 +94,6 @@ export default createStyles(
},
},
},

labelWrapper: {
...theme.fn.fontStyles(),
fontSize: theme.fontSizes[size] || theme.fontSizes.md,
lineHeight: `${theme.fn.size({ sizes, size })}px`,
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
cursor: theme.cursorType,
order: labelPosition === 'left' ? 1 : 2,

'& label[data-disabled]': {
color: theme.colorScheme === 'dark' ? theme.colors.dark[3] : theme.colors.gray[5],
},
},
};
}
);

0 comments on commit 3f3f5d9

Please sign in to comment.