Skip to content

Commit f4662f4

Browse files
JulienAuvoJulienAuvo
andauthored
feat: add button action component (#47)
* feat: add button action component * fix: add correct weights to Archivo fontt * feat: add base button * fix: set prop classes last * feat: improve loading state * chore: cleanup * feat: add button action component * fix: add correct weights to Archivo fontt * feat: add base button * fix: set prop classes last * feat: improve loading state * chore: cleanup * chore: add documentation * fix: configure Storybook * chore: storybook gunk removal * feat: enhance ButtonAction component with type prop and better error handling --------- Co-authored-by: JulienAuvo <[email protected]>
1 parent 06ad375 commit f4662f4

37 files changed

+204
-751
lines changed
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
1-
import type { StorybookConfig } from "@storybook/sveltekit";
1+
import type { StorybookConfig } from '@storybook/sveltekit'
22

3-
import { join, dirname } from "path";
4-
5-
/**
6-
* This function is used to resolve the absolute path of a package.
7-
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
8-
*/
9-
function getAbsolutePath(value: string): any {
10-
return dirname(require.resolve(join(value, "package.json")));
11-
}
123
const config: StorybookConfig = {
13-
// add back support for .svelte files when addon csf works
14-
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|ts)"],
4+
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts)'],
155
addons: [
6+
167
getAbsolutePath("@storybook/addon-essentials"),
178
getAbsolutePath("@chromatic-com/storybook"),
189
getAbsolutePath("@storybook/experimental-addon-test"),
1910
],
2011
framework: {
21-
name: getAbsolutePath("@storybook/sveltekit"),
12+
name: '@storybook/sveltekit',
2213
options: {},
2314
},
15+
2416
staticDirs: ["../static"],
2517
};
2618
export default config;

infrastructure/eid-wallet/.storybook/preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ const preview: Preview = {
1212
},
1313
};
1414

15-
export default preview;
15+
export default preview;

infrastructure/eid-wallet/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@
2626
"devDependencies": {
2727
"@chromatic-com/storybook": "^3",
2828
"@storybook/addon-essentials": "^8.6.7",
29-
"@storybook/addon-svelte-csf": "^5.0.0-next.28",
29+
"@storybook/addon-interactions": "^8.6.7",
3030
"@storybook/blocks": "^8.6.7",
3131
"@storybook/experimental-addon-test": "^8.6.7",
3232
"@storybook/svelte": "^8.6.7",
3333
"@storybook/sveltekit": "^8.6.7",
3434
"@storybook/test": "^8.6.7",
35+
"@storybook/testing-library": "^0.2.2",
3536
"@sveltejs/adapter-static": "^3.0.6",
3637
"@sveltejs/kit": "^2.9.0",
3738
"@sveltejs/vite-plugin-svelte": "^5.0.0",

infrastructure/eid-wallet/src/app.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
@font-face {
88
font-family: 'Archivo';
99
src: url('/fonts/Archivo-VariableFont_wdth,wght.ttf') format('truetype');
10-
font-weight: normal;
10+
font-weight: 100 900;
1111
font-style: normal;
1212
}
1313

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

infrastructure/eid-wallet/src/lib/ui/Button/ButtonIcon.svelte

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script context="module">
2+
export { ButtonText }
3+
</script>
4+
5+
{#snippet ButtonText()}
6+
Button
7+
{/snippet}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Action from './ButtonAction.svelte'
2+
import Icon from './ButtonIcon.svelte'
3+
4+
export { Action, Icon }
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
23
import { Drawer } from "$lib/ui";
34
let isPaneOpen = $state(false);
45
</script>
@@ -7,10 +8,11 @@
78

89

910

11+
1012
<Drawer bind:isPaneOpen isCancelRequired={true}>
1113
<div class="bg-red-300">aslkfamfoasdiownednciaosndoasfnas </div>
1214
<div>asfnladmfpamsfl asd</div>
1315
<div>aslkfasf;,;</div>
1416
<button class="btn btn-soft" >Open</button>
1517
<button class="btn btn-soft">Open</button>
16-
</Drawer>
18+
</Drawer>

0 commit comments

Comments
 (0)