Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b1ef540
feat: nuovo componente upload
Virtute90 Apr 28, 2024
0cb3e35
docs: documentazione per il nuovo componente Uplaod
Virtute90 Apr 28, 2024
56692d5
feat: add Uploadlist
Virtute90 May 15, 2024
cdd917c
docs: aggiunta story per listitem
Virtute90 May 17, 2024
6cbb6f8
feat: add UploadList and UploadListItem
Virtute90 May 17, 2024
c195f78
Merge branch 'italia:main' into feat/input-upload
Virtute90 May 22, 2024
67a6846
feat: aggiunte migliorie al componente e alla storia
Virtute90 May 23, 2024
0099bb2
feat: aggiunto item con immagine
Virtute90 May 23, 2024
21a8b7c
feat: sistemate classi css
Virtute90 May 24, 2024
872e45a
feat: iniziato con upload con avatar
Virtute90 May 24, 2024
9cd0207
feat: upload con avatar ok
Virtute90 May 27, 2024
32da478
deps: update
Virtute90 May 27, 2024
c62893e
Merge remote-tracking branch 'origin/main' into feat/input-upload
Virtute90 Mar 13, 2025
b4f73d7
Installate nuovi pacchetti
Virtute90 Mar 13, 2025
ff75f89
Rimosso test id e formattazione
Virtute90 Mar 13, 2025
4b723e1
feat: nuovo componente upload
Virtute90 Apr 28, 2024
668bdaa
docs: documentazione per il nuovo componente Uplaod
Virtute90 Apr 28, 2024
a70bfd2
feat: add Uploadlist
Virtute90 May 15, 2024
9c33d35
docs: aggiunta story per listitem
Virtute90 May 17, 2024
0c17929
feat: add UploadList and UploadListItem
Virtute90 May 17, 2024
a80a6a5
feat: aggiunte migliorie al componente e alla storia
Virtute90 May 23, 2024
23908be
feat: aggiunto item con immagine
Virtute90 May 23, 2024
c2d10e8
feat: sistemate classi css
Virtute90 May 24, 2024
c533fea
feat: iniziato con upload con avatar
Virtute90 May 24, 2024
d606080
feat: upload con avatar ok
Virtute90 May 27, 2024
dce473f
deps: update
Virtute90 May 27, 2024
b77ea64
Installate nuovi pacchetti
Virtute90 Mar 13, 2025
e067ed1
Rimosso test id e formattazione
Virtute90 Mar 13, 2025
df4c4ec
Merge branch 'feat/input-upload' of https://github.com/Virtute90/desi…
Virtute90 Mar 13, 2025
184de29
Merge remote-tracking branch 'origin/main' into feat/input-upload
Virtute90 Mar 13, 2025
5446dbb
fix: rimosso doppione
Virtute90 Mar 13, 2025
65d9b8b
feat: esporto i nuovi componenti
Virtute90 Mar 13, 2025
b6a07f8
feat: aggiunga formato gallery
Virtute90 Mar 14, 2025
14f2588
Merge branch 'italia:main' into feat/input-upload
Virtute90 Mar 18, 2025
5afad4c
Merge branch 'main' into feat/input-upload
astagi Mar 19, 2025
eedb071
Merge branch 'main' into feat/input-upload
astagi Mar 20, 2025
7a2d383
Merge branch 'main' into feat/input-upload
astagi Apr 9, 2025
6ba3560
Merge branch 'main' into feat/input-upload
astagi Apr 9, 2025
2a9309b
Merge branch 'main' into feat/input-upload
astagi Apr 28, 2025
9613c05
feat: aggiunto componente drag and drop
Virtute90 May 7, 2025
24662d8
docs: completata documentazione
Virtute90 May 7, 2025
17efcdc
build: aggiunto jest-transform-stub per trasformazione svg
Virtute90 May 7, 2025
31aa031
test: aggiornato snap
Virtute90 May 7, 2025
3f1a58f
Merge branch 'main' into feat/input-upload
astagi May 16, 2025
e97458c
Merge branch 'main' into feat/input-upload
astagi May 19, 2025
68fd21b
fix: set different upload ids for each components to trigger upload p…
AlexPalaz Jun 19, 2025
257e0a5
fix(input): update snapshots
AlexPalaz Jun 19, 2025
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"husky": "^8.0.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-transform-stub": "^2.0.0",
"lint-staged": "15.2.10",
"prettier": "^3.2.5",
"react": "^18.2.0",
Expand Down
98 changes: 98 additions & 0 deletions src/Upload/Upload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import classNames from 'classnames';
import React, { ElementType, InputHTMLAttributes, ReactNode } from 'react';
import { Icon, IconProps } from '../Icon/Icon';

