Skip to content

Commit 491cf4b

Browse files
authored
Merge pull request #404 from appwrite/feat-upload-progress-bar
feat: add progress bar support to Upload.Box
2 parents 160c7d9 + 8dcaa17 commit 491cf4b

2 files changed

Lines changed: 145 additions & 61 deletions

File tree

v2/pink-sb/src/lib/upload/Box.svelte

Lines changed: 106 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
size?: number;
2323
extension?: string;
2424
error?: string;
25+
progress?: number;
2526
status?: 'failed' | 'pending' | 'success';
2627
})[] = [];
2728
@@ -67,75 +68,100 @@
6768
<div transition:slide={{ axis: 'y', duration: 200 }}>
6869
{#each files as file}
6970
{@const fileSize = file?.size ? humanFileSize(file.size) : false}
71+
{@const hasProgress = typeof file?.progress === 'number' && file.progress > 0}
72+
{@const clampedProgress = hasProgress
73+
? Math.round(Math.min(Math.max(file.progress ?? 0, 0), 100))
74+
: 0}
75+
{@const isError = !!file?.error || file?.status === 'failed'}
7076
<section>
71-
<Stack
72-
direction="row"
73-
gap="s"
74-
justifyContent="space-between"
75-
alignItems="center"
76-
>
77-
<Stack direction="row" gap="s" style="overflow: hidden;">
78-
<Icon
79-
icon={IconDocument}
80-
color={file?.error
81-
? '--fgcolor-error'
82-
: '--fgcolor-neutral-tertiary'}
83-
/>
84-
<Stack direction="row" gap="xxs" inline style="min-width: 0;">
85-
<Text truncate>
86-
{file.name}
87-
</Text>
88-
{#if fileSize}
89-
<Text color="--fgcolor-neutral-tertiary">
90-
<span style="white-space: nowrap">
91-
({fileSize.value}
92-
{fileSize.unit})
93-
</span>
77+
<Stack direction="column" gap="xs">
78+
<Stack
79+
direction="row"
80+
gap="s"
81+
justifyContent="space-between"
82+
alignItems="center"
83+
>
84+
<Stack direction="row" gap="s" style="overflow: hidden;">
85+
<Icon
86+
icon={IconDocument}
87+
color={file?.error
88+
? '--fgcolor-error'
89+
: '--fgcolor-neutral-tertiary'}
90+
/>
91+
<Stack direction="row" gap="xxs" inline style="min-width: 0;">
92+
<Text truncate>
93+
{file.name}
9494
</Text>
95-
{/if}
95+
{#if fileSize}
96+
<Text color="--fgcolor-neutral-tertiary">
97+
<span style="white-space: nowrap">
98+
({fileSize.value}
99+
{fileSize.unit})
100+
</span>
101+
</Text>
102+
{/if}
103+
</Stack>
96104
</Stack>
97-
</Stack>
98-
<Stack direction="row" gap="xxs" inline>
99-
{#if file?.status === 'success'}
100-
<Button variant="text" icon size="s">
105+
<Stack direction="row" gap="xxs" inline>
106+
{#if file?.status === 'success'}
101107
<Icon icon={IconCheck} color="--fgcolor-success" size="s" />
102-
</Button>
103-
{:else}
104-
{#if file?.error}
105-
<Stack inline justifyContent="center">
106-
<Badge
107-
variant="secondary"
108-
type="error"
109-
content="Failed"
110-
size="s"
111-
/>
112-
</Stack>
113-
{:else if file?.status === 'pending'}
114-
<Stack inline justifyContent="center">
115-
<Badge
116-
variant="secondary"
117-
type="warning"
118-
content="Pending"
108+
{:else}
109+
{#if isError}
110+
<Stack inline justifyContent="center">
111+
<Badge
112+
variant="secondary"
113+
type="error"
114+
content="Failed"
115+
size="s"
116+
/>
117+
</Stack>
118+
{:else if hasProgress}
119+
<Text color="--fgcolor-neutral-tertiary">
120+
{clampedProgress}%
121+
</Text>
122+
{:else if file?.status === 'pending'}
123+
<Stack inline justifyContent="center">
124+
<Badge
125+
variant="secondary"
126+
type="warning"
127+
content="Pending"
128+
size="s"
129+
/>
130+
</Stack>
131+
{/if}
132+
<Button
133+
variant="text"
134+
icon
135+
size="s"
136+
on:click={() => {
137+
dispatch('remove', file);
138+
}}
139+
>
140+
<Icon
141+
icon={IconX}
142+
color="--fgcolor-neutral-tertiary"
119143
size="s"
120144
/>
121-
</Stack>
145+
</Button>
122146
{/if}
123-
<Button
124-
variant="text"
125-
icon
126-
size="s"
127-
on:click={() => {
128-
dispatch('remove', file);
129-
}}
130-
>
131-
<Icon
132-
icon={IconX}
133-
color="--fgcolor-neutral-tertiary"
134-
size="s"
135-
/>
136-
</Button>
137-
{/if}
147+
</Stack>
138148
</Stack>
149+
{#if hasProgress && file?.status !== 'success'}
150+
<div
151+
class="upload-progress-bar"
152+
class:is-error={isError}
153+
role="progressbar"
154+
aria-valuenow={clampedProgress}
155+
aria-valuemin={0}
156+
aria-valuemax={100}
157+
aria-label="{file.name} upload progress"
158+
>
159+
<div
160+
class="upload-progress-bar-fill"
161+
style="width: {clampedProgress}%"
162+
/>
163+
</div>
164+
{/if}
139165
</Stack>
140166
</section>
141167
{/each}
@@ -171,4 +197,23 @@
171197
flex-shrink: 1;
172198
}
173199
}
200+
201+
.upload-progress-bar {
202+
height: 4px;
203+
width: 100%;
204+
border-radius: var(--border-radius-XS, 4px);
205+
background: var(--bgcolor-neutral-secondary, hsl(0 0% 90%));
206+
overflow: hidden;
207+
208+
&-fill {
209+
height: 100%;
210+
border-radius: inherit;
211+
background: var(--bgcolor-accent);
212+
transition: width 0.3s ease;
213+
}
214+
215+
&.is-error &-fill {
216+
background: var(--bgcolor-error, hsl(0 70% 55%));
217+
}
218+
}
174219
</style>

v2/pink-sb/src/stories/upload/Box.stories.svelte

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,38 @@
2828
status: 'pending'
2929
}
3030
];
31+
32+
let filesWithProgress = [
33+
{
34+
name: 'uploading-file.png',
35+
extension: 'png',
36+
size: 15728640,
37+
status: 'pending',
38+
progress: 45
39+
},
40+
{
41+
name: 'almost-done.pdf',
42+
extension: 'pdf',
43+
size: 8388608,
44+
status: 'pending',
45+
progress: 92
46+
},
47+
{
48+
name: 'completed-file.jpg',
49+
extension: 'jpg',
50+
size: 41024,
51+
status: 'success',
52+
progress: 100
53+
},
54+
{
55+
name: 'failed-upload.zip',
56+
extension: 'zip',
57+
size: 52428800,
58+
status: 'failed',
59+
error: 'File exceeds size limit',
60+
progress: 60
61+
}
62+
];
3163
</script>
3264

3365
<script>
@@ -44,3 +76,10 @@
4476
files
4577
}}
4678
/>
79+
80+
<Story
81+
name="With Progress"
82+
args={{
83+
files: filesWithProgress
84+
}}
85+
/>

0 commit comments

Comments
 (0)