Skip to content

Commit

Permalink
Merge pull request #3062 from vivid-planet/feature/custom-button
Browse files Browse the repository at this point in the history
Feature: New `Button` component
  • Loading branch information
johnnyomair authored Jan 21, 2025
2 parents ebe773b + e3604c2 commit e055e35
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 12 deletions.
48 changes: 48 additions & 0 deletions .changeset/olive-fans-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
"@comet/admin": minor
---

Add a new `Button` component to replace `ToolbarActionButton` and MUI's `Button`

Compared to MUI's `Button` component, the `color` prop has been removed, and the `variant` prop now defines those variants, defined by the Comet design guidelines, `primary` is the default variant.

```diff
-import { Button } from "@mui/material";
+import { Button } from "@comet/admin";

export const AllButtonVariants = () => (
<>
- <Button variant="contained" color="primary">Primary</Button>
+ <Button>Primary</Button>
- <Button variant="contained" color="secondary">Secondary</Button>
+ <Button variant="secondary">Secondary</Button>
- <Button variant="outlined">Outlined</Button>
+ <Button variant="outlined">Outlined</Button>
- <Button variant="outlined" color="error">Destructive</Button>
+ <Button variant="destructive">Destructive</Button>
- <Button variant="contained" color="success">Success</Button>
+ <Button variant="success">Success</Button>
- <Button variant="text" sx={{ color: "white" }}>Text Light</Button>
+ <Button variant="textLight">Text Light</Button>
- <Button variant="text" sx={{ color: "black" }}>Text Dark</Button>
+ <Button variant="textDark">Text Dark</Button>
</>
);
```

**Responsive behavior**

`ToolbarActionButton` is now deprecated.
Previously, `ToolbarActionButton` would hide its text content on mobile and add it with a tooltip instead.
This behavior can now be achieved by setting the `responsive` prop on the `Button` component.

```diff
-import { ToolbarActionButton } from "@comet/admin/lib/common/toolbar/actions/ToolbarActionButton";
+import { Button } from "@comet/admin";
import { Favorite } from "@comet/admin-icons";

const Example = () => {
- return <ToolbarActionButton startIcon={<Favorite />}>Hello</ToolbarActionButton>;
+ return <Button responsive startIcon={<Favorite />}>Hello</Button>;
};
```
158 changes: 158 additions & 0 deletions packages/admin/admin/src/common/buttons/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {
Breakpoint,
Button as MuiButton,
ButtonProps as MuiButtonProps,
ComponentsOverrides,
css,
Theme,
Tooltip,
useTheme,
useThemeProps,
} from "@mui/material";
import { OverridableComponent, OverridableTypeMap } from "@mui/material/OverridableComponent";
import { ElementType, ForwardedRef, forwardRef, ReactNode } from "react";

import { createComponentSlot } from "../../helpers/createComponentSlot";
import { ThemedComponentBaseProps } from "../../helpers/ThemedComponentBaseProps";
import { useWindowSize } from "../../helpers/useWindowSize";

type Variant = "primary" | "secondary" | "outlined" | "destructive" | "success" | "textLight" | "textDark";
type Slot = "root" | "mobileTooltip";
type ComponentState = Variant | "usingResponsiveBehavior";
export type ButtonClassKey = Slot | ComponentState;

export type ButtonProps<C extends ElementType = "button"> = Omit<MuiButtonProps<C>, "variant" | "color"> &
ThemedComponentBaseProps<{
root: typeof MuiButton;
mobileTooltip: typeof Tooltip;
}> & {
variant?: Variant;
responsive?: boolean;
mobileIcon?: "auto" | "startIcon" | "endIcon" | ReactNode;
mobileBreakpoint?: Breakpoint;
};

interface ButtonTypeMap<C extends ElementType = "button"> extends OverridableTypeMap {
props: ButtonProps<C>;
defaultComponent: C;
}

type OwnerState = {
variant: Variant;
usingResponsiveBehavior: boolean;
};

const variantToMuiProps: Record<Variant, Partial<MuiButtonProps>> = {
primary: { variant: "contained", color: "primary" },
secondary: { variant: "contained", color: "secondary" },
outlined: { variant: "outlined" },
destructive: { variant: "outlined", color: "error" },
success: { variant: "contained", color: "success" },
textLight: { variant: "text", sx: { color: "white" } },
textDark: { variant: "text", sx: { color: "black" } },
};

const getMobileIconNode = ({ mobileIcon, startIcon, endIcon }: Pick<ButtonProps, "mobileIcon" | "startIcon" | "endIcon">) => {
if (mobileIcon === "auto") {
return startIcon || endIcon;
}

if (mobileIcon === "startIcon") {
return startIcon;
}

if (mobileIcon === "endIcon") {
return endIcon;
}

return mobileIcon;
};

