Compare commits
3 Commits
da34c24f91
...
d32d88b56c
| Author | SHA1 | Date | |
|---|---|---|---|
| d32d88b56c | |||
| 71df72f843 | |||
| 6069fd29ae |
52
src/lib/components/ui/button/Button.svelte
Normal file
52
src/lib/components/ui/button/Button.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Button as BitsButton, type WithoutChildren } from 'bits-ui';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { COLOR_MAP, VARIANT_MAP, SIZE_MAP } from './button.variants';
|
||||||
|
import type { Color, Variant, Size } from './button.variants';
|
||||||
|
|
||||||
|
type Props = WithoutChildren<BitsButton.RootProps> & {
|
||||||
|
color?: Color;
|
||||||
|
variant?: Variant;
|
||||||
|
size?: Size;
|
||||||
|
wide?: boolean;
|
||||||
|
block?: boolean;
|
||||||
|
square?: boolean;
|
||||||
|
circle?: boolean;
|
||||||
|
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
color,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
wide,
|
||||||
|
block,
|
||||||
|
square,
|
||||||
|
circle,
|
||||||
|
class: className = '',
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const classes = $derived(
|
||||||
|
[
|
||||||
|
'btn',
|
||||||
|
color && COLOR_MAP[color],
|
||||||
|
variant && VARIANT_MAP[variant],
|
||||||
|
size && SIZE_MAP[size],
|
||||||
|
wide && 'btn-wide',
|
||||||
|
block && 'btn-block',
|
||||||
|
square && 'btn-square',
|
||||||
|
circle && 'btn-circle',
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<BitsButton.Root class={classes.trim()} {...restProps}>
|
||||||
|
{@render children?.()}
|
||||||
|
</BitsButton.Root>
|
||||||
30
src/lib/components/ui/button/button.variants.ts
Normal file
30
src/lib/components/ui/button/button.variants.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export const COLOR_MAP = {
|
||||||
|
neutral: 'btn-neutral',
|
||||||
|
primary: 'btn-primary',
|
||||||
|
secondary: 'btn-secondary',
|
||||||
|
accent: 'btn-accent',
|
||||||
|
info: 'btn-info',
|
||||||
|
success: 'btn-success',
|
||||||
|
warning: 'btn-warning',
|
||||||
|
error: 'btn-error',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const VARIANT_MAP = {
|
||||||
|
outline: 'btn-outline',
|
||||||
|
dash: 'btn-dash',
|
||||||
|
soft: 'btn-soft',
|
||||||
|
ghost: 'btn-ghost',
|
||||||
|
link: 'btn-link',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const SIZE_MAP = {
|
||||||
|
xs: 'btn-xs',
|
||||||
|
sm: 'btn-sm',
|
||||||
|
md: 'btn-md',
|
||||||
|
lg: 'btn-lg',
|
||||||
|
xl: 'btn-xl',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Color = keyof typeof COLOR_MAP;
|
||||||
|
export type Variant = keyof typeof VARIANT_MAP;
|
||||||
|
export type Size = keyof typeof SIZE_MAP;
|
||||||
1
src/lib/components/ui/button/index.ts
Normal file
1
src/lib/components/ui/button/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Button } from './Button.svelte';
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Select as BitsSelect, type WithoutChildren } from 'bits-ui';
|
import { Select as BitsSelect, type WithoutChildren } from 'bits-ui';
|
||||||
|
import { elasticOut, cubicOut } from 'svelte/easing';
|
||||||
|
|
||||||
type Props = WithoutChildren<BitsSelect.ContentProps> & {
|
type Props = WithoutChildren<BitsSelect.ContentProps> & {
|
||||||
items: { value: string; label: string; disabled?: boolean }[];
|
items: { value: string; label: string; disabled?: boolean }[];
|
||||||
@ -7,14 +8,61 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let { items, class: className = '', ...restProps }: Props = $props();
|
let { items, class: className = '', ...restProps }: Props = $props();
|
||||||
|
|
||||||
|
function selectTransition(
|
||||||
|
node: HTMLElement,
|
||||||
|
params: {
|
||||||
|
delay?: number;
|
||||||
|
duration?: number;
|
||||||
|
easing?: (t: number) => number;
|
||||||
|
y?: number;
|
||||||
|
start?: number;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
delay = 0,
|
||||||
|
duration = 1000,
|
||||||
|
easing = cubicOut,
|
||||||
|
y = -6,
|
||||||
|
start = 0.95,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
const existingTransform = getComputedStyle(node).transform.replace(
|
||||||
|
'none',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
delay,
|
||||||
|
duration,
|
||||||
|
easing,
|
||||||
|
css: (t: number, u: number) => {
|
||||||
|
const translate = `translateY(${u * y}px)`;
|
||||||
|
const scale = `scale(${start + t * (1 - start)})`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
opacity: ${t};
|
||||||
|
transform: ${existingTransform} ${translate} ${scale};
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BitsSelect.Portal>
|
<BitsSelect.Portal>
|
||||||
<BitsSelect.Content
|
<BitsSelect.Content
|
||||||
forceMount
|
forceMount
|
||||||
sideOffset={4}
|
sideOffset={4}
|
||||||
class={`data=[state=closed]:fade-out-0 select-motion w-(--bits-select-anchor-width) min-w-(--bits-select-anchor-width) rounded-xl border-[1.5px] border-base-content/20 bg-base-100 select-none ${className}`}
|
class={`w-(--bits-select-anchor-width) min-w-(--bits-select-anchor-width) rounded-xl border-[1.5px] border-base-content/20 bg-base-100 select-none ${className}`}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
>
|
||||||
|
{#snippet child({ wrapperProps, props, open })}
|
||||||
|
{#if open}
|
||||||
|
<div {...wrapperProps}>
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
in:selectTransition={{ easing: elasticOut }}
|
||||||
|
out:selectTransition={{ duration: 200 }}
|
||||||
>
|
>
|
||||||
<BitsSelect.ScrollUpButton>up</BitsSelect.ScrollUpButton>
|
<BitsSelect.ScrollUpButton>up</BitsSelect.ScrollUpButton>
|
||||||
<BitsSelect.Viewport class="p-1">
|
<BitsSelect.Viewport class="p-1">
|
||||||
@ -30,25 +78,9 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</BitsSelect.Viewport>
|
</BitsSelect.Viewport>
|
||||||
<BitsSelect.ScrollDownButton>down</BitsSelect.ScrollDownButton>
|
<BitsSelect.ScrollDownButton>down</BitsSelect.ScrollDownButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
</BitsSelect.Content>
|
</BitsSelect.Content>
|
||||||
</BitsSelect.Portal>
|
</BitsSelect.Portal>
|
||||||
|
|
||||||
<style lang="postcss">
|
|
||||||
@reference "tailwindcss";
|
|
||||||
|
|
||||||
:global(.select-content-animate) {
|
|
||||||
@apply transition-[opacity,transform] duration-200 ease-out will-change-transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.select-content-animate[data-state='open']) {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.select-content-animate[data-state='closed']) {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "../styles/select-motion.css";
|
|
||||||
@plugin "@tailwindcss/forms";
|
@plugin "@tailwindcss/forms";
|
||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
logs: false;
|
logs: false;
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
@layer components {
|
|
||||||
.select-motion {
|
|
||||||
@apply transition-all duration-200 ease-out will-change-transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-motion[data-state="open"] {
|
|
||||||
@apply pointer-events-auto translate-y-0 scale-100 opacity-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-motion[data-state="closed"] {
|
|
||||||
@apply pointer-events-none -translate-y-2 scale-95 opacity-0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user