export interface UploadProps extends InputHTMLAttributes<HTMLInputElement> {
/** L'id che lega il componente con la label */
id: string;
/** Etichetta del per il componente Upload, default 'Upload' */
label?: string | ReactNode;
/**
* Il nome dell'icona da mostrare, default è 'it-upload'. Per una lista completa vedi:
* <a href="https://italia.github.io/design-react-kit/?path=/story/componenti-icon--lista-icone" target="_blank">Lista icone</a>
* In caso di un'immagine esterna l'URL da utilizzare.
**/
icon?: string;
iconSize?: IconProps['size'];
/** Utilizzarlo in caso di utilizzo di componenti personalizzati. Il valore di default è 'input' */
tag?: ElementType;
/** Classi aggiuntive da usare per il componente Upload */
className?: string;
/** Indica che l'input è con un avatar */
isAvatar?: boolean;
avatarImg?: ReactNode;
/** Indica che l'avatar è piccolo */
avatarSmall?: boolean;
testId?: string;
}

export const Upload = ({
id,
className,
icon = 'it-upload',
iconSize,
label = 'Upload',
tag = 'input',
isAvatar,
avatarImg,
avatarSmall,
testId,
...attributes
}: UploadProps) => {
const Tag = tag,
classes = classNames(
className,
{
upload: isAvatar ? false : true
},
{ 'upload-avatar': isAvatar }
),
classesAvatarWrapper = classNames({
'avatar-upload-wrapper': true,
'size-sm': avatarSmall
}),
extraAttributes: {
id?: string;
type: 'file';
['aria-describedby']?: string;
} = {
id,
type: 'file'
};

// associate the input field with the help text
const infoId = id ? `${id}Description` : undefined;
if (id) {
extraAttributes['aria-describedby'] = infoId;
}

if (isAvatar) {
return (
<div className={classesAvatarWrapper}>
<div className='avatar size-xxl avatar-upload'>
{avatarImg}
<div className='upload-avatar-container'>
<Tag {...attributes} {...extraAttributes} className={classes} data-testid={testId} />
<label htmlFor={id}>
<Icon icon={icon} size={iconSize} />
<span>{label}</span>
</label>
</div>
</div>
<div className='avatar-upload-icon'>
<Icon icon='it-camera' size='sm' />
</div>
</div>
);
}

return (
<>
<Tag {...attributes} {...extraAttributes} className={classes} data-testid={testId} />
<label htmlFor={id}>
<Icon icon={icon} size={iconSize} />
<span>{label}</span>
</label>
</>
);
};
101 changes: 101 additions & 0 deletions src/Upload/UploadDragNdrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import classNames from 'classnames';
import React, { DragEventHandler, FC, useState } from 'react';
import { Icon } from '../Icon/Icon';
import DragandDropIcon from '../assets/upload-drag-drop-icon.svg';

interface UploadDragNdropProps {
/**
* Array di file per il caricamento del file
*/
files: File[];
/**
* Funzione per impostare lo stato dei files
*/
setFiles: React.Dispatch<React.SetStateAction<File[]>>;
}

