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
37 changes: 2 additions & 35 deletions packages/assets/src/scss/_input.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use 'sass:map';
@use 'functions' as *;
@use 'variables' as *;
@use 'mixins/icons' as mixins;
@use 'mixins/inputs' as inputs-mixins;

$input-sizes: (
'small': (
Expand All @@ -15,45 +15,12 @@ $input-sizes: (
);

.ids-input {
border-radius: $border-radius-medium;
background: var(--ids-input-default-bg-color, #{$color-neutral-10});
border: calculateRem(1px) solid var(--ids-input-default-border-color, #{$color-neutral-80});
color: var(--ids-input-default-text-color, #{$color-neutral-240});
outline: 0;
@include inputs-mixins.input-base;

@each $name, $properties in $input-sizes {
&--#{$name} {
font-size: map.get($properties, 'font-size');
padding: map.get($properties, 'padding');
}
}

&.ids-input--error {
background: var(--ids-input-error-bg-color, #{$color-error-20});
border-color: var(--ids-input-error-border-color, #{$color-error-80});
color: var(--ids-input-error-text-color, #{$color-error-90});
}

&.ids-input--disabled,
&.disabled,
&[disabled],
&:disabled {
color: var(--ids-input-disabled-text-color, #{$color-neutral-150});
background: var(--ids-input-disabled-bg-color, #{$color-neutral-40});
border-color: var(--ids-input-disabled-border-color, #{$color-neutral-80});
pointer-events: none;
}

&:hover {
border-color: var(--ids-input-hover-border-color, #{$color-primary-80});
}

&:active {
border-color: var(--ids-input-active-border-color, #{$color-primary-90});
}

&:focus {
border-color: var(--ids-input-focus-border-color, #{$color-primary-80});
box-shadow: var(--ids-input-focus-box-shadow, #{$box-shadow-focus-primary});
}
}
27 changes: 27 additions & 0 deletions packages/assets/src/scss/inputs/_alt-radio.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '../functions' as *;
@use '../variables' as *;
@use '../mixins/inputs' as inputs-mixins;
@use '../mixins/utils' as utils-mixins;

.ids-alt-radio {
&__source {
@include utils-mixins.hidden;
}

&__tile {
@include inputs-mixins.input-base;

& {
cursor: pointer;
padding: calculateRem(8px) calculateRem(16px);
}

&--checked {
background-color: var(--ids-input-checked-bg-color, #{$color-neutral-50});
}

&--focused {
@include inputs-mixins.input-focus;
}
}
}
43 changes: 43 additions & 0 deletions packages/assets/src/scss/mixins/_inputs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@use '../functions' as *;
@use '../variables' as *;

@mixin input-focus {
border-color: var(--ids-input-focus-border-color, #{$color-primary-80});
box-shadow: var(--ids-input-focus-box-shadow, #{$box-shadow-focus-primary});
}

@mixin input-base {
border-radius: $border-radius-medium;
background: var(--ids-input-default-bg-color, #{$color-neutral-10});
border: calculateRem(1px) solid var(--ids-input-default-border-color, #{$color-neutral-80});
color: var(--ids-input-default-text-color, #{$color-neutral-240});
outline: 0;

&--error {
background: var(--ids-input-error-bg-color, #{$color-error-20});
border-color: var(--ids-input-error-border-color, #{$color-error-80});
color: var(--ids-input-error-text-color, #{$color-error-90});
}

&--disabled,
&.disabled,
&[disabled],
&:disabled {
color: var(--ids-input-disabled-text-color, #{$color-neutral-150});
background: var(--ids-input-disabled-bg-color, #{$color-neutral-40});
border-color: var(--ids-input-disabled-border-color, #{$color-neutral-80});
pointer-events: none;
}

&:hover {
border-color: var(--ids-input-hover-border-color, #{$color-primary-80});
}

&:active {
border-color: var(--ids-input-active-border-color, #{$color-primary-90});
}

&:focus {
@include input-focus;
}
}
8 changes: 8 additions & 0 deletions packages/assets/src/scss/mixins/_utils.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@use '../functions' as *;

@mixin hidden {
width: 0;
height: 0;
overflow: hidden;
opacity: 0;
}
1 change: 1 addition & 0 deletions packages/assets/src/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@use 'inputs-list';
@use 'label';

@use 'inputs/alt-radio';
@use 'inputs/checkbox';
@use 'inputs/input-text';
@use 'inputs/radio-button';
Expand Down
87 changes: 87 additions & 0 deletions packages/components/src/inputs/AltRadio/AltRadio.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';

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

import { AltRadioStateful } from './AltRadio';

const meta: Meta<typeof AltRadioStateful> = {
component: AltRadioStateful,
parameters: {
layout: 'centered',
},
tags: ['autodocs', 'foundation', 'inputs'],
argTypes: {
className: {
control: 'text',
},
tileClassName: {
control: 'text',
},
title: {
control: 'text',
},
},
args: {
label: '1:1',
name: 'default-input',
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 AltRadioStateful>;

export const Empty: Story = {
name: 'Empty',
};

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

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

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

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

export const CheckedError: Story = {
name: 'Checked (Error)',
args: {
error: true,
checked: true,
},
};
50 changes: 50 additions & 0 deletions packages/components/src/inputs/AltRadio/AltRadio.test.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from 'storybook/test';

import { AltRadioStateful } from './AltRadio';

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

export default meta;

type Story = StoryObj<typeof AltRadioStateful>;

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

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

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();
});
},
};
70 changes: 70 additions & 0 deletions packages/components/src/inputs/AltRadio/AltRadio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useRef, useState } 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 { AltRadioProps } from './AltRadio.types';

const AltRadio = ({ className = '', label, tileClassName = '', title = '', ...inputProps }: AltRadioProps) => {
const { checked = false, disabled = false, error = false, onBlur, onChange, onFocus, onInput } = inputProps;
const inputRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
const altRadioClassName = createCssClassNames({
'ids-alt-radio': true,
[className]: !!className,
});
const altRadioTileClassName = createCssClassNames({
'ids-alt-radio__tile': true,
'ids-alt-radio__tile--checked': checked,
'ids-alt-radio__tile--disabled': disabled,
'ids-alt-radio__tile--error': error,
'ids-alt-radio__tile--focused': isFocused,
[tileClassName]: !!tileClassName,
});
const onTileClick = () => {
inputRef.current?.focus();

if (!checked) {
onChange?.(true);
onInput?.(true);
}
};
const onInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(true);
onFocus?.(event);
};
const onInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(false);
onBlur?.(event);
};

return (
<div className={altRadioClassName} title={title}>
<div className="ids-alt-radio__source">
<BaseChoiceInput
{...inputProps}
onBlur={onInputBlur}
onFocus={onInputFocus}
ref={(node) => {
inputRef.current = node;

if (typeof inputProps.ref === 'function') {
inputProps.ref(node);
} else if (inputProps.ref) {
inputProps.ref.current = node; // eslint-disable-line no-param-reassign
}
}}
type="radio"
/>
</div>
<div className={altRadioTileClassName} onClick={onTileClick} role="button">
{label}
</div>
</div>
);
};

export default AltRadio;

export const AltRadioStateful = withStateChecked<AltRadioProps>(AltRadio);
8 changes: 8 additions & 0 deletions packages/components/src/inputs/AltRadio/AltRadio.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react';

import { BaseChoiceInputProps } from '@ids-internal/partials/BaseChoiceInput';

export interface AltRadioProps extends Omit<BaseChoiceInputProps, 'type'> {
label: ReactNode;
tileClassName?: string;
}
6 changes: 6 additions & 0 deletions packages/components/src/inputs/AltRadio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import AltRadio, { AltRadioStateful } from './AltRadio';
import { AltRadioProps } from './AltRadio.types';

export default AltRadio;
export { AltRadioStateful };
export type { AltRadioProps };