Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:Amsterdam/design-system into fea…
Browse files Browse the repository at this point in the history
…t/DES-769-upgrade-eslint-to-v9
  • Loading branch information
RubenSibon committed Dec 13, 2024
2 parents 882866d + 0192404 commit b9e430b
Show file tree
Hide file tree
Showing 20 changed files with 637 additions and 145 deletions.
5 changes: 5 additions & 0 deletions packages/css/src/components/file-list/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- @license CC0-1.0 -->

# File List

An overview of files, showing their name, type, size, and a preview.
55 changes: 55 additions & 0 deletions packages/css/src/components/file-list/file-list.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

@use "../../common/text-rendering" as *;

@mixin reset-ul {
list-style: none;
margin-block: 0;
padding-inline: 0;
}

.ams-file-list {
display: flex;
flex-direction: column;
gap: var(--ams-file-list-gap);
padding-block: var(--ams-file-list-padding-block);

@include text-rendering;
@include reset-ul;
}

.ams-file-list__item {
display: flex;
flex-direction: row;
font-family: var(--ams-file-list-file-font-family);
font-size: var(--ams-file-list-file-font-size);
font-weight: var(--ams-file-list-file-font-weight);
gap: var(--ams-file-list-file-gap);
line-height: var(--ams-file-list-file-line-height);
}

.ams-file-list__item-preview {
display: grid;
flex: 0 0 var(--ams-file-list-file-preview-width);
place-items: center;

img {
inline-size: 100%;
min-block-size: auto;
}
}

