diff --git a/src/components/form/avatar-input/form-avatar-input.stories.tsx b/src/components/form/avatar-input/form-avatar-input.stories.tsx new file mode 100644 index 00000000..1bf3a598 --- /dev/null +++ b/src/components/form/avatar-input/form-avatar-input.stories.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import { ControllerProps, FieldPath, FieldValues } from "react-hook-form"; +import FormAvatarInput from "./form-avatar-input"; + +type FormAvatarInputProps = Pick< + ControllerProps>, + "name" | "defaultValue" +> & { + disabled?: boolean; + testId?: string; +}; + +export default { + title: "Components/Form/AvatarInput", + component: FormAvatarInput, +} as Meta; + +const Template: StoryFn = (args) => { + const methods = useForm({ + defaultValues: { + sampleAvatar: null, + }, + }); + + return ( + +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + name: "sampleAvatar", + testId: "sampleAvatar", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + name: "sampleAvatar", + testId: "disabledAvatar", + disabled: true, +}; + +export const WithPrefilledValue = Template.bind({}); +WithPrefilledValue.args = { + name: "sampleAvatar", + testId: "prefilledAvatar", + defaultValue: { + id: "1", + path: "https://via.placeholder.com/100x100?text=Avatar", + }, +}; diff --git a/src/components/form/avatar-input/form-avatar-input.tsx b/src/components/form/avatar-input/form-avatar-input.tsx index 9c650367..b737153e 100644 --- a/src/components/form/avatar-input/form-avatar-input.tsx +++ b/src/components/form/avatar-input/form-avatar-input.tsx @@ -18,7 +18,7 @@ import { useTranslation } from "react-i18next"; import IconButton from "@mui/material/IconButton"; import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined"; -type AvatarInputProps = { +export type AvatarInputProps = { error?: string; onChange: (value: FileEntity | null) => void; onBlur: () => void; diff --git a/src/components/form/date-pickers/date-picker.stories.tsx b/src/components/form/date-pickers/date-picker.stories.tsx new file mode 100644 index 00000000..c392522d --- /dev/null +++ b/src/components/form/date-pickers/date-picker.stories.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import FormDatePickerInput, { DatePickerFieldProps } from "./date-picker"; + +export default { + title: "Components/Form/DatePickerInput", + component: FormDatePickerInput, +} as Meta; + +const Template: StoryFn = (args) => { + const methods = useForm({ + defaultValues: { + sampleDate: null, + }, + }); + + return ( + +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + label: "Sample Date Picker", + name: "sampleDate", + testId: "sampleDate", +}; + +export const WithMinMaxDate = Template.bind({}); +WithMinMaxDate.args = { + label: "Date Picker with Min/Max", + name: "sampleDate", + minDate: new Date(2020, 0, 1), + maxDate: new Date(2030, 11, 31), + testId: "minMaxDate", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + label: "Disabled Date Picker", + name: "sampleDate", + disabled: true, + testId: "disabledDate", +}; + +export const ReadOnly = Template.bind({}); +ReadOnly.args = { + label: "Read Only Date Picker", + name: "sampleDate", + readOnly: true, + testId: "readOnlyDate", +}; + +export const YearMonthView = Template.bind({}); +YearMonthView.args = { + label: "Year/Month View", + name: "sampleDate", + views: ["year", "month"], + testId: "yearMonthDate", +}; diff --git a/src/components/form/date-pickers/date-picker.tsx b/src/components/form/date-pickers/date-picker.tsx index 07049b7e..cb010e86 100644 --- a/src/components/form/date-pickers/date-picker.tsx +++ b/src/components/form/date-pickers/date-picker.tsx @@ -16,7 +16,7 @@ import useLanguage from "@/services/i18n/use-language"; import { getValueByKey } from "@/components/form/date-pickers/helper"; type ValueDateType = Date | null | undefined; -type DatePickerFieldProps = { +export type DatePickerFieldProps = { disabled?: boolean; className?: string; views?: readonly DateView[] | undefined; @@ -55,6 +55,7 @@ function DatePickerInput( disabled={props.disabled} autoFocus={props.autoFocus} defaultValue={props.defaultValue} + readOnly={props.readOnly} slotProps={{ textField: { helperText: props.error, diff --git a/src/components/form/date-pickers/date-time-picker.stories.tsx b/src/components/form/date-pickers/date-time-picker.stories.tsx new file mode 100644 index 00000000..5649dfb4 --- /dev/null +++ b/src/components/form/date-pickers/date-time-picker.stories.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import FormDateTimePickerInput, { + DateTimePickerFieldProps, +} from "./date-time-picker"; + +export default { + title: "Components/Form/DateTimePickerInput", + component: FormDateTimePickerInput, +} as Meta; + +const Template: StoryFn = ( + args +) => { + const methods = useForm({ + defaultValues: { + sampleDateTime: null, + }, + }); + + return ( + +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + label: "Sample Date Time Picker", + name: "sampleDateTime", + testId: "sampleDateTime", +}; + +export const WithMinMaxDate = Template.bind({}); +WithMinMaxDate.args = { + label: "Date Time Picker with Min/Max", + name: "sampleDateTime", + minDate: new Date(2020, 0, 1), + maxDate: new Date(2030, 11, 31), + testId: "minMaxDateTime", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + label: "Disabled Date Time Picker", + name: "sampleDateTime", + disabled: true, + testId: "disabledDateTime", +}; + +export const ReadOnly = Template.bind({}); +ReadOnly.args = { + label: "Read Only Date Time Picker", + name: "sampleDateTime", + readOnly: true, + testId: "readOnlyDateTime", +}; + +export const CustomViews = Template.bind({}); +CustomViews.args = { + label: "Custom Views Date Time Picker", + name: "sampleDateTime", + views: ["day", "hours", "minutes"], + testId: "customViewsDateTime", +}; diff --git a/src/components/form/date-pickers/date-time-picker.tsx b/src/components/form/date-pickers/date-time-picker.tsx index 1b7c0caf..b202b61b 100644 --- a/src/components/form/date-pickers/date-time-picker.tsx +++ b/src/components/form/date-pickers/date-time-picker.tsx @@ -16,7 +16,7 @@ import useLanguage from "@/services/i18n/use-language"; import { getValueByKey } from "@/components/form/date-pickers/helper"; type ValueDateType = Date | null | undefined; -type DateTimePickerFieldProps = { +export type DateTimePickerFieldProps = { disabled?: boolean; className?: string; views?: readonly DateOrTimeView[]; @@ -55,6 +55,7 @@ function DateTimePickerInput( disabled={props.disabled} autoFocus={props.autoFocus} defaultValue={props.defaultValue} + readOnly={props.readOnly} slotProps={{ textField: { helperText: props.error, diff --git a/src/components/form/date-pickers/time-picker.stories.tsx b/src/components/form/date-pickers/time-picker.stories.tsx new file mode 100644 index 00000000..929db412 --- /dev/null +++ b/src/components/form/date-pickers/time-picker.stories.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import FormTimePickerInput, { TimePickerFieldProps } from "./time-picker"; + +export default { + title: "Components/Form/TimePickerInput", + component: FormTimePickerInput, +} as Meta; + +const Template: StoryFn = (args) => { + const methods = useForm({ + defaultValues: { + sampleTime: null, + }, + }); + + return ( + +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + label: "Sample Time Picker", + name: "sampleTime", + testId: "sampleTime", +}; + +export const WithCustomFormat = Template.bind({}); +WithCustomFormat.args = { + label: "Custom Format Time Picker", + name: "sampleTime", + format: "HH:mm:ss", + testId: "customFormatTime", +}; + +export const WithMinMaxTime = Template.bind({}); +WithMinMaxTime.args = { + label: "Time Picker with Min/Max", + name: "sampleTime", + minTime: new Date(0, 0, 0, 9, 0), + maxTime: new Date(0, 0, 0, 17, 0), + testId: "minMaxTime", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + label: "Disabled Time Picker", + name: "sampleTime", + disabled: true, + testId: "disabledTime", +}; + +export const ReadOnly = Template.bind({}); +ReadOnly.args = { + label: "Read Only Time Picker", + name: "sampleTime", + readOnly: true, + testId: "readOnlyTime", +}; + +export const HoursMinutesView = Template.bind({}); +HoursMinutesView.args = { + label: "Hours/Minutes View", + name: "sampleTime", + views: ["hours", "minutes"], + testId: "hoursMinutesTime", +}; diff --git a/src/components/form/date-pickers/time-picker.tsx b/src/components/form/date-pickers/time-picker.tsx index 990a585c..3ba3cefe 100644 --- a/src/components/form/date-pickers/time-picker.tsx +++ b/src/components/form/date-pickers/time-picker.tsx @@ -17,7 +17,7 @@ import useLanguage from "@/services/i18n/use-language"; import { getValueByKey } from "@/components/form/date-pickers/helper"; type ValueDateType = Date | null | undefined; -type TimePickerFieldProps = { +export type TimePickerFieldProps = { disabled?: boolean; className?: string; views?: readonly TimeView[] | undefined; @@ -57,6 +57,7 @@ function TimePickerInput( disabled={props.disabled} autoFocus={props.autoFocus} defaultValue={props.defaultValue} + readOnly={props.readOnly} onClose={props.onBlur} slotProps={{ textField: { diff --git a/src/components/form/image-picker/image-picker.stories.tsx b/src/components/form/image-picker/image-picker.stories.tsx new file mode 100644 index 00000000..68c8b5ba --- /dev/null +++ b/src/components/form/image-picker/image-picker.stories.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import { ControllerProps, FieldPath, FieldValues } from "react-hook-form"; +import FormImagePicker from "./image-picker"; + +type FormImagePickerProps = Pick< + ControllerProps>, + "name" | "defaultValue" +> & { + disabled?: boolean; + testId?: string; + label?: React.ReactNode; +}; + +export default { + title: "Components/Form/ImagePicker", + component: FormImagePicker, +} as Meta; + +const Template: StoryFn = (args) => { + const methods = useForm({ + defaultValues: { + sampleImage: null, + }, + }); + + return ( + +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + name: "sampleImage", + testId: "sampleImage", + label: "Upload Image", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + name: "sampleImage", + testId: "disabledImage", + label: "Disabled Image Upload", + disabled: true, +}; + +export const WithPrefilledValue = Template.bind({}); +WithPrefilledValue.args = { + name: "sampleImage", + testId: "prefilledImage", + label: "Image with Default Value", + defaultValue: { + id: "1", + path: "https://via.placeholder.com/300x200?text=Sample+Image", + }, +}; diff --git a/src/components/form/image-picker/image-picker.tsx b/src/components/form/image-picker/image-picker.tsx index 5ad4555d..dea8902c 100644 --- a/src/components/form/image-picker/image-picker.tsx +++ b/src/components/form/image-picker/image-picker.tsx @@ -20,7 +20,7 @@ import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined"; import ImageListItem from "@mui/material/ImageListItem"; import ImageList from "@mui/material/ImageList"; -type ImagePickerProps = { +export type ImagePickerProps = { error?: string; onChange: (value: FileEntity | null) => void; onBlur: () => void; diff --git a/src/components/form/multiple-image-picker/multiple-image-picker.stories.tsx b/src/components/form/multiple-image-picker/multiple-image-picker.stories.tsx new file mode 100644 index 00000000..8db1b2f1 --- /dev/null +++ b/src/components/form/multiple-image-picker/multiple-image-picker.stories.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import { ControllerProps, FieldPath, FieldValues } from "react-hook-form"; +import FormMultipleImagePicker from "./multiple-image-picker"; + +type FormMultipleImagePickerProps = Pick< + ControllerProps>, + "name" | "defaultValue" +> & { + disabled?: boolean; + testId?: string; + label?: React.ReactNode; +}; + +export default { + title: "Components/Form/MultipleImagePicker", + component: FormMultipleImagePicker, +} as Meta; + +const Template: StoryFn = (args) => { + const methods = useForm({ + defaultValues: { + sampleImages: [], + }, + }); + + return ( + +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + name: "sampleImages", + testId: "sampleImages", + label: "Upload Multiple Images", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + name: "sampleImages", + testId: "disabledImages", + label: "Disabled Multiple Image Upload", + disabled: true, +}; + +export const WithPrefilledValue = Template.bind({}); +WithPrefilledValue.args = { + name: "sampleImages", + testId: "prefilledImages", + label: "Images with Default Values", + defaultValue: [ + { + id: "1", + path: "https://via.placeholder.com/300x200?text=Image+1", + }, + { + id: "2", + path: "https://via.placeholder.com/300x200?text=Image+2", + }, + ], +}; diff --git a/src/components/form/multiple-image-picker/multiple-image-picker.tsx b/src/components/form/multiple-image-picker/multiple-image-picker.tsx index e5113e93..5717a1e7 100644 --- a/src/components/form/multiple-image-picker/multiple-image-picker.tsx +++ b/src/components/form/multiple-image-picker/multiple-image-picker.tsx @@ -20,7 +20,7 @@ import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined"; import ImageListItem from "@mui/material/ImageListItem"; import ImageList from "@mui/material/ImageList"; -type MultipleImagePickerProps = { +export type MultipleImagePickerProps = { error?: string; onChange: (value: FileEntity[] | null) => void; onBlur: () => void; diff --git a/src/components/form/multiple-select-extended/form-multiple-select-extended.stories.tsx b/src/components/form/multiple-select-extended/form-multiple-select-extended.stories.tsx new file mode 100644 index 00000000..c6ab2ac2 --- /dev/null +++ b/src/components/form/multiple-select-extended/form-multiple-select-extended.stories.tsx @@ -0,0 +1,118 @@ +import React, { useState } from "react"; +import { Meta, StoryFn } from "@storybook/nextjs"; +import { useForm, FormProvider } from "react-hook-form"; +import FormMultipleSelectExtendedInput, { + MultipleSelectExtendedInputProps, +} from "./form-multiple-select-extended"; + +interface Option { + id: number; + name: string; +} + +type SearchableProps = MultipleSelectExtendedInputProps