import { EnteSwitch } from "@/base/components/EnteSwitch"; import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; import { Box, Divider, Stack, styled, Typography, type ButtonProps, type TypographyProps, } from "@mui/material"; import React from "react"; interface RowButtonGroupTitleProps { /** * An optional leading SvgIcon before the title. */ icon?: React.ReactNode; } /** * A section title, meant to precede a {@link RowButtonGroup}. */ export const RowButtonGroupTitle: React.FC< React.PropsWithChildren > = ({ children, icon }) => ( svg": { fontSize: "17px", color: "stroke.muted", }, }} > {icon && icon} {children} ); /** * A short description text meant to come after a {@link RowButtonGroup}. */ export const RowButtonGroupHint: React.FC = ({ children, }) => ( {children} ); /** * A divider for buttons in a {@link RowButtonGroup}. */ export const RowButtonDivider = () => ( ); /** * A group of {@link RowButton}s that visually look together as a single * section. * * {@link RowButtonGroupTitle} can be used to provide a title for the entire * group, and {@link RowButtonGroupHint} can be used to provide a hint text that * follows the group. * * {@link RowButtonDivider} can be used to to separate the individual buttons in * the group. * * --- * * Note: {@link RowButtonGroup} is not designed to work with the "secondary" * variant of {@link RowButton}. */ export const RowButtonGroup = styled("div")( ({ theme }) => ` background-color: ${theme.vars.palette.fill.faint}; border-radius: 8px; /* Modify the RowButton style when it is placed inside a RowButtonGroup. */ & > button { border-radius: 8px; background-color: transparent; } /* Need to retarget the disabled state with increased specificity. */ & > button.Mui-disabled { background-color: transparent; } & > button:not(:last-of-type) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } & > button:not(:first-of-type) { border-top-left-radius: 0; border-top-right-radius: 0; } & > button:hover { /* These fills are translucent and additive, and the RowButtonGroup already has a background, so pick a (transparent) color that gives us a similar outcome of the hover state as for a RowButton that is not inside a RowButtonGroup */ background-color: ${theme.vars.palette.fill.faintHover}; } /* Reset the border radius when showing focus outlines that will be added by the styles attached to FocusVisibleButton. */ & > button.Mui-focusVisible { border-radius: 8px; } /* Modify the outline offset added by FocusVisibleButton for a visual effect that fits better with these row buttons. */ & > button:active { outline-offset: 0; } `, ); interface RowButtonProps { /** * Variants: * * - "primary" (default): A row button with a filled in background color. * * - "secondary": A row button without a fill. */ variant?: "primary" | "secondary"; /** * Color of the row button. * * Semantically, this is similar to the "color" props for a MUI button, * except we only support two cases for this row button component: * * - "primary" (default): A row button that uses "text.base" as the color of * the text (and an approprite background color, if needed, based on the * value of the {@link variant}). * * - "critical": A row button that uses "critical.main" as the color of the * text. The background fill, if any, will be the same as what "primary" * would've entailed. */ color?: "primary" | "critical"; /** * Modify the font weight of the {@link label}, when label is a string. * * Default: "medium". */ fontWeight?: TypographyProps["fontWeight"]; /** * If true, then the row button will be disabled. */ disabled?: boolean; /** * Called when the row button is activated (e.g. by a click). */ onClick: () => void; /** * Optional icon shown at the leading edge of the row button. * * This is usually an icon like an {@link SvgIcon}, but it can be any * arbitrary component, the row button does not make any assumptions as to * what this is. * * If it is an {@link SvgIcon}, the row button will size it appropriately. */ startIcon?: React.ReactNode; /** * Optional icon shown at the trailing edge of the row button. * * Similar to {@link startIcon} this can be any arbitrary component, though * usually it is an {@link SvgIcon} whose size the row button will set. */ endIcon?: React.ReactNode; /** * The label for the component. * * Usually this is expected to be a string, in which case it is wrapped up * in an appropriate {@link Typography} and shown on the row button. But it * can be an arbitrary react component too, to allow customizing its * appearance or otherwise modifying it in one off cases (it will be used as * it is and not wrapped in a {@link Typography} if it is not a string). */ label: React.ReactNode; /** * The text (or icon) to show alongside {@link label}, separated from it by * a bullet. * * This is usually expected to be a string, in which case it is wrapped in a * {@link Typography} component and styled with a muted text color before * being rendered. * * However, it can also be an {@link SvgIcon} (or any an arbitrary * component), though in terms of styling, only an {@link SvgIcon} usually * makes sense. * * Similar to {@link label}, it will not be wrapped in a {@link Typography} * if it is not a string. */ caption?: React.ReactNode; } /** * A button that looks like a row in a group of options / choices. * * It can be used both standalone, or as part of a {@link RowButtonGroup}. */ export const RowButton: React.FC = ({ variant = "primary", color = "primary", fontWeight = "medium", disabled = false, startIcon, endIcon, label, caption, onClick, }) => ( {startIcon && startIcon} {typeof label !== "string" ? ( label ) : caption ? ( {label} {"•"} {caption} ) : ( {label} )} {endIcon && endIcon} ); type RowButtonRootProps = ButtonProps & { // Prefix with "rb" to differentiate this from the "variant" prop of the MUI // button that we're styling. rbVariant: RowButtonProps["variant"]; }; const RowButtonRoot = styled(FocusVisibleButton, { shouldForwardProp: (prop) => prop != "rbVariant", // name: "MuiMenuItem", })>(({ theme }) => ({ // Remove button's default padding. padding: 0, // Set the size of the any icons (SvgIcon instances) provided to us to make // them fit with the Typography within the button's content. "& .MuiSvgIcon-root": { fontSize: "20px", }, variants: [ { props: { rbVariant: "primary" }, style: { backgroundColor: theme.vars.palette.fill.faint, "&:hover": { backgroundColor: theme.vars.palette.fill.muted, }, }, }, { props: { rbVariant: "secondary" }, style: { backgroundColor: "transparent", color: "white", "&:hover": { backgroundColor: theme.vars.palette.fill.faint }, }, }, ], })); const CaptionTypography: React.FC< React.PropsWithChildren<{ color: RowButtonProps["color"] }> > = ({ color, children }) => ( {children} ); interface RowSwitchProps { /** * The state of the contained {@link EnteSwitch}. */ checked?: boolean; /** * Called when the user activates the contained {@link EnteSwitch}. */ onClick: () => void; /** * The label for the component. * * Similar to the {@link label} prop for {@link RowButton}, but can only be * a string instead of an arbitrary component. */ label: string; } /** * A row that visually looks similar to a {@link RowButton}, but instead of a * button is a normal {@link Typography} with a {@link EnteSwitch} at its * trailing edge. * * It only works (visually) when placed within a {@link RowButtonGroup} since * that is where it gets its background color from. */ export const RowSwitch: React.FC = ({ checked, label, onClick, }) => ( {label} ); interface RowLabelProps { /** * Optional icon shown at the leading edge of the row button. * * This is usually an icon like an {@link SvgIcon}, but it can be any * arbitrary component, the row button does not make any assumptions as to * what this is. * * Unlike a {@link RowButton}, there is not sizing applied to it. */ startIcon?: React.ReactNode; /** * The label for the component. * * Similar to the {@link label} prop for {@link RowButton}, but can only be * a string instead of an arbitrary component. */ label: string; } /** * A row that visually looks similar to a {@link RowButton}, but instead of a * button is a normal {@link Typography}. * * This is useful for creating non-interactive, static, labels. * * It only works (visually) when placed within a {@link RowButtonGroup} since * that is where it gets its background color from. */ export const RowLabel: React.FC = ({ startIcon, label }) => ( {startIcon && startIcon} {/* Nb: Unlike the button, this has "regular" fontWeight. */} {label} );