Skip to content

Commit

Permalink
feat(material): add Fab component
Browse files Browse the repository at this point in the history
  • Loading branch information
juanrgm committed Mar 25, 2022
1 parent f7f67e9 commit 26ffb46
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-lemons-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@suid/material": patch
---

Add `Fab` component
177 changes: 177 additions & 0 deletions packages/material/src/Fab/Fab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import ButtonBase from "../ButtonBase";
import styled from "../styles/styled";
import capitalize from "../utils/capitalize";
import { FabTypeMap } from "./FabProps";
import fabClasses, { getFabUtilityClass } from "./fabClasses";
import createComponentFactory from "@suid/base/createComponentFactory";
import { ComponentInProps } from "@suid/types";
import clsx from "clsx";

const $ = createComponentFactory<FabTypeMap>()({
name: "MuiFab",
propDefaults: ({ set }) =>
set({
color: "default",
component: "button",
disabled: false,
disableFocusRipple: false,
size: "large",
variant: "circular",
}),
selfPropNames: [
"children",
"classes",
"color",
"disableFocusRipple",
"disableRipple",
"disabled",
"href",
"size",
"variant",
],
utilityClass: getFabUtilityClass,
slotClasses: (ownerState) => ({
root: [
"root",
ownerState.variant,
`size${capitalize(ownerState.size)}`,
ownerState.color === "inherit" ? "colorInherit" : ownerState.color,
],
}),
});

const FabRoot = styled(ButtonBase, {
name: "MuiFab",
slot: "Root",
overridesResolver: (props, styles) => {
const { ownerState } = props;

return [
styles.root,
styles[ownerState.variant],
styles[`size${capitalize(ownerState.size)}`],
ownerState.color === "inherit" && styles.colorInherit,
styles[capitalize(ownerState.size)],
styles[ownerState.color],
];
},
})<ComponentInProps<FabTypeMap>>(
({ theme, ownerState }) => ({
...theme.typography.button,
minHeight: 36,
transition: theme.transitions.create(
["background-color", "box-shadow", "border-color"],
{
duration: theme.transitions.duration.short,
}
),
borderRadius: "50%",
padding: 0,
minWidth: 0,
width: 56,
height: 56,
boxShadow: theme.shadows[6],
"&:active": {
boxShadow: theme.shadows[12],
},
color: theme.palette.getContrastText(theme.palette.grey[300]),
backgroundColor: theme.palette.grey[300],
"&:hover": {
backgroundColor: theme.palette.grey.A100,
// Reset on touch devices, it doesn't add specificity
"@media (hover: none)": {
backgroundColor: theme.palette.grey[300],
},
textDecoration: "none",
},
[`&.${fabClasses.focusVisible}`]: {
boxShadow: theme.shadows[6],
},
[`&.${fabClasses.disabled}`]: {
color: theme.palette.action.disabled,
boxShadow: theme.shadows[0],
backgroundColor: theme.palette.action.disabledBackground,
},
...(ownerState.size === "small" && {
width: 40,
height: 40,
}),
...(ownerState.size === "medium" && {
width: 48,
height: 48,
}),
...(ownerState.variant === "extended" && {
borderRadius: 48 / 2,
padding: "0 16px",
width: "auto",
minHeight: "auto",
minWidth: 48,
height: 48,
}),
...(ownerState.variant === "extended" &&
ownerState.size === "small" && {
width: "auto",
padding: "0 8px",
borderRadius: 34 / 2,
minWidth: 34,
height: 34,
}),
...(ownerState.variant === "extended" &&
ownerState.size === "medium" && {
width: "auto",
padding: "0 16px",
borderRadius: 40 / 2,
minWidth: 40,
height: 40,
}),
...(ownerState.color === "inherit" && {
color: "inherit",
}),
}),
({ theme, ownerState }) => ({
...(ownerState.color !== "inherit" &&
ownerState.color !== "default" &&
theme.palette[ownerState.color] != null && {
color: theme.palette[ownerState.color].contrastText,
backgroundColor: theme.palette[ownerState.color].main,
"&:hover": {
backgroundColor: theme.palette[ownerState.color].dark,
// Reset on touch devices, it doesn't add specificity
"@media (hover: none)": {
backgroundColor: theme.palette[ownerState.color].main,
},
},
}),
})
);

/**
*
* Demos:
*
* - [Floating Action Button](https://mui.com/components/floating-action-button/)
*
* API:
*
* - [Fab API](https://mui.com/api/fab/)
* - inherits [ButtonBase API](https://mui.com/api/button-base/)
*/
const Fab = $.component(function Fab({ allProps, classes, otherProps, props }) {
return (
<FabRoot
className={clsx(classes.root, otherProps.className)}
disabled={props.disabled}
focusRipple={!props.disableFocusRipple}
focusVisibleClassName={clsx(
props.classes?.focusVisible,
otherProps.focusVisibleClassName
)}
ownerState={allProps}
{...otherProps}
>
{props.children}
</FabRoot>
);
});

