mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-11 05:59:15 +00:00
feat: support for url based icons (#840)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
85
frontend/src/lib/components/form/url-file-input.svelte
Normal file
85
frontend/src/lib/components/form/url-file-input.svelte
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import FileInput from '$lib/components/form/file-input.svelte';
|
||||
import FormattedMessage from '$lib/components/formatted-message.svelte';
|
||||
import { Button, buttonVariants } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { LucideChevronDown } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
label,
|
||||
accept,
|
||||
onchange
|
||||
}: {
|
||||
label: string;
|
||||
accept?: string;
|
||||
onchange: (file: File | string | null) => void;
|
||||
} = $props();
|
||||
|
||||
let url = $state('');
|
||||
let hasError = $state(false);
|
||||
|
||||
async function handleFileChange(e: Event) {
|
||||
const file = (e.target as HTMLInputElement).files?.[0] || null;
|
||||
url = '';
|
||||
hasError = false;
|
||||
onchange(file);
|
||||
}
|
||||
|
||||
async function handleUrlChange(e: Event) {
|
||||
const url = (e.target as HTMLInputElement).value.trim();
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
new URL(url);
|
||||
hasError = false;
|
||||
} catch {
|
||||
hasError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
onchange(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
<FileInput
|
||||
id="logo"
|
||||
variant="secondary"
|
||||
{accept}
|
||||
onchange={handleFileChange}
|
||||
onclick={(e: any) => (e.target.value = '')}
|
||||
>
|
||||
<Button variant="secondary" class="rounded-r-none">
|
||||
{label}
|
||||
</Button>
|
||||
</FileInput>
|
||||
<Popover.Root>
|
||||
<Popover.Trigger
|
||||
class={cn(buttonVariants({ variant: 'secondary' }), 'rounded-l-none border-l')}
|
||||
>
|
||||
<LucideChevronDown class="size-4" /></Popover.Trigger
|
||||
>
|
||||
<Popover.Content class="w-80">
|
||||
<Label for="file-url" class="text-xs">URL</Label>
|
||||
<Input
|
||||
id="file-url"
|
||||
placeholder=""
|
||||
value={url}
|
||||
oninput={(e) => (url = e.currentTarget.value)}
|
||||
onfocusout={handleUrlChange}
|
||||
aria-invalid={hasError}
|
||||
/>
|
||||
{#if hasError}
|
||||
<p class="text-destructive mt-1 text-start text-xs">{m.invalid_url()}</p>
|
||||
{/if}
|
||||
|
||||
<p class="text-muted-foreground mt-2 text-xs">
|
||||
<FormattedMessage m={m.logo_from_url_description()} />
|
||||
</p>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</div>
|
||||
@@ -1,10 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { LucideImageOff } from '@lucide/svelte';
|
||||
import type { HTMLImgAttributes } from 'svelte/elements';
|
||||
|
||||
let props: HTMLImgAttributes & {} = $props();
|
||||
let error = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
props.src;
|
||||
error = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class={'bg-muted flex items-center justify-center rounded-2xl p-3'}>
|
||||
<img class={cn('size-24 object-contain', props.class)} {...props} />
|
||||
{#if error}
|
||||
<LucideImageOff class={cn('text-muted-foreground p-5', props.class)} />
|
||||
{:else}
|
||||
<img
|
||||
{...props}
|
||||
class={cn('object-contain', props.class)}
|
||||
onerror={() => (error = true)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user