Skip to content

Commit

Permalink
feat(button): ✨ finish button theming
Browse files Browse the repository at this point in the history
  • Loading branch information
navin-moorthy committed Jun 15, 2022
1 parent 22b8f21 commit 3cb557b
Show file tree
Hide file tree
Showing 10 changed files with 3,028 additions and 210 deletions.
1 change: 1 addition & 0 deletions preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ module.exports = {
...defaultTheme.spacing,
},
boxShadow: {
sm: "0px 1px 2px rgba(0, 0, 0, 0.05)",
csm: "0px 0px 1px rgba(0, 0, 0, 0.4), 0px 1px 2px rgba(0, 0, 0, 0.15)",
thumbHover:
"0px 0px 1px rgba(0, 0, 0, 0.2), 0px 2px 3px rgba(0, 0, 0, 0.05), 0px 3px 5px rgba(0, 0, 0, 0.15)",
Expand Down
8 changes: 4 additions & 4 deletions src/badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const useBadge = createHook<BadgeOptions>(
size = "md",
themeColor = "base",
variant = "solid",
prefix,
prefix: _prefix,
...props
}) => {
const badge = useTheme("badge");
Expand All @@ -26,13 +26,13 @@ export const useBadge = createHook<BadgeOptions>(
);

const prefixStyles = cx(badge.size[size]?.prefix);
const _prefix = prefix
? withIconA11yNew(prefix, { className: prefixStyles })
const prefix = _prefix
? withIconA11yNew(_prefix, { className: prefixStyles })
: null;

const children = (
<>
{_prefix}
{prefix}
<Box as="span">{props.children}</Box>
</>
);
Expand Down
88 changes: 51 additions & 37 deletions src/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@ import {
import { As, Props } from "ariakit-utils/types";
import { announce } from "@react-aria/live-announcer";

import { useBox } from "../box";
import { Box, useBox } from "../box";
import { useButtonGroupContext } from "../button-group";
import { usePrevious } from "../hooks";
import { Spinner } from "../spinner";
import { useTheme } from "../theme";
import { cx, RenderProp, tcm, withIconA11y } from "../utils";
import { cx, RenderProp, withIconA11yNew } from "../utils";

import { ButtonFullWidthSpinner, ButtonSpinner } from "./ButtonSpinner";

export const useButton = createHook<ButtonOptions>(
({
size: _size = "md",
themeColor: _themeColor = "base",
variant: _variant = "solid",
prefix,
suffix,
iconOnly,
prefix: _prefix,
suffix: _suffix,
iconOnly: _iconOnly,
loading = false,
spinner = <Spinner size="em" />,
spinner = <Spinner size="em" themeColor="current" />,
...props
}) => {
const disabled = props.disabled || loading;
Expand All @@ -41,21 +42,26 @@ export const useButton = createHook<ButtonOptions>(
const className = cx(
button.base.default,
groupcontext?.collapsed ? "" : button.base.notCollapsed,
!iconOnly
? button.size.default[size]
: tcm(
button.size.iconOnly.base[size],
button.size.iconOnly.spinner[size],
),
button.variant.default[variant],
button.variant.hover[variant],
button.variant.active[variant],
button.variant.focus[variant],
button.variant.disabled[variant],
!_iconOnly ? button.size[size].base : button.size[size].iconOnly.base,
button.themeColor[_themeColor]?.[variant].default,
button.themeColor[_themeColor]?.[variant].hover,
button.themeColor[_themeColor]?.[variant].active,
button.themeColor[_themeColor]?.[variant].focus,
button.themeColor[_themeColor]?.[variant].disabled,
props.className,
);
const suffixStyles = cx(button.size.suffix[size]);
const prefixStyles = cx(button.size.prefix[size]);
const prefixStyles = cx(button.size[size].prefix);
const prefix = _prefix
? withIconA11yNew(_prefix, { className: prefixStyles })
: null;
const suffixStyles = cx(button.size[size].suffix);
const suffix = _suffix
? withIconA11yNew(_suffix, { className: suffixStyles })
: null;
const iconOnlyStyles = cx(button.size[size].iconOnly.icon);
const iconOnly = _iconOnly
? withIconA11yNew(_iconOnly, { className: iconOnlyStyles })
: null;

const prevLoading = usePrevious(loading);

Expand All @@ -70,30 +76,26 @@ export const useButton = createHook<ButtonOptions>(
{(!prefix && !suffix) || iconOnly ? (
loading ? (
<ButtonFullWidthSpinner size={size} spinner={spinner}>
<>
{iconOnly
? withIconA11y(iconOnly)
: (props.children as React.ReactNode)}
</>
<>{iconOnly || props.children}</>
</ButtonFullWidthSpinner>
) : (
<>{iconOnly ? withIconA11y(iconOnly) : props.children}</>
<>{iconOnly || props.children}</>
)
) : (
<>
{prefix ? (
loading && !suffix ? (
<ButtonSpinner spinner={spinner} prefix={prefix} size={size} />
<ButtonSpinner size={size} spinner={spinner} prefix={prefix} />
) : (
<>{withIconA11y(prefix, { className: prefixStyles })}</>
<>{prefix}</>
)
) : null}
<span>{props.children as React.ReactNode}</span>
<Box>{props.children}</Box>
{suffix ? (
loading ? (
<ButtonSpinner size={size} spinner={spinner} suffix={suffix} />
) : (
<>{withIconA11y(suffix, { className: suffixStyles })}</>
<>{suffix}</>
)
) : null}
</>
Expand All @@ -114,6 +116,11 @@ export const Button = createComponent<ButtonOptions>(props => {
return createElement("button", htmlProps);
});

export type BadgePrefixProps = { className?: string };
export type BadgeSuffixProps = { className?: string };
export type BadgeIconOnlyProps = { className?: string };
export type BadgeSpinnerProps = { className?: string };

export type ButtonOptions<T extends As = "button"> = Omit<
AriakitButtonOptions<T>,
"size" | "prefix"
Expand All @@ -123,29 +130,36 @@ export type ButtonOptions<T extends As = "button"> = Omit<
*
* @default md
*/
size?: keyof AdaptUI.GetThemeValue<"button", "size", "default">;
size?: keyof AdaptUI.GetThemeValue<"button", "size">;

/**
* How the button should be themed?
*
* @default base
*/
themeColor?: keyof AdaptUI.GetThemeValue<"button", "themeColor">;

/**
* How the button should look?
*
* @default solid
*/
variant?: keyof AdaptUI.GetThemeValue<"button", "variant", "default">;
variant?: keyof AdaptUI.GetThemeValue<"button", "themeColor", "base">;

/**
* If added, the button will only show an icon ignoring other childrens.
* If added, the button will show an icon before the button's text.
*/
iconOnly?: RenderProp;
prefix?: RenderProp<BadgePrefixProps>;

/**
* If added, the button will show an icon before the button's text.
*/
suffix?: RenderProp;
suffix?: RenderProp<BadgeSuffixProps>;

/**
* If added, the button will show an icon before the button's text.
* If added, the button will only show an icon ignoring other childrens.
*/
prefix?: RenderProp;
iconOnly?: RenderProp<BadgeIconOnlyProps>;

/**
* If `true`, the button will show a spinner.
Expand All @@ -159,7 +173,7 @@ export type ButtonOptions<T extends As = "button"> = Omit<
*
* @default Spinner Component
*/
spinner?: RenderProp;
spinner?: RenderProp<BadgeSpinnerProps>;
};

export type ButtonProps<T extends As = "button"> = Props<ButtonOptions<T>>;
25 changes: 14 additions & 11 deletions src/button/ButtonSpinner.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,50 @@
import * as React from "react";

import { Box } from "../box";
import { Spinner } from "../spinner";
import { useTheme } from "../theme";
import { cx, passProps } from "../utils";
import { cx, passPropsNew } from "../utils";

import { ButtonProps } from "./Button";

export type ButtonSpinnerProps = Partial<
Pick<ButtonProps, "spinner" | "size" | "prefix" | "suffix" | "children">
> & {
children?: React.ReactNode;
};
>;

export const ButtonSpinner: React.FC<ButtonSpinnerProps> = props => {
const {
size = "md",
spinner = <Spinner size="em" />,
spinner = <Spinner size="em" themeColor="current" />,
prefix,
suffix,
} = props;

const button = useTheme("button");
const spinnerStyles = cx(
prefix
? button.size.prefix[size]
? button.size[size].prefix
: suffix
? button.size.suffix[size]
: button.size.iconOnly[size],
? button.size[size].suffix
: button.size[size].iconOnly.spinner,
);

return <>{passProps(spinner, { className: spinnerStyles })}</>;
return <>{passPropsNew(spinner, { className: spinnerStyles })}</>;
};

export const ButtonFullWidthSpinner: React.FC<ButtonSpinnerProps> = props => {
const { size = "md", spinner = <Spinner size="em" />, children } = props;
const {
size = "md",
spinner = <Spinner size="em" themeColor="current" />,
children,
} = props;

// This is only the grey area in button for now which user cannot customize
return (
<>
<div className="absolute flex items-center justify-center">
<ButtonSpinner spinner={spinner} size={size} />
</div>
<div className="opacity-0">{children}</div>
<Box className="opacity-0">{children}</Box>
</>
);
};
5 changes: 3 additions & 2 deletions src/button/__tests__/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { screen } from "@testing-library/react";
import { render, testA11y } from "../../utils/testUtils";
import { Button } from "../Button";

describe("Testing Button", () => {
describe("Button", () => {
it("should render properly", () => {
render(<Button>Hello World</Button>);
const { asFragment } = render(<Button>Hello World</Button>);

expect(asFragment()).toMatchSnapshot();
expect(screen.getByRole("button")).toHaveTextContent(/hello world/i);
});

Expand Down
13 changes: 13 additions & 0 deletions src/button/__tests__/__snapshots__/Button.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Button should render properly 1`] = `
<DocumentFragment>
<button
class="inline-flex items-center justify-center relative transition-all disabled:cursor-not-allowed outline-none appearance-none select-none whitespace-nowrap align-middle translate-y-0 hover:-translate-y-px active:translate-y-0 will-change-transform min-h-[30px] min-w-[30px] w-auto px-2.5 border rounded-lg text-sm font-medium bg-gray-900 text-white-900 border-transparent enabled:hover:bg-gray-800 enabled:hover:z-10 enabled:active:bg-gray-700 focus-visible:ring-3 focus-visible:ring-gray-500 focus-visible:z-10 disabled:bg-gray-200 disabled:text-gray-500"
data-command=""
type="button"
>
Hello World
</button>
</DocumentFragment>
`;
Loading

0 comments on commit 3cb557b

Please sign in to comment.