Skip to content

Commit 8f3bf4c

Browse files
committed
fix(pixels): try to make Button portable and extendable
1 parent 963f1cd commit 8f3bf4c

File tree

20 files changed

+304
-241
lines changed

20 files changed

+304
-241
lines changed

packages/pixels/src/Button/Button.stories.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { FaEnvelope } from 'react-icons/fa';
55

66
import { action } from 'storybook/actions';
77

8+
import { tv } from '@fuf-stack/pixel-utils';
9+
810
import Button, { buttonVariants } from './Button';
911

1012
const meta: Meta<typeof Button> = {
@@ -50,6 +52,9 @@ export const IconOnly: Story = {
5052
args: {
5153
icon: <FaEnvelope />,
5254
children: undefined,
55+
color: 'danger',
56+
size: 'sm',
57+
// variant: 'light',
5358
},
5459
};
5560

@@ -143,3 +148,39 @@ export const AllVariants: Story = {
143148
);
144149
},
145150
};
151+
152+
const buttonVariantsExtended = tv({
153+
extend: buttonVariants,
154+
variants: {
155+
size: {
156+
sm: 'rounded-none',
157+
md: 'rounded-none',
158+
lg: 'rounded-none',
159+
},
160+
},
161+
compoundVariants: [
162+
{
163+
variant: 'solid',
164+
color: 'default',
165+
class: 'bg-warning-500',
166+
},
167+
],
168+
});
169+
170+
export const ExtendVariantStyles: Story = {
171+
args: {
172+
children: 'Extended Button',
173+
className: 'text-success',
174+
},
175+
render: ({ color = 'default', variant = 'solid', size = 'md', ...rest }) => {
176+
// className from slots
177+
const extendedClassNames = buttonVariantsExtended({
178+
color,
179+
variant,
180+
size,
181+
...rest,
182+
});
183+
184+
return <Button {...rest} className={extendedClassNames} />;
185+
},
186+
};

packages/pixels/src/Button/Button.tsx

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
import type { TVClassName, TVProps } from '@fuf-stack/pixel-utils';
21
import type { ButtonProps as HeroButtonProps } from '@heroui/button';
32
import type { ReactNode } from 'react';
43

54
import { Button as HeroButton } from '@heroui/button';
65
import { button as heroButtonVariants } from '@heroui/theme';
76

8-
import { tv, variantsToClassNames } from '@fuf-stack/pixel-utils';
7+
import { tv } from '@fuf-stack/pixel-utils';
98

109
import LoadingSpinner from './subcomponents/LoadingSpinner';
1110

12-
export const buttonVariants = tv({
13-
slots: {
14-
base: '',
15-
},
11+
const tvConfig = <T extends Parameters<typeof tv>[0]>(c: T) => {
12+
return c;
13+
};
14+
15+
const buttonVariantsConfig = tvConfig({
16+
extend: heroButtonVariants,
1617
variants: {
1718
color: {
19+
...heroButtonVariants.variants.color,
1820
// see: https://github.com/heroui-inc/heroui/blob/canary/packages/core/theme/src/components/button.ts
1921
info: '',
20-
...heroButtonVariants.variants.color,
2122
},
2223
variant: heroButtonVariants.variants.variant,
2324
size: heroButtonVariants.variants.size,
@@ -26,13 +27,23 @@ export const buttonVariants = tv({
2627
// white text on solid / shadow success button
2728
{
2829
color: 'success',
29-
variant: ['solid', 'shadow'],
30+
variant: 'solid',
31+
class: 'text-white',
32+
},
33+
{
34+
color: 'success',
35+
variant: 'shadow',
3036
class: 'text-white',
3137
},
3238
// white text on solid / shadow warning button
3339
{
3440
color: 'warning',
35-
variant: ['solid', 'shadow'],
41+
variant: 'solid',
42+
class: 'text-white',
43+
},
44+
{
45+
color: 'warning',
46+
variant: 'shadow',
3647
class: 'text-white',
3748
},
3849
{
@@ -43,7 +54,7 @@ export const buttonVariants = tv({
4354
{
4455
color: 'info',
4556
variant: 'shadow',
46-
class: 'text-info-foreground" bg-info shadow-info/40 shadow-lg',
57+
class: 'text-info-foreground bg-info shadow-info/40 shadow-lg',
4758
},
4859
{
4960
color: 'info',
@@ -74,18 +85,17 @@ export const buttonVariants = tv({
7485
],
7586
});
7687

77-
type VariantProps = TVProps<typeof buttonVariants>;
78-
type ClassName = TVClassName<typeof buttonVariants>;
88+
export const buttonVariants: ReturnType<typeof tv> = tv(buttonVariantsConfig);
7989

80-
export interface ButtonProps extends VariantProps {
90+
export interface ButtonProps {
8191
/** sets HTML aria-label attribute */
8292
ariaLabel?: string;
8393
/** content of the button */
8494
children?: ReactNode;
8595
/** CSS class name */
86-
className?: ClassName;
96+
className?: string;
8797
/** color of the button */
88-
color?: VariantProps['color'];
98+
color?: HeroButtonProps['color'] | 'info';
8999
/** disables the button */
90100
disabled?: boolean;
91101
/** disables all animations */
@@ -107,7 +117,7 @@ export interface ButtonProps extends VariantProps {
107117
/** HTML button type attribute */
108118
type?: 'button' | 'submit' | 'reset' | undefined;
109119
/** visual style variant */
110-
variant?: VariantProps['variant'];
120+
variant?: HeroButtonProps['variant'];
111121
}
112122

113123
/**
@@ -116,7 +126,7 @@ export interface ButtonProps extends VariantProps {
116126
const Button = ({
117127
ariaLabel = undefined,
118128
children = undefined,
119-
className = undefined,
129+
className: _className = undefined,
120130
color = 'default',
121131
disableAnimation = false,
122132
disabled = false,
@@ -130,27 +140,27 @@ const Button = ({
130140
type = undefined,
131141
variant = 'solid',
132142
}: ButtonProps) => {
133-
// classNames from slots
134-
const variants = buttonVariants({ color, variant, size });
135-
const classNames = variantsToClassNames(variants, className, 'base');
143+
const className = buttonVariants({
144+
className: _className,
145+
disableAnimation,
146+
isDisabled: disabled,
147+
color,
148+
size,
149+
variant,
150+
isIconOnly: !!(icon && !children),
151+
radius,
152+
});
136153

137154
return (
138155
<HeroButton
139156
aria-label={ariaLabel}
140-
className={classNames.base}
141-
color={color as HeroButtonProps['color']}
157+
className={className}
142158
data-testid={testId}
143-
disableAnimation={disableAnimation}
144159
disableRipple={disableAnimation || !ripple}
145-
isDisabled={disabled}
146-
isIconOnly={!!(icon && !children)}
147160
isLoading={loading}
148161
onPress={onClick}
149-
radius={radius}
150-
size={size}
151162
spinner={<LoadingSpinner />}
152163
type={type}
153-
variant={variant}
154164
>
155165
{icon}
156166
{children}

0 commit comments

Comments
 (0)