summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/atoms/forms/select.stories.tsx13
-rw-r--r--src/components/atoms/forms/select.test.tsx30
-rw-r--r--src/components/atoms/forms/select.tsx15
-rw-r--r--src/components/molecules/buttons/help-button.stories.tsx28
-rw-r--r--src/components/molecules/buttons/help-button.tsx15
-rw-r--r--src/components/molecules/forms/select-with-tooltip.module.scss52
-rw-r--r--src/components/molecules/forms/select-with-tooltip.stories.tsx158
-rw-r--r--src/components/molecules/forms/select-with-tooltip.test.tsx32
-rw-r--r--src/components/molecules/forms/select-with-tooltip.tsx58
9 files changed, 395 insertions, 6 deletions
diff --git a/src/components/atoms/forms/select.stories.tsx b/src/components/atoms/forms/select.stories.tsx
index ed487f8..c7bb253 100644
--- a/src/components/atoms/forms/select.stories.tsx
+++ b/src/components/atoms/forms/select.stories.tsx
@@ -11,6 +11,19 @@ export default {
title: 'Atoms/Forms',
component: SelectComponent,
argTypes: {
+ classes: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classes',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
disabled: {
control: {
type: 'boolean',
diff --git a/src/components/atoms/forms/select.test.tsx b/src/components/atoms/forms/select.test.tsx
new file mode 100644
index 0000000..22efb86
--- /dev/null
+++ b/src/components/atoms/forms/select.test.tsx
@@ -0,0 +1,30 @@
+import { render, screen } from '@test-utils';
+import Select from './select';
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+const selected = selectOptions[0];
+
+describe('Select', () => {
+ it('should correctly set default option', () => {
+ render(
+ <Select
+ id="jest-select"
+ name="jest-select"
+ options={selectOptions}
+ value={selected.value}
+ setValue={() => null}
+ />
+ );
+ expect(screen.getByRole('combobox')).toHaveValue(selected.value);
+ expect(screen.queryByRole('combobox')).not.toHaveValue(
+ selectOptions[1].value
+ );
+ expect(screen.queryByRole('combobox')).not.toHaveValue(
+ selectOptions[2].value
+ );
+ });
+});
diff --git a/src/components/atoms/forms/select.tsx b/src/components/atoms/forms/select.tsx
index e434a82..a42dbda 100644
--- a/src/components/atoms/forms/select.tsx
+++ b/src/components/atoms/forms/select.tsx
@@ -1,7 +1,7 @@
import { ChangeEvent, FC, SetStateAction } from 'react';
import styles from './forms.module.scss';
-type SelectOptions = {
+export type SelectOptions = {
id: string;
name: string;
value: string;
@@ -9,6 +9,10 @@ type SelectOptions = {
export type SelectProps = {
/**
+ * Set additional classes.
+ */
+ classes?: string;
+ /**
* Field state. Either enabled (false) or disabled (true).
*/
disabled?: boolean;
@@ -43,7 +47,12 @@ export type SelectProps = {
*
* Render a HTML select element.
*/
-const Select: FC<SelectProps> = ({ options, setValue, ...props }) => {
+const Select: FC<SelectProps> = ({
+ classes = '',
+ options,
+ setValue,
+ ...props
+}) => {
/**
* Update select value when an option is selected.
* @param e - The option change event.
@@ -65,7 +74,7 @@ const Select: FC<SelectProps> = ({ options, setValue, ...props }) => {
return (
<select
- className={`${styles.field} ${styles['field--select']}`}
+ className={`${styles.field} ${styles['field--select']} ${classes}`}
onChange={updateValue}
{...props}
>
diff --git a/src/components/molecules/buttons/help-button.stories.tsx b/src/components/molecules/buttons/help-button.stories.tsx
index 2b04a9c..7ed953e 100644
--- a/src/components/molecules/buttons/help-button.stories.tsx
+++ b/src/components/molecules/buttons/help-button.stories.tsx
@@ -5,6 +5,34 @@ import HelpButtonComponent from './help-button';
export default {
title: 'Molecules/Buttons',
component: HelpButtonComponent,
+ argTypes: {
+ classes: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classes to the button.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ onClick: {
+ control: {
+ type: null,
+ },
+ description: 'A callback function to handle click on button.',
+ table: {
+ category: 'Events',
+ },
+ type: {
+ name: 'function',
+ required: false,
+ },
+ },
+ },
} as ComponentMeta<typeof HelpButtonComponent>;
const Template: ComponentStory<typeof HelpButtonComponent> = (args) => (
diff --git a/src/components/molecules/buttons/help-button.tsx b/src/components/molecules/buttons/help-button.tsx
index 4945bb4..d933829 100644
--- a/src/components/molecules/buttons/help-button.tsx
+++ b/src/components/molecules/buttons/help-button.tsx
@@ -3,14 +3,19 @@ import { FC } from 'react';
import { useIntl } from 'react-intl';
import styles from './help-button.module.scss';
-export type HelpButtonProps = Pick<ButtonProps, 'onClick'>;
+export type HelpButtonProps = Pick<ButtonProps, 'onClick'> & {
+ /**
+ * Set additional classes to the button.
+ */
+ classes?: string;
+};
/**
* HelpButton component
*
* Render a button with an interrogation mark icon.
*/
-const HelpButton: FC<HelpButtonProps> = ({ onClick }) => {
+const HelpButton: FC<HelpButtonProps> = ({ classes = '', onClick }) => {
const intl = useIntl();
const text = intl.formatMessage({
defaultMessage: 'Help',
@@ -19,7 +24,11 @@ const HelpButton: FC<HelpButtonProps> = ({ onClick }) => {
});
return (
- <Button shape="circle" additionalClasses={styles.btn} onClick={onClick}>
+ <Button
+ shape="circle"
+ additionalClasses={`${styles.btn} ${classes}`}
+ onClick={onClick}
+ >
<span className="screen-reader-text">{text}</span>
<span className={styles.icon}>?</span>
</Button>
diff --git a/src/components/molecules/forms/select-with-tooltip.module.scss b/src/components/molecules/forms/select-with-tooltip.module.scss
new file mode 100644
index 0000000..1f91f74
--- /dev/null
+++ b/src/components/molecules/forms/select-with-tooltip.module.scss
@@ -0,0 +1,52 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+.wrapper {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ gap: var(--spacing-xs);
+ position: relative;
+}
+
+.label {
+ margin-right: auto;
+}
+
+.select {
+ width: auto;
+
+ @include mix.pointer("fine") {
+ padding: fun.convert-px(3) var(--spacing-xs);
+ }
+}
+
+.btn {
+ &--activated {
+ background: var(--color-primary);
+
+ * {
+ color: var(--color-fg-inverted);
+ }
+ }
+}
+
+.tooltip {
+ position: absolute;
+ top: calc(100% + var(--spacing-xs));
+ right: 0;
+ transform-origin: top right;
+ transition: all 0.75s ease-in-out 0s;
+
+ &--hidden {
+ opacity: 0;
+ visibility: hidden;
+ transform: scale(0);
+ }
+
+ &--visible {
+ opacity: 1;
+ visibility: visible;
+ transform: scale(1);
+ }
+}
diff --git a/src/components/molecules/forms/select-with-tooltip.stories.tsx b/src/components/molecules/forms/select-with-tooltip.stories.tsx
new file mode 100644
index 0000000..d2d36fa
--- /dev/null
+++ b/src/components/molecules/forms/select-with-tooltip.stories.tsx
@@ -0,0 +1,158 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
+import { IntlProvider } from 'react-intl';
+import SelectWithTooltipComponent from './select-with-tooltip';
+
+export default {
+ title: 'Molecules/Forms',
+ component: SelectWithTooltipComponent,
+ argTypes: {
+ content: {
+ control: {
+ type: 'text',
+ },
+ description: 'The tooltip body.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ title: {
+ control: {
+ type: 'text',
+ },
+ description: 'The tooltip title',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ label: {
+ control: {
+ type: 'text',
+ },
+ description: 'The select label.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ disabled: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Field state: either enabled or disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ options: {
+ control: {
+ type: null,
+ },
+ description: 'Select options.',
+ type: {
+ name: 'array',
+ required: true,
+ value: {
+ name: 'string',
+ },
+ },
+ },
+ required: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Determine if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ setValue: {
+ control: {
+ type: null,
+ },
+ description: 'Callback function to set field value.',
+ table: {
+ category: 'Events',
+ },
+ type: {
+ name: 'function',
+ required: true,
+ },
+ },
+ value: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta<typeof SelectWithTooltipComponent>;
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+
+const Template: ComponentStory<typeof SelectWithTooltipComponent> = ({
+ value: _value,
+ setValue: _setValue,
+ ...args
+}) => {
+ const [selected, setSelected] = useState<string>('option1');
+ return (
+ <IntlProvider locale="en">
+ <SelectWithTooltipComponent
+ value={selected}
+ setValue={setSelected}
+ {...args}
+ />
+ </IntlProvider>
+ );
+};
+
+export const SelectWithTooltip = Template.bind({});
+SelectWithTooltip.args = {
+ content: 'Illo voluptatibus quia minima placeat sit nostrum excepturi.',
+ title: 'Possimus quidem dolor',
+ id: 'storybook-select',
+ label: 'Officiis:',
+ name: 'storybook-select',
+ options: selectOptions,
+};
diff --git a/src/components/molecules/forms/select-with-tooltip.test.tsx b/src/components/molecules/forms/select-with-tooltip.test.tsx
new file mode 100644
index 0000000..7a423f5
--- /dev/null
+++ b/src/components/molecules/forms/select-with-tooltip.test.tsx
@@ -0,0 +1,32 @@
+import { render, screen } from '@test-utils';
+import SelectWithTooltip from './select-with-tooltip';
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+const selectLabel = 'Jest select';
+const selectValue = selectOptions[0].value;
+const tooltipTitle = 'Jest tooltip';
+const tooltipContent = 'Nesciunt voluptatibus voluptatem omnis at quia libero.';
+
+describe('SelectWithTooltip', () => {
+ it('renders a select', () => {
+ render(
+ <SelectWithTooltip
+ id="jest-select"
+ name="jest-select"
+ label={selectLabel}
+ options={selectOptions}
+ value={selectValue}
+ setValue={() => null}
+ title={tooltipTitle}
+ content={tooltipContent}
+ />
+ );
+ expect(screen.getByRole('combobox', { name: selectLabel })).toHaveValue(
+ selectValue
+ );
+ });
+});
diff --git a/src/components/molecules/forms/select-with-tooltip.tsx b/src/components/molecules/forms/select-with-tooltip.tsx
new file mode 100644
index 0000000..5e48d62
--- /dev/null
+++ b/src/components/molecules/forms/select-with-tooltip.tsx
@@ -0,0 +1,58 @@
+import Select, { SelectProps } from '@components/atoms/forms/select';
+import { FC, useState } from 'react';
+import HelpButton from '../buttons/help-button';
+import Tooltip, { TooltipProps } from '../modals/tooltip';
+import styles from './select-with-tooltip.module.scss';
+
+export type SelectWithTooltipProps = SelectProps &
+ Pick<TooltipProps, 'title' | 'content'> & {
+ /**
+ * The select label.
+ */
+ label: string;
+ /**
+ * Set additional classes to the tooltip wrapper.
+ */
+ tooltipClasses?: string;
+ };
+
+/**
+ * SelectWithTooltip component
+ *
+ * Render a select with a button to display a tooltip about options.
+ */
+const SelectWithTooltip: FC<SelectWithTooltipProps> = ({
+ title,
+ content,
+ id,
+ label,
+ tooltipClasses = '',
+ ...props
+}) => {
+ const [isTooltipOpened, setIsTooltipOpened] = useState<boolean>(false);
+ const buttonModifier = isTooltipOpened ? styles['btn--activated'] : '';
+ const tooltipModifier = isTooltipOpened
+ ? styles['tooltip--visible']
+ : styles['tooltip--hidden'];
+
+ return (
+ <div className={styles.wrapper}>
+ <label htmlFor={id} className={styles.label}>
+ {label}
+ </label>
+ <Select id={id} {...props} classes={styles.select} />
+ <HelpButton
+ onClick={() => setIsTooltipOpened(!isTooltipOpened)}
+ classes={buttonModifier}
+ />
+ <Tooltip
+ title={title}
+ content={content}
+ icon="?"
+ classes={`${styles.tooltip} ${tooltipModifier} ${tooltipClasses}`}
+ />
+ </div>
+ );
+};
+
+export default SelectWithTooltip;