Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 49a3f5b

Browse files
committed
feat: button loading state
1 parent 65c81ce commit 49a3f5b

File tree

8 files changed

+125
-60
lines changed

8 files changed

+125
-60
lines changed

apps/docs/src/components/ui/button/button.stories.tsx

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Button } from '@codefixlabs/ui/react';
22
import type { Meta, StoryObj } from '@storybook/react';
33
import {
4-
ActivitySquareIcon,
5-
ArrowUpRightSquareIcon,
6-
GanttChartSquareIcon,
4+
AlarmCheckIcon,
5+
CogIcon,
6+
GalleryThumbnailsIcon,
77
GitForkIcon,
88
} from 'lucide-react';
99

1010
const icons = {
11-
ActivitySquareIcon: <ActivitySquareIcon className="h-4 w-4" />,
12-
ArrowUpRightSquareIcon: <ArrowUpRightSquareIcon className="h-4 w-4" />,
13-
GanttChartSquareIcon: <GanttChartSquareIcon className="h-4 w-4" />,
11+
AlarmCheckIcon: <AlarmCheckIcon className="h-4 w-4" />,
12+
CogIcon: <CogIcon className="h-4 w-4" />,
13+
GalleryThumbnailsIcon: <GalleryThumbnailsIcon className="h-4 w-4" />,
1414
GitForkIcon: <GitForkIcon className="h-4 w-4" />,
1515
};
1616

