diff --git a/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx
index c5383c6a3..8e9694cdf 100644
--- a/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx
@@ -5,7 +5,7 @@ import { useChipField } from '../../hooks/useChipField';
import { FieldInputEvent } from './DateFieldInput';
-type ChipFieldInputProps = {
+export type ChipFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx
index 5b0e2689a..06f874dc7 100644
--- a/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx
@@ -6,7 +6,7 @@ import { useDoubleTextChipField } from '../../hooks/useDoubleTextChipField';
import { FieldInputEvent } from './DateFieldInput';
-type DoubleTextChipFieldInputProps = {
+export type DoubleTextChipFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx
index 0adce6ee0..aa7d0d50d 100644
--- a/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx
@@ -6,7 +6,7 @@ import { useDoubleTextField } from '../../hooks/useDoubleTextField';
import { FieldInputEvent } from './DateFieldInput';
-type DoubleTextFieldInputProps = {
+export type DoubleTextFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx
index e52ede464..a43425a1f 100644
--- a/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx
@@ -5,7 +5,7 @@ import { useEmailField } from '../../hooks/useEmailField';
import { FieldInputEvent } from './DateFieldInput';
-type EmailFieldInputProps = {
+export type EmailFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx
index 40cc6c337..53a60474c 100644
--- a/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx
@@ -4,7 +4,7 @@ import { useMoneyField } from '../../hooks/useMoneyField';
export type FieldInputEvent = (persist: () => void) => void;
-type MoneyFieldInputProps = {
+export type MoneyFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx
index a40af62a2..427fb0c88 100644
--- a/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx
@@ -4,7 +4,7 @@ import { useNumberField } from '../../hooks/useNumberField';
export type FieldInputEvent = (persist: () => void) => void;
-type NumberFieldInputProps = {
+export type NumberFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx
index dfd1e42cc..377652616 100644
--- a/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx
@@ -4,7 +4,7 @@ import { usePhoneField } from '../../hooks/usePhoneField';
import { FieldInputEvent } from './DateFieldInput';
-type PhoneFieldInputProps = {
+export type PhoneFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx
index 7884152bb..f8f200785 100644
--- a/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx
@@ -5,7 +5,7 @@ import { useProbabilityField } from '../../hooks/useProbabilityField';
import { FieldInputEvent } from './DateFieldInput';
-type ProbabilityFieldInputProps = {
+export type ProbabilityFieldInputProps = {
onSubmit?: FieldInputEvent;
};
diff --git a/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx
index 4404e1f81..59b6d43a7 100644
--- a/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx
@@ -17,7 +17,7 @@ const StyledRelationPickerContainer = styled.div`
top: -8px;
`;
-type RelationFieldInputProps = {
+export type RelationFieldInputProps = {
onSubmit?: FieldInputEvent;
onCancel?: () => void;
};
diff --git a/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx
index 7473314cc..55d7b3716 100644
--- a/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx
@@ -5,7 +5,7 @@ import { useTextField } from '../../hooks/useTextField';
import { FieldInputEvent } from './DateFieldInput';
-type TextFieldInputProps = {
+export type TextFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx
index 633cd3622..aa7909578 100644
--- a/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx
+++ b/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx
@@ -4,7 +4,7 @@ import { useURLField } from '../../hooks/useURLField';
import { FieldInputEvent } from './DateFieldInput';
-type URLFieldInputProps = {
+export type URLFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/ChipFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/ChipFieldInput.stories.tsx
new file mode 100644
index 000000000..2b78eed28
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/ChipFieldInput.stories.tsx
@@ -0,0 +1,177 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useChipField } from '../../../hooks/useChipField';
+import { ChipFieldInput, ChipFieldInputProps } from '../ChipFieldInput';
+
+const ChipFieldValueSetterEffect = ({ value }: { value: string }) => {
+ const { setContentFieldValue } = useChipField();
+
+ useEffect(() => {
+ setContentFieldValue(value);
+ }, [setContentFieldValue, value]);
+
+ return <>>;
+};
+
+type ChipFieldInputWithContextProps = ChipFieldInputProps & {
+ value: string;
+ entityId?: string;
+};
+
+const ChipFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: ChipFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/ChipFieldInput',
+ component: ChipFieldInputWithContext,
+ args: {
+ value: 'chip',
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ parameters: {
+ clearMocks: true,
+ },
+ decorators: [clearMocksDecorator],
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/DoubleTextChipFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/DoubleTextChipFieldInput.stories.tsx
new file mode 100644
index 000000000..ea28dddeb
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/DoubleTextChipFieldInput.stories.tsx
@@ -0,0 +1,194 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useDoubleTextChipField } from '../../../hooks/useDoubleTextChipField';
+import {
+ DoubleTextChipFieldInput,
+ DoubleTextChipFieldInputProps,
+} from '../DoubleTextChipFieldInput';
+
+const DoubleTextChipFieldValueSetterEffect = ({
+ firstValue,
+ secondValue,
+}: {
+ firstValue: string;
+ secondValue: string;
+}) => {
+ const { setFirstValue, setSecondValue } = useDoubleTextChipField();
+
+ useEffect(() => {
+ setFirstValue(firstValue);
+ setSecondValue(secondValue);
+ }, [firstValue, secondValue, setFirstValue, setSecondValue]);
+
+ return <>>;
+};
+
+type DoubleTextChipFieldInputWithContextProps =
+ DoubleTextChipFieldInputProps & {
+ firstValue: string;
+ secondValue: string;
+ entityId?: string;
+ };
+
+const DoubleTextChipFieldInputWithContext = ({
+ entityId,
+ firstValue,
+ secondValue,
+ onClickOutside,
+ onEnter,
+ onEscape,
+ onTab,
+ onShiftTab,
+}: DoubleTextChipFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/DoubleTextChipFieldInput',
+ component: DoubleTextChipFieldInputWithContext,
+ args: {
+ firstValue: 'first value',
+ secondValue: 'second value',
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ parameters: {
+ clearMocks: true,
+ },
+ decorators: [clearMocksDecorator],
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/DoubleTextFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/DoubleTextFieldInput.stories.tsx
new file mode 100644
index 000000000..41a03cb4e
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/DoubleTextFieldInput.stories.tsx
@@ -0,0 +1,191 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useDoubleTextField } from '../../../hooks/useDoubleTextField';
+import {
+ DoubleTextFieldInput,
+ DoubleTextFieldInputProps,
+} from '../DoubleTextFieldInput';
+
+const DoubleTextFieldValueSetterEffect = ({
+ firstValue,
+ secondValue,
+}: {
+ firstValue: string;
+ secondValue: string;
+}) => {
+ const { setFirstValue, setSecondValue } = useDoubleTextField();
+
+ useEffect(() => {
+ setFirstValue(firstValue);
+ setSecondValue(secondValue);
+ }, [firstValue, secondValue, setFirstValue, setSecondValue]);
+
+ return <>>;
+};
+
+type DoubleTextFieldInputWithContextProps = DoubleTextFieldInputProps & {
+ firstValue: string;
+ secondValue: string;
+ entityId?: string;
+};
+
+const DoubleTextFieldInputWithContext = ({
+ entityId,
+ firstValue,
+ secondValue,
+ onClickOutside,
+ onEnter,
+ onEscape,
+ onTab,
+ onShiftTab,
+}: DoubleTextFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/DoubleTextFieldInput',
+ component: DoubleTextFieldInputWithContext,
+ args: {
+ firstValue: 'first value',
+ secondValue: 'second value',
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = await canvas.findByTestId(
+ 'data-field-input-click-outside-div',
+ );
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx
new file mode 100644
index 000000000..bc427e1ce
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx
@@ -0,0 +1,174 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useEmailField } from '../../../hooks/useEmailField';
+import { EmailFieldInput, EmailFieldInputProps } from '../EmailFieldInput';
+
+const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
+ const { setFieldValue } = useEmailField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type EmailFieldInputWithContextProps = EmailFieldInputProps & {
+ value: string;
+ entityId?: string;
+};
+
+const EmailFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: EmailFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/EmailFieldInput',
+ component: EmailFieldInputWithContext,
+ args: {
+ value: 'username@email.com',
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/MoneyFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/MoneyFieldInput.stories.tsx
new file mode 100644
index 000000000..df5add509
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/MoneyFieldInput.stories.tsx
@@ -0,0 +1,175 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useMoneyField } from '../../../hooks/useMoneyField';
+import { MoneyFieldInput, MoneyFieldInputProps } from '../MoneyFieldInput';
+
+const MoneyFieldValueSetterEffect = ({ value }: { value: number }) => {
+ const { setFieldValue } = useMoneyField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type MoneyFieldInputWithContextProps = MoneyFieldInputProps & {
+ value: number;
+ entityId?: string;
+};
+
+const MoneyFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: MoneyFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/MoneyFieldInput',
+ component: MoneyFieldInputWithContext,
+ args: {
+ value: 1000,
+ isPositive: true,
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx
new file mode 100644
index 000000000..4be6fbe32
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx
@@ -0,0 +1,175 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useNumberField } from '../../../hooks/useNumberField';
+import { NumberFieldInput, NumberFieldInputProps } from '../NumberFieldInput';
+
+const NumberFieldValueSetterEffect = ({ value }: { value: number }) => {
+ const { setFieldValue } = useNumberField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type NumberFieldInputWithContextProps = NumberFieldInputProps & {
+ value: number;
+ entityId?: string;
+};
+
+const NumberFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: NumberFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/NumberFieldInput',
+ component: NumberFieldInputWithContext,
+ args: {
+ value: 1000,
+ isPositive: true,
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx
new file mode 100644
index 000000000..bdadd31c6
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx
@@ -0,0 +1,175 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { usePhoneField } from '../../../hooks/usePhoneField';
+import { PhoneFieldInput, PhoneFieldInputProps } from '../PhoneFieldInput';
+
+const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
+ const { setFieldValue } = usePhoneField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type PhoneFieldInputWithContextProps = PhoneFieldInputProps & {
+ value: string;
+ entityId?: string;
+};
+
+const PhoneFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: PhoneFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/PhoneFieldInput',
+ component: PhoneFieldInputWithContext,
+ args: {
+ value: '+1-12-123-456',
+ isPositive: true,
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/ProbabilityFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/ProbabilityFieldInput.stories.tsx
new file mode 100644
index 000000000..e4bf4c767
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/ProbabilityFieldInput.stories.tsx
@@ -0,0 +1,106 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useProbabilityField } from '../../../hooks/useProbabilityField';
+import {
+ ProbabilityFieldInput,
+ ProbabilityFieldInputProps,
+} from '../ProbabilityFieldInput';
+
+const ProbabilityFieldValueSetterEffect = ({ value }: { value: number }) => {
+ const { setFieldValue } = useProbabilityField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type ProbabilityFieldInputWithContextProps = ProbabilityFieldInputProps & {
+ value: number;
+ entityId?: string;
+};
+
+const ProbabilityFieldInputWithContext = ({
+ entityId,
+ value,
+ onSubmit,
+}: ProbabilityFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+
+
+
+ );
+};
+
+const submitJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ submitJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/ProbabilityFieldInput',
+ component: ProbabilityFieldInputWithContext,
+ args: {
+ value: 25,
+ isPositive: true,
+ onSubmit: submitJestFn,
+ },
+ argTypes: {
+ onSubmit: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Submit: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(submitJestFn).toHaveBeenCalledTimes(0);
+
+ const item = (await canvas.findByText('25%'))?.nextElementSibling
+ ?.firstElementChild;
+
+ if (item) {
+ userEvent.click(item);
+ }
+
+ expect(submitJestFn).toHaveBeenCalledTimes(1);
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx
new file mode 100644
index 000000000..d1a5c59ae
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx
@@ -0,0 +1,134 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
+import { graphqlMocks } from '~/testing/graphqlMocks';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useRelationField } from '../../../hooks/useRelationField';
+import {
+ RelationFieldInput,
+ RelationFieldInputProps,
+} from '../RelationFieldInput';
+
+const RelationFieldValueSetterEffect = ({ value }: { value: number }) => {
+ const { setFieldValue } = useRelationField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type RelationFieldInputWithContextProps = RelationFieldInputProps & {
+ value: number;
+ entityId?: string;
+};
+
+const RelationFieldInputWithContext = ({
+ entityId,
+ value,
+ onSubmit,
+ onCancel,
+}: RelationFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const submitJestFn = jest.fn();
+const cancelJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ submitJestFn.mockClear();
+ cancelJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/RelationFieldInput',
+ component: RelationFieldInputWithContext,
+ args: {
+ useEditButton: true,
+ onSubmit: submitJestFn,
+ onCancel: cancelJestFn,
+ },
+ argTypes: {
+ onSubmit: { control: false },
+ onCancel: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ msw: graphqlMocks,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ decorators: [ComponentWithRecoilScopeDecorator],
+};
+
+export const Submit: Story = {
+ decorators: [ComponentWithRecoilScopeDecorator],
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(submitJestFn).toHaveBeenCalledTimes(0);
+
+ const item = await canvas.findByText('Jane Doe');
+
+ userEvent.click(item);
+
+ expect(submitJestFn).toHaveBeenCalledTimes(1);
+ },
+};
+
+export const Cancel: Story = {
+ decorators: [ComponentWithRecoilScopeDecorator],
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(cancelJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(cancelJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx
new file mode 100644
index 000000000..6235a994f
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx
@@ -0,0 +1,174 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useTextField } from '../../../hooks/useTextField';
+import { TextFieldInput, TextFieldInputProps } from '../TextFieldInput';
+
+const TextFieldValueSetterEffect = ({ value }: { value: string }) => {
+ const { setFieldValue } = useTextField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type TextFieldInputWithContextProps = TextFieldInputProps & {
+ value: string;
+ entityId?: string;
+};
+
+const TextFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: TextFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/TextFieldInput',
+ component: TextFieldInputWithContext,
+ args: {
+ value: 'text',
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/modules/ui/field/meta-types/input/components/__stories__/URLFieldInput.stories.tsx b/front/src/modules/ui/field/meta-types/input/components/__stories__/URLFieldInput.stories.tsx
new file mode 100644
index 000000000..30bde150d
--- /dev/null
+++ b/front/src/modules/ui/field/meta-types/input/components/__stories__/URLFieldInput.stories.tsx
@@ -0,0 +1,174 @@
+import { useEffect } from 'react';
+import { expect, jest } from '@storybook/jest';
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+
+import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+
+import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
+import { useURLField } from '../../../hooks/useURLField';
+import { URLFieldInput, URLFieldInputProps } from '../URLFieldInput';
+
+const URLFieldValueSetterEffect = ({ value }: { value: string }) => {
+ const { setFieldValue } = useURLField();
+
+ useEffect(() => {
+ setFieldValue(value);
+ }, [setFieldValue, value]);
+
+ return <>>;
+};
+
+type URLFieldInputWithContextProps = URLFieldInputProps & {
+ value: string;
+ entityId?: string;
+};
+
+const URLFieldInputWithContext = ({
+ entityId,
+ value,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: URLFieldInputWithContextProps) => {
+ const setHotKeyScope = useSetHotkeyScope();
+
+ useEffect(() => {
+ setHotKeyScope('hotkey-scope');
+ }, [setHotKeyScope]);
+
+ return (
+
+ );
+};
+
+const enterJestFn = jest.fn();
+const escapeJestfn = jest.fn();
+const clickOutsideJestFn = jest.fn();
+const tabJestFn = jest.fn();
+const shiftTabJestFn = jest.fn();
+
+const clearMocksDecorator: Decorator = (Story, context) => {
+ if (context.parameters.clearMocks) {
+ enterJestFn.mockClear();
+ escapeJestfn.mockClear();
+ clickOutsideJestFn.mockClear();
+ tabJestFn.mockClear();
+ shiftTabJestFn.mockClear();
+ }
+ return ;
+};
+
+const meta: Meta = {
+ title: 'UI/Field/Input/URLFieldInput',
+ component: URLFieldInputWithContext,
+ args: {
+ value: 'https://username.domain',
+ onEnter: enterJestFn,
+ onEscape: escapeJestfn,
+ onClickOutside: clickOutsideJestFn,
+ onTab: tabJestFn,
+ onShiftTab: shiftTabJestFn,
+ },
+ argTypes: {
+ onEnter: { control: false },
+ onEscape: { control: false },
+ onClickOutside: { control: false },
+ onTab: { control: false },
+ onShiftTab: { control: false },
+ },
+ decorators: [clearMocksDecorator],
+ parameters: {
+ clearMocks: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Enter: Story = {
+ play: async () => {
+ expect(enterJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{enter}');
+ expect(enterJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Escape: Story = {
+ play: async () => {
+ expect(escapeJestfn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{esc}');
+ expect(escapeJestfn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ClickOutside: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
+
+ const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
+
+ await waitFor(() => {
+ userEvent.click(emptyDiv);
+ expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const Tab: Story = {
+ play: async () => {
+ expect(tabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{tab}');
+ expect(tabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
+
+export const ShiftTab: Story = {
+ play: async () => {
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
+
+ await waitFor(() => {
+ userEvent.keyboard('{shift>}{tab}');
+ expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
+ });
+ },
+};
diff --git a/front/src/testing/mock-data/users.ts b/front/src/testing/mock-data/users.ts
index 1c0a33833..fcd426b69 100644
--- a/front/src/testing/mock-data/users.ts
+++ b/front/src/testing/mock-data/users.ts
@@ -1,5 +1,6 @@
import {
Activity,
+ Attachment,
ColorScheme,
Company,
User,
@@ -34,6 +35,7 @@ type MockedUser = Pick<
>;
assignedActivities: Array;
authoredActivities: Array;
+ authoredAttachments: Array;
companies: Array;
comments: Array;
};
@@ -74,6 +76,7 @@ export const mockedUsersData: Array = [
locale: 'en',
colorScheme: ColorScheme.System,
},
+ authoredAttachments: [],
assignedActivities: [],
authoredActivities: [],
companies: [],
@@ -113,6 +116,7 @@ export const mockedUsersData: Array = [
locale: 'en',
colorScheme: ColorScheme.System,
},
+ authoredAttachments: [],
assignedActivities: [],
authoredActivities: [],
companies: [],
@@ -156,6 +160,7 @@ export const mockedOnboardingUsersData: Array = [
locale: 'en',
colorScheme: ColorScheme.System,
},
+ authoredAttachments: [],
assignedActivities: [],
authoredActivities: [],
companies: [],
@@ -196,6 +201,7 @@ export const mockedOnboardingUsersData: Array = [
locale: 'en',
colorScheme: ColorScheme.System,
},
+ authoredAttachments: [],
assignedActivities: [],
authoredActivities: [],
companies: [],
diff --git a/front/yarn.lock b/front/yarn.lock
index e0211e924..317b5fccd 100644
--- a/front/yarn.lock
+++ b/front/yarn.lock
@@ -19490,6 +19490,7 @@ workbox-window@6.6.1:
workbox-core "6.6.1"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+ name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==