🎨 Add new badge, button & input UI components

This commit is contained in:
pheralb 2025-02-04 22:53:21 +00:00
parent 21cbb24f17
commit b9db482f0c
6 changed files with 180 additions and 0 deletions

18
src/ui/badge/badge.svelte Normal file
View File

@ -0,0 +1,18 @@
<script lang="ts">
import { type Variant, badgeVariants } from '.';
import { cn } from '@/utils/cn';
let className: string | undefined | null = undefined;
export let href: string | undefined = undefined;
export let variant: Variant = 'default';
export { className as class };
</script>
<svelte:element
this={href ? 'a' : 'span'}
{href}
class={cn(badgeVariants({ variant, className }))}
{...$$restProps}
>
<slot />
</svelte:element>

17
src/ui/badge/index.ts Normal file
View File

@ -0,0 +1,17 @@
import { type VariantProps, tv } from 'tailwind-variants';
export { default as Badge } from './badge.svelte';
export const badgeVariants = tv({
base: 'inline-flex items-center px-2.5 py-0.5 rounded-full font-medium text-xs font-mono hover:underline transition-colors duration-100',
variants: {
variant: {
default:
'bg-neutral-100 dark:bg-neutral-800/50 border border-neutral-200 dark:border-neutral-800 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700/50'
}
},
defaultVariants: {
variant: 'default'
}
});
export type Variant = VariantProps<typeof badgeVariants>['variant'];

View File

@ -0,0 +1,25 @@
<script lang="ts">
import { Button as ButtonPrimitive } from 'bits-ui';
import { type Events, type Props, buttonVariants } from '.';
import { cn } from '@/utils/cn';
type $$Props = Props;
type $$Events = Events;
let className: $$Props['class'] = undefined;
export let variant: $$Props['variant'] = 'default';
export let size: $$Props['size'] = 'default';
export let builders: $$Props['builders'] = [];
export { className as class };
</script>
<ButtonPrimitive.Root
{builders}
class={cn(buttonVariants({ variant, size }), className)}
type="button"
{...$$restProps}
on:click
on:keydown
>
<slot />
</ButtonPrimitive.Root>

53
src/ui/button/index.ts Normal file
View File

@ -0,0 +1,53 @@
import type { Button as ButtonPrimitive } from 'bits-ui';
import { type VariantProps, tv } from 'tailwind-variants';
import Root from './button.svelte';
const buttonVariants = tv({
base: 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-300 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 dark:focus-visible:ring-neutral-700',
variants: {
variant: {
default:
'bg-neutral-900 text-neutral-50 shadow hover:bg-neutral-900/90 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/70',
destructive:
'bg-red-500 text-neutral-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90',
outline:
'border border-neutral-200 bg-white shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50',
secondary:
'bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80',
ghost:
'hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50',
link: 'text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50'
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
type Variant = VariantProps<typeof buttonVariants>['variant'];
type Size = VariantProps<typeof buttonVariants>['size'];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export {
Root,
type Props,
type Events,
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants
};

31
src/ui/input/index.ts Normal file
View File

@ -0,0 +1,31 @@
import { cn } from '@/utils/cn';
import Root from './input.svelte';
export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export const InputStyles = cn(
'flex h-9 w-full rounded-md border border-neutral-200 bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-300 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-800 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-700'
);
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
mousemove: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
wheel: FormInputEvent<WheelEvent>;
};
export { Root, Root as Input };

36
src/ui/input/input.svelte Normal file
View File

@ -0,0 +1,36 @@
<script lang="ts">
import type { HTMLInputAttributes } from 'svelte/elements';
import { InputStyles, type InputEvents } from '.';
import { cn } from '@/utils/cn';
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
let className: $$Props['class'] = undefined;
export let value: $$Props['value'] = undefined;
export { className as class };
export let readonly: $$Props['readonly'] = undefined;
</script>
<input
class={cn(InputStyles, className)}
bind:value
{readonly}
on:blur
on:change
on:click
on:focus
on:focusin
on:focusout
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:mousemove
on:paste
on:input
on:wheel|passive
{...$$restProps}
/>