diff --git a/src/components/container-form/attribute.tsx b/src/components/container-form/attribute.tsx index 7f55143..85f9b48 100644 --- a/src/components/container-form/attribute.tsx +++ b/src/components/container-form/attribute.tsx @@ -69,6 +69,10 @@ const typeOptions = [ label: , value: TYPES.RichText, }, + { + label: , + value: TYPES.Asset, + }, ]; type Props = { diff --git a/src/components/container-form/messages.ts b/src/components/container-form/messages.ts index f1aaa7e..89f53a1 100644 --- a/src/components/container-form/messages.ts +++ b/src/components/container-form/messages.ts @@ -192,4 +192,9 @@ export default defineMessages({ description: 'Label for attributes rich text value', defaultMessage: 'Rich Text', }, + assetLabel: { + id: 'Container.form.type.label.asset', + description: 'Asset type label', + defaultMessage: 'Asset', + }, }); diff --git a/src/components/custom-object-form/asset-input/asset-input.tsx b/src/components/custom-object-form/asset-input/asset-input.tsx new file mode 100644 index 0000000..8d6aa78 --- /dev/null +++ b/src/components/custom-object-form/asset-input/asset-input.tsx @@ -0,0 +1,123 @@ +// src/components/attribute-input/asset-input.js + +import Spacings from '@commercetools-uikit/spacings'; +import TextInput from '@commercetools-uikit/text-input'; +import LocalizedTextInput from '@commercetools-uikit/localized-text-input'; +import { useApplicationContext } from '@commercetools-frontend/application-shell-connectors'; +import Text from '@commercetools-uikit/text'; +import { FC } from 'react'; +import SourceArrayInput from './source-array-input'; +import { Asset, LocalizedString, Source } from './types'; + +type Props = { + name: string; + value?: any; + touched?: any; + errors?: any; + onChange: (...args: any[]) => void; + onBlur: (...args: any[]) => void; +}; + +const AssetInput: FC = ({ + name, + value = {}, + onChange, + touched, + errors, +}) => { + const { dataLocale } = useApplicationContext((context) => ({ + dataLocale: context.dataLocale ?? '', + })); + + const triggerChange = (updatedValue: Partial) => { + onChange({ target: { name, value: updatedValue } }); + }; + + const handleChange = (e: React.ChangeEvent) => { + const { name: fieldName, value: fieldValue } = e.target; + triggerChange({ ...value, [fieldName]: fieldValue }); + }; + + const handleLocalizedChange = ( + localizedValue: LocalizedString, + fieldName: string + ) => { + triggerChange({ ...value, [fieldName]: localizedValue }); + }; + + const handleSourcesChange = (sources: Source[]) => { + triggerChange({ ...value, sources }); + }; + + const handleTagsChange = (e: React.ChangeEvent) => { + const newTags = e.target.value + ? e.target.value.split(',').map((tag) => tag.trim()) + : []; + triggerChange({ ...value, tags: newTags }); + }; + + return ( + + + + + Name: + + + } + value={value?.name || {}} + selectedLanguage={dataLocale} + onChange={(event) => + handleLocalizedChange( + event.target.value as unknown as LocalizedString, + 'name' + ) + } + hasError={!!(LocalizedTextInput.isTouched(touched) && errors)} + /> + Description: + } + value={value?.description || {}} + selectedLanguage={dataLocale} + onChange={(event) => + handleLocalizedChange( + event.target.value as unknown as LocalizedString, + 'description' + ) + } + hasError={!!(LocalizedTextInput.isTouched(touched) && errors)} + /> + + + + + Sources + + + + ); +}; + +export default AssetInput; diff --git a/src/components/custom-object-form/asset-input/source-array-input.tsx b/src/components/custom-object-form/asset-input/source-array-input.tsx new file mode 100644 index 0000000..a4ccac1 --- /dev/null +++ b/src/components/custom-object-form/asset-input/source-array-input.tsx @@ -0,0 +1,53 @@ +// src/components/attribute-input/source-array-input.tsx + +import React from 'react'; +import Spacings from '@commercetools-uikit/spacings'; +import SecondaryButton from '@commercetools-uikit/secondary-button'; +import Constraints from '@commercetools-uikit/constraints'; +import SourceInput from './source-input'; +import type { Source } from './types'; + +type Props = { + value: Source[]; + onChange: (value: Source[]) => void; +}; + +const SourceArrayInput: React.FC = ({ value = [], onChange }) => { + const handleItemChange = (index: number, itemValue: Source) => { + const newSources = value.map((item, i) => (i === index ? itemValue : item)); + onChange(newSources); + }; + + const handleAddItem = () => { + const newSources = [...value, { key: '', uri: '', contentType: '' }]; + onChange(newSources); + }; + + const handleRemoveItem = (index: number) => { + const newSources = value.filter((_, i) => i !== index); + onChange(newSources); + }; + + return ( + + {value.map((source, index) => ( + + ))} + + + + + ); +}; + +export default SourceArrayInput; diff --git a/src/components/custom-object-form/asset-input/source-input.tsx b/src/components/custom-object-form/asset-input/source-input.tsx new file mode 100644 index 0000000..f6ab047 --- /dev/null +++ b/src/components/custom-object-form/asset-input/source-input.tsx @@ -0,0 +1,83 @@ +// src/components/attribute-input/source-input.tsx + +import React from 'react'; +import Grid from '@commercetools-uikit/grid'; +import TextInput from '@commercetools-uikit/text-input'; +import NumberInput from '@commercetools-uikit/number-input'; +import { CloseIcon } from '@commercetools-uikit/icons'; +import Text from '@commercetools-uikit/text'; +import SecondaryButton from '@commercetools-uikit/secondary-button'; +import Spacings from '@commercetools-uikit/spacings'; +import Card from '@commercetools-uikit/card'; +import type { Source } from './types'; + +type Props = { + value: Source; + index: number; + onChange: (index: number, value: Source) => void; + onRemove: (index: number) => void; +}; + +const SourceInput: React.FC = ({ value, index, onChange, onRemove }) => { + const handleChange = (e: React.ChangeEvent) => { + const { name, value: fieldValue } = e.target; + onChange(index, { ...value, [name]: fieldValue }); + }; + + const handleNumberChange = (e: React.ChangeEvent) => { + const { name, value: fieldValue } = e.target; + onChange(index, { + ...value, + [name]: fieldValue ? parseInt(fieldValue, 10) : null, + }); + }; + + return ( + + + Sources {index + 1} + } + label="Remove Source" + size="small" + data-testid={`remove-source-${index}`} + onClick={() => onRemove(index)} + /> + + + + + + + + + + ); +}; + +export default SourceInput; diff --git a/src/components/custom-object-form/asset-input/types.ts b/src/components/custom-object-form/asset-input/types.ts new file mode 100644 index 0000000..e98ccbf --- /dev/null +++ b/src/components/custom-object-form/asset-input/types.ts @@ -0,0 +1,18 @@ +export type LocalizedString = Record; + +export interface Source { + key: string; + uri: string; + contentType: string; + width?: number | null; + height?: number | null; +} + +export interface Asset { + key?: string; + name: LocalizedString; + description?: LocalizedString; + tags?: string[]; + folder?: string; + sources?: Source[]; +} diff --git a/src/components/custom-object-form/attribute-input.tsx b/src/components/custom-object-form/attribute-input.tsx index c071f7a..f79165d 100644 --- a/src/components/custom-object-form/attribute-input.tsx +++ b/src/components/custom-object-form/attribute-input.tsx @@ -19,6 +19,7 @@ import { TYPES } from '../../constants'; import nestedStyles from '../container-form/nested-attributes.module.css'; import AttributeField from './attribute-field'; // eslint-disable-line import/no-cycle import LexicalEditorField from './lexical-editor-field'; +import AssetInput from './asset-input/asset-input'; type Props = { type: string; @@ -271,7 +272,9 @@ const AttributeInput: FC = ({ }} /> {touched && errors && ( - {errors} + + {errors} + )} ); @@ -307,6 +310,25 @@ const AttributeInput: FC = ({ ); + case TYPES.Asset: + return ( + + + {touched && errors && ( + + {typeof errors === 'string' ? errors : 'Invalid Asset data'} + + )} + + ); + default: return null; } diff --git a/src/constants.ts b/src/constants.ts index 0fd24dd..f5d28d2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -37,6 +37,7 @@ export const TYPES = { Object: 'Object', Reference: 'Reference', RichText: 'RichText', + Asset: 'Asset', }; export enum TYPES_ENUM { @@ -53,6 +54,7 @@ export enum TYPES_ENUM { Object = 'Object', Reference = 'Reference', RichText = 'RichText', + Asset= 'Asset' } export const REFERENCE_BY = {