finish custom class and improved options overhaul
This commit is contained in:
@@ -1,21 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import type { MouseEventHandler } from 'svelte/elements';
|
import type { ClassValue, MouseEventHandler } from 'svelte/elements';
|
||||||
import Spinner from './Spinner.svelte';
|
import Spinner from './Spinner.svelte';
|
||||||
|
|
||||||
let {
|
interface Props {
|
||||||
icon,
|
|
||||||
animate = true,
|
|
||||||
loading,
|
|
||||||
children,
|
|
||||||
onclick
|
|
||||||
}: {
|
|
||||||
icon?: string;
|
icon?: string;
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
onclick?: MouseEventHandler<HTMLButtonElement>;
|
onclick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
} = $props();
|
}
|
||||||
|
|
||||||
|
let { icon, animate = true, loading, class: classValue, children, onclick }: Props = $props();
|
||||||
|
|
||||||
let iconElement = $state<HTMLSpanElement | null>(null);
|
let iconElement = $state<HTMLSpanElement | null>(null);
|
||||||
|
|
||||||
@@ -68,7 +65,8 @@
|
|||||||
class={[
|
class={[
|
||||||
'button group relative flex gap-3 overflow-hidden rounded-sm px-5',
|
'button group relative flex gap-3 overflow-hidden rounded-sm px-5',
|
||||||
'text-background cursor-pointer py-3 font-medium transition-colors',
|
'text-background cursor-pointer py-3 font-medium transition-colors',
|
||||||
!loading ? ' bg-primary hover:bg-secondary' : 'bg-primary/50 cursor-not-allowed '
|
!loading ? ' bg-primary hover:bg-secondary' : 'bg-primary/50 cursor-not-allowed ',
|
||||||
|
classValue
|
||||||
]}
|
]}
|
||||||
onclick={handleButtonClick}
|
onclick={handleButtonClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
let { children }: { children: Snippet } = $props();
|
interface Props {
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { class: classValue, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="min-h-screen items-center justify-center sm:flex">
|
<div class={['min-h-screen items-center justify-center sm:flex', classValue]}>
|
||||||
<main class="m-4 max-w-(--breakpoint-sm) sm:mb-[5%] sm:w-full lg:max-w-[950px]">
|
<main class="m-4 max-w-(--breakpoint-sm) sm:mb-[5%] sm:w-full lg:max-w-[950px]">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { validate } from '@svelte-toolkit/validate';
|
import { validate } from '@svelte-toolkit/validate';
|
||||||
import { generateIdentifier } from './util.js';
|
import { generateIdentifier } from './util.js';
|
||||||
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name?: string;
|
name?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
value?: CheckboxState;
|
value?: CheckboxState;
|
||||||
color?: 'default' | 'contrast';
|
color?: 'default' | 'contrast';
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
onchange: (value: CheckboxState) => void;
|
onchange: (value: CheckboxState) => void;
|
||||||
}
|
}
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
required = false,
|
required = false,
|
||||||
value = $bindable(false),
|
value = $bindable(false),
|
||||||
color = 'contrast',
|
color = 'contrast',
|
||||||
|
class: classValue,
|
||||||
children,
|
children,
|
||||||
onchange
|
onchange
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
@@ -30,7 +33,7 @@
|
|||||||
let valid = $state(true);
|
let valid = $state(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class={['flex items-center', classValue]}>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { scale } from 'svelte/transition';
|
import { scale } from 'svelte/transition';
|
||||||
import { generateIdentifier } from './util.js';
|
import { generateIdentifier } from './util.js';
|
||||||
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
notFoundMessage?: string;
|
notFoundMessage?: string;
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
onvalidate?: (e: InputValidatorEvent) => void;
|
onvalidate?: (e: InputValidatorEvent) => void;
|
||||||
onchange?: (e: ComboboxChangeEvent) => void;
|
onchange?: (e: ComboboxChangeEvent) => void;
|
||||||
}
|
}
|
||||||
@@ -54,10 +56,11 @@
|
|||||||
matchWidth = false,
|
matchWidth = false,
|
||||||
options,
|
options,
|
||||||
required = false,
|
required = false,
|
||||||
invalidMessage,
|
invalidMessage = 'Please select an option',
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
notFoundMessage = 'No results found',
|
notFoundMessage = 'No results found',
|
||||||
|
class: classValue,
|
||||||
onvalidate,
|
onvalidate,
|
||||||
onchange
|
onchange
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
@@ -324,7 +327,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Portal>
|
</Portal>
|
||||||
|
|
||||||
<div>
|
<div class={classValue}>
|
||||||
<!-- Combobox Label -->
|
<!-- Combobox Label -->
|
||||||
{#if label}
|
{#if label}
|
||||||
<Label for={id}>{label}</Label>
|
<Label for={id}>{label}</Label>
|
||||||
@@ -335,7 +338,7 @@
|
|||||||
{name}
|
{name}
|
||||||
{id}
|
{id}
|
||||||
value={value?.value ?? ''}
|
value={value?.value ?? ''}
|
||||||
class="hidden"
|
type="hidden"
|
||||||
use:validate={validateOpts}
|
use:validate={validateOpts}
|
||||||
onvalidate={(e) => {
|
onvalidate={(e) => {
|
||||||
valid = e.detail.valid;
|
valid = e.detail.valid;
|
||||||
@@ -349,11 +352,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error message if invalid -->
|
<!-- Error message if invalid -->
|
||||||
{#if invalidMessage}
|
|
||||||
<div class={['opacity-0 transition-opacity', !valid && 'opacity-100']}>
|
<div class={['opacity-0 transition-opacity', !valid && 'opacity-100']}>
|
||||||
<Label for={id} error>{invalidMessage}</Label>
|
<Label for={id} error>{invalidMessage}</Label>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#snippet searchInputBox(caret: boolean = true)}
|
{#snippet searchInputBox(caret: boolean = true)}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
label,
|
label,
|
||||||
required = false,
|
required = false,
|
||||||
invalidMessage = 'Valid date is required',
|
invalidMessage = 'Valid date is required',
|
||||||
class: classList,
|
class: classValue,
|
||||||
format = ['year', 'month', 'day']
|
format = ['year', 'month', 'day']
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class={classValue}>
|
||||||
<!-- Date input Label -->
|
<!-- Date input Label -->
|
||||||
{#if label}
|
{#if label}
|
||||||
<Label for={id}>{label}</Label>
|
<Label for={id}>{label}</Label>
|
||||||
@@ -213,8 +213,7 @@
|
|||||||
'border-accent rounded-sm border bg-white px-[1.125rem] py-3.5 font-normal transition-colors',
|
'border-accent rounded-sm border bg-white px-[1.125rem] py-3.5 font-normal transition-colors',
|
||||||
'text-text dark:border-accent/50 dark:bg-text-800 dark:text-background dark:sm:bg-slate-800',
|
'text-text dark:border-accent/50 dark:bg-text-800 dark:text-background dark:sm:bg-slate-800',
|
||||||
'ring-accent focus-within:ring-1',
|
'ring-accent focus-within:ring-1',
|
||||||
!valid && 'border-red-500!',
|
!valid && 'border-red-500!'
|
||||||
classList
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import type { MouseEventHandler } from 'svelte/elements';
|
import type { ClassValue, MouseEventHandler } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon?: string;
|
||||||
|
iconPosition?: 'left' | 'right';
|
||||||
|
disabled?: boolean;
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
|
children: Snippet;
|
||||||
|
onclick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
icon,
|
icon,
|
||||||
iconPosition = 'right',
|
iconPosition = 'right',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
class: classValue,
|
||||||
children,
|
children,
|
||||||
onclick
|
onclick
|
||||||
}: {
|
}: Props = $props();
|
||||||
icon?: string;
|
|
||||||
iconPosition?: 'left' | 'right';
|
|
||||||
disabled?: boolean;
|
|
||||||
children: Snippet;
|
|
||||||
onclick?: MouseEventHandler<HTMLButtonElement>;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet iconSnippet()}
|
{#snippet iconSnippet()}
|
||||||
@@ -24,7 +28,8 @@
|
|||||||
<button
|
<button
|
||||||
class={[
|
class={[
|
||||||
'text-accent hover:text-primary inline-flex cursor-pointer items-center gap-1.5 transition-colors',
|
'text-accent hover:text-primary inline-flex cursor-pointer items-center gap-1.5 transition-colors',
|
||||||
disabled && 'pointer-events-none cursor-not-allowed opacity-50'
|
disabled && 'pointer-events-none cursor-not-allowed opacity-50',
|
||||||
|
classValue
|
||||||
]}
|
]}
|
||||||
{onclick}
|
{onclick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
interface Props {
|
||||||
for: target,
|
for: string;
|
||||||
error,
|
error?: boolean;
|
||||||
bigError,
|
bigError?: boolean;
|
||||||
children
|
class?: ClassValue | null | undefined;
|
||||||
}: { for: string; error?: boolean; bigError?: boolean; children: Snippet } = $props();
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { for: target, error, bigError, class: classValue, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
@@ -16,7 +20,8 @@
|
|||||||
error && !bigError
|
error && !bigError
|
||||||
? 'mt-1 text-sm font-normal text-red-500'
|
? 'mt-1 text-sm font-normal text-red-500'
|
||||||
: 'text-text dark:text-background mb-2 text-base font-medium',
|
: 'text-text dark:text-background mb-2 text-base font-medium',
|
||||||
bigError && 'text-red-500!'
|
bigError && 'text-red-500!',
|
||||||
|
classValue
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -21,21 +21,25 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import type { MouseEventHandler } from 'svelte/elements';
|
import type { ClassValue, MouseEventHandler } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
href: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
tab?: 'current' | 'new';
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
|
children: Snippet;
|
||||||
|
onclick?: MouseEventHandler<HTMLAnchorElement>;
|
||||||
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
href,
|
href,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
tab = 'current',
|
tab = 'current',
|
||||||
|
class: classValue,
|
||||||
children,
|
children,
|
||||||
onclick
|
onclick
|
||||||
}: {
|
}: Props = $props();
|
||||||
href: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
tab?: 'current' | 'new';
|
|
||||||
children: Snippet;
|
|
||||||
onclick?: MouseEventHandler<HTMLAnchorElement>;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
if (PUBLIC_BASEPATH && !href.startsWith('http://') && !href.startsWith('https://')) {
|
if (PUBLIC_BASEPATH && !href.startsWith('http://') && !href.startsWith('https://')) {
|
||||||
let prefix = trim(PUBLIC_BASEPATH, '/');
|
let prefix = trim(PUBLIC_BASEPATH, '/');
|
||||||
@@ -46,7 +50,8 @@
|
|||||||
<a
|
<a
|
||||||
class={[
|
class={[
|
||||||
'text-accent hover:text-primary inline-flex items-center gap-1.5 transition-colors',
|
'text-accent hover:text-primary inline-flex items-center gap-1.5 transition-colors',
|
||||||
disabled && 'pointer-events-none cursor-not-allowed opacity-50'
|
disabled && 'pointer-events-none cursor-not-allowed opacity-50',
|
||||||
|
classValue
|
||||||
]}
|
]}
|
||||||
{href}
|
{href}
|
||||||
target={tab === 'new' ? '_blank' : undefined}
|
target={tab === 'new' ? '_blank' : undefined}
|
||||||
|
|||||||
@@ -1,39 +1,13 @@
|
|||||||
<script lang="ts" module>
|
|
||||||
export type RadioGroupOption =
|
|
||||||
| {
|
|
||||||
value: string;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
| string;
|
|
||||||
|
|
||||||
/** getLabel returns the option label if it exists*/
|
|
||||||
const getLabel = (option: RadioGroupOption): string => {
|
|
||||||
if (typeof option === 'string') {
|
|
||||||
return option;
|
|
||||||
} else {
|
|
||||||
return option.label ?? option.value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** getValue returns the option value */
|
|
||||||
const getValue = (option: RadioGroupOption): string => {
|
|
||||||
if (typeof option === 'string') {
|
|
||||||
return option;
|
|
||||||
} else {
|
|
||||||
return option.value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RadioGroup, type RadioGroupProps } from 'melt/builders';
|
import { RadioGroup, type RadioGroupProps } from 'melt/builders';
|
||||||
import type { ClassValue } from 'svelte/elements';
|
import type { ClassValue } from 'svelte/elements';
|
||||||
import { scale } from 'svelte/transition';
|
import { scale } from 'svelte/transition';
|
||||||
import Label from './Label.svelte';
|
import Label from './Label.svelte';
|
||||||
import { validate } from '@svelte-toolkit/validate';
|
import { validate } from '@svelte-toolkit/validate';
|
||||||
|
import { getLabel, getValue, type Option } from './util.js';
|
||||||
|
|
||||||
interface Props extends RadioGroupProps {
|
interface Props extends RadioGroupProps {
|
||||||
options: RadioGroupOption[];
|
options: Option[];
|
||||||
label?: string;
|
label?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
invalidMessage?: string;
|
invalidMessage?: string;
|
||||||
|
|||||||
@@ -28,13 +28,13 @@
|
|||||||
validate: validateOpts,
|
validate: validateOpts,
|
||||||
invalidMessage = 'Field is required',
|
invalidMessage = 'Field is required',
|
||||||
ref = $bindable<HTMLInputElement | null>(null),
|
ref = $bindable<HTMLInputElement | null>(null),
|
||||||
class: classList
|
class: classValue
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let valid: boolean = $state(true);
|
let valid: boolean = $state(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class={classValue}>
|
||||||
{#if label}
|
{#if label}
|
||||||
<Label for={id}>{label}</Label>
|
<Label for={id}>{label}</Label>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -50,7 +50,6 @@
|
|||||||
onvalidate={(e) => {
|
onvalidate={(e) => {
|
||||||
valid = e.detail.valid;
|
valid = e.detail.valid;
|
||||||
}}
|
}}
|
||||||
class={classList}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if validateOpts}
|
{#if validateOpts}
|
||||||
|
|||||||
@@ -18,21 +18,27 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ExpandableCombobox, { type ComboboxItem } from './Combobox.svelte';
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
|
import Combobox, { type ComboboxItem } from './Combobox.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label?: string;
|
||||||
|
name?: string;
|
||||||
|
value?: string;
|
||||||
|
invalidMessage?: string;
|
||||||
|
required?: boolean;
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
label,
|
label,
|
||||||
name,
|
name,
|
||||||
value = $bindable(''),
|
value = $bindable(''),
|
||||||
invalidMessage,
|
invalidMessage = 'Please select a timezone',
|
||||||
required
|
required,
|
||||||
}: {
|
class: classValue
|
||||||
label?: string;
|
}: Props = $props();
|
||||||
name: string;
|
|
||||||
value?: string;
|
|
||||||
invalidMessage?: string;
|
|
||||||
required?: boolean;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
const sortedTimeZones = Intl.supportedValuesOf('timeZone')
|
const sortedTimeZones = Intl.supportedValuesOf('timeZone')
|
||||||
.map((timeZone) => {
|
.map((timeZone) => {
|
||||||
@@ -88,7 +94,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ExpandableCombobox
|
<Combobox
|
||||||
{label}
|
{label}
|
||||||
{name}
|
{name}
|
||||||
{invalidMessage}
|
{invalidMessage}
|
||||||
@@ -97,6 +103,7 @@
|
|||||||
{options}
|
{options}
|
||||||
matchWidth
|
matchWidth
|
||||||
placeholder="Select a timezone"
|
placeholder="Select a timezone"
|
||||||
|
class={classValue}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#snippet timezoneLabel(item: ComboboxItem)}
|
{#snippet timezoneLabel(item: ComboboxItem)}
|
||||||
|
|||||||
@@ -2,22 +2,30 @@
|
|||||||
import Label from './Label.svelte';
|
import Label from './Label.svelte';
|
||||||
import ToggleSelect from './ToggleSelect.svelte';
|
import ToggleSelect from './ToggleSelect.svelte';
|
||||||
import { validate, validateInput } from '@svelte-toolkit/validate';
|
import { validate, validateInput } from '@svelte-toolkit/validate';
|
||||||
|
import { generateIdentifier, getLabel, getValue, type Option } from './util.js';
|
||||||
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
options: Option[];
|
||||||
|
selected?: string[];
|
||||||
|
name?: string;
|
||||||
|
label?: string;
|
||||||
|
required?: boolean;
|
||||||
|
missingMessage?: string;
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
items,
|
options,
|
||||||
selected = $bindable([]),
|
selected = $bindable([]),
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
required,
|
required,
|
||||||
missingMessage
|
missingMessage,
|
||||||
}: {
|
class: classValue
|
||||||
items: string[];
|
}: Props = $props();
|
||||||
selected?: string[];
|
|
||||||
name?: string;
|
const id = $derived(generateIdentifier('toggle-group', name));
|
||||||
label: string;
|
|
||||||
required?: boolean;
|
|
||||||
missingMessage?: string;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
let inputElement: HTMLInputElement | undefined = $state<HTMLInputElement>();
|
let inputElement: HTMLInputElement | undefined = $state<HTMLInputElement>();
|
||||||
let valid: boolean = $state(true);
|
let valid: boolean = $state(true);
|
||||||
@@ -33,15 +41,16 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class={classValue}>
|
||||||
{#if label && name}
|
{#if label}
|
||||||
<Label for={name}>{label}</Label>
|
<Label for={id}>{label}</Label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-3">
|
<div class="flex flex-wrap gap-3">
|
||||||
{#if name}
|
{#if name}
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
{id}
|
||||||
{name}
|
{name}
|
||||||
required={required ? true : false}
|
required={required ? true : false}
|
||||||
use:validate={{ required, baseval: '[]' }}
|
use:validate={{ required, baseval: '[]' }}
|
||||||
@@ -52,20 +61,19 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#each items as item}
|
{#each options as opt}
|
||||||
<ToggleSelect name={name ? undefined : `toggl_${item}`} ontoggle={makeSelectedHandler(item)}>
|
<ToggleSelect
|
||||||
<span class="capitalize">{item}</span>
|
name={name ? undefined : `toggle_${getValue(opt)}`}
|
||||||
|
ontoggle={makeSelectedHandler(getValue(opt))}
|
||||||
|
>
|
||||||
|
<span class={[typeof opt === 'string' && 'capitalize']}>{getLabel(opt)}</span>
|
||||||
</ToggleSelect>
|
</ToggleSelect>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if name}
|
|
||||||
<div class={['mt-2 opacity-0 transition-opacity', !valid && 'opacity-100']}>
|
<div class={['mt-2 opacity-0 transition-opacity', !valid && 'opacity-100']}>
|
||||||
<Label for={name} error>
|
<Label for={id} error>
|
||||||
{missingMessage !== undefined && missingMessage !== ''
|
{missingMessage !== undefined && missingMessage !== '' ? missingMessage : 'Field is required'}
|
||||||
? missingMessage
|
|
||||||
: 'Field is required'}
|
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import type { MouseEventHandler } from 'svelte/elements';
|
import type { ClassValue, MouseEventHandler } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
interface Props {
|
||||||
name,
|
|
||||||
selected = false,
|
|
||||||
children,
|
|
||||||
onclick,
|
|
||||||
ontoggle
|
|
||||||
}: {
|
|
||||||
name?: string;
|
name?: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
class?: ClassValue | null | undefined;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
onclick?: MouseEventHandler<HTMLButtonElement>;
|
onclick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
ontoggle?: (selected: boolean) => void;
|
ontoggle?: (selected: boolean) => void;
|
||||||
} = $props();
|
}
|
||||||
|
|
||||||
|
let { name, selected = false, class: classValue, children, onclick, ontoggle }: Props = $props();
|
||||||
|
|
||||||
const handleToggleSelectClick: MouseEventHandler<HTMLButtonElement> = (event) => {
|
const handleToggleSelectClick: MouseEventHandler<HTMLButtonElement> = (event) => {
|
||||||
selected = !selected; // update state
|
selected = !selected; // update state
|
||||||
@@ -28,9 +25,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded-3xl border px-6 py-2.5 font-medium transition-colors {selected
|
class={[
|
||||||
|
'rounded-3xl border px-6 py-2.5 font-medium transition-colors',
|
||||||
|
selected
|
||||||
? 'border-secondary bg-primary text-background hover:bg-primary-600'
|
? 'border-secondary bg-primary text-background hover:bg-primary-600'
|
||||||
: 'border-accent text-text dark:border-accent/50 dark:bg-text dark:text-background dark:hover:bg-text-900 bg-white hover:bg-slate-100'}"
|
: 'border-accent text-text dark:border-accent/50 dark:bg-text dark:text-background dark:hover:bg-text-900 bg-white hover:bg-slate-100',
|
||||||
|
classValue
|
||||||
|
]}
|
||||||
onclick={handleToggleSelectClick}
|
onclick={handleToggleSelectClick}
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export { default as Link } from './Link.svelte';
|
|||||||
export { default as PhoneInput } from './PhoneInput.svelte';
|
export { default as PhoneInput } from './PhoneInput.svelte';
|
||||||
export { default as PinInput } from './PinInput.svelte';
|
export { default as PinInput } from './PinInput.svelte';
|
||||||
export { default as RadioGroup } from './RadioGroup.svelte';
|
export { default as RadioGroup } from './RadioGroup.svelte';
|
||||||
export { default as Select } from './Select.svelte';
|
|
||||||
export { default as Spinner } from './Spinner.svelte';
|
export { default as Spinner } from './Spinner.svelte';
|
||||||
export { default as StateMachine, type StateMachinePage } from './StateMachine.svelte';
|
export { default as StateMachine, type StateMachinePage } from './StateMachine.svelte';
|
||||||
export { default as StyledRawInput } from './StyledRawInput.svelte';
|
export { default as StyledRawInput } from './StyledRawInput.svelte';
|
||||||
@@ -19,3 +18,4 @@ export { default as TimeInput } from './TimeInput.svelte';
|
|||||||
export { default as TimezoneInput } from './TimezoneInput.svelte';
|
export { default as TimezoneInput } from './TimezoneInput.svelte';
|
||||||
export { default as ToggleGroup } from './ToggleGroup.svelte';
|
export { default as ToggleGroup } from './ToggleGroup.svelte';
|
||||||
export { default as ToggleSelect } from './ToggleSelect.svelte';
|
export { default as ToggleSelect } from './ToggleSelect.svelte';
|
||||||
|
export { type Option, getLabel, getValue } from './util.js';
|
||||||
|
|||||||
@@ -15,3 +15,31 @@ export const generateIdentifier = (prefix?: string, identifier?: string): string
|
|||||||
const timestampPart = Date.now().toString(36);
|
const timestampPart = Date.now().toString(36);
|
||||||
return `${prefix ? prefix + '-' : ''}${randomPart}-${timestampPart}`;
|
return `${prefix ? prefix + '-' : ''}${randomPart}-${timestampPart}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option type definition for a select option. Used in various components.
|
||||||
|
*/
|
||||||
|
export type Option =
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
| string;
|
||||||
|
|
||||||
|
/** getLabel returns the option label if it exists*/
|
||||||
|
export const getLabel = (option: Option): string => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return option;
|
||||||
|
} else {
|
||||||
|
return option.label ?? option.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** getValue returns the option value */
|
||||||
|
export const getValue = (option: Option): string => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return option;
|
||||||
|
} else {
|
||||||
|
return option.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
name="example-toggle-group"
|
name="example-toggle-group"
|
||||||
label="Toggler"
|
label="Toggler"
|
||||||
items={['item one', 'item two', 'item three']}
|
options={['item one', 'item two', { value: 'complex', label: 'Complex item' }]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user