Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better labels #94

Merged
merged 1 commit into from
Jan 23, 2025
Merged
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
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 @@ -28,13 +28,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 @@ -56,6 +56,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
Loading