@@ -25,10 +25,10 @@ const meta: Meta<typeof Button> = {
2525
endIcon: {
2626
control: {
2727
labels: {
28-
BellIcon: 'Bell',
29-
BookmarkIcon: 'Bookmark',
30-
GearIcon: 'Gear',
31-
LayersIcon: 'Layers',
28+
AlarmCheckIcon: 'AlarmCheckIcon',
29+
CogIcon: 'CogIcon',
30+
GalleryThumbnailsIcon: 'GalleryThumbnailsIcon',
31+
GitForkIcon: 'GitForkIcon',
3232
},
3333
type: 'select',
3434
},
@@ -49,10 +49,10 @@ const meta: Meta<typeof Button> = {
4949
startIcon: {
5050
control: {
5151
labels: {
52-
BellIcon: 'Bell',
53-
BookmarkIcon: 'Bookmark',
54-
GearIcon: 'Gear',
55-
LayersIcon: 'Layers',
52+
AlarmCheckIcon: 'AlarmCheckIcon',
53+
CogIcon: 'CogIcon',
54+
GalleryThumbnailsIcon: 'GalleryThumbnailsIcon',
55+
GitForkIcon: 'GitForkIcon',
5656
},
5757
type: 'select',
5858
},
@@ -156,6 +156,31 @@ export const Loading: Story = {
156156
},
157157
};
158158

159+
export const LoadingWithIcon: Story = {
160+
args: {
161+
...Basic.args,
162+
loading: true,
163+
startIcon: <GitForkIcon className="h-4 w-4" />,
164+
},
165+
};
166+
167+
export const LoadingWithIconRight: Story = {
168+
args: {
169+
...Basic.args,
170+
endIcon: <CogIcon className="h-4 w-4" />,
171+
loading: true,
172+
},
173+
};
174+
175+
export const LoadingWithIconBoth: Story = {
176+
args: {
177+
...Basic.args,
178+
endIcon: <CogIcon className="h-4 w-4" />,
179+
loading: true,
180+
startIcon: <GitForkIcon className="h-4 w-4" />,
181+
},
182+
};
183+
159184
export const Disabled: Story = {
160185
args: {
161186
...Basic.args,
@@ -173,6 +198,6 @@ export const IconLeft: Story = {
173198
export const IconRight: Story = {
174199
args: {
175200
...Basic.args,
176-
endIcon: <GanttChartSquareIcon className="h-4 w-4" />,
201+
endIcon: <CogIcon className="h-4 w-4" />,
177202
},
178203
};

packages/ui/src/cva/button.ts

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const btnSizes: {
2121
{
2222
icon: true,
2323
sizes: [
24-
// --- sm
24+
// --- 32px
2525
{
2626
className: 'px-2 h-8',
2727
size: 'sm',
@@ -32,7 +32,7 @@ const btnSizes: {
3232
size: 'sm',
3333
variant: ['outline'],
3434
},
35-
// --- md
35+
// --- 40px
3636
{
3737
className: 'px-3 h-10',
3838
size: 'md',
@@ -43,7 +43,7 @@ const btnSizes: {
4343
size: 'md',
4444
variant: ['outline'],
4545
},
46-
// --- lg
46+
// --- 48px
4747
{
4848
className: 'px-4 h-12',
4949
size: 'lg',
@@ -59,7 +59,7 @@ const btnSizes: {
5959
{
6060
icon: false,
6161
sizes: [
62-
// --- sm
62+
// --- 32px
6363
{
6464
className: 'px-4 h-8',
6565
size: 'sm',
@@ -70,7 +70,7 @@ const btnSizes: {
7070
size: 'sm',
7171
variant: ['outline'],
7272
},
73-
// --- md
73+
// --- 40px
7474
{
7575
className: 'px-5 h-10',
7676
size: 'md',
@@ -81,7 +81,7 @@ const btnSizes: {
8181
size: 'md',
8282
variant: ['outline'],
8383
},
84-
// --- lg
84+
// --- 48px
8585
{
8686
className: 'px-6 h-12',
8787
size: 'lg',
@@ -113,23 +113,12 @@ const compoundSizes = btnSizes.flatMap<{
113113

114114
export const buttonVariants = cva(
115115
[
116-
'relative select-none items-center gap-2 whitespace-nowrap text-sm font-medium transition-colors',
116+
'relative select-none items-center gap-2 overflow-hidden whitespace-nowrap text-sm font-medium transition-colors',
117117
'focus:ring-ring/40 focus:outline-none focus:ring-2',
118-
'data-disabled:cursor-not-allowed',
119-
'data-disabled:ring-0',
118+
'data-disabled:cursor-not-allowed data-disabled:ring-0',
120119
],
121120
{
122-
compoundVariants: [
123-
{
124-
className: 'data-disabled:bg-opacity-50',
125-
variant: ['primary', 'secondary', 'destructive'],
126-
},
127-
{
128-
className: 'data-disabled:opacity-50',
129-
variant: ['ghost', 'outline'],
130-
},
131-
...compoundSizes,
132-
],
121+
compoundVariants: [...compoundSizes],
133122
defaultVariants: {
134123
block: false,
135124
icon: false,
@@ -144,8 +133,8 @@ export const buttonVariants = cva(
144133
true: 'flex w-full',
145134
},
146135
icon: {
147-
false: '',
148-
true: '',
136+
false: undefined,
137+
true: undefined,
149138
},
150139
justify: {
151140
between: 'justify-between',
@@ -157,21 +146,40 @@ export const buttonVariants = cva(
157146
square: 'rounded-sm',
158147
},
159148
size: {
160-
lg: '',
161-
md: '',
162-
sm: '',
149+
lg: undefined,
150+
md: undefined,
151+
sm: undefined,
163152
},
164153
variant: {
165-
destructive:
166-
'bg-destructive text-destructive-foreground [&:not([data-disabled])]:hover:bg-destructive/90',
167-
ghost: '[&:not([data-disabled])]:hover:bg-accent',
154+
destructive: [
155+
'bg-destructive text-destructive-foreground',
156+
'[&:not([data-disabled])]:hover:bg-destructive/90',
157+
'[&:not([data-disabled])]:focus:bg-destructive/90',
158+
'data-disabled:bg-destructive/60',
159+
],
160+
ghost: [
161+
'[&:not([data-disabled])]:hover:bg-accent',
162+
'data-disabled:opacity-60',
163+
],
168164
link: 'text-primary rounded-none hover:underline',
169-
outline:
170-
'border-input [&:not([data-disabled])]:hover:bg-accent [&:not([data-disabled])]:hover:text-accent-foreground border',
171-
primary:
172-
'bg-primary text-primary-foreground [&:not([data-disabled])]:hover:bg-primary/90',
173-
secondary:
174-
'bg-secondary text-secondary-foreground [&:not([data-disabled])]:hover:bg-secondary/90',
165+
outline: [
166+
'border-input border',
167+
'[&:not([data-disabled])]:hover:bg-accent [&:not([data-disabled])]:hover:text-accent-foreground',
168+
'[&:not([data-disabled])]:focus:bg-accent [&:not([data-disabled])]:focus:text-accent-foreground',
169+
'data-disabled:opacity-60',
170+
],
171+
primary: [
172+
'bg-primary text-primary-foreground',
173+
'[&:not([data-disabled])]:hover:bg-primary/90',
174+
'[&:not([data-disabled])]:focus:bg-primary/90',
175+
'data-disabled:bg-primary/60',
176+
],
177+
secondary: [
178+
'bg-secondary text-secondary-foreground',
179+
'[&:not([data-disabled])]:hover:bg-secondary/90',
180+
'[&:not([data-disabled])]:focus:bg-secondary/90',
181+
'data-disabled:bg-secondary/60',
182+
],
175183
},
176184
},
177185
},

packages/ui/src/cva/command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const commandVariants = cva(
88
},
99
variants: {
1010
variant: {
11-
dialog: '',
11+
dialog: undefined,
1212
primary: 'border shadow-lg',
1313
},
1414
},

packages/ui/src/cva/form.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cva } from 'class-variance-authority';
22

3-
export const formItemVariants = cva('', {
3+
export const formItemVariants = cva(undefined, {
44
defaultVariants: {
55
inline: false,
66
},

packages/ui/src/cva/radio-group.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { cva } from 'class-variance-authority';
22

3-
export const radioGroupVariants = cva('', {
3+
export const radioGroupVariants = cva(undefined, {
44
defaultVariants: {
55
variant: 'default',
66
},
77
variants: {
88
variant: {
99
default: 'grid gap-2',
10-
simple: '',
10+
simple: undefined,
1111
},
1212
},
1313
});

packages/ui/src/cva/select.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const selectContentVariants = cva(
4848
},
4949
variants: {
5050
position: {
51-
'item-aligned': '',
51+
'item-aligned': undefined,
5252
popper:
5353
'max-h-[var(--radix-select-content-available-height)] w-[var(--radix-select-trigger-width)]',
5454
},

packages/ui/src/cva/tabs.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { cva } from 'class-variance-authority';
22

3-
export const tabsListVariants = cva('', {
3+
export const tabsListVariants = cva(undefined, {
44
defaultVariants: {
55
variant: 'default',
66
},
77
variants: {
88
variant: {
99
default:
1010
'bg-muted text-muted-foreground inline-flex h-10 items-center justify-center gap-1 rounded-lg p-1',
11-
simple: '',
11+
simple: undefined,
1212
},
1313
},
1414
});
@@ -29,7 +29,7 @@ export const tabsTriggerVariants = cva(
2929
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium',
3030
'data-state-active:bg-background data-state-active:text-foreground data-state-active:shadow-sm',
3131
],
32-
simple: '',
32+
simple: undefined,
3333
},
3434
},
3535
},
@@ -44,7 +44,7 @@ export const tabsContentVariants = cva(
4444
variants: {
4545
variant: {
4646
default: 'mt-2 rounded-lg',
47-
simple: '',
47+
simple: undefined,
4848
},
4949
},
5050
},

packages/ui/src/react/button.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { VariantProps } from 'class-variance-authority';
44
import * as React from 'react';
55
import { Children, forwardRef } from 'react';
66
import { twMerge } from 'tailwind-merge';
7+
import { Loader2Icon } from 'lucide-react';
78
import { buttonVariants } from '@/cva/button';
89

910
/* -----------------------------------------------------------------------------
@@ -55,14 +56,45 @@ export const Button = forwardRef<
5556
type="button"
5657
{...props}
5758
>
58-
{startIcon}
59+
<IconButton loading={loading}>{startIcon}</IconButton>
5960

6061
{children}
6162

62-
{endIcon}
63+
<IconButton loading={loading ? !startIcon : false}>
64+
{endIcon}
65+
</IconButton>
66+
67+
{loading && !(startIcon || endIcon) ? (
68+
<span className="absolute inset-0 flex items-center justify-center bg-inherit">
69+
<Loader2Icon className="h-4.5 w-4.5 animate-spin" />
70+
</span>
71+
) : null}
6372
</button>
6473
);
6574
},
6675
);
6776

6877
Button.displayName = 'Button';
78+
79+
/* -----------------------------------------------------------------------------
80+
* Component: IconButton
81+
* -------------------------------------------------------------------------- */
82+
83+
export function IconButton({
84+
children,
85+
86+
loading,
87+
}: {
88+
children: React.ReactNode;
89+
loading?: boolean;
90+
}): React.JSX.Element {
91+
return (
92+
<>
93+
{loading && children ? (
94+
<Loader2Icon className="h-4 w-4 animate-spin" />
95+
) : (
96+
children
97+
)}
98+
</>
99+
);
100+
}

0 commit comments

Comments
 (0)