export const UploadDragNdrop: FC<UploadDragNdropProps> = ({ files, setFiles }) => {
const [dragOverClass, setDragOverClass] = useState(false),
dragOverClasses = classNames('upload-dragdrop', {
dragover: dragOverClass,
success: files.length > 0
});

const handleDrop: DragEventHandler<HTMLDivElement> = (event) => {
handleDrag(event);
const droppedFiles = event.dataTransfer.files;
if (droppedFiles.length > 0) {
setFiles(Array.from(droppedFiles));
}
setDragOverClass(false);
},
handleDrag: DragEventHandler<HTMLDivElement> = (event) => {
event.preventDefault();
event.stopPropagation();
},
handleDragOver: DragEventHandler<HTMLDivElement> = (event) => {
handleDrag(event);
setDragOverClass(true);
},
handleDragLeave: DragEventHandler<HTMLDivElement> = (event) => {
handleDrag(event);
setDragOverClass(false);
};

const byteConverter = (bytes: number) => {
const K_UNIT = 1024;
const SIZES = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];

if (bytes == 0) return '0 Byte';

const i = Math.floor(Math.log(bytes) / Math.log(K_UNIT)),
resp = parseFloat((bytes / Math.pow(K_UNIT, i)).toFixed(2)) + ' ' + SIZES[i];

return resp;
};

return (
<div className={dragOverClasses}>
<div
className='upload-dragdrop-image'
onDrop={handleDrop}
onDrag={handleDrag}
onDragStart={handleDrag}
onDragEnd={handleDrag}
onDragExit={handleDrag}
onDragOver={handleDragOver}
onDragEnter={handleDrag}
onDragLeave={handleDragLeave}
>
<img src={DragandDropIcon} alt='descrizione immagine' aria-hidden='true' />
{files.length > 0 && (
<div className='upload-dragdrop-success'>
<Icon icon='it-check' size='xs' />
</div>
)}
</div>
<div className='upload-dragdrop-text'>
{files.length > 0 && (
<p className='upload-dragdrop-weight'>
<Icon icon='it-file' size='xs' />
{files.map((file) => file.type + ' ' + byteConverter(file.size))}
</p>
)}
{files.length > 0 ? (
<>
<h5>{files.map((file) => file.name)}</h5>
<p>Caricamento completato</p>
</>
) : (
<>
<h5>Trascina il file per caricarlo</h5>
<p>
oppure <input type='file' name='upload7' id='upload7' className='upload-dragdrop-input' />
<label htmlFor='upload7'>selezionalo dal dispositivo</label>
</p>
</>
)}
</div>
</div>
);
};
30 changes: 30 additions & 0 deletions src/Upload/UploadList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import classNames from 'classnames';
import React, { FC, HTMLAttributes } from 'react';
import { UploadContext } from './useUploadContext';

export interface UploadListProps extends HTMLAttributes<HTMLUListElement> {
/** Classi aggiuntive da usare per il componente lista del UploadList */
className?: string;
/** Indica che gli item list hanno l'immagine come anteprima */
previewImage?: boolean;
/** Indica la tipologia di lista
* @default file
*/
tipologia: 'file' | 'gallery';
testId?: string;
}

export const UploadList: FC<UploadListProps> = ({ className, previewImage, tipologia = 'file', ...attributes }) => {
const classes = classNames(
{ 'upload-file-list': tipologia == 'file' },
{ 'upload-pictures-wall': tipologia == 'gallery' },
{ 'upload-file-list-image': previewImage },
className
);

return (
<UploadContext.Provider value={{ tipologia }}>
<ul {...attributes} className={classes} />
</UploadContext.Provider>
);
};
120 changes: 120 additions & 0 deletions src/Upload/UploadListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import classNames from 'classnames';
import React, { ElementType, FC, HTMLAttributes, InputHTMLAttributes } from 'react';
import { Icon } from '../Icon/Icon';
import { Progress } from '../Progress/Progress';
import { useUploadContext } from './useUploadContext';

