Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@
* {
font-size: 15px;
}
@media (prefers-color-scheme: dark) {
.select {
&,
& select {
&::picker(select) {
border: var(--border) solid var(--color-base-content);
}
}
}
}
}

.dropdown-content {
Expand Down
34 changes: 34 additions & 0 deletions src/lib/components/CancelButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import IconContainer from '../icons/IconContainer.svelte';
import IconButton, { type IconButtonProps } from './IconButton.svelte';
import { Icons } from '$lib/icons';

interface Props extends IconButtonProps {
returnTo?: string;
resetForm?: boolean;
}

let {
class: classes,
icon = Icons.Cancel,
label = 'Cancel',
returnTo: href,
resetForm = false,
...rest
}: Props = $props();
</script>

{#if !rest.children && href}
<a class={['btn btn-secondary', classes]} {href}>
<IconContainer {icon} width={20} />
{label}
</a>
{:else}
<IconButton
type={resetForm ? 'reset' : 'button'}
class={['btn-secondary', classes]}
{icon}
{label}
{...rest}
/>
{/if}
48 changes: 48 additions & 0 deletions src/lib/components/CopyField.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import IconContainer from '../icons/IconContainer.svelte';
import { Icons } from '$lib/icons';

interface Props {
value: string;
}

const { value }: Props = $props();

let copied = $state(false);

let timeout: ReturnType<typeof setTimeout> | null = $state(null);

onDestroy(() => {
if (timeout) {
clearTimeout(timeout);
}
});
</script>

<button
class="cursor-copy"
type="button"
onclick={() => {
if (value) {
navigator.clipboard.writeText(value).then(
() => {
copied = true;
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
copied = false;
}, 5000);
},
(r) => console.log(r)
);
}
}}
>
{#if copied}
<IconContainer icon={Icons.Checkmark} width={24} class="text-success" />
{:else}
<IconContainer icon={Icons.Copy} width={24} />
{/if}
</button>
Comment thread
FyreByrd marked this conversation as resolved.
43 changes: 43 additions & 0 deletions src/lib/components/IconButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts" module>
export interface IconButtonProps {
label?: string;
class?: ClassValue;
form?: string;
type?: HTMLButtonElement['type'];
icon?: IconType;
disabled?: boolean;
onclick?: (
e: MouseEvent & {
currentTarget: EventTarget & HTMLButtonElement;
}
) => void;
children?: Snippet;
}
</script>

<script lang="ts">
import type { Snippet } from 'svelte';
import type { ClassValue } from 'svelte/elements';
import IconContainer from '../icons/IconContainer.svelte';
import { type IconType, Icons } from '$lib/icons';
Comment thread
FyreByrd marked this conversation as resolved.

let {
form,
type = 'button',
class: classes,
icon = Icons.Save,
disabled = false,
label = 'Save',
onclick,
children
}: IconButtonProps = $props();
</script>

<button {type} {form} class={['btn', classes]} {disabled} {onclick}>
{#if children}
{@render children()}
{:else}
<IconContainer {icon} width={20} />
{label}
{/if}
</button>
40 changes: 40 additions & 0 deletions src/lib/components/InputWithMessage.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
@component
An input slot with an inline message (stylized)

-->
<script lang="ts">
import type { Snippet } from 'svelte';
import type { ClassValue } from 'svelte/elements';

interface Props {
title?: { label?: string; class?: ClassValue };
message?: { label?: string; class?: ClassValue };
class?: ClassValue;
children?: Snippet;
}

let { title, message, class: classes, children }: Props = $props();
</script>

<label class={['flex flex-row items-center gap-2', classes]}>
<div class="fieldset-label flex-col items-start grow text-base-content">
{#if title}
<span class={['title', title.class]}>
{title.label}
</span>
{/if}
{#if message}
<span class={['text-sm', message.class]}>
{message.label}
</span>
{/if}
</div>
{@render children?.()}
</label>

<style>
.title {
font-weight: bold;
}
</style>
57 changes: 50 additions & 7 deletions src/lib/components/LabeledFormInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,66 @@
@component
An input slot for a form (stylized)
-->
<script lang="ts">
<script lang="ts" module>
import type { Snippet } from 'svelte';

interface Props {
import type { ClassValue, HTMLInputAttributes } from 'svelte/elements';
import type { IconType } from '$lib/icons';
export interface Props {
label: string;
classes?: string;
class?: ClassValue;
children?: Snippet;
input?: HTMLInputAttributes & {
class?: ClassValue;
err?: string;
icon?: IconType;
iconClass?: ClassValue;
after?: Snippet;
};
validate?: boolean;
}
</script>

<script lang="ts" generics="T">
import IconContainer from '$lib/icons/IconContainer.svelte';

interface InstanceProps extends Props {
value?: T;
}

let { label, classes, children }: Props = $props();
let {
label,
class: classes,
children,
input,
value = $bindable(),
validate = true
}: InstanceProps = $props();
</script>

<label class="flex flex-col w-full {classes}">
<label class={['flex flex-col w-full', classes]}>
<div class="label">
<span class="fieldset-label">
{label}
</span>
</div>
{@render children?.()}
{#if input}
<div class={['input w-full', validate && 'validator']}>
{#if input.icon}
<IconContainer
icon={input.icon}
width={20}
class={['cursor-pointer opacity-80', input.iconClass]}
/>
{/if}
<input type="text" {...input} bind:value />
{@render input.after?.()}
</div>
{#if validate}
<span class="validator-hint">
{#if input.err}{input.err}{:else}&nbsp;{/if}
</span>
{/if}
{:else}
{@render children?.()}
{/if}
</label>
28 changes: 28 additions & 0 deletions src/lib/components/LinkToScriptoria.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import type { Prisma } from '@prisma/client';
import type { ClassValue } from 'svelte/elements';
import { env } from '$env/dynamic/public';
import { Icons } from '$lib/icons';
import IconContainer from '$lib/icons/IconContainer.svelte';

interface Props {
client: Prisma.clientGetPayload<{ select: { development: true } }> | null;
bucket: Prisma.projectGetPayload<{ select: { url: true } }>['url'];
scope: 'project' | 'job' | 'build' | 'release';
id: number;
class?: ClassValue;
}

let { client, bucket, scope, id, class: classes }: Props = $props();
</script>

<a
class={['link', classes]}
target="_blank"
href="{client?.development
? 'http://localhost:6173'
: env.PUBLIC_SCRIPTORIA_URL}/link/{scope}/{id}{bucket &&
`?bucket=${encodeURIComponent(bucket)}`}"
>
View in Scriptoria <IconContainer icon={Icons.Open} width={16} />
</a>
17 changes: 17 additions & 0 deletions src/lib/components/PaginationHeader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts">
interface Props {
page: { page: number; size: number };
count: number;
}

const { page, count }: Props = $props();
</script>

<p>
Showing <b>
{Math.min(page.page * page.size + 1, count)}-{Math.min((page.page + 1) * page.size, count)}
Comment thread
FyreByrd marked this conversation as resolved.
</b>
of
<b>{count}</b>
items
</p>
31 changes: 31 additions & 0 deletions src/lib/components/PasswordInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts" generics="T">
import LabeledFormInput, {
type Props as LabelProps
} from '$lib/components/LabeledFormInput.svelte';
import { Icons } from '$lib/icons';
import IconContainer from '$lib/icons/IconContainer.svelte';

interface Props extends LabelProps {
value?: T;
}
let { value = $bindable(), ...rest }: Props = $props();

let visible = $state(false);
</script>

{#snippet after()}
<button type="button" onclick={() => (visible = !visible)}>
<IconContainer icon={visible ? Icons.Visible : Icons.Invisible} width={16} />
</button>
{/snippet}
Comment thread
FyreByrd marked this conversation as resolved.

<LabeledFormInput
{...rest}
input={{
...rest.input,
type: visible ? 'text' : 'password',
icon: rest.input?.icon ?? Icons.Key,
after
}}
bind:value
/>
6 changes: 5 additions & 1 deletion src/lib/components/SecureDisplay.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
class="w-full"
/>
{#if value?.length}
<button type="button" class="flex flex-row items-center" onclick={() => (visible = !visible)}>
<button
type="button"
class="flex flex-row items-center cursor-pointer"
onclick={() => (visible = !visible)}
>
<IconContainer icon={visible ? Icons.Visible : Icons.Invisible} width={16} />
</button>
{/if}
Expand Down
Loading