Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ComponentProps } from "svelte";
import { ButtonText } from "./Button.stories.snippet.svelte";
import ButtonAction from "./ButtonAction.svelte";
import { ButtonText } from "./ButtonSnippets.svelte";

export default {
title: "UI/ButtonAction",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface IButtonProps extends HTMLButtonAttributes {
size?: "sm" | "md";
}

let {
const {
variant = "solid",
isLoading,
callback,
Expand All @@ -23,7 +23,7 @@ let {
}: IButtonProps = $props();

let isSubmitting = $state(false);
let disabled = $derived(restProps.disabled || isLoading || isSubmitting);
const disabled = $derived(restProps.disabled || isLoading || isSubmitting);

const handleClick = async () => {
if (typeof callback !== "function") return;
Expand Down Expand Up @@ -59,7 +59,7 @@ const sizeVariant = {
md: "px-8 py-2.5 text-xl h-14",
};

let classes = $derived({
const classes = $derived({
common: cn(
"cursor-pointer w-min flex items-center justify-center rounded-full font-semibold duration-100",
sizeVariant[size],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { FlashlightIcon } from "@hugeicons/core-free-icons";
import type { ComponentProps } from "svelte";
import ButtonIcon from "./ButtonIcon.svelte";

export default {
title: "UI/ButtonIcon",
component: ButtonIcon,
tags: ["autodocs"],
render: (args: {
Component: ButtonIcon<{ icon: typeof FlashlightIcon }>;
props: ComponentProps<typeof ButtonIcon>;
}) => ({
Component: ButtonIcon,
props: args,
}),
};

export const Default = {
render: () => ({
Component: ButtonIcon,
props: {
variant: "white",
ariaLabel: "Default button",
size: "md",
icon: FlashlightIcon,
},
}),
};

export const Loading = {
render: () => ({
Component: ButtonIcon,
props: {
variant: "white",
ariaLabel: "Loading button",
size: "md",
icon: FlashlightIcon,
isLoading: true,
},
}),
};

export const Active = {
render: () => ({
Component: ButtonIcon,
props: {
variant: "white",
ariaLabel: "Active button",
size: "md",
icon: FlashlightIcon,
isActive: true,
},
}),
};
165 changes: 165 additions & 0 deletions infrastructure/eid-wallet/src/lib/ui/Button/ButtonIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<script lang="ts" generics="T">
import { cn } from "$lib/utils";
import { HugeiconsIcon, type IconSvgElement } from "@hugeicons/svelte";
import type { HTMLButtonAttributes } from "svelte/elements";

interface IButtonProps extends HTMLButtonAttributes {
variant?: "white" | "clear-on-light" | "clear-on-dark";
isLoading?: boolean;
callback?: () => Promise<void>;
onclick?: () => void;
blockingClick?: boolean;
type?: "button" | "submit" | "reset";
size?: "sm" | "md" | "lg";
iconSize?: "sm" | "md" | "lg" | number;
icon: IconSvgElement;
isActive?: boolean;
}

const {
variant = "white",
isLoading,
callback,
onclick,
blockingClick,
type = "button",
size = "md",
icon,
iconSize = undefined,
isActive = false,
children = undefined,
...restProps
}: IButtonProps = $props();

let isSubmitting = $state(false);
const disabled = $derived(restProps.disabled || isLoading || isSubmitting);

const handleClick = async () => {
if (typeof callback !== "function") return;

if (blockingClick) isSubmitting = true;
try {
await callback();
} catch (error) {
console.error("Error in button callback:", error);
} finally {
isSubmitting = false;
}
};

const variantClasses = {
white: { background: "bg-white", text: "text-black" },
"clear-on-light": { background: "transparent", text: "text-black" },
"clear-on-dark": { background: "transparent", text: "text-white" },
};

const disabledClasses = {
white: { background: "bg-white", text: "text-black-500" },
"clear-on-light": { background: "bg-transparent", text: "text-black-500" },
"clear-on-dark": { background: "bg-transparent", text: "text-black-500" },
};

const isActiveClasses = {
white: { background: "bg-secondary-500", text: "text-black" },
"clear-on-light": { background: "bg-secondary-500", text: "text-black" },
"clear-on-dark": { background: "bg-secondary-500", text: "text-black" },
};

const sizeVariant = {
sm: "h-8 w-8",
md: "h-[54px] w-[54px]",
lg: "h-[108px] w-[108px]",
};

const iconSizeVariant = {
sm: 24,
md: 24,
lg: 36,
};

const resolvedIconSize =
typeof iconSize === "number" ? iconSize : iconSizeVariant[iconSize ?? size];

const classes = $derived({
common: cn(
"cursor-pointer w-min flex items-center justify-center rounded-full font-semibold duration-100",
sizeVariant[size],
),
background: disabled
? disabledClasses[variant].background
: isActive
? isActiveClasses[variant].background
: variantClasses[variant].background,
text: disabled
? disabledClasses[variant].text
: isActive
? isActiveClasses[variant].text
: variantClasses[variant].text,
disabled: "cursor-not-allowed",
});
</script>

<button
{...restProps}
class={cn(
[
classes.common,
classes.background,
classes.text,
disabled && classes.disabled,
restProps.class,
].join(' ')
)}
{disabled}
onclick={callback ? handleClick : onclick}
{type}
>
{#if isLoading || isSubmitting}
<div
class="loading loading-spinner absolute loading-lg {variantClasses[
variant
].text}"
></div>
{:else}
<HugeiconsIcon {icon} size={resolvedIconSize} />
{/if}
</button>

<!--
@component
export default ButtonIcon
@description
ButtonIcon component is a button with an icon.

@props
- variant: 'white' | 'clear-on-light' | 'clear-on-dark' .
- isLoading: boolean
- callback: () => Promise<void>
- onclick: () => void
- blockingClick: boolean - Prevents multiple clicks
- type: 'button' | 'submit' | 'reset'
- size: 'sm' | 'md' | 'lg'
- iconSize: 'sm' | 'md' | 'lg' | number
- icon: IconSvgElement - Needs icon from Hugeicon library
- isActive: boolean


@usage
```html
<script lang="ts">
import * as Button from '$lib/ui/Button'
import { FlashlightIcon } from '@hugeicons/core-free-icons'

let flashlightOn = $state(false)
</script>

<Button.Icon
variant="white"
aria-label="Open pane"
size="md"
icon={FlashlightIcon}
onclick={() => (flashlightOn = !flashlightOn)}
isActive={flashlightOn}
></Button.Icon>
```
-->