Skip to content

Commit

Permalink
feat: better labels (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 authored Jan 23, 2025
1 parent ce9876b commit da4f813
Show file tree
Hide file tree
Showing 17 changed files with 157 additions and 118 deletions.
13 changes: 12 additions & 1 deletion src/lib/common/context.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,15 @@ const fieldKey = Symbol(withPrefix('field'));

export const setFieldContext = (field: FieldContext) => setContext(fieldKey, field);
export const hasFieldContext = (): boolean => hasContext(fieldKey);
export const getFieldContext = (): FieldContext => (getContext(fieldKey) || {}) as FieldContext;
export const getFieldContext = () => {
const {
label,
color = 'secondary',
invalid = false,
readOnly = false,
required = false,
disabled = false,
} = (getContext(fieldKey) as FieldContext) || {};

return { label, color, invalid, readOnly, required, disabled };
};
25 changes: 4 additions & 21 deletions src/lib/components/Form/Checkbox.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { getFieldContext } from '$lib/common/context.svelte.js';
import Icon from '$lib/components/Icon/Icon.svelte';
import Label from '$lib/components/Label/Label.svelte';
import type { Color, Shape, Size } from '$lib/types.js';
import { cleanClass, generateId } from '$lib/utils.js';
import { mdiCheck, mdiMinus } from '@mdi/js';
Expand All @@ -23,13 +24,8 @@
...restProps
}: CheckboxProps = $props();
const {
label,
readOnly = false,
required = false,
invalid = false,
disabled = false,
} = $derived(getFieldContext());
const { readOnly, required, invalid, disabled, label, ...labelProps } =
$derived(getFieldContext());
const containerStyles = tv({
base: 'ring-offset-background focus-visible:ring-ring peer box-content overflow-hidden border-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[disabled=true]:opacity-50',
Expand Down Expand Up @@ -80,27 +76,14 @@
},
});
const labelStyles = tv({
base: '',
variants: {
size: {
tiny: 'text-xs',
small: 'text-sm',
medium: 'text-md',
large: 'text-lg',
giant: 'text-xl',
},
},
});
const id = generateId();
const inputId = `input-${id}`;
const labelId = `label-${id}`;
</script>

