Skip to content

Progress Button

ComponentMantine ≥ 9MantineProvider

Button with an integrated timed progress layer and completion callback.

Install

One command copies the item's source into your project, where you own and edit it.

Terminal window
npx manteen add @house/button-progress

Dependencies

  • @mantine/core@^9npm
  • @mantine/hooks@^9npm

Installing this item also installs @house/mantine-ui-license from the registry.

Preview

Usage

A copy-ready example published by the registry author. Imports use the aliases Manteen configures at init.

button-progress.usage.tsx
import { ButtonProgress } from "@/components/ui/button-progress";
export function UploadFilesButton() {
return (
<ButtonProgress
idleLabel="Upload files"
progressLabel="Uploading files"
completeLabel="Files uploaded"
durationMs={2500}
onComplete={() => console.log("upload complete")}
/>
);
}

Props

Author-documented props, carried verbatim from the registry item.

PropTypeDefaultDescription
idleLabelstring"Upload files"Sets the label shown before the action has started.
progressLabelstring"Uploading files"Sets the label shown while progress is animating toward completion.
completeLabelstring"Files uploaded"Sets the label shown once the progress run has finished.
durationMsnumber2000Controls how long the simulated progress run takes to reach completion.
onComplete() => voidFires once the progress bar reaches 100% and the button switches to its complete state.
...othersOmit<ButtonProps, "children" | "onClick" | "classNames" | "styles" | "vars" | "unstyled" | "attributes" | "variant">All other props are forwarded to the root <Button>, after its own fullWidth, so both can be overridden; onClick and color are controlled by the component's internal progress/complete state and always win. Mantine Styles API props (classNames, styles, unstyled) are also accepted.

* required

Styling

ButtonProgress exposes these parts through the public classNames/styles interface:

  • root
  • label

Author-declared; Manteen reports the declaration but does not verify each selector is wired.

Source

2 installable files ship with this item.

registry/mantine-ui/button-progress/button-progress.tsxcomponent · 3.3 kB · tsx
/**
* Adapted from Mantine UI's ButtonProgress at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*/
import {
Button,
type ButtonProps,
type ButtonVariant,
type Factory,
factory,
Progress,
rgba,
type StylesApiProps,
useMantineTheme,
useProps,
useStyles,
} from "@mantine/core";
import { useInterval } from "@mantine/hooks";
import { useState } from "react";
import classes from "./button-progress.module.css";
export type ButtonProgressStylesNames = "root" | "label";
export interface ButtonProgressProps
extends Omit<
ButtonProps,
| "children"
| "onClick"
| "classNames"
| "styles"
| "vars"
| "unstyled"
| "attributes"
| "variant"
>,
StylesApiProps<ButtonProgressFactory> {
/** Key of Button's variant, e.g. `"filled"` @default 'filled' */
variant?: ButtonVariant;
idleLabel?: string;
progressLabel?: string;
completeLabel?: string;
durationMs?: number;
onComplete?: () => void;
}
export type ButtonProgressFactory = Factory<{
props: ButtonProgressProps;
ref: HTMLButtonElement;
stylesNames: ButtonProgressStylesNames;
}>;
const TICK_MS = 20;
export const ButtonProgress = factory<ButtonProgressFactory>((_props) => {
const props = useProps("ButtonProgress", null, _props);
const {
classNames,
className,
style,
styles,
unstyled,
vars,
attributes,
ref,
idleLabel = "Upload files",
progressLabel = "Uploading files",
completeLabel = "Files uploaded",
durationMs = 2000,
onComplete,
color,
...others
} = props;
const theme = useMantineTheme();
const [progress, setProgress] = useState(0);
const [complete, setComplete] = useState(false);
const step = (TICK_MS / Math.max(durationMs, TICK_MS)) * 100;
const getStyles = useStyles<ButtonProgressFactory>({
name: "ButtonProgress",
classes,
props,
className,
style,
classNames,
styles,
unstyled,
attributes,
vars,
});
const interval = useInterval(() => {
setProgress((current) => {
const next = current + step;
if (next < 100) return next;
interval.stop();
setComplete(true);
onComplete?.();
return 0;
});
}, TICK_MS);
const start = () => {
if (complete) {
setComplete(false);
return;
}
if (!interval.active) interval.start();
};
return (
<Button
ref={ref}
fullWidth
unstyled={unstyled}
{...getStyles("root")}
{...others}
onClick={start}
color={complete ? "teal.9" : (color ?? `${theme.primaryColor}.9`)}
>
<span {...getStyles("label")} aria-live="polite">
{progress > 0 ? progressLabel : complete ? completeLabel : idleLabel}
</span>
{progress > 0 && (
<Progress
value={progress}
className={classes.progress}
color={rgba(theme.colors.blue[2], 0.35)}
radius="sm"
/>
)}
</Button>
);
});
ButtonProgress.classes = classes;
ButtonProgress.displayName = "ButtonProgress";
export namespace ButtonProgress {
export type Props = ButtonProgressProps;
export type StylesNames = ButtonProgressStylesNames;
export type Factory = ButtonProgressFactory;
}
registry/mantine-ui/button-progress/button-progress.module.cssstyle · 402 B · css
/*
* Adapted from Mantine UI's ButtonProgress at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*/
.root {
position: relative;
transition: background-color 150ms ease;
}
.progress {
position: absolute;
z-index: 0;
inset: -1px;
height: auto;
background-color: transparent;
}
.label {
position: relative;
z-index: 1;
}

Author notes

Adapted from Mantine UI ButtonProgress at ffbf61c559…; labels, duration and completion are configurable.