Skip to content
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
11 changes: 3 additions & 8 deletions packages/assets/src/scss/inputs/_checkbox.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@use '../functions' as *;
@use '../variables' as *;
@use '../mixins/choice-inputs' as *;

@mixin checkbox-active-state {
border-color: $color-primary-80;
Expand All @@ -26,15 +27,9 @@

.ids-checkbox {
.ids-input--checkbox {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: calculateRem(16px);
height: calculateRem(16px);
@include choice-input-base;

border-radius: calculateRem(2px);
padding: 0;
margin: 0;
cursor: pointer;
position: relative;

&::after {
Expand Down
24 changes: 24 additions & 0 deletions packages/assets/src/scss/inputs/_radio-button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@use '../functions' as *;
@use '../variables' as *;
@use '../mixins/choice-inputs' as *;

.ids-radio-button {
.ids-input--radio {
@include choice-input-base;

border-radius: 50%;

&:checked {
border: calculateRem(5px) solid $color-primary-80;

&:disabled {
border-color: $color-primary-60;
}

&.ids-input--error {
background-color: $color-error-20;
border-color: $color-error-80;
}
}
}
}
13 changes: 13 additions & 0 deletions packages/assets/src/scss/mixins/_choice-inputs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '../functions' as *;
@use '../variables' as *;

@mixin choice-input-base {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: calculateRem(16px);
height: calculateRem(16px);
padding: 0;
margin: 0;
cursor: pointer;
}
1 change: 1 addition & 0 deletions packages/assets/src/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

@use 'inputs/checkbox';
@use 'inputs/input-text';
@use 'inputs/radio-button';
@use 'inputs/three-state-checkbox';

@use 'ui/clear-btn';
4 changes: 2 additions & 2 deletions packages/components/src/inputs/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import BaseCheckbox from '@ids-internal/partials/BaseCheckbox';
import BaseChoiceInput from '@ids-internal/partials/BaseChoiceInput';
import { createCssClassNames } from '@ibexa/ids-core/helpers/cssClassNames';
import withStateChecked from '@ids-internal/hoc/withStateChecked';

Expand All @@ -12,7 +12,7 @@ const Checkbox = ({ className = '', ...restProps }: CheckboxProps) => {
[className]: true,
});

return <BaseCheckbox {...restProps} className={checkboxClassName} />;
return <BaseChoiceInput {...restProps} className={checkboxClassName} type="checkbox" />;
};

export default Checkbox;
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/inputs/Checkbox/Checkbox.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { BaseCheckboxProps } from '@ids-internal/partials/BaseCheckbox';
import { BaseChoiceInputProps } from '@ids-internal/partials/BaseChoiceInput';

export type CheckboxProps = BaseCheckboxProps;
export type CheckboxProps = Omit<BaseChoiceInputProps, 'type'>;
93 changes: 93 additions & 0 deletions packages/components/src/inputs/RadioButton/RadioButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';

import type { Meta, StoryObj } from '@storybook/react';
import { action } from 'storybook/actions';

import { RadioButtonStateful } from './RadioButton';

const meta: Meta<typeof RadioButtonStateful> = {
component: RadioButtonStateful,
parameters: {
layout: 'centered',
},
tags: ['autodocs', 'foundation', 'inputs'],
argTypes: {
className: {
control: 'text',
},
title: {
control: 'text',
},
checked: {
control: 'boolean',
},
},
args: {
onBlur: action('on-blur'),
onChange: action('on-change'),
onFocus: action('on-focus'),
onInput: action('on-input'),
},
decorators: [
(Story) => {
return (
<form name="default-form">
<Story />
</form>
);
},
],
};

export default meta;

type Story = StoryObj<typeof RadioButtonStateful>;

export const Empty: Story = {
name: 'Empty',
args: {
name: 'default-input',
},
};

export const EmptyDisabled: Story = {
name: 'Empty (Disabled)',
args: {
name: 'default-input',
disabled: true,
},
};

export const EmptyError: Story = {
name: 'Empty (Error)',
args: {
name: 'default-input',
error: true,
},
};

export const Checked: Story = {
name: 'Checked',
args: {
name: 'default-input',
checked: true,
},
};

export const CheckedDisabled: Story = {
name: 'Checked (Disabled)',
args: {
name: 'default-input',
disabled: true,
checked: true,
},
};

export const CheckedError: Story = {
name: 'Checked (Error)',
args: {
name: 'default-input',
error: true,
checked: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from 'storybook/test';

import { RadioButtonStateful } from './RadioButton';

const meta: Meta<typeof RadioButtonStateful> = {
component: RadioButtonStateful,
parameters: {
layout: 'centered',
},
tags: ['!dev'],
args: {
name: 'default-input',
onBlur: fn(),
onChange: fn(),
onFocus: fn(),
onInput: fn(),
},
};

export default meta;

type Story = StoryObj<typeof RadioButtonStateful>;

export const Default: Story = {
name: 'Default',
play: async ({ canvasElement, step, args }) => {
const canvas = within(canvasElement);
const input = canvas.getByRole('radio');

await step('Radio Button handles focus event', async () => {
await expect(args.onFocus).not.toHaveBeenCalled();

await userEvent.click(input);

await expect(args.onFocus).toHaveBeenCalledOnce();

await userEvent.click(input);

await expect(args.onFocus).toHaveBeenCalledOnce();
await expect(args.onChange).toHaveBeenCalledOnce();
await expect(args.onInput).toHaveBeenCalledOnce();
});

await step('Radio Button handles blur event', async () => {
await expect(args.onBlur).not.toHaveBeenCalled();

await userEvent.click(canvasElement);

await expect(args.onBlur).toHaveBeenCalledOnce();
});
},
};
20 changes: 20 additions & 0 deletions packages/components/src/inputs/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

import BaseChoiceInput from '@ids-internal/partials/BaseChoiceInput';
import { createCssClassNames } from '@ibexa/ids-core/helpers/cssClassNames';
import withStateChecked from '@ids-internal/hoc/withStateChecked';

import { RadioButtonProps } from './RadioButton.types';

const RadioButton = ({ className = '', ...restProps }: RadioButtonProps) => {
const radioButtonClassName = createCssClassNames({
'ids-radio-button': true,
[className]: true,
});

return <BaseChoiceInput {...restProps} className={radioButtonClassName} type="radio" />;
};

export default RadioButton;

export const RadioButtonStateful = withStateChecked(RadioButton);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BaseChoiceInputProps } from '@ids-internal/partials/BaseChoiceInput';

export type RadioButtonProps = Omit<BaseChoiceInputProps, 'type'>;
6 changes: 6 additions & 0 deletions packages/components/src/inputs/RadioButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import RadioButton, { RadioButtonStateful } from './RadioButton';
import { RadioButtonProps } from './RadioButton.types';

export default RadioButton;
export { RadioButtonStateful };
export type { RadioButtonProps };
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import BaseCheckbox from '@ids-internal/partials/BaseCheckbox';
import BaseChoiceInput from '@ids-internal/partials/BaseChoiceInput';
import { createCssClassNames } from '@ibexa/ids-core/helpers/cssClassNames';
import withStateChecked from '@ids-internal/hoc/withStateChecked';

Expand All @@ -16,14 +16,15 @@ const ThreeStateCheckbox = ({ className = '', indeterminate = false, ...restProp
});

return (
<BaseCheckbox
<BaseChoiceInput
className={checkboxClassName}
inputClassName={inputClassName}
ref={(node) => {
if (node) {
node.indeterminate = indeterminate; // eslint-disable-line no-param-reassign
}
}}
type="checkbox"
{...restProps}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BaseCheckboxProps } from '@ids-internal/partials/BaseCheckbox';
import { BaseChoiceInputProps } from '@ids-internal/partials/BaseChoiceInput';

interface ThreeStateCheckboxIsIndeterminateProps extends BaseCheckboxProps {
interface ThreeStateCheckboxIsIndeterminateProps extends Omit<BaseChoiceInputProps, 'type'> {
indeterminate: true;
checked: false;
}

interface ThreeStateCheckboxIsNotIndeterminateProps extends BaseCheckboxProps {
interface ThreeStateCheckboxIsNotIndeterminateProps extends Omit<BaseChoiceInputProps, 'type'> {
indeterminate: false;
checked: boolean;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React from 'react';

import BaseInput from '@ids-internal/partials/BaseInput';

import { BaseCheckboxProps } from './BaseCheckbox.types';
import { BaseChoiceInputProps } from './BaseChoiceInput.types';

const Checkbox = ({
const BaseChoiceInput = ({
name,
type,
onBlur = () => undefined,
onChange = () => undefined,
onFocus = () => undefined,
Expand All @@ -21,7 +22,7 @@ const Checkbox = ({
required = false,
title = '',
value = '',
}: BaseCheckboxProps) => {
}: BaseChoiceInputProps) => {
const componentOnBlur = (event: React.FocusEvent<HTMLInputElement>) => {
onBlur(event);
};
Expand Down Expand Up @@ -54,11 +55,11 @@ const Checkbox = ({
ref={ref}
required={required}
title={title}
type="checkbox"
type={type}
value={value}
/>
</div>
);
};

export default Checkbox;
export default BaseChoiceInput;
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Ref } from 'react';

import { BaseComponentAriaAttributes } from '@ids-types/general';
import { BaseInputTypesType } from '../BaseInput/BaseInput.types';

export interface BaseCheckboxProps extends BaseComponentAriaAttributes {
export const INPUT_TYPE_VALUES = ['checkbox', 'radio'] as const satisfies BaseInputTypesType[];

export type InputChoiceTypesType = (typeof INPUT_TYPE_VALUES)[number];

export interface BaseChoiceInputProps extends BaseComponentAriaAttributes {
name: string;
type: InputChoiceTypesType;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
onChange?: (value: boolean, event?: React.ChangeEvent<HTMLInputElement>) => void;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import BaseChoiceInput from './BaseChoiceInput';
import { BaseChoiceInputProps } from './BaseChoiceInput.types';

export default BaseChoiceInput;
export type { BaseChoiceInputProps };