<div class="flex flex-col gap-1">
{#if label}
<label id={labelId} for={inputId} class={labelStyles({ size })}>{label}</label>
<Label id={labelId} for={inputId} {label} {...labelProps} />
{/if}

<CheckboxPrimitive.Root
Expand Down
5 changes: 3 additions & 2 deletions src/lib/components/Form/Field.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import { type Snippet } from 'svelte';
type Props = FieldContext & {
class?: string;
children: Snippet;
};
const { children, ...props }: Props = $props();
const { class: className, children, ...props }: Props = $props();
const state = $state(props);
Expand All @@ -20,7 +21,7 @@
const helperTextChildren = $derived(getChildSnippet(ChildKey.HelperText));
</script>

<div class="w-full">
<div class={cleanClass('w-full', className)}>
{@render children()}
{#if helperTextChildren}
<div class={cleanClass('pt-1', helperTextChildren.class)}>
Expand Down
37 changes: 14 additions & 23 deletions src/lib/components/Form/Input.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { getFieldContext } from '$lib/common/context.svelte.js';
import Label from '$lib/components/Label/Label.svelte';
import type { InputProps } from '$lib/types.js';
import { cleanClass, generateId } from '$lib/utils.js';
import { tv } from 'tailwind-variants';
Expand All @@ -14,26 +15,8 @@
...restProps
}: InputProps = $props();
const {
label,
readOnly = false,
required = false,
invalid = false,
disabled = false,
} = $derived(getFieldContext());
const labelStyles = tv({
base: '',
variants: {
size: {
tiny: 'text-xs',
small: 'text-sm',
medium: 'text-md',
large: 'text-lg',
giant: 'text-xl',
},
},
});
const { label, readOnly, required, invalid, disabled, ...labelProps } =
$derived(getFieldContext());
const inputStyles = tv({
base: 'w-full bg-gray-200 outline-none disabled:cursor-not-allowed disabled:bg-gray-300 disabled:text-gray-200 aria-readonly:text-dark/50 dark:bg-gray-600 dark:disabled:bg-gray-800 dark:aria-readonly:text-dark/75',
Expand All @@ -54,6 +37,14 @@
large: 'rounded-2xl',
giant: 'rounded-2xl',
},
// match with Button `iconSize` variants
paddingRight: {
tiny: 'pr-6',
small: 'pr-8',
medium: 'pr-10',
large: 'pr-12',
giant: 'pr-14',
},
textSize: {
tiny: 'text-xs',
small: 'text-sm',
Expand All @@ -75,12 +66,12 @@

<div class="flex w-full flex-col gap-1" bind:this={containerRef}>
{#if label}
<label id={labelId} for={inputId} class={labelStyles({ size })}>{label}</label>
<Label id={labelId} for={inputId} {label} {...labelProps} />
{/if}

<div class="relative mt-1.5 w-full">
<input
id={label && inputId}
id={inputId}
aria-labelledby={label && labelId}
{required}
aria-required={required}
Expand All @@ -93,10 +84,10 @@
shape,
textSize: size,
padding: shape === 'round' ? 'round' : 'base',
paddingRight: trailingIcon ? size : undefined,
roundedSize: shape === 'semi-round' ? size : undefined,
invalid,
}),
trailingIcon && '!pr-10',
className,
)}
bind:value
Expand Down
19 changes: 0 additions & 19 deletions src/lib/components/Form/Label.svelte

This file was deleted.

8 changes: 5 additions & 3 deletions src/lib/components/Form/PasswordInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,24 @@
translations,
isVisible = $bindable<boolean>(false),
color = 'secondary',
size,
...props
}: PasswordInputProps = $props();
</script>

<Input bind:value type={isVisible ? 'text' : 'password'} {color} {...props}>
<Input bind:value {size} type={isVisible ? 'text' : 'password'} {color} {...props}>
{#snippet trailingIcon()}
{#if value?.length > 0}
<IconButton
variant="ghost"
shape="round"
color="secondary"
class="m-1"
{size}
class="mr-1"
icon={isVisible ? mdiEyeOffOutline : mdiEyeOutline}
onclick={() => (isVisible = !isVisible)}
title={isVisible ? t('hidePassword', translations) : t('showPassword', translations)}
></IconButton>
/>
{/if}
{/snippet}
</Input>
2 changes: 1 addition & 1 deletion src/lib/components/IconButton/IconButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
</script>

<Button icon {...buttonProps}>
<Icon {icon} {flipped} {flopped} size="45%"></Icon>
<Icon {icon} {flipped} {flopped} size="45%" />
</Button>
12 changes: 12 additions & 0 deletions src/lib/components/Label/Label.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
import { styles } from '$lib/styles.js';
import type { LabelProps } from '$lib/types.js';
import { cleanClass } from '$lib/utils.js';
const { label, size, color, class: className, children, ...restProps }: LabelProps = $props();
</script>

<label class={cleanClass(styles.label({ size, color }), className)} {...restProps}>
{#if label}{label}{/if}
{@render children?.()}
</label>
32 changes: 20 additions & 12 deletions src/lib/components/Switch/Switch.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
import { getFieldContext } from '$lib/common/context.svelte.js';
import Label from '$lib/components/Label/Label.svelte';
import type { Color } from '$lib/types.js';
import { cleanClass } from '$lib/utils.js';
import { cleanClass, generateId } from '$lib/utils.js';
import type { HTMLInputAttributes } from 'svelte/elements';
import { tv } from 'tailwind-variants';
Expand All @@ -11,22 +12,19 @@
disabled?: boolean;
class?: string;
onToggle?: ((checked: boolean) => void) | undefined;
} & HTMLInputAttributes;
inputSize?: HTMLInputAttributes['size'];
} & Omit<HTMLInputAttributes, 'size'>;
let {
checked = $bindable(false),
class: className,
color = 'primary',
onToggle = undefined,
inputSize,
...restProps
}: Props = $props();
const {
label,
readOnly = false,
required = false,
disabled = false,
} = $derived(getFieldContext());
const { readOnly, required, disabled, label, ...labelProps } = $derived(getFieldContext());
const enabled = $derived(checked && !disabled);
Expand Down Expand Up @@ -75,12 +73,21 @@
},
},
});
const id = generateId();
const inputId = `input-${id}`;
const labelId = `label-${id}`;
</script>

