Skip to content

Commit 5ea2ef4

Browse files
authored
Merge pull request #311 from dd3tech/feat/file-list
Feat: add file list components and redisign of input file
2 parents cbb6395 + 41e0d4d commit 5ea2ef4

20 files changed

Lines changed: 1059 additions & 108 deletions

exports.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,21 @@
152152
"module": "./dist/esm/components/FeedBackBox/index.js",
153153
"default": "./dist/esm/components/FeedBackBox/index.js"
154154
},
155+
"./FileItem": {
156+
"require": "./dist/cjs/components/Form/File/FileItem.js",
157+
"module": "./dist/esm/components/Form/File/FileItem.js",
158+
"default": "./dist/esm/components/Form/File/FileList.js"
159+
},
160+
"./FileImageItem": {
161+
"require": "./dist/cjs/components/Form/File/FileImageItem.js",
162+
"module": "./dist/esm/components/Form/File/FileImageItem.js",
163+
"default": "./dist/esm/components/Form/File/FileImageItem.js"
164+
},
165+
"./FileList": {
166+
"require": "./dist/cjs/components/Form/File/FileList.js",
167+
"module": "./dist/esm/components/Form/File/FileList.js",
168+
"default": "./dist/esm/components/Form/File/FileList.js"
169+
},
155170
"./FileViewer": {
156171
"require": "./dist/cjs/components/FileViewer/index.js",
157172
"module": "./dist/esm/components/FileViewer/index.js",
@@ -442,6 +457,9 @@
442457
"Dropdown": ["./dist/esm/components/Dropdown/index.d.ts"],
443458
"DynamicHeroIcon": ["./dist/esm/common/DynamicHeroIcon/index.d.ts"],
444459
"FeedBackBox": ["./dist/esm/components/FeedBackBox/index.d.ts"],
460+
"FileItem": ["./dist/esm/components/Form/File/FileItem.d.ts"],
461+
"FileImageItem": ["./dist/esm/components/Form/File/FileImageItem.d.ts"],
462+
"FileList": ["./dist/esm/components/Form/File/FileList.d.ts"],
445463
"FileViewer": ["./dist/esm/components/FileViewer/index.d.ts"],
446464
"Filters": ["./dist/esm/components/Filters/index.d.ts"],
447465
"FormControl": ["./dist/esm/components/FormControl/index.d.ts"],

package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,21 @@
288288
"module": "./dist/esm/components/FeedBackBox/index.js",
289289
"default": "./dist/esm/components/FeedBackBox/index.js"
290290
},
291+
"./FileItem": {
292+
"require": "./dist/cjs/components/Form/File/FileItem.js",
293+
"module": "./dist/esm/components/Form/File/FileItem.js",
294+
"default": "./dist/esm/components/Form/File/FileList.js"
295+
},
296+
"./FileImageItem": {
297+
"require": "./dist/cjs/components/Form/File/FileImageItem.js",
298+
"module": "./dist/esm/components/Form/File/FileImageItem.js",
299+
"default": "./dist/esm/components/Form/File/FileImageItem.js"
300+
},
301+
"./FileList": {
302+
"require": "./dist/cjs/components/Form/File/FileList.js",
303+
"module": "./dist/esm/components/Form/File/FileList.js",
304+
"default": "./dist/esm/components/Form/File/FileList.js"
305+
},
291306
"./FileViewer": {
292307
"require": "./dist/cjs/components/FileViewer/index.js",
293308
"module": "./dist/esm/components/FileViewer/index.js",
@@ -636,6 +651,15 @@
636651
"FeedBackBox": [
637652
"./dist/esm/components/FeedBackBox/index.d.ts"
638653
],
654+
"FileItem": [
655+
"./dist/esm/components/Form/File/FileItem.d.ts"
656+
],
657+
"FileImageItem": [
658+
"./dist/esm/components/Form/File/FileImageItem.d.ts"
659+
],
660+
"FileList": [
661+
"./dist/esm/components/Form/File/FileList.d.ts"
662+
],
639663
"FileViewer": [
640664
"./dist/esm/components/FileViewer/index.d.ts"
641665
],
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { vi } from 'vitest'
2+
import { render, fireEvent } from '@testing-library/react'
3+
import FileImageItem from './FileImageItem'
4+
5+
const defaultProps = {
6+
name: 'image.jpg',
7+
type: 'image/jpeg',
8+
src: 'https://example.com/image.jpg',
9+
title: 'Test Image',
10+
description: 'This is a test image description',
11+
onChangeTitle: vi.fn(),
12+
onChangeDescription: vi.fn()
13+
}
14+
15+
describe('<FileImageItem>', () => {
16+
it('renders component correctly', () => {
17+
const { getByRole } = render(<FileImageItem {...defaultProps} />)
18+
19+
expect(getByRole('file-item-image')).toBeDefined()
20+
})
21+
22+
it('displays the image with correct src', () => {
23+
const { getByRole } = render(<FileImageItem {...defaultProps} />)
24+
const image = getByRole('file-image')
25+
26+
expect(image).toHaveAttribute('src', 'https://example.com/image.jpg')
27+
})
28+
29+
it('calls onChangeTitle when title input changes', () => {
30+
const { getByDisplayValue } = render(<FileImageItem {...defaultProps} />)
31+
const titleInput = getByDisplayValue('Test Image')
32+
33+
fireEvent.change(titleInput, {
34+
target: { value: 'Updated Title', name: 'title' }
35+
})
36+
37+
expect(defaultProps.onChangeTitle).toHaveBeenCalled()
38+
expect(getByDisplayValue('Updated Title')).toBeDefined()
39+
})
40+
41+
it('calls onChangeDescription when description input changes', () => {
42+
const { getByDisplayValue } = render(<FileImageItem {...defaultProps} />)
43+
const descriptionInput = getByDisplayValue(
44+
'This is a test image description'
45+
)
46+
47+
fireEvent.change(descriptionInput, {
48+
target: { value: 'Updated description', name: 'description' }
49+
})
50+
51+
expect(defaultProps.onChangeDescription).toHaveBeenCalled()
52+
expect(getByDisplayValue('Updated description')).toBeDefined()
53+
})
54+
55+
it('displays character count for description', () => {
56+
const { getByRole, getByDisplayValue } = render(
57+
<FileImageItem {...defaultProps} />
58+
)
59+
const counter = getByRole('file-description-length')
60+
const descriptionInput = getByDisplayValue(
61+
'This is a test image description'
62+
)
63+
64+
fireEvent.change(descriptionInput, {
65+
target: { value: 'Short', name: 'description' }
66+
})
67+
68+
expect(counter).toHaveTextContent('5/100')
69+
})
70+
})
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import {
2+
ChangeEvent,
3+
ChangeEventHandler,
4+
CSSProperties,
5+
ReactNode,
6+
useState
7+
} from 'react'
8+
import { Flex } from 'components/Layout'
9+
import Text from 'components/Typography'
10+
import BaseInput from '../Input/BaseInput'
11+
import TextArea from '../TextArea'
12+
import FileItem from './FileItem'
13+
14+
export interface FileImageItemProps {
15+
/**
16+
* Source URL for the image preview
17+
*/
18+
src: string
19+
/**
20+
* Title of the file
21+
*/
22+
title: string
23+
/**
24+
* Description of the file
25+
*/
26+
description: string
27+
/**
28+
* Placeholder text for the title input
29+
*/
30+
titlePlaceholder?: string
31+
/**
32+
* Placeholder text for the description input
33+
*/
34+
descriptionPlaceholder?: string
35+
/**
36+
* Label for the title input
37+
*/
38+
titleLabel?: string
39+
/**
40+
* Label for the description input
41+
*/
42+
descriptionLabel?: string
43+
/**
44+
* Maximum length for the description text
45+
*/
46+
descriptionMaxLength?: number
47+
/**
48+
* Name of the file to display
49+
*/
50+
name: string
51+
/**
52+
* Type/format of the file
53+
*/
54+
type: string
55+
/**
56+
* Size of the file in KB (optional)
57+
*/
58+
fileSize?: number
59+
/**
60+
* Additional CSS class names
61+
*/
62+
className?: string
63+
/**
64+
* Custom CSS styles
65+
*/
66+
style?: CSSProperties
67+
/**
68+
* Children components (typically action buttons)
69+
*/
70+
children?: ReactNode
71+
/**
72+
* Handler for title changes
73+
*/
74+
onChangeTitle: ChangeEventHandler<HTMLInputElement>
75+
/**
76+
* Handler for description changes
77+
*/
78+
onChangeDescription: ChangeEventHandler<HTMLTextAreaElement>
79+
}
80+
81+
interface stateType {
82+
title: string
83+
description: string
84+
}
85+
86+
export const FileImageItem = ({
87+
name,
88+
type,
89+
fileSize,
90+
src,
91+
title,
92+
description,
93+
titlePlaceholder,
94+
descriptionPlaceholder,
95+
titleLabel,
96+
descriptionLabel,
97+
descriptionMaxLength = 100,
98+
className,
99+
style,
100+
children,
101+
onChangeTitle,
102+
onChangeDescription
103+
}: FileImageItemProps) => {
104+
const [state, setState] = useState<stateType>({
105+
title: title || '',
106+
description: description || ''
107+
})
108+
109+
const handleChange = (
110+
e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>
111+
) => {
112+
setState((prev) => ({
113+
...prev,
114+
[e.target.name]: e.target.value
115+
}))
116+
e.target.name === 'title' &&
117+
onChangeTitle(e as ChangeEvent<HTMLInputElement>)
118+
e.target.name === 'description' &&
119+
onChangeDescription(e as ChangeEvent<HTMLTextAreaElement>)
120+
}
121+
122+
return (
123+
<Flex
124+
role="file-item-image"
125+
className="flex-col bg-white rounded-lg pb-2"
126+
gap="2"
127+
>
128+
<FileItem
129+
name={name}
130+
type={type}
131+
fileSize={fileSize}
132+
className={className}
133+
style={style}
134+
>
135+
{children}
136+
</FileItem>
137+
<div
138+
className="px-4 w-full h-full max-h-[164px] grid gap-4"
139+
style={{
140+
gridTemplateColumns: '350px 1fr'
141+
}}
142+
>
143+
<figure
144+
className="w-full border border-gray-100 rounded-lg overflow-hidden"
145+
style={{ maxWidth: '350px', height: '164px' }}
146+
>
147+
<img
148+
role="file-image"
149+
src={src}
150+
alt="File"
151+
className="w-full h-full object-cover"
152+
/>
153+
</figure>
154+
<Flex className="flex-col" gap="4">
155+
<BaseInput
156+
name="title"
157+
style={{ marginTop: 0 }}
158+
label={titleLabel}
159+
placeholder={titlePlaceholder}
160+
value={state.title}
161+
onChange={handleChange}
162+
/>
163+
<Flex className="flex-col gap-2">
164+
<TextArea
165+
name="description"
166+
maxLength={descriptionMaxLength}
167+
label={descriptionLabel}
168+
placeholder={descriptionPlaceholder}
169+
value={state.description}
170+
onChange={handleChange}
171+
/>
172+
<Text
173+
role="file-description-length"
174+
size="xs"
175+
textMuted500
176+
className="text-right"
177+
>
178+
{state.description?.length || 0}/{descriptionMaxLength}
179+
</Text>
180+
</Flex>
181+
</Flex>
182+
</div>
183+
</Flex>
184+
)
185+
}
186+
187+
export default FileImageItem
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { render } from '@testing-library/react'
2+
import FileItem from './FileItem'
3+
4+
const defaultProps = {
5+
name: 'test-file.pdf',
6+
type: 'application/pdf',
7+
fileSize: 1024
8+
}
9+
10+
describe('<FileItem>', () => {
11+
it('should render correctly', () => {
12+
const { getByRole } = render(<FileItem {...defaultProps} />)
13+
14+
expect(getByRole('file-item')).toBeDefined()
15+
})
16+
17+
it('displays file size when provided', () => {
18+
const { getByRole } = render(<FileItem {...defaultProps} />)
19+
20+
expect(getByRole('file-size')).toBeDefined()
21+
})
22+
23+
it('renders children components', () => {
24+
const { getByTestId } = render(
25+
<FileItem {...defaultProps}>
26+
<button data-testid="test-button">Action</button>
27+
</FileItem>
28+
)
29+
30+
expect(getByTestId('test-button')).toBeInTheDocument()
31+
})
32+
})

0 commit comments

Comments
 (0)