aboutsummaryrefslogtreecommitdiffstats
path: root/code/app/src/lib/components
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-10-25 11:51:37 +0200
committerivarlovlie <git@ivarlovlie.no>2022-10-25 11:51:37 +0200
commit0005595703b2f3f7083ce4ba19bf5770057c75bd (patch)
tree193a897f61a9a5e566961601de4cf42ae85984a0 /code/app/src/lib/components
parent585c5c8537eb21dfc9f16108548e63d9ced3d971 (diff)
downloadgreatoffice-0005595703b2f3f7083ce4ba19bf5770057c75bd.tar.xz
greatoffice-0005595703b2f3f7083ce4ba19bf5770057c75bd.zip
.
Diffstat (limited to 'code/app/src/lib/components')
-rw-r--r--code/app/src/lib/components/checkbox.svelte7
-rw-r--r--code/app/src/lib/components/combobox.svelte57
-rw-r--r--code/app/src/lib/components/input.svelte3
-rw-r--r--code/app/src/lib/components/textarea.svelte21
4 files changed, 55 insertions, 33 deletions
diff --git a/code/app/src/lib/components/checkbox.svelte b/code/app/src/lib/components/checkbox.svelte
index b2fcddb..12ebedb 100644
--- a/code/app/src/lib/components/checkbox.svelte
+++ b/code/app/src/lib/components/checkbox.svelte
@@ -7,6 +7,7 @@
export let name: string | undefined = undefined;
export let disabled: boolean | null = null;
export let checked: boolean;
+ export let required: boolean | null = null;
export let _pwKey: string | undefined = undefined;
</script>
@@ -16,9 +17,13 @@
use:pwKey={_pwKey}
{disabled}
{id}
+ {required}
type="checkbox"
bind:checked
class="h-4 w-4 text-teal-600 focus:ring-teal-500 border-gray-300 rounded"
/>
- <label for={id} class="ml-2 block text-sm text-gray-900">{label}</label>
+ <label for={id} class="ml-2 block text-sm text-gray-900">
+ {@html required ? "<span class='text-red-500'>*</span>" : ""}
+ {label}
+ </label>
</div>
diff --git a/code/app/src/lib/components/combobox.svelte b/code/app/src/lib/components/combobox.svelte
index ee69917..4e7b1cd 100644
--- a/code/app/src/lib/components/combobox.svelte
+++ b/code/app/src/lib/components/combobox.svelte
@@ -7,7 +7,7 @@
</script>
<script lang="ts">
- import { CheckCircleIcon, ChevronUpDownIcon, XIcon } from "$lib/components/icons";
+ import { CheckCircleIcon, ChevronUpDownIcon, XIcon } from "./icons";
import { element_has_focus, random_string } from "$lib/helpers";
import { go, highlight } from "fuzzysort";
import Badge from "./badge.svelte";
@@ -20,19 +20,19 @@
export let disabled: boolean | undefined = undefined;
export let required: boolean | undefined = undefined;
export let maxlength: number | undefined = undefined;
- export let placeholder = $LL.combobox.search();
+ export let placeholder: string = $LL.combobox.search();
export let options: Array<ComboboxOption> | undefined = [];
export let createable = false;
export let loading = false;
export let multiple = false;
- export let noResultsText = $LL.combobox.noRecordsFound();
+ export let noResultsText: string = $LL.combobox.noRecordsFound();
export let on_create_async = async ({ name: string }) => {};
export const reset = () => methods.reset();
export const select = (id: string) => methods.select_entry(id);
export const deselect = (id: string) => methods.deselect_entry(id);
- const INTERNAL_ID = "INTERNAL__combobox-" + random_string(3);
+ const INTERNAL_ID = "INTERNAL__" + id;
let optionsListId = id + "--options";
let searchInputNode;
@@ -75,7 +75,7 @@
if (!value) {
return "";
}
- return value.toString().trim().toLowerCase();
+ return value.trim().toLowerCase();
},
do() {
const query = search.normalise_value(searchValue);
@@ -85,9 +85,9 @@
return;
}
- //@ts-ignore
+ // @ts-ignore
searchResults = go(query, options, {
- limit: 10,
+ limit: 15,
allowTypo: true,
threshold: -10000,
key: "name",
@@ -206,11 +206,11 @@
if (searchValue) {
return options;
}
- return (options as any).sort((a, b) => {
- search.normalise_value(a.name).localeCompare(search.normalise_value(b.name));
- });
+
+ return options.sort((a, b) => search.normalise_value(a.name).localeCompare(search.normalise_value(b.name)));
},
};
+
const windowEvents = {
on_mousemove(event: any) {
if (!event.target) return;
@@ -230,7 +230,7 @@
const spacePressed = event.code === "Space";
const arrowDownPressed = event.code === "ArrowDown";
const searchInputHasFocus = element_has_focus(searchInputNode);
- const focusedEntry = document.querySelector("#" + INTERNAL_ID + " ul .focus");
+ const focusedEntry = document.querySelector("#" + INTERNAL_ID + " ul li.focus") as HTMLLIElement;
if (showDropdown && (enterPressed || arrowDownPressed || arrowUpPressed)) {
event.preventDefault();
@@ -262,16 +262,18 @@
focusedEntry.nextElementSibling.classList.add("focus");
focusedEntry.nextElementSibling.scrollIntoView(false);
} else {
- document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type").classList.add("focus");
- document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type").scrollIntoView(false);
+ const firstLIEl = document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type");
+ firstLIEl.classList.add("focus");
+ firstLIEl.scrollIntoView(false);
}
} else if (arrowUpPressed) {
if (focusedEntry.previousElementSibling) {
focusedEntry.previousElementSibling.classList.add("focus");
focusedEntry.previousElementSibling.scrollIntoView(false);
} else {
- document.querySelector("#" + INTERNAL_ID + " ul li:last-of-type").classList.add("focus");
- document.querySelector("#" + INTERNAL_ID + " ul li:last-of-type").scrollIntoView(false);
+ const lastLIEl = document.querySelector("#" + INTERNAL_ID + " ul li:last-of-type");
+ lastLIEl.classList.add("focus");
+ lastLIEl.scrollIntoView(false);
}
}
focusedEntry.classList.remove("focus");
@@ -279,7 +281,6 @@
}
if (focusedEntry && (spacePressed || enterPressed)) {
- //@ts-ignore
methods.select_entry(focusedEntry.dataset.id);
return;
}
@@ -303,14 +304,17 @@
<div id={INTERNAL_ID} class:cursor-wait={loading}>
{#if label}
- <label for={id} class="block text-sm font-medium text-gray-700">{label}</label>
+ <label for={id} class="block text-sm font-medium text-gray-700">
+ {label}
+ {@html required ? "<span class='text-red-500'>*</span>" : ""}
+ </label>
{/if}
<div class="relative {label ? 'mt-1' : ''}">
<div
on:click={search.on_input_wrapper_focus}
on:keypress={search.on_input_wrapper_focus}
- class="cursor-text w-full rounded-md border bg-white py-2 pl-3 pr-12 sm:text-sm
- {inputHasFocus ? `border-${colorName}-500 outline-none ring-1 ring-{colorName}-500` : 'shadow-sm border-gray-300'}"
+ class="cursor-text w-full flex rounded-md border bg-white py-2 pl-3 pr-12 sm:text-sm
+ {inputHasFocus ? `border-${colorName}-500 outline-none ring-1 ring-${colorName}-500` : 'shadow-sm border-gray-300'}"
>
{#if multiple === true && hasSelection}
<div class="flex gap-1 flex-wrap">
@@ -325,7 +329,7 @@
{/each}
</div>
{/if}
- <div class={multiple === true && hasSelection ? "mt-2" : ""}>
+ <div>
<input
{...attributes}
type="text"
@@ -346,12 +350,13 @@
type="button"
on:click={() => reset()}
title={$LL.reset()}
+ tabindex="-1"
class="text-gray-400 absolute cursor-pointer inset-y-0 right-0 flex items-center rounded-r-md px-2"
>
<XIcon />
</button>
{:else}
- <span class="text-gray-400 absolute inset-y-0 right-0 flex items-center rounded-r-md px-2">
+ <span tabindex="-1" class="text-gray-400 absolute inset-y-0 right-0 flex items-center rounded-r-md px-2">
<ChevronUpDownIcon />
</span>
{/if}
@@ -406,10 +411,12 @@
</li>
{/each}
{:else}
- <p class="px-2">{noResultsText}</p>
- {#if createable && !searchValue}
- <p class="px-2 text-gray-500">{$LL.combobox.createRecordHelpText()}</p>
- {/if}
+ <slot name="no-records">
+ <p class="px-2">{noResultsText}</p>
+ {#if createable && !searchValue}
+ <p class="px-2 text-gray-500">{$LL.combobox.createRecordHelpText()}</p>
+ {/if}
+ </slot>
{/if}
</ul>
{#if showCreationHint}
diff --git a/code/app/src/lib/components/input.svelte b/code/app/src/lib/components/input.svelte
index efd8946..5d38597 100644
--- a/code/app/src/lib/components/input.svelte
+++ b/code/app/src/lib/components/input.svelte
@@ -1,6 +1,7 @@
<script lang="ts">
import pwKey from "$actions/pwKey";
import { random_string } from "$lib/helpers";
+ import { htmlLangAttributeDetector } from "typesafe-i18n/detectors";
import { ExclamationCircleIcon } from "./icons";
export let label: string | undefined = undefined;
@@ -47,12 +48,14 @@
{#if label && !cornerHint && !hideLabel}
<label for={id} class={hideLabel ? "sr-only" : "block text-sm font-medium text-gray-700"}>
{label}
+ {@html required ? "<span class='text-red-500'>*</span>" : ""}
</label>
{:else if cornerHint && !hideLabel}
<div class="flex justify-between">
{#if label}
<label for={id} class={hideLabel ? "sr-only" : "block text-sm font-medium text-gray-700"}>
{label}
+ {@html required ? "<span class='text-red-500'>*</span>" : ""}
</label>
{/if}
<span class="text-sm text-gray-500">
diff --git a/code/app/src/lib/components/textarea.svelte b/code/app/src/lib/components/textarea.svelte
index 65127af..6629260 100644
--- a/code/app/src/lib/components/textarea.svelte
+++ b/code/app/src/lib/components/textarea.svelte
@@ -9,27 +9,31 @@
export let placeholder = "";
export let value;
export let label = "";
+ export let required = false;
export let errorText = "";
- $: shared_props = {
+ $: attributes = {
rows: rows || null,
cols: cols || null,
name: name || null,
id: id || null,
disabled: disabled || null,
+ required: required || null,
};
- let textarea;
+ let textareaElement;
let scrollHeight = 0;
const defaultColorClass = "border-gray-300 focus:border-teal-500 focus:ring-teal-500";
let colorClass = defaultColorClass;
+
$: if (errorText) {
colorClass = "placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500 text-red-900 pr-10 border-red-300";
} else {
colorClass = defaultColorClass;
}
- $: if (textarea) {
- scrollHeight = textarea.scrollHeight;
+
+ $: if (textareaElement) {
+ scrollHeight = textareaElement.scrollHeight;
}
function on_input(event) {
@@ -40,17 +44,20 @@
<div>
{#if label}
- <label for={id} class="block text-sm font-medium text-gray-700">{label}</label>
+ <label for={id} class="block text-sm font-medium text-gray-700">
+ {label}
+ {@html required ? "<span class='text-red-500'>*</span>" : ""}
+ </label>
{/if}
<div class="mt-1">
<textarea
{rows}
{name}
{id}
- {...shared_props}
+ {...attributes}
style="overflow-y:hidden;min-height:calc(1.5em + .75rem + 2px);{scrollHeight ? 'height:{scrollHeight}px' : ''};"
bind:value
- bind:this={textarea}
+ bind:this={textareaElement}
on:input={on_input}
{placeholder}
class="block w-full rounded-md {colorClass} shadow-sm sm:text-sm"