<label class={cleanClass(className)}>
{label}
<span class={wrapper({ disabled })}>
<div class="flex w-full justify-between gap-1">
{#if label}
<Label id={labelId} for={inputId} {label} {...labelProps} />
{/if}

<span class={cleanClass(wrapper({ disabled }), className)}>
<input
id={inputId}
aria-labelledby={label && labelId}
class="hidden"
type="checkbox"
bind:checked
Expand All @@ -91,9 +98,10 @@
aria-disabled={disabled}
readonly={readOnly}
aria-readonly={readOnly}
size={inputSize}
{...restProps}
/>
<span class={bar({ fillColor: enabled ? color : 'default' })}> </span>
<span class={dot({ checked: enabled, fillColor: enabled ? color : 'default' })}></span>
</span>
</label>
</div>
4 changes: 2 additions & 2 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export { default as Checkbox } from '$lib/components/Form/Checkbox.svelte';
export { default as Field } from '$lib/components/Form/Field.svelte';
export { default as HelperText } from '$lib/components/Form/HelperText.svelte';
export { default as Input } from '$lib/components/Form/Input.svelte';
export { default as Label } from '$lib/components/Form/Label.svelte';
export { default as PasswordInput } from '$lib/components/Form/PasswordInput.svelte';
export { default as FormatBytes } from '$lib/components/FormatBytes/FormatBytes.svelte';
export { default as Heading } from '$lib/components/Heading/Heading.svelte';
export { default as Icon } from '$lib/components/Icon/Icon.svelte';
export { default as IconButton } from '$lib/components/IconButton/IconButton.svelte';
export { default as Kbd } from '$lib/components/Kbd/Kbd.svelte';
export { default as Label } from '$lib/components/Label/Label.svelte';
export { default as Link } from '$lib/components/Link/Link.svelte';
export { default as LoadingSpinner } from '$lib/components/LoadingSpinner/LoadingSpinner.svelte';
export { default as Logo } from '$lib/components/Logo/Logo.svelte';
Expand All @@ -57,6 +57,6 @@ export { default as ThemeSwitcher } from '$lib/components/ThemeSwitcher/ThemeSwi

// helpers
export * from '$lib/services/theme.svelte.js';
export * from '$lib/services/translation.svelte.js';
export * from '$lib/types.js';
export * from '$lib/utilities/byte-units.js';
export * from '$lib/services/translation.svelte.js';
10 changes: 5 additions & 5 deletions src/lib/internal/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
giant: 'text-xl',
},
iconSize: {
tiny: 'h-4 w-4 text-xs',
small: 'h-6 w-6 text-sm',
medium: 'text-md h-8 w-8',
large: 'h-10 w-10 text-lg',
giant: 'h-12 w-12 text-lg',
tiny: 'h-6 w-6',
small: 'h-8 w-8',
medium: 'h-10 w-10',
large: 'h-12 w-12',
giant: 'h-14 w-14',
},
roundedSize: {
tiny: 'rounded-lg',
Expand Down
Loading

0 comments on commit da4f813

Please sign in to comment.