Skip to content

Commit c9d5192

Browse files
committed
feat/upload-post
1 parent 60bf4eb commit c9d5192

File tree

9 files changed

+209
-60
lines changed

9 files changed

+209
-60
lines changed

platforms/metagram/src/lib/fragments/InputFile/InputFile.svelte

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
11
<script lang="ts">
2+
import { cn } from '$lib/utils';
23
import { Album01Icon } from '@hugeicons/core-free-icons';
34
import { HugeiconsIcon } from '@hugeicons/svelte';
5+
import type { HTMLLabelAttributes } from 'svelte/elements';
46
5-
interface IInputFileProps {
7+
interface IInputFileProps extends HTMLLabelAttributes {
68
files: FileList | undefined;
79
accept: string;
8-
label: string;
9-
cancelLabel: string;
10-
oncancel: () => void;
10+
label?: string;
11+
multiple?: boolean;
12+
cancelLabel?: string;
13+
oncancel?: () => void;
1114
}
1215
1316
let {
1417
files = $bindable(),
1518
accept = 'image/*',
1619
label = 'Click to upload a photo',
20+
multiple = false,
1721
cancelLabel = 'Delete upload',
18-
oncancel
22+
oncancel,
23+
...restProps
1924
}: IInputFileProps = $props();
2025
2126
const uniqueId = Math.random().toString().split('.')[1];
2227
let inputFile: HTMLInputElement | undefined = $state();
28+
29+
let cBase =
30+
'bg-grey text-black-400 font-geist flex min-h-[158px] w-full items-center justify-center rounded-4xl text-base font-normal';
2331
</script>
2432

25-
<input id={uniqueId} type="file" bind:files class="hidden" {accept} bind:this={inputFile} />
33+
<input
34+
id={uniqueId}
35+
type="file"
36+
bind:files
37+
class="hidden"
38+
{multiple}
39+
{accept}
40+
bind:this={inputFile}
41+
/>
2642

27-
<label
28-
for={uniqueId}
29-
class="bg-grey text-black-400 font-geist flex h-[158px] w-full items-center justify-center rounded-4xl text-base font-normal"
30-
>
43+
<label {...restProps} for={uniqueId} class={cn([cBase, restProps.class].join(' '))}>
3144
{#if files}
3245
<div class="flex flex-col items-center gap-2">
3346
<HugeiconsIcon size="24px" icon={Album01Icon} color="var(--color-black-600)" />

platforms/metagram/src/lib/fragments/Modal/Modal.svelte

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,22 @@
77
interface IDrawerProps extends HTMLAttributes<HTMLDivElement> {
88
modalEl?: HTMLDivElement;
99
paneModal?: CupertinoPane;
10+
initialBreak?: 'bottom' | 'top' | 'middle';
11+
handleDismiss?: () => void;
1012
children?: Snippet;
1113
}
1214
1315
let {
1416
modalEl = $bindable(),
1517
paneModal = $bindable(),
1618
children = undefined,
19+
initialBreak,
20+
handleDismiss,
1721
...restProps
1822
}: IDrawerProps = $props();
1923
20-
function present() {
21-
if (paneModal) paneModal.present({ animate: true });
22-
}
23-
2424
function dismiss() {
25+
handleDismiss && handleDismiss();
2526
if (paneModal) paneModal.destroy({ animate: true });
2627
}
2728
@@ -30,25 +31,25 @@
3031
paneModal = new CupertinoPane(modalEl, {
3132
modal: true,
3233
backdrop: true,
34+
backdropBlur: true,
3335
backdropOpacity: 0.4,
36+
animationType: 'ease',
37+
animationDuration: 300,
3438
fitHeight: true,
39+
bottomClose: true,
3540
showDraggable: true,
3641
buttonDestroy: false,
42+
initialBreak: initialBreak,
3743
breaks: {
38-
bottom: { enabled: true, height: 250 }
44+
top: { enabled: true, height: 600 },
45+
middle: { enabled: true, height: 400 },
46+
bottom: { enabled: true, height: 200 }
3947
},
40-
initialBreak: 'bottom',
4148
cssClass: 'modal',
4249
events: {
4350
onBackdropTap: () => dismiss()
4451
}
4552
});
46-
47-
present();
48-
49-
return () => {
50-
if (paneModal) paneModal.destroy({ animate: false });
51-
};
5253
});
5354
</script>
5455

@@ -60,13 +61,13 @@
6061

