Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/Checkbox Components #646

Merged
merged 5 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/components-css/breadcrumb-nav/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
text-decoration: underline !important;
text-decoration-color: var(--utrecht-breadcrumb-nav-link-active-color) !important;
}
&--arrow-wrapper {
align-items: center;
display: flex;
justify-content: center;
justify-items: center;
}
}
&__separator {
--utrecht-breadcrumb-nav-separator-icon-size: var(--utrecht-breadcrumb-divider-size, 24px);
Expand Down
9 changes: 6 additions & 3 deletions packages/components-css/checkbox/index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
/**
* @license EUPL-1.2
* Copyright (c) 2021 Community for NL Design System
*/

@import "./mixin";

.utrecht-checkbox {
margin-block-start: var(--rhc-space-50) !important;
&--disabled {
@include utrecht-checkbox--disabled;
}
Expand All @@ -19,9 +25,6 @@
@include utrecht-checkbox--invalid;
}

.utrecht-checkbox__label {
white-space: nowrap;
}
.rhc-checkbox-group {
display: flex;
flex-direction: column;
Expand Down
4 changes: 0 additions & 4 deletions packages/components-css/form-field/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,3 @@
margin-block-end: var(--rhc-space-100, 0.5rem);
margin-block-start: var(--rhc-space-100, 0.5rem);
}

.rhc-form-field-checkbox {
align-items: center;
}
13 changes: 0 additions & 13 deletions packages/components-react/src/Checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,4 @@ describe('Checkbox', () => {
fireEvent.click(checkbox);
expect(handleChange).toHaveBeenCalledTimes(1);
});

test('uses provided id', () => {
render(<Checkbox id="custom-id" />);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveAttribute('id', 'custom-id');
});

test('generates id when not provided', () => {
render(<Checkbox />);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveAttribute('id');
expect(checkbox.id).not.toBe('');
});
});
29 changes: 1 addition & 28 deletions packages/components-react/src/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
import { CheckboxProps, Checkbox as UtrechtCheckbox } from '@utrecht/component-library-react';
import clsx from 'clsx';
import { ForwardedRef, forwardRef, PropsWithChildren, useId } from 'react';

export { type CheckboxProps };

export const Checkbox = forwardRef(
(
{ id, className, name, value, invalid, ...restProps }: PropsWithChildren<CheckboxProps>,
ref: ForwardedRef<HTMLInputElement>,
) => {
const checkboxId = id || useId();

return (
<UtrechtCheckbox
className={clsx('utrecht-checkbox', className)}
id={checkboxId}
invalid={invalid}
name={name}
ref={ref}
value={value}
{...restProps}
/>
);
},
);

Checkbox.displayName = 'Checkbox';
export { Checkbox, type CheckboxProps } from '@utrecht/component-library-react';
20 changes: 10 additions & 10 deletions packages/components-react/src/FormFieldCheckboxGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { FormFieldCheckbox } from './FormFieldCheckbox';
import { FormFieldCheckboxGroup } from './FormFieldCheckboxGroup';
import { FormFieldCheckboxOption } from './FormFieldCheckboxOption';

