-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3062 from vivid-planet/feature/custom-button
Feature: New `Button` component
- Loading branch information
Showing
8 changed files
with
371 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]; | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.