6162
<style>
6263
:global(.modal .pane) {
63-
width: 95% !important;
64-
max-height: 300px !important;
64+
width: 100% !important;
65+
max-height: 600px !important;
6566
min-height: 100px !important;
6667
height: auto !important;
6768
position: fixed !important;
6869
bottom: 30px !important;
69-
left: 50% !important;
70+
left: 40% !important;
7071
transform: translateX(-50%) !important;
7172
border-radius: 32px !important;
7273
padding: 20px !important;

platforms/metagram/src/lib/fragments/UserRequest/UserRequest.svelte

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@
1515
$props();
1616
</script>
1717

18-
<article {...restProps} class={cn(['flex justify-between items-center', restProps.class].join(' '))}>
19-
<div class="me-4.5 flex items-start">
20-
<Avatar size="sm" src={userImgSrc} />
21-
<div class="ms-2">
22-
<h3 class="font-semibold text-black">{userName}</h3>
23-
<p class="text-black-600">{description}</p>
24-
</div>
25-
</div>
26-
<Button class="max-w-[100px]" variant="secondary" size="sm" callback={handleFollow}>Follow</Button>
18+
<article
19+
{...restProps}
20+
class={cn(['flex items-center justify-between', restProps.class].join(' '))}
21+
>
22+
<div class="me-4.5 flex items-start">
23+
<Avatar size="sm" src={userImgSrc} />
24+
<div class="ms-2">
25+
<h3 class="font-semibold text-black">{userName}</h3>
26+
<p class="text-black-600">{description}</p>
27+
</div>
28+
</div>
29+
<Button class="max-w-[100px]" variant="secondary" size="sm" callback={handleFollow}
30+
>Follow</Button
31+
>
2732
</article>
2833

2934
<!--
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Textarea } from '..';
2+
3+
export default {
4+
title: 'UI/Textarea',
5+
component: Textarea,
6+
tags: ['autodocs'],
7+
render: (args: { type: string; placeholder: string }) => ({
8+
Component: Textarea,
9+
props: args
10+
})
11+
};
12+
13+
export const Main = {
14+
args: {
15+
placeholder: 'Joe Biden'
16+
}
17+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script lang="ts">
2+
import { cn } from '$lib/utils';
3+
import type { HTMLTextareaAttributes } from 'svelte/elements';
4+
5+
interface IInputProps extends HTMLTextareaAttributes {
6+
textarea?: HTMLTextAreaElement;
7+
value: string | number | any;
8+
placeholder?: string;
9+
}
10+
11+
let {
12+
textarea = $bindable(),
13+
value = $bindable(),
14+
placeholder = '',
15+
...restProps
16+
}: IInputProps = $props();
17+
18+
const cbase = $derived(
19+
'w-full bg-grey resize-none py-3.5 px-6 text-[15px] text-black-800 font-geist font-normal placeholder:text-black-600 rounded-4xl outline-0 border border-transparent invalid:border-red invalid:text-red focus:invalid:text-black-800 focus:invalid:border-transparent'
20+
);
21+
</script>
22+
23+
<!-- svelte-ignore element_invalid_self_closing_tag -->
24+
<textarea
25+
{...restProps}
26+
rows={6}
27+
{placeholder}
28+
bind:value
29+
bind:this={textarea}
30+
class={cn([cbase, restProps.class].join(' '))}
31+
tabindex="0"
32+
/>

platforms/metagram/src/lib/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { default as Select } from './Select/Select.svelte';
55
export { default as Label } from './Label/Label.svelte';
66
export { default as Toggle } from './Toggle/Toggle.svelte';
77
export { default as Helper } from './Helper/Helper.svelte';
8+
export { default as Textarea } from './Textarea/Textarea.svelte';

platforms/metagram/src/routes/(protected)/+layout.svelte

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
import { goto } from '$app/navigation';
33
import { page } from '$app/state';
44
import { comments } from '$lib/dummyData';
5-
import { BottomNav, Header, Comment, MessageInput, SideBar } from '$lib/fragments';
5+
import { BottomNav, Header, Comment, MessageInput, SideBar, Modal } from '$lib/fragments';
6+
import InputFile from '$lib/fragments/InputFile/InputFile.svelte';
67
import UserRequest from '$lib/fragments/UserRequest/UserRequest.svelte';
78
import { Settings } from '$lib/icons';
89
import { showComments } from '$lib/store/store.svelte';
910
import type { CommentType } from '$lib/types';
11+
import Button from '$lib/ui/Button/Button.svelte';
12+
import Label from '$lib/ui/Label/Label.svelte';
13+
import Textarea from '$lib/ui/Textarea/Textarea.svelte';
14+
import type { CupertinoPane } from 'cupertino-pane';
1015
let { children } = $props();
1116
1217
let route = $derived(page.url.pathname);
@@ -16,6 +21,11 @@
1621
let _comments = $state(comments);
1722
let activeReplyToId: string | null = $state(null);
1823
let chatFriendId = $state();
24+
let paneModal: CupertinoPane | undefined = $state();
25+
let files: FileList | undefined = $state();
26+
let imagePreviews: string[] = $state([]);
27+
let isAddCaption: boolean = $state(false);
28+
let caption: string = $state('');
1929
2030
const handleSend = async () => {
2131
const newComment = {
@@ -71,12 +81,35 @@
7181
heading = 'Profile';
7282
}
7383
});
84+
85+
$effect(() => {
86+
if (files) {
87+
const readers = Array.from(files).map((file) => {
88+
return new Promise<string>((resolve) => {
89+
const reader = new FileReader();
90+
reader.onload = (e) => resolve(e.target?.result as string);
91+
reader.readAsDataURL(file);
92+
});
93+
});
94+
95+
Promise.all(readers).then((previews) => {
96+
imagePreviews = previews;
97+
});
98+
} else {
99+
imagePreviews = [];
100+
}
101+
});
74102
</script>
75103

