Skip to content

Commit f8bee3d

Browse files
committed
feat: add icon button component
1 parent e97f652 commit f8bee3d

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<script lang="ts" generics="T">
2+
import { cn } from '$lib/utils'
3+
import type { HTMLButtonAttributes } from 'svelte/elements'
4+
5+
interface IButtonProps extends HTMLButtonAttributes {
6+
variant?: 'white' | 'clear'
7+
isLoading?: boolean
8+
cb?: () => Promise<void>
9+
blockingClick?: boolean
10+
type?: 'button' | 'submit' | 'reset'
11+
}
12+
13+
let {
14+
variant = 'white',
15+
isLoading,
16+
cb,
17+
blockingClick,
18+
type = 'button',
19+
children = undefined,
20+
...restProps
21+
}: IButtonProps = $props()
22+
23+
let isSubmitting = $state(false)
24+
let disabled = $derived(restProps.disabled || isLoading || isSubmitting)
25+
26+
const handleClick = async () => {
27+
if (typeof cb !== 'function') return
28+
29+
if (blockingClick) isSubmitting = true
30+
try {
31+
await cb()
32+
} catch (error) {
33+
console.error('Error in button callback:', error)
34+
} finally {
35+
isSubmitting = false
36+
}
37+
}
38+
39+
const variantClasses = {
40+
white: { background: 'bg-white-900', text: 'text-black' },
41+
clear: { background: 'transparent', text: 'text-white-900' },
42+
}
43+
44+
const disabledVariantClasses = {
45+
white: { background: 'bg-white-300', text: 'text-black' },
46+
clear: { background: 'bg-transparent', text: 'text-white-300' },
47+
}
48+
49+
let classes = $derived({
50+
common:
51+
'cursor-pointer flex items-center justify-center p-8 rounded-full text-xl font-semibold h-[56px] duration-100',
52+
background: disabled
53+
? disabledVariantClasses[variant].background ||
54+
variantClasses[variant].background
55+
: variantClasses[variant].background,
56+
text: disabled
57+
? disabledVariantClasses[variant].text || variantClasses[variant].text
58+
: variantClasses[variant].text,
59+
disabled: 'cursor-not-allowed',
60+
})
61+
</script>
62+
63+
<button
64+
{...restProps}
65+
class={cn(
66+
[
67+
classes.common,
68+
classes.background,
69+
classes.text,
70+
disabled && classes.disabled,
71+
restProps.class,
72+
].join(' ')
73+
)}
74+
{disabled}
75+
onclick={handleClick}
76+
{type}
77+
>
78+
<div class="relative flex items-center justify-center">
79+
{#if isLoading || isSubmitting}
80+
<div class="loading loading-spinner loading-md absolute -left-4"></div>
81+
{/if}
82+
<div
83+
class="flex items-center justify-center duration-100"
84+
class:translate-x-4={isLoading || isSubmitting}
85+
>
86+
{@render children?.()}
87+
</div>
88+
</div>
89+
</button>
90+
91+
<!--
92+
@component
93+
export default ButtonAction
94+
@description
95+
This component is a button with a loading spinner that can be used to indicate that an action is being performed.
96+
97+
@props
98+
- variant: The variant of the button. Default is `solid`.
99+
- isLoading: A boolean to indicate if the button is in a loading state.
100+
- cb: A callback function that will be called when the button is clicked.
101+
- blockingClick: A boolean to indicate if the button should block the click event while the callback function is being executed.
102+
- icon: A slot for an icon to be displayed inside the button.
103+
- ...restProps: Any other props that can be passed to a button element.
104+
105+
@usage
106+
```html
107+
<script lang="ts">
108+
import * as Button from '$lib/ui/Button'
109+
</script>
110+
111+
<Button.Action variant="solid" cb={() => console.log('clicked')}>
112+
Click me
113+
</Button.Action>
114+
```
115+
-->

0 commit comments

Comments
 (0)