export const Button = forwardRef(<C extends ElementType = "button">(inProps: ButtonProps<C>, ref: ForwardedRef<any>) => {
const {
slotProps,
variant = "primary",
responsive,
mobileIcon = "auto",
mobileBreakpoint = "sm",
startIcon,
endIcon,
children,
...restProps
} = useThemeProps({ props: inProps, name: "CometAdminButton" });

const windowSize = useWindowSize();
const theme = useTheme();

const mobileIconNode = getMobileIconNode({ mobileIcon, startIcon, endIcon });

if (responsive && !mobileIconNode) {
throw new Error("When `responsive` is true, you must provide `startIcon`, `endIcon` or `mobileIcon`.");
}

const ownerState: OwnerState = {
variant,
usingResponsiveBehavior: Boolean(responsive) && windowSize.width < theme.breakpoints.values[mobileBreakpoint],
};

const commonButtonProps = {
...variantToMuiProps[variant],
...restProps,
ownerState,
...slotProps?.root,
ref,
};

if (ownerState.usingResponsiveBehavior) {
return (
<MobileTooltip title={children} {...slotProps?.mobileTooltip}>
<span>
<Root {...commonButtonProps}>{mobileIconNode}</Root>
</span>
</MobileTooltip>
);
}

return (
<Root startIcon={startIcon} endIcon={endIcon} {...commonButtonProps}>
{children}
</Root>
);
}) as OverridableComponent<ButtonTypeMap>;

const Root = createComponentSlot(MuiButton)<ButtonClassKey, OwnerState>({
componentName: "Button",
slotName: "root",
classesResolver(ownerState) {
return [ownerState.usingResponsiveBehavior && "usingResponsiveBehavior", ownerState.variant];
},
})(
({ ownerState }) => css`
${ownerState.usingResponsiveBehavior &&
css`
min-width: 0;
`}
`,
);

const MobileTooltip = createComponentSlot(Tooltip)<ButtonClassKey>({
componentName: "Button",
slotName: "mobileTooltip",
})();

declare module "@mui/material/styles" {
interface ComponentNameToClassKey {
CometAdminButton: ButtonClassKey;
}

interface ComponentsPropsList {
CometAdminButton: ButtonProps;
}

interface Components {
CometAdminButton?: {
defaultProps?: Partial<ComponentsPropsList["CometAdminButton"]>;
styleOverrides?: ComponentsOverrides<Theme>["CometAdminButton"];
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ const StyledButton = createComponentSlot(Button)<ToolbarActionButtonClassKey, Ow
},
})();

/**
* @deprecated Use `Button` from `@comet/admin` with the `responsive` prop instead.
*/
export const ToolbarActionButton = (props: ToolbarActionButtonProps) => {
const { children, slotProps = {}, ...restProps } = useThemeProps({ props, name: "CometAdminToolbarActionButton" });
const { iconButton: iconButtonProps, tooltip: tooltipProps, button: buttonProps } = slotProps;
Expand Down
7 changes: 3 additions & 4 deletions packages/admin/admin/src/dataGrid/GridColumnsButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Columns4 } from "@comet/admin-icons";
import { ButtonProps } from "@mui/material";
import { GridPreferencePanelsValue, useGridApiContext } from "@mui/x-data-grid";
import { useCallback } from "react";
import { FormattedMessage } from "react-intl";

import { ToolbarActionButton } from "../common/toolbar/actions/ToolbarActionButton";
import { Button, ButtonProps } from "../common/buttons/Button";
import { messages } from "../messages";

export function GridColumnsButton(props: ButtonProps) {
Expand All @@ -15,8 +14,8 @@ export function GridColumnsButton(props: ButtonProps) {
}, [apiRef]);

return (
<ToolbarActionButton startIcon={<Columns4 />} variant="outlined" onClick={handleFilterClick} {...props}>
<Button responsive startIcon={<Columns4 />} variant="outlined" onClick={handleFilterClick} {...props}>
<FormattedMessage {...messages.columns} />
</ToolbarActionButton>
</Button>
);
}
7 changes: 3 additions & 4 deletions packages/admin/admin/src/dataGrid/GridFilterButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Filter } from "@comet/admin-icons";
import { ButtonProps } from "@mui/material";
import { useGridApiContext } from "@mui/x-data-grid";
import { useCallback } from "react";
import { FormattedMessage } from "react-intl";

import { ToolbarActionButton } from "../common/toolbar/actions/ToolbarActionButton";
import { Button, ButtonProps } from "../common/buttons/Button";
import { messages } from "../messages";

export function GridFilterButton(props: ButtonProps) {
Expand All @@ -15,8 +14,8 @@ export function GridFilterButton(props: ButtonProps) {
}, [apiRef]);

return (
<ToolbarActionButton startIcon={<Filter />} variant="outlined" onClick={handleFilterClick} {...props}>
<Button responsive startIcon={<Filter />} variant="outlined" onClick={handleFilterClick} {...props}>
<FormattedMessage {...messages.filter} />
</ToolbarActionButton>
</Button>
);
}
1 change: 1 addition & 0 deletions packages/admin/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { AppHeaderMenuButton, AppHeaderMenuButtonClassKey, AppHeaderMenuButtonPr
export { buildCreateRestMutation, buildDeleteRestMutation, buildUpdateRestMutation } from "./buildRestMutation";
export { readClipboardText } from "./clipboard/readClipboardText";
export { writeClipboardText } from "./clipboard/writeClipboardText";
export { Button, ButtonClassKey, ButtonProps } from "./common/buttons/Button";
export { CancelButton, CancelButtonClassKey, CancelButtonProps } from "./common/buttons/cancel/CancelButton";
export { ClearInputButton, ClearInputButtonClassKey, ClearInputButtonProps } from "./common/buttons/clearinput/ClearInputButton";
export { CopyToClipboardButton, CopyToClipboardButtonClassKey, CopyToClipboardButtonProps } from "./common/buttons/CopyToClipboardButton";
Expand Down
Loading

0 comments on commit e055e35

Please sign in to comment.