export default Fab;
92 changes: 92 additions & 0 deletions packages/material/src/Fab/FabProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { PropTypes, Theme } from "..";
import { ButtonBaseTypeMap } from "../ButtonBase";
import { OverrideProps } from "../OverridableComponent";
import { FabClasses } from "./fabClasses";
import { SxProps } from "@suid/system";
import { OverridableStringUnion, ElementType } from "@suid/types";
import { JSXElement } from "solid-js";

export interface FabPropsVariantOverrides {}

export interface FabPropsSizeOverrides {}

export interface FabPropsColorOverrides {}

export type FabTypeMap<P = {}, D extends ElementType = "button"> = {
name: "MuiFab";
defaultPropNames:
| "color"
| "disabled"
| "disableFocusRipple"
| "size"
| "variant";
selfProps: {
/**
* The content of the component.
*/
children?: JSXElement;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<FabClasses>;
/**
* The color of the component. It supports those theme colors that make sense for this component.
* @default 'default'
*/
color?: OverridableStringUnion<
PropTypes.Color | "success" | "error" | "info" | "warning",
FabPropsColorOverrides
>;
/**
* If `true`, the component is disabled.
* @default false
*/
disabled?: boolean;
/**
* If `true`, the keyboard focus ripple is disabled.
* @default false
*/
disableFocusRipple?: boolean;
/**
* If `true`, the ripple effect is disabled.
*/
disableRipple?: boolean;
/**
* The URL to link to when the button is clicked.
* If defined, an `a` element will be used as the root node.
*/
href?: string;
/**
* The size of the component.
* `small` is equivalent to the dense button styling.
* @default 'large'
*/
size?: OverridableStringUnion<
"small" | "medium" | "large",
FabPropsSizeOverrides
>;
/**
* The variant to use.
* @default 'circular'
*/
variant?: OverridableStringUnion<
"circular" | "extended",
FabPropsVariantOverrides
>;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
};
props: P &
FabTypeMap["selfProps"] &
Omit<ButtonBaseTypeMap["props"], "classes">;
defaultComponent: D;
};

export type FabProps<
D extends ElementType = FabTypeMap["defaultComponent"],
P = {}
> = OverrideProps<FabTypeMap<P, D>, D>;

export default FabProps;
50 changes: 50 additions & 0 deletions packages/material/src/Fab/fabClasses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { generateUtilityClass, generateUtilityClasses } from "@suid/base";

export interface FabClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the root element if `color="primary"`. */
primary: string;
/** Styles applied to the root element if `color="secondary"`. */
secondary: string;
/** Styles applied to the root element if `variant="extended"`. */
extended: string;
/** Styles applied to the root element if `variant="circular"`. */
circular: string;
/** State class applied to the ButtonBase root element if the button is keyboard focused. */
focusVisible: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
/** Styles applied to the root element if `color="inherit"`. */
colorInherit: string;
/** Styles applied to the root element if `size="small"``. */
sizeSmall: string;
/** Styles applied to the root element if `size="medium"``. */
sizeMedium: string;
}

export type FabClassKey = keyof FabClasses;

export function getFabUtilityClass(slot: string): string {
return generateUtilityClass("MuiFab", slot);
}

const fabClasses: FabClasses = generateUtilityClasses("MuiFab", [
"root",
"primary",
"secondary",
"extended",
"circular",
"focusVisible",
"disabled",
"colorInherit",
"sizeSmall",
"sizeMedium",
"sizeLarge",
"info",
"error",
"warning",
"success",
]);

export default fabClasses;
6 changes: 6 additions & 0 deletions packages/material/src/Fab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default } from "./Fab";
export * from "./Fab";
export * from "./FabProps";

export { default as fabClasses } from "./fabClasses";
export * from "./fabClasses";
1 change: 1 addition & 0 deletions packages/material/src/styles/components-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { default as MuiContainer } from "../Container";
export { default as MuiCssBaseline } from "../CssBaseline";
export { default as MuiDivider } from "../Divider";
export { default as MuiDrawer } from "../Drawer";
export { default as MuiFab } from "../Fab";
export { default as MuiFade } from "../Fade";
export { default as MuiGrid } from "../Grid";
export { default as MuiIcon } from "../Icon";
Expand Down

0 comments on commit 26ffb46

Please sign in to comment.