export interface UploadListItemProps extends HTMLAttributes<HTMLUListElement> {
/** Classi aggiuntive da usare per il componente lista del UploadList */
className?: string;
/**
* Indica l'icona del file
* @default it-file
*/
icon?: string;
/**
* Indica lo stato dell'upload
* @default success
*/
uploadStatus?: 'success' | 'uploading' | 'error';
/** Utilizzarlo in caso di utilizzo di componenti personalizzati */
buttonTag?: ElementType;
/** Nome del file */
fileName?: string;
/** Peso del file */
fileWeight?: string;
/** Valore della barra progress in caso uploadStatus sia uploading */
progressValue?: number;
/** Indica che gli item list hanno l'immagine come anteprima */
previewImage?: boolean;
previewImageSrc?: string;
previewImageAlt?: string;
testId?: string;
}

export const UploadListItem: FC<UploadListItemProps> & {
UploadButton: typeof UploadButton;
} = ({
className,
icon = 'it-file',
uploadStatus = 'success',
buttonTag = 'button',
progressValue,
previewImage,
previewImageSrc,
previewImageAlt,
fileName,
fileWeight,
children
}) => {
const tipologia = useUploadContext(),
classes = classNames(
{
'upload-file': tipologia === 'file',
success: uploadStatus === 'success' && tipologia === 'file',
uploading: uploadStatus === 'uploading' && tipologia === 'file',
error: uploadStatus === 'error' && tipologia === 'file'
},
className
),
classesProgress = classNames({ 'progress-image': previewImage }),
ButtonTag = buttonTag;

const elementiListItem = {
success: {
testoVHFile: 'File caricato:',
testoVHRightIcon: 'Caricamento ultimato',
rightIcon: 'it-check'
},
uploading: {
testoVHFile: 'File caricato:',
testoVHRightIcon: 'Annulla caricamento',
rightIcon: 'it-close'
},
error: {
testoVHFile: 'Errore caricamento file:',
testoVHRightIcon: 'Elimina file caricato',
rightIcon: 'it-close'
}
};

return (
<li className={classes}>
{children}
{previewImage ? (
<div className='upload-image'>
<img src={previewImageSrc} alt={previewImageAlt} />
</div>
) : (
tipologia === 'file' && <Icon icon={icon} size='sm' />
)}
{tipologia === 'file' && (
<>
<p>
<span className='visually-hidden'>{elementiListItem[uploadStatus].testoVHFile}</span>
{fileName} {uploadStatus === 'success' && <span className='upload-file-weight'>{fileWeight}</span>}
</p>
<ButtonTag disabled={uploadStatus === 'success' ? true : false}>
<span className='visually-hidden'>{elementiListItem[uploadStatus].testoVHRightIcon}</span>
<Icon icon={elementiListItem[uploadStatus].rightIcon} />
</ButtonTag>
{uploadStatus === 'uploading' && <Progress value={progressValue} wrapperClassName={classesProgress} />}
</>
)}
</li>
);
};

const UploadButton: FC<InputHTMLAttributes<HTMLInputElement>> = ({ id, children, ...props }) => {
return (
<>
<input type='file' id={id} className='upload pictures-wall' multiple={true} {...props} />
<label htmlFor={id}>
<Icon icon='it-plus' />
<span>{children}</span>
</label>
</>
);
};

UploadListItem.UploadButton = UploadButton;
14 changes: 14 additions & 0 deletions src/Upload/useUploadContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createContext, useContext } from 'react';

interface UploadContextType {
tipologia: 'file' | 'gallery';
}

export const UploadContext = createContext<UploadContextType>({
tipologia: 'file'
});

export const useUploadContext = () => {
const { tipologia } = useContext(UploadContext);
return tipologia;
};
1 change: 1 addition & 0 deletions src/assets/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '*.svg';
Loading
Loading