.ams-file-list__item-info {
flex: 1;
gap: var(--ams-file-list-file-gap);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.ams-file-input__item-details {
color: var(--ams-file-list-file-details-color);
}
1 change: 1 addition & 0 deletions packages/css/src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

/* Append here */
@use "file-list/file-list";
@use "action-group/action-group";
@use "breakout/breakout";
@use "hint/hint";
Expand Down
41 changes: 41 additions & 0 deletions packages/react/src/FileList/FileList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
import { FileList } from './FileList'
import '@testing-library/jest-dom'

describe('FileList', () => {
it('renders', () => {
render(<FileList />)

const component = screen.getByRole('list')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders a design system BEM class name', () => {
render(<FileList />)

const component = screen.getByRole('list')

expect(component).toHaveClass('ams-file-list')
})

it('renders an additional class name', () => {
render(<FileList className="extra" />)

const component = screen.getByRole('list')

expect(component).toHaveClass('ams-file-list extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLOListElement>()

render(<FileList ref={ref} />)

const component = screen.getByRole('list')

expect(ref.current).toBe(component)
})
})
25 changes: 25 additions & 0 deletions packages/react/src/FileList/FileList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
import { FileListItem } from './FileListItem'

export type FileListProps = {} & PropsWithChildren<HTMLAttributes<HTMLUListElement>>

export const FileListRoot = forwardRef(
({ children, className, ...restProps }: FileListProps, ref: ForwardedRef<HTMLOListElement>) => (
<ul {...restProps} ref={ref} className={clsx('ams-file-list', className)}>
{children}
</ul>
),
)

FileListRoot.displayName = 'FileList'

export const FileList = Object.assign(FileListRoot, {
Item: FileListItem,
})
57 changes: 57 additions & 0 deletions packages/react/src/FileList/FileListItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { createRef } from 'react'
import '@testing-library/jest-dom'
import { FileListItem } from './FileListItem'

describe('FileListItem', () => {
const file = new File(['sample content'], 'sample.txt', { type: 'text/plain' })
it('renders', () => {
render(<FileListItem file={file} />)

const component = screen.getByRole('listitem')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders a design system BEM class name', () => {
render(<FileListItem file={file} />)

const component = screen.getByRole('listitem')

expect(component).toHaveClass('ams-file-list__item')
})

it('renders an additional class name', () => {
render(<FileListItem file={file} className="extra" />)

const component = screen.getByRole('listitem')

expect(component).toHaveClass('ams-file-list__item extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLLIElement>()

render(<FileListItem file={file} ref={ref} />)

const component = screen.getByRole('listitem')

expect(ref.current).toBe(component)
})

it('renders the file name', () => {
render(<FileListItem file={file} />)

expect(screen.getByText('sample.txt')).toBeInTheDocument()
})

it('calls onDelete when the remove button is clicked', () => {
const onDelete = jest.fn()
render(<FileListItem file={file} onDelete={onDelete} />)

fireEvent.click(screen.getByRole('button'))

expect(onDelete).toHaveBeenCalledTimes(1)
})
})
47 changes: 47 additions & 0 deletions packages/react/src/FileList/FileListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import { DocumentIcon } from '@amsterdam/design-system-react-icons'
import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, HTMLAttributes } from 'react'
import { Button } from '../Button'
import { Icon } from '../Icon'
import { formatFileSize } from '../common/formatFileSize'
import { formatFileType } from '../common/formatFileType'

export type FileListItemProps = {
file: File
onDelete?: () => void
} & HTMLAttributes<HTMLLIElement>

export const FileListItem = forwardRef(
({ file, onDelete, className, ...restProps }: FileListItemProps, ref: ForwardedRef<HTMLLIElement>) => (
<li {...restProps} ref={ref} className={clsx('ams-file-list__item', className)}>
<div className="ams-file-list__item-preview">
{file.type.startsWith('image/') ? (
<img src={URL.createObjectURL(file)} alt={file.name} />
) : (
<Icon svg={DocumentIcon} size="level-3" square />
)}
</div>
<div className="ams-file-list__item-info">
{file.name}
<div className="ams-file-input__item-details">
({formatFileType(file.type)}, {formatFileSize(file.size)})
</div>
</div>
{onDelete && (
<div>
<Button variant="tertiary" onClick={() => onDelete()}>
Verwijder
</Button>
</div>
)}
</li>
),
)

FileListItem.displayName = 'FileList.Item'
5 changes: 5 additions & 0 deletions packages/react/src/FileList/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- @license CC0-1.0 -->

# React File List component

[File List documentation](../../../css/src/components/file-list/README.md)
2 changes: 2 additions & 0 deletions packages/react/src/FileList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { FileList } from './FileList'
export type { FileListProps } from './FileList'
22 changes: 22 additions & 0 deletions packages/react/src/common/formatFileSize.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { formatFileSize } from './formatFileSize'

describe('formatFileSize', () => {
it('formats bytes correctly', () => {
expect(formatFileSize(500)).toBe('500 bytes')
})

it('formats kilobytes correctly', () => {
expect(formatFileSize(1024, 1)).toBe('1 kB')
expect(formatFileSize(2048, 1)).toBe('2 kB')
})

it('formats megabytes correctly', () => {
expect(formatFileSize(1048576, 1)).toBe('1 MB')
expect(formatFileSize(2097152, 1)).toBe('2 MB')
})

it('formats gigabytes correctly', () => {
expect(formatFileSize(1073741824, 1)).toBe('1 GB')
expect(formatFileSize(2147483648, 1)).toBe('2 GB')
})
})
20 changes: 20 additions & 0 deletions packages/react/src/common/formatFileSize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

/**
* @param fileSize The size of the file in bytes.
* @param precision The number of significant digits in the output.
* @returns A human readable file size
*/
export const formatFileSize = (fileSize: number, precision = 3) => {
const UNITS = ['bytes', 'kB', 'MB', 'GB']

if (fileSize === 0) return '0 bytes'

const exponent = Math.floor(Math.log10(fileSize) / 3)
const size = (fileSize / Math.pow(1000, exponent)).toPrecision(precision)

return `${size} ${UNITS[exponent]}`
}
29 changes: 29 additions & 0 deletions packages/react/src/common/formatFileType.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { formatFileType } from './formatFileType'

describe('formatFileType', () => {
it('formats image types correctly', () => {
expect(formatFileType('image/gif')).toBe('gif')
expect(formatFileType('image/jpeg')).toBe('jpg')
expect(formatFileType('image/png')).toBe('png')
})

it('formats text types correctly', () => {
expect(formatFileType('text/plain')).toBe('txt')
})

it('formats application types correctly', () => {
expect(formatFileType('application/pdf')).toBe('pdf')
expect(formatFileType('application/msword')).toBe('Word')
expect(formatFileType('application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toBe('Word')
expect(formatFileType('application/vnd.ms-excel')).toBe('Excel')
expect(formatFileType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')).toBe('Excel')
expect(formatFileType('application/vnd.ms-powerpoint')).toBe('PowerPoint')
expect(formatFileType('application/vnd.openxmlformats-officedocument.presentationml.presentation')).toBe(
'PowerPoint',
)
})

it('returns the original file type for unknown types', () => {
expect(formatFileType('unknown/type')).toBe('Document')
})
})
35 changes: 35 additions & 0 deletions packages/react/src/common/formatFileType.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

/**
*
* @param fileType
* @returns Human readable file type
*/
export const formatFileType = (fileType: string) => {
switch (fileType) {
case 'image/gif':
return 'gif'
case 'image/jpeg':
return 'jpg'
case 'image/png':
return 'png'
case 'text/plain':
return 'txt'
case 'application/pdf':
return 'pdf'
case 'application/msword':
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
return 'Word'
case 'application/vnd.ms-excel':
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
return 'Excel'
case 'application/vnd.ms-powerpoint':
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
return 'PowerPoint'
default:
return 'Document'
}
}
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

/* Append here */
export * from './FileList'
export * from './ActionGroup'
export * from './Breakout'
export * from './Hint'
Expand Down
Loading

0 comments on commit b9e430b

Please sign in to comment.