describe('FormFieldCheckboxGroup', () => {
it('renders successfully with required props', () => {
render(
<FormFieldCheckboxGroup label="Test Checkbox Group">
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckbox label="Option 2" />
<FormFieldCheckboxOption label="Option 1" />
<FormFieldCheckboxOption label="Option 2" />
</FormFieldCheckboxGroup>,
);
expect(screen.getByText('Test Checkbox Group')).toBeInTheDocument();
Expand All @@ -19,7 +19,7 @@ describe('FormFieldCheckboxGroup', () => {
it('renders with description', () => {
render(
<FormFieldCheckboxGroup description="This is a description" label="Test Checkbox Group">
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckboxOption label="Option 1" />
</FormFieldCheckboxGroup>,
);
expect(screen.getByText('This is a description')).toBeInTheDocument();
Expand All @@ -28,7 +28,7 @@ describe('FormFieldCheckboxGroup', () => {
it('renders with error message when invalid', () => {
render(
<FormFieldCheckboxGroup invalid errorMessage="This field is required" label="Test Checkbox Group">
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckboxOption label="Option 1" />
</FormFieldCheckboxGroup>,
);
expect(screen.getByText('This field is required')).toBeInTheDocument();
Expand All @@ -37,8 +37,8 @@ describe('FormFieldCheckboxGroup', () => {
it('applies the correct role when there are multiple checkboxes', () => {
const { container } = render(
<FormFieldCheckboxGroup label="Test Checkbox Group">
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckbox label="Option 2" />
<FormFieldCheckboxOption label="Option 1" />
<FormFieldCheckboxOption label="Option 2" />
</FormFieldCheckboxGroup>,
);
expect(container.querySelector('[role="group"]')).toBeInTheDocument();
Expand All @@ -47,7 +47,7 @@ describe('FormFieldCheckboxGroup', () => {
it('does not apply role when there is only one checkbox', () => {
const { container } = render(
<FormFieldCheckboxGroup label="Test Checkbox Group">
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckboxOption label="Option 1" />
</FormFieldCheckboxGroup>,
);
expect(container.querySelector('[role="group"]')).not.toBeInTheDocument();
Expand All @@ -56,7 +56,7 @@ describe('FormFieldCheckboxGroup', () => {
it('applies dir attribute when provided', () => {
const { container } = render(
<FormFieldCheckboxGroup dir="rtl" label="Test Checkbox Group">
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckboxOption label="Option 1" />
</FormFieldCheckboxGroup>,
);
expect(container.firstChild).toHaveAttribute('dir', 'rtl');
Expand All @@ -66,7 +66,7 @@ describe('FormFieldCheckboxGroup', () => {
const ref = jest.fn();
render(
<FormFieldCheckboxGroup label="Test Checkbox Group" ref={ref}>
<FormFieldCheckbox label="Option 1" />
<FormFieldCheckboxOption label="Option 1" />
</FormFieldCheckboxGroup>,
);
expect(ref).toHaveBeenCalled();
Expand Down
33 changes: 16 additions & 17 deletions packages/components-react/src/FormFieldCheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { FormField, FormFieldDescription, FormFieldProps, FormLabel } from '@utrecht/component-library-react';
import clsx from 'clsx';
import { Children, ForwardedRef, forwardRef, PropsWithChildren, ReactNode, useId } from 'react';
import { CheckboxGroup } from './CheckboxGroup';
import { FormFieldErrorMessage } from './FormFieldErrorMessage';

export interface FormFieldCheckboxGroupProps extends FormFieldProps {
errorMessage?: string;
status?: ReactNode;
description?: ReactNode;
input?: ReactNode;
label?: ReactNode;
inputRef?: ForwardedRef<HTMLDivElement>;
}
const hasManyChildren = (children: ReactNode) => {
return Children.count(children) > 1;
Expand Down Expand Up @@ -37,9 +36,9 @@ export const FormFieldCheckboxGroup = forwardRef(
<FormField
dir={dir}
invalid={invalid}
ref={ref}
type={hasManyChildren(children) ? 'group' : undefined}
{...restProps}
ref={ref}
>
<div className="utrecht-form-field__label">
<FormLabel htmlFor={id}>{label}</FormLabel>
Expand All @@ -54,20 +53,20 @@ export const FormFieldCheckboxGroup = forwardRef(
{errorMessage}
</FormFieldErrorMessage>
)}
<div
className="utrecht-form-field__input"
dir={dir}
id={id}
role={hasManyChildren(children) ? 'group' : undefined}
aria-describedby={
clsx({
[descriptionId]: description,
[errorMessageId]: invalid,
[statusId]: status,
}) || undefined
}
>
{children}
<div className="utrecht-form-field__input">
<CheckboxGroup
dir={dir}
id={id}
role={hasManyChildren(children) ? 'group' : undefined}
aria-describedby={
clsx({
[descriptionId]: description,
[errorMessageId]: invalid && errorMessage,
}) || undefined
}
>
{children}
</CheckboxGroup>
</div>
{status && (
<div className="utrecht-form-field__status" id={statusId}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
import { fireEvent, render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { FormFieldCheckbox } from './FormFieldCheckbox';
import { FormFieldCheckboxOption } from './FormFieldCheckboxOption';

describe('FormFieldCheckbox', () => {
describe('FormFieldCheckboxOption', () => {
it('renders successfully with required props', () => {
render(<FormFieldCheckbox label="Test Checkbox" />);
render(<FormFieldCheckboxOption label="Test Checkbox" />);
expect(screen.getByLabelText('Test Checkbox')).toBeInTheDocument();
});

it('renders with description', () => {
render(<FormFieldCheckbox description="This is a description" label="Test Checkbox" />);
render(<FormFieldCheckboxOption description="This is a description" label="Test Checkbox" />);
expect(screen.getByText('This is a description')).toBeInTheDocument();
});

it('renders with error message when invalid', () => {
render(<FormFieldCheckbox invalid errorMessage="This field is required" label="Test Checkbox" />);
render(<FormFieldCheckboxOption invalid errorMessage="This field is required" label="Test Checkbox" />);
expect(screen.getByText('This field is required')).toBeInTheDocument();
});

it('disables the checkbox when disabled prop is true', () => {
render(<FormFieldCheckbox disabled label="Test Checkbox" />);
render(<FormFieldCheckboxOption disabled label="Test Checkbox" />);
expect(screen.getByLabelText('Test Checkbox')).toBeDisabled();
});

it('calls onChange when checkbox is clicked', () => {
const handleChange = jest.fn();
render(<FormFieldCheckbox label="Test Checkbox" onChange={handleChange} />);
render(<FormFieldCheckboxOption label="Test Checkbox" onChange={handleChange} />);
fireEvent.click(screen.getByLabelText('Test Checkbox'));
expect(handleChange).toHaveBeenCalledTimes(1);
});

it('applies the correct value to the checkbox', () => {
render(<FormFieldCheckbox label="Test Checkbox" value="test-value" />);
render(<FormFieldCheckboxOption label="Test Checkbox" value="test-value" />);
expect(screen.getByLabelText('Test Checkbox')).toHaveAttribute('value', 'test-value');
});

it('sets the name attribute correctly', () => {
render(<FormFieldCheckbox label="Test Checkbox" name="test-name" />);
render(<FormFieldCheckboxOption label="Test Checkbox" name="test-name" />);
expect(screen.getByLabelText('Test Checkbox')).toHaveAttribute('name', 'test-name');
});

it('applies invalid state correctly', () => {
render(<FormFieldCheckbox invalid label="Test Checkbox" />);
render(<FormFieldCheckboxOption invalid label="Test Checkbox" />);
expect(screen.getByLabelText('Test Checkbox')).toHaveAttribute('aria-invalid', 'true');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@ import {
FormLabel,
} from '@utrecht/component-library-react';
import clsx from 'clsx';
import { ForwardedRef, forwardRef, useId } from 'react';
import { ForwardedRef, forwardRef, ReactNode, useId } from 'react';
import { Checkbox } from './Checkbox';
import { FormFieldErrorMessage } from './FormFieldErrorMessage';

export interface FormFieldCheckboxProps
export interface FormFieldCheckboxOptionProps
extends Omit<FormFieldProps, 'onBlur' | 'onFocus' | 'onChange' | 'onInput'>,
Pick<CheckboxProps, 'name' | 'value' | 'disabled' | 'invalid' | 'onInput' | 'onBlur' | 'onFocus' | 'onChange'> {
label: string;
description?: string;
errorMessage?: string;
Pick<
CheckboxProps,
'name' | 'value' | 'disabled' | 'invalid' | 'onInput' | 'onBlur' | 'onFocus' | 'onChange' | 'defaultValue'
> {
label: ReactNode;
description?: ReactNode;
errorMessage?: ReactNode;
inputRef?: ForwardedRef<HTMLInputElement>;
status?: string;
}

export const FormFieldCheckbox = forwardRef(
export const FormFieldCheckboxOption = forwardRef(
(
{
label,
Expand All @@ -36,8 +39,9 @@ export const FormFieldCheckbox = forwardRef(
onBlur,
onFocus,
onChange,
defaultValue,
...restProps
}: FormFieldCheckboxProps,
}: FormFieldCheckboxOptionProps,
ref: ForwardedRef<HTMLDivElement>,
) => {
const id = useId();
Expand All @@ -46,23 +50,9 @@ export const FormFieldCheckbox = forwardRef(
const errorMessageId = useId();
return (
<FormField className="rhc-form-field-checkbox" invalid={invalid} ref={ref} type="checkbox" {...restProps}>
<div className="utrecht-form-field__label">
<FormLabel className="rhc-form-label--checkbox" htmlFor={id}>
{label}
</FormLabel>
</div>
{description && (
<FormFieldDescription className="utrecht-form-field__description" id={descriptionId}>
{description}
</FormFieldDescription>
)}
{invalid && errorMessage && (
<FormFieldErrorMessage className="utrecht-form-field__error-message" id={errorMessageId}>
{errorMessage}
</FormFieldErrorMessage>
)}
<div className="utrecht-form-field__input">
<Checkbox
defaultValue={defaultValue}
disabled={disabled}
id={id}
invalid={invalid}
Expand All @@ -72,8 +62,7 @@ export const FormFieldCheckbox = forwardRef(
aria-describedby={
clsx({
[descriptionId]: description,
[errorMessageId]: invalid,
[statusId]: status,
[errorMessageId]: invalid && errorMessage,
}) || undefined
}
onBlur={onBlur}
Expand All @@ -82,6 +71,19 @@ export const FormFieldCheckbox = forwardRef(
onInput={onInput}
/>
</div>
<div className="utrecht-form-field__label rhc-form-label--checkbox">
<FormLabel htmlFor={id}>{label}</FormLabel>
</div>
{description && (
<FormFieldDescription className="utrecht-form-field__description" id={descriptionId}>
{description}
</FormFieldDescription>
)}
{invalid && errorMessage && (
<FormFieldErrorMessage className="utrecht-form-field__error-message" id={errorMessageId}>
{errorMessage}
</FormFieldErrorMessage>
)}
{status && (
<div className="utrecht-form-field__status" id={statusId}>
{status}
Expand All @@ -92,4 +94,4 @@ export const FormFieldCheckbox = forwardRef(
},
);

FormFieldCheckbox.displayName = 'FormFieldCheckbox';
FormFieldCheckboxOption.displayName = 'FormFieldCheckboxOption';
Loading