76104
<main
77105
class={`block h-[100dvh] ${route !== '/home' && route !== '/messages' ? 'grid-cols-[20vw_auto]' : 'grid-cols-[20vw_auto_30vw]'} md:grid`}
78106
>
79-
<SideBar profileSrc="https://picsum.photos/200" handlePost={async () => alert('adas')} />
107+
<SideBar
108+
profileSrc="https://picsum.photos/200"
109+
handlePost={async () => {
110+
if (paneModal) paneModal.present({ animate: true });
111+
}}
112+
/>
80113
<section class="hide-scrollbar h-[100dvh] overflow-y-auto px-4 pb-8 md:px-8 md:pt-8">
81114
<div class="flex items-center justify-between">
82115
<Header
@@ -152,3 +185,50 @@
152185
<BottomNav class="btm-nav" profileSrc="https://picsum.photos/200" />
153186
{/if}
154187
</main>
188+
189+
<Modal bind:paneModal initialBreak="middle" handleDismiss={() => (files = undefined)}>
190+
<h1 class="mb-6 font-semibold text-black">Upload a Photo</h1>
191+
{#if !isAddCaption}
192+
{#if !files}
193+
<InputFile class="mb-4 h-[40vh]" bind:files accept="images/*" multiple={true} />
194+
{:else}
195+
<div class="mb-4 grid grid-cols-3 gap-2">
196+
{#each imagePreviews as src}
197+
<div class="aspect-[4/5] overflow-hidden rounded-lg border">
198+
<!-- svelte-ignore a11y_img_redundant_alt -->
199+
<img {src} alt="Selected image" class="h-full w-full object-cover" />
200+
</div>
201+
{/each}
202+
</div>
203+
{/if}
204+
{:else}
205+
<Label>Add a Caption</Label>
206+
<Textarea class="mb-4" bind:value={caption} placeholder="enter caption" />
207+
<div class="mb-4 grid grid-cols-3 gap-2">
208+
{#each imagePreviews as src}
209+
<div class="h-[100px] w-[80px] overflow-hidden rounded-lg border">
210+
<!-- svelte-ignore a11y_img_redundant_alt -->
211+
<img {src} alt="Selected image" class="h-[100px] w-[80px] object-cover" />
212+
</div>
213+
{/each}
214+
</div>
215+
{/if}
216+
{#if files}
217+
<div class="grid grid-cols-2 gap-2">
218+
<Button
219+
variant="secondary"
220+
size="sm"
221+
callback={async () => {
222+
files = undefined;
223+
}}>Cancel</Button
224+
>
225+
<Button
226+
variant="secondary"
227+
size="sm"
228+
callback={async () => {
229+
isAddCaption = true;
230+
}}>Next</Button
231+
>
232+
</div>
233+
{/if}
234+
</Modal>

platforms/metagram/src/routes/(protected)/discover/+page.svelte

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77

88
<section>
99
<Input type="text" bind:value={searchValue} placeholder="Search user, post and more." />
10-
{#if searchValue}
11-
<ul class="pb-4 mt-6">
12-
{#each { length: 5 } as _}
13-
<li class="mb-4">
14-
<UserRequest
15-
userImgSrc="https://www.gravatar.com/avatar/2c7d99fe281ecd3bcd65ab915bac6dd5?s=250"
16-
userName="luffythethird"
17-
description="I’ve always wished life came at me fast. Funny how that wish never came through"
18-
handleFollow={async () => alert('Adsad')}
19-
/>
20-
</li>
21-
{/each}
22-
</ul>
23-
{/if}
10+
{#if searchValue}
11+
<ul class="mt-6 pb-4">
12+
{#each { length: 5 } as _}
13+
<li class="mb-4">
14+
<UserRequest
15+
userImgSrc="https://www.gravatar.com/avatar/2c7d99fe281ecd3bcd65ab915bac6dd5?s=250"
16+
userName="luffythethird"
17+
description="I’ve always wished life came at me fast. Funny how that wish never came through"
18+
handleFollow={async () => alert('Adsad')}
19+
/>
20+
</li>
21+
{/each}
22+
</ul>
23+
{/if}
2424
</section>

0 commit comments

Comments
 (0)