Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
fda2780
Image upload component wip
Magnusrm Aug 29, 2025
6d64978
update cropping component
Magnusrm Aug 29, 2025
aefafc0
add circle config
Magnusrm Sep 1, 2025
35af5c2
move eventlisteners to canvas
Magnusrm Sep 1, 2025
7f3fe76
explicit use of constrainToArea
Magnusrm Sep 1, 2025
daf3f37
further refactor image upload component
Magnusrm Sep 1, 2025
12e2bca
add common utils for draw and handlecrop functions + variable viewpor…
lassopicasso Sep 2, 2025
f78e84d
add common utils for draw and handlecrop functions + variable viewpor…
lassopicasso Sep 2, 2025
530b1a1
extract functions to utils (#3663)
Magnusrm Sep 3, 2025
7e6c115
implement cropAsConfig prop into viewport config (#3662)
lassopicasso Sep 3, 2025
741579f
prevent scrolling (#3658)
lassopicasso Sep 3, 2025
d15cc89
Feat/refine image upload buttons according to design (#3660)
lassopicasso Sep 3, 2025
211edd7
Move the image cropper into a card (#3665)
Magnusrm Sep 3, 2025
6602e9c
clean up css and style closer to figma design (#3669)
Magnusrm Sep 4, 2025
a82fe47
added save and cancel buttons with functionality (some left for save …
lassopicasso Sep 5, 2025
c336050
Redesign slider + buttons in img controller (#3671)
lassopicasso Sep 5, 2025
658e9fd
Extract canvas and effects into its own component (#3674)
Magnusrm Sep 5, 2025
bcaf56f
feat/ handle saved image and new display when image is saved (#3675)
lassopicasso Sep 8, 2025
6b65f5c
add check at higher level for stored image (#3680)
lassopicasso Sep 9, 2025
420fc34
zoom to the center of viewport (#3681)
lassopicasso Sep 9, 2025
38a6ada
Feat/custom crop area config + renaming (#3682)
lassopicasso Sep 9, 2025
a115242
Feat/remove-dropzone-icon-and-change-background (#3679)
Magnusrm Sep 9, 2025
1629e7f
fix zooming limits (#3684)
lassopicasso Sep 9, 2025
0d8477f
fix: active pointer canvas and keyboard support on change image (#3683)
lassopicasso Sep 10, 2025
7d33561
update icons
Magnusrm Sep 10, 2025
821e4d1
Add validationmessages for imageupload size and types (#3687)
Magnusrm Sep 10, 2025
805df2f
Feat/use-language-for-component-texts (#3689)
Magnusrm Sep 10, 2025
24da4ac
Display uploaded image+more (#3690)
lassopicasso Sep 10, 2025
5976437
refactor: calculations (#3697)
lassopicasso Sep 11, 2025
fbb9514
feat: Support config (#3700)
lassopicasso Sep 12, 2025
38db36c
small adjustment
lassopicasso Sep 16, 2025
b57f83c
change config from one object to 3 props
lassopicasso Sep 16, 2025
acedcd9
Merge remote-tracking branch 'origin/main' into feat/image-upload-com…
Magnusrm Sep 17, 2025
2484b05
New dropzone using the new figma design (#3708)
Magnusrm Sep 17, 2025
abb0710
clean up comment, fix text-alignment
Magnusrm Sep 17, 2025
97d9977
remove redundant css class
Magnusrm Sep 17, 2025
ed86243
fix cropped preview
Magnusrm Sep 17, 2025
26b53c9
feat: Handle canvas size based on grid or mobile viewport and use fil…
lassopicasso Sep 17, 2025
1681b0a
add unit tests and fix validation (#3714)
lassopicasso Sep 17, 2025
c0c0cb8
Add displaydata expression test to ImageUpload (#3719)
Magnusrm Sep 18, 2025
0e685d9
adjust to equal height and width if circle and they are uneven
lassopicasso Sep 18, 2025
cfd3a11
fix duplicate ids and use ref instead of getElementById
Magnusrm Sep 18, 2025
ee0aab3
clean up css
Magnusrm Sep 18, 2025
76e1893
fix sizing
lassopicasso Sep 18, 2025
1f948f4
Merge branch 'feat/image-upload-component' of https://github.com/Alti…
lassopicasso Sep 18, 2025
be47527
use unique id, remove preventDefault for dropzone
Magnusrm Sep 18, 2025
753184f
remove a type-casting
Magnusrm Sep 18, 2025
e9aa62a
add accesible title to reset zoom button
Magnusrm Sep 18, 2025
f7958fe
remove redundant css
Magnusrm Sep 18, 2025
0a02587
fix change image button
Magnusrm Sep 18, 2025
6f79243
add cypress tests for image upload component (#3721)
Magnusrm Sep 18, 2025
ae76f44
make filetypes the same
Magnusrm Sep 19, 2025
99a3dc6
make filetypes match
Magnusrm Sep 19, 2025
0c15edf
support summary2 (#3725)
lassopicasso Sep 19, 2025
825d32c
support validFileEndings config
lassopicasso Sep 25, 2025
edf4926
merge
lassopicasso Sep 25, 2025
4065306
replace chess background when image is saved
lassopicasso Sep 25, 2025
715a1b4
fix edge case where canvas gets too low between 1160-1200px when grid…
lassopicasso Sep 26, 2025
4afbe86
simplify responsivness and keep fixed size of the canvas
lassopicasso Sep 26, 2025
dc21ba9
set min zoom as default
lassopicasso Sep 26, 2025
d57d892
remove error message when save
lassopicasso Sep 26, 2025
b6e0b1e
add tests for imageupload in summary2
lassopicasso Sep 29, 2025
e683284
reverse validfileendings config support
lassopicasso Oct 1, 2025
8d6ac3d
add cypress test where replacing image
lassopicasso Oct 1, 2025
3823a85
react recommendations
lassopicasso Oct 1, 2025
0294604
add utility tests
lassopicasso Oct 2, 2025
51d8654
Merge branch 'main' into feat/image-upload-component
lassopicasso Oct 2, 2025
221082c
remove some comments
lassopicasso Oct 2, 2025
20c479c
add specific image types to be allowed
lassopicasso Oct 3, 2025
019ea8f
small adjustment
lassopicasso Oct 6, 2025
d0993c6
new summary of image upload, show image
lassopicasso Oct 6, 2025
8e526e7
some text changes
lassopicasso Oct 7, 2025
5be3187
update tests for new summary of image upload
lassopicasso Oct 7, 2025
057189a
check for possible animation file type and inform user that only firs…
lassopicasso Oct 7, 2025
5f0dbcf
support readOnly config
lassopicasso Oct 7, 2025
1d068cd
fix test
lassopicasso Oct 7, 2025
50f6b68
rabbit feedback
lassopicasso Oct 7, 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: 0 additions & 1 deletion src/app-components/Card/Card.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.mediaCard {
padding: 0;
margin-bottom: -7px;
}
.mediaCard img {
object-fit: cover;
Expand Down
6 changes: 6 additions & 0 deletions src/app-components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type AppCardProps = {
color?: Parameters<typeof Card>[0]['color'];
children?: React.ReactNode;
variant?: 'tinted' | 'default';
className?: string;
ref?: React.Ref<HTMLDivElement>;
};

export function AppCard({
Expand All @@ -24,11 +26,15 @@ export function AppCard({
mediaPosition = 'top',
children,
variant = 'tinted',
className,
ref,
}: AppCardProps) {
return (
<Card
data-color={color}
variant={variant}
className={className}
ref={ref}
>
{media && mediaPosition === 'top' && <Card.Block className={classes.mediaCard}>{media}</Card.Block>}
<Card.Block>
Expand Down
26 changes: 16 additions & 10 deletions src/app-components/Dropzone/Dropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,21 @@ import type { FileRejection } from 'react-dropzone';
import cn from 'classnames';

import classes from 'src/app-components/Dropzone/Dropzone.module.css';
import { mapExtensionToAcceptMime } from 'src/app-components/Dropzone/mapExtensionToAcceptMime';

type MaxFileSize = {
sizeInMB: number;
text: string;
};

export type IDropzoneComponentProps = {
export type IDropzoneProps = {
id: string;
maxFileSize?: MaxFileSize;
readOnly: boolean;
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onDrop: (acceptedFiles: File[], rejectedFiles: FileRejection[]) => void;
onDragActiveChange?: (isDragActive: boolean) => void;
hasValidationMessages: boolean;
hasCustomFileEndings?: boolean;
validFileEndings?: string | string[];
acceptedFiles?: { [key: string]: string[] };
labelId?: string;
describedBy?: string;
className?: string;
Expand All @@ -35,15 +34,15 @@ export function Dropzone({
readOnly,
onClick,
onDrop,
onDragActiveChange,
hasValidationMessages,
hasCustomFileEndings,
validFileEndings,
acceptedFiles,
labelId,
children,
className,
describedBy,
...rest
}: IDropzoneComponentProps): React.JSX.Element {
}: IDropzoneProps): React.JSX.Element {
const maxSizeLabelId = `file-upload-max-size-${id}`;
const describedby =
[describedBy, maxFileSize?.sizeInMB ? maxSizeLabelId : undefined].filter(Boolean).join(' ') || undefined;
Expand All @@ -52,9 +51,16 @@ export function Dropzone({
onDrop,
maxSize: maxFileSize && maxFileSize.sizeInMB * bytesInOneMB,
disabled: readOnly,
accept:
hasCustomFileEndings && validFileEndings !== undefined ? mapExtensionToAcceptMime(validFileEndings) : undefined,
accept: acceptedFiles,
});

// set drag active state in parent component if callback is provided
React.useEffect(() => {
if (onDragActiveChange) {
onDragActiveChange(isDragActive);
}
}, [isDragActive, onDragActiveChange]);

return (
<div>
{maxFileSize && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "Display value of FileUpload component",
"expression": [
"displayValue",
"image"
],
"context": {
"component": "image",
"currentLayout": "Page"
},
"expects": "my-image.jpg",
"layouts": {
"Page": {
"$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"id": "image",
"type": "ImageUpload"
}
]
}
}
},
"instanceDataElements": [
{
"id": "bb2b2222-2b22-2b22-222b-222222222222",
"instanceGuid": "aa1a1111-1a11-1a11-111a-111111111111",
"dataType": "image",
"filename": "my-image.jpg",
"contentType": "image/jpeg",
"blobStoragePath": "",
"size": 100,
"locked": false,
"refs": [],
"created": "2021-01-01T00:00:00.000Z",
"createdBy": "testUser",
"lastChanged": "2021-01-01T00:00:00.000Z",
"lastChangedBy": "testUser"
}
]
}
10 changes: 10 additions & 0 deletions src/language/texts/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ export function en() {
'iframe_component.unsupported_browser_title': 'Your browser is unsupported',
'iframe_component.unsupported_browser':
'Your browser does not support iframes that use srcdoc. This may result in not being able to see all the content intended to be displayed here. We recommend trying a different browser.',
'image_upload_component.animated_warning': 'If the image is animated, only the first frame will be shown.',
'image_upload_component.button_change': 'Change image',
'image_upload_component.button_delete': 'Delete image',
'image_upload_component.button_save': 'Save image',
'image_upload_component.slider_zoom': 'Zoom',
'image_upload_component.summary_empty': "You haven't uploaded an image",
'image_upload_component.reset': 'Reset position and zoom',
'image_upload_component.error_invalid_file_type': 'Invalid file format. Please upload an image file.',
'image_upload_component.error_file_size_exceeded': 'File size exceeds 10MB limit.',
'image_upload_component.valid_file_types': 'Image files only',
'input_components.remaining_characters': 'You have %d characters left',
'input_components.exceeded_max_limit': 'You have exceeded the maximum limit with %d characters',
'instance_selection.changed_by': 'Changed by',
Expand Down
12 changes: 11 additions & 1 deletion src/language/texts/nb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function nb() {
'form_filler.file_upload_valid_file_format_all': 'alle',
'form_filler.file_uploader_add_attachment': 'Legg til flere vedlegg',
'form_filler.file_uploader_drag': 'Dra og slipp eller',
'form_filler.file_uploader_find': 'let etter fil',
'form_filler.file_uploader_find': 'finn fil',
'form_filler.file_uploader_list_delete': 'Slett vedlegg',
'form_filler.file_uploader_delete_warning': 'Er du sikker på at du vil slette dette vedlegget?',
'form_filler.file_uploader_delete_button_confirm': 'Ja, slett vedlegg',
Expand Down Expand Up @@ -209,6 +209,16 @@ export function nb() {
'iframe_component.unsupported_browser_title': 'Nettleseren din støttes ikke',
'iframe_component.unsupported_browser':
'Nettleseren du bruker støtter ikke iframes som benytter seg av srcdoc. Dette kan føre til at du ikke ser all innholdet som er ment å vises her. Vi anbefaler deg å prøve en annen nettleser.',
'image_upload_component.animated_warning': 'Hvis bildet er animert, vises bare det første bildet.',
'image_upload_component.button_change': 'Bytt bilde',
'image_upload_component.button_delete': 'Slett bildet',
'image_upload_component.button_save': 'Lagre bilde',
'image_upload_component.slider_zoom': 'Tilpass bildet',
'image_upload_component.summary_empty': 'Du har ikke lastet opp noe bilde',
'image_upload_component.reset': 'Tilbakestill zoom og plassering',
'image_upload_component.error_invalid_file_type': 'Ugyldig filformat. Last opp en bildefil.',
'image_upload_component.error_file_size_exceeded': 'Filen er for stor. Største tillatte filstørrelse er 10MB.',
'image_upload_component.valid_file_types': 'Bildefiler er tillatt',
'input_components.remaining_characters': 'Du har %d tegn igjen',
'input_components.exceeded_max_limit': 'Du har overskredet maks antall tegn med %d',
'instance_selection.changed_by': 'Endret av',
Expand Down
12 changes: 11 additions & 1 deletion src/language/texts/nn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function nn() {
'form_filler.file_upload_valid_file_format_all': 'alle',
'form_filler.file_uploader_add_attachment': 'Legg til fleire vedlegg',
'form_filler.file_uploader_drag': 'Dra og slepp eller',
'form_filler.file_uploader_find': 'leit etter fil',
'form_filler.file_uploader_find': 'finn fil',
'form_filler.file_uploader_list_delete': 'Slett vedlegg',
'form_filler.file_uploader_delete_warning': 'Er du sikker på at du vil sletta dette vedlegget?',
'form_filler.file_uploader_delete_button_confirm': 'Ja, slett vedlegg',
Expand Down Expand Up @@ -209,6 +209,16 @@ export function nn() {
'iframe_component.unsupported_browser_title': 'Nettlesaren din støttas ikkje',
'iframe_component.unsupported_browser':
'Nettlesaren di støttar ikkje iframes som brukar srcdoc. Dette kan føre til at du ikkje ser all innhaldet som er meint å visast her. Vi anbefalar deg å prøve ein annan nettlesar.',
'image_upload_component.animated_warning': 'Hvis bildet er animert, vises bare det første bildet.',
'image_upload_component.button_change': 'Bytt bilde',
'image_upload_component.button_delete': 'Slett bildet',
'image_upload_component.button_save': 'Lagre bilde',
'image_upload_component.slider_zoom': 'Tilpass bildet',
'image_upload_component.summary_empty': 'Du har ikkje lasta opp noko bilde',
'image_upload_component.reset': 'Tilbakestill zoom og plassering',
'image_upload_component.error_invalid_file_type': 'Ugyldig filformat. Last opp ein bildefil.',
'image_upload_component.error_file_size_exceeded': 'Fila er for stor. Største tillatte filstorleik er 10MB.',
'image_upload_component.valid_file_types': 'Bildefiler er tillatne',
'input_components.remaining_characters': 'Du har %d teikn igjen',
'input_components.exceeded_max_limit': 'Du har overskride maks teikn med %d',
'instance_selection.changed_by': 'Endra av',
Expand Down
4 changes: 4 additions & 0 deletions src/layout/Cards/Cards.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ video {
.cardMedia:last-of-type {
margin-top: auto;
}

.mediaCard {
margin-bottom: -7px;
}
2 changes: 2 additions & 0 deletions src/layout/Cards/Cards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AppCard } from 'src/app-components/Card/Card';
import { Flex } from 'src/app-components/Flex/Flex';
import { Lang } from 'src/features/language/Lang';
import { CardProvider } from 'src/layout/Cards/CardContext';
import classes from 'src/layout/Cards/Cards.module.css';
import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper';
import { GenericComponent } from 'src/layout/GenericComponent';
import { useHasCapability } from 'src/utils/layout/canRenderIn';
Expand Down Expand Up @@ -117,6 +118,7 @@ function CardItem({ baseComponentId, parentBaseId, isMedia, minMediaHeight }: Ca
<div
data-componentid={id}
data-componentbaseid={baseComponentId}
className={classes.mediaCard}
>
<GenericComponent
baseComponentId={baseComponentId}
Expand Down
2 changes: 1 addition & 1 deletion src/layout/FileUpload/FileUploadComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ describe('File uploading components', () => {
});

expect(screen.getByRole('presentation', { name: /attachment-title/i }).textContent).toMatch(
'Dra og slipp eller let etter filTillatte filformater er: alle',
'Dra og slipp eller finn filTillatte filformater er: alle',
);
});

Expand Down
7 changes: 4 additions & 3 deletions src/layout/FileUpload/FileUploadComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CloudUpIcon } from '@navikt/aksel-icons';
import cn from 'classnames';

import { Dropzone } from 'src/app-components/Dropzone/Dropzone';
import { mapExtensionToAcceptMime } from 'src/app-components/Dropzone/mapExtensionToAcceptMime';
import { getDescriptionId, getLabelId, Label } from 'src/components/label/Label';
import { useAddRejectedAttachments, useAttachmentsFor, useAttachmentsUploader } from 'src/features/attachments/hooks';
import { Lang } from 'src/features/language/Lang';
Expand Down Expand Up @@ -59,7 +60,8 @@ export function FileUploadComponent({
const validations = useUnifiedValidationsForNode(baseComponentId).filter(
(v) => !('attachmentId' in v) || !v.attachmentId,
);

const filesToAccept =
hasCustomFileEndings && validFileEndings !== undefined ? mapExtensionToAcceptMime(validFileEndings) : undefined;
const { options, isFetching } = useGetOptions(baseComponentId, 'single');
const indexedId = useIndexedId(baseComponentId);

Expand Down Expand Up @@ -139,8 +141,7 @@ export function FileUploadComponent({
onClick={(e) => e.preventDefault()}
onDrop={handleDrop}
hasValidationMessages={hasValidationErrors(validations)}
hasCustomFileEndings={hasCustomFileEndings}
validFileEndings={validFileEndings}
acceptedFiles={filesToAccept}
labelId={textResourceBindings?.title ? getLabelId(id) : undefined}
describedBy={ariaDescribedBy}
>
Expand Down
4 changes: 2 additions & 2 deletions src/layout/FileUpload/FileUploadTable/FileTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export function FileTable({
isSummary,
isFetching,
}: FileTableProps): React.JSX.Element | null {
const { textResourceBindings, type, readOnly } = useItemWhenType<'FileUpload' | 'FileUploadWithTag'>(
const { textResourceBindings, type, readOnly } = useItemWhenType<'FileUpload' | 'FileUploadWithTag' | 'ImageUpload'>(
baseComponentId,
(t) => t === 'FileUpload' || t === 'FileUploadWithTag',
(t) => t === 'FileUpload' || t === 'FileUploadWithTag' || t === 'ImageUpload',
);
const hasTag = type === 'FileUploadWithTag';
const pdfModeActive = usePdfModeActive();
Expand Down
22 changes: 22 additions & 0 deletions src/layout/ImageUpload/ImageCanvas.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.canvas {
display: block;
position: relative;
left: 50%;
transform: translateX(-50%);
cursor: grab;
touch-action: none;
}

.canvas:active {
cursor: grabbing;
}

.previewBackground {
background-color: #f4f5f6; /* Following does not exist in v1: var(--ds-color-neutral-background-subtle); */
height: 320px;
display: flex;
justify-content: center;
align-items: center;
padding: var(--ds-size-4);
box-sizing: border-box;
}
Loading
Loading