Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/clever-pets-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@heroui/spinner": patch
"@heroui/theme": patch
---

Adding variants to the Spinner Component.
2 changes: 2 additions & 0 deletions apps/docs/content/components/spinner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import sizes from "./sizes";
import colors from "./colors";
import label from "./label";
import labelColors from "./label-colors";
import variants from "./variants";

export const spinnerContent = {
usage,
sizes,
colors,
label,
labelColors,
variants,
};
13 changes: 13 additions & 0 deletions apps/docs/content/components/spinner/variants.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Spinner} from "@heroui/react";

export default function App() {
return (
<div className="flex flex-wrap items-end gap-8">
<Spinner classNames={{label: "text-primary-400 mt-4"}} label="default" variant="default" />
<Spinner classNames={{label: "text-primary-400 mt-4"}} label="gradient" variant="gradient" />
<Spinner classNames={{label: "text-primary-400 mt-4"}} label="spinner" variant="spinner" />
<Spinner classNames={{label: "text-primary-400 mt-4"}} label="wave" variant="wave" />
<Spinner classNames={{label: "text-primary-400 mt-4"}} label="dots" variant="dots" />
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/spinner/variants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./variants.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
9 changes: 9 additions & 0 deletions apps/docs/content/docs/api-references/heroui-provider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,15 @@ The available options are:
- **Type**: `"user" | "always" | "never"`
- **Default**: `"never"`

`spinnerVariant`

- **Description**: The default variant of the spinner.
- **Type**: `string` | `undefined`
- **Possible Values**: `default` | `gradient` | `wave` | `dots` | `spinner`
- **Default**: `default`

<Spacer y={2}/>

---

## Types
Expand Down
18 changes: 15 additions & 3 deletions apps/docs/content/docs/components/spinner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,18 @@ Spinner express an unspecified wait time or display the length of a process.

<CodeDemo title="Label colors" files={spinnerContent.labelColors} />

### Variants

<CodeDemo title="Variants" files={spinnerContent.variants} />

## Slots

- **base**: The base slot of the spinner, it wraps the circles and the label.
- **wrapper**: The wrapper of the circles.
- **circle1**: The first circle of the spinner.
- **circle2**: The second circle of the spinner.
- **circle1**: The first circle of the spinner component. (Effective only when variant is `default` or `gradient`)
- **circle2**: The second circle of the spinner component. (Effective only when variant is `default` or `gradient`)
- **dots**: Dots of the spinner component. (Effective only when variant is `wave` or `dots`)
- **spinnerBars**: Bars of the spinner component. (Effective only when variant is `spinner`)
- **label**: The label content.

<Spacer y={4} />
Expand Down Expand Up @@ -94,6 +100,12 @@ Spinner express an unspecified wait time or display the length of a process.
description: "The color of the spinner circles.",
default: "primary"
},
{
attribute: "variant",
type: "default | gradient | wave | dots | spinner",
description: "The variant of the spinner",
default: "default"
},
{
attribute: "labelColor",
type: "default | primary | secondary | success | warning | danger",
Expand All @@ -102,7 +114,7 @@ Spinner express an unspecified wait time or display the length of a process.
},
{
attribute: "classNames",
type: "Partial<Record<\"base\"|\"wrapper\"|\"circle1\"|\"circle2\"|\"label\", string>>",
type: "Partial<Record<'base' | 'wrapper' | 'circle1' | 'circle2' | 'dots' | 'spinnerBars' | 'label', string>>",
description: "Allows to set custom class names for the spinner slots.",
default: "-"
}
Expand Down
5 changes: 3 additions & 2 deletions packages/components/spinner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@
"peerDependencies": {
"react": ">=18 || >=19.0.0-rc.0",
"react-dom": ">=18 || >=19.0.0-rc.0",
"@heroui/theme": ">=2.4.0"
"@heroui/theme": ">=2.4.7"
},
"dependencies": {
"@heroui/system-rsc": "workspace:*",
"@heroui/shared-utils": "workspace:*",
"@heroui/react-utils": "workspace:*"
"@heroui/react-utils": "workspace:*",
"@heroui/system": "workspace:*"
},
"devDependencies": {
"@heroui/theme": "workspace:*",
Expand Down
44 changes: 43 additions & 1 deletion packages/components/spinner/src/spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,49 @@ import {UseSpinnerProps, useSpinner} from "./use-spinner";
export interface SpinnerProps extends UseSpinnerProps {}

const Spinner = forwardRef<"div", SpinnerProps>((props, ref) => {
const {slots, classNames, label, getSpinnerProps} = useSpinner({...props});
const {slots, classNames, label, variant, getSpinnerProps} = useSpinner({...props});

if (variant === "wave" || variant === "dots") {
return (
<div ref={ref} {...getSpinnerProps()}>
<div className={slots.wrapper({class: classNames?.wrapper})}>
{[...new Array(3)].map((_, index) => (
<i
key={`dot-${index}`}
className={slots.dots({class: classNames?.dots})}
style={
{
"--dot-index": index,
} as React.CSSProperties
}
/>
))}
</div>
{label && <span className={slots.label({class: classNames?.label})}>{label}</span>}
</div>
);
}

if (variant === "spinner") {
return (
<div ref={ref} {...getSpinnerProps()}>
<div className={slots.wrapper({class: classNames?.wrapper})}>
{[...new Array(12)].map((_, index) => (
<i
key={`star-${index}`}
className={slots.spinnerBars({class: classNames?.spinnerBars})}
style={
{
"--bar-index": index,
} as React.CSSProperties
}
/>
))}
</div>
{label && <span className={slots.label({class: classNames?.label})}>{label}</span>}
</div>
);
}

return (
<div ref={ref} {...getSpinnerProps()}>
Expand Down
6 changes: 5 additions & 1 deletion packages/components/spinner/src/use-spinner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {mapPropsVariants} from "@heroui/system-rsc";
import {spinner} from "@heroui/theme";
import {clsx, objectToDeps} from "@heroui/shared-utils";
import {useMemo, useCallback, Ref} from "react";
import {useProviderContext} from "@heroui/system";

interface Props extends HTMLHeroUIProps<"div"> {
/**
Expand Down Expand Up @@ -38,6 +39,9 @@ export type UseSpinnerProps = Props & SpinnerVariantProps;
export function useSpinner(originalProps: UseSpinnerProps) {
const [props, variantProps] = mapPropsVariants(originalProps, spinner.variantKeys);

const globalContext = useProviderContext();
const variant = originalProps?.variant ?? globalContext?.spinnerVariant ?? "default";

const {children, className, classNames, label: labelProp, ...otherProps} = props;

const slots = useMemo(() => spinner({...variantProps}), [objectToDeps(variantProps)]);
Expand Down Expand Up @@ -65,7 +69,7 @@ export function useSpinner(originalProps: UseSpinnerProps) {
[ariaLabel, slots, baseStyles, otherProps],
);

return {label, slots, classNames, getSpinnerProps};
return {label, slots, classNames, variant, getSpinnerProps};
}

export type UseSpinnerReturn = ReturnType<typeof useSpinner>;
31 changes: 30 additions & 1 deletion packages/components/spinner/stories/spinner.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import {Meta} from "@storybook/react";
import {spinner} from "@heroui/theme";

import {Spinner} from "../src";
import {Spinner, SpinnerProps} from "../src";

export default {
title: "Components/Spinner",
Expand All @@ -26,6 +26,12 @@ export default {
},
options: ["sm", "md", "lg"],
},
variant: {
control: {
type: "select",
},
options: ["default", "gradient", "spinner", "wave", "dots"],
},
},
decorators: [
(Story) => (
Expand All @@ -40,6 +46,18 @@ const defaultProps = {
...spinner.defaultVariants,
};

const VariantsTemplate = (args: SpinnerProps) => {
return (
<div className="flex flex-wrap items-end gap-8 py-4">
<Spinner {...args} label="default" variant="default" />
<Spinner {...args} label="gradient" variant="gradient" />
<Spinner {...args} label="spinner" variant="spinner" />
<Spinner {...args} label="wave" variant="wave" />
<Spinner {...args} label="dots" variant="dots" />
</div>
);
};

export const Default = {
args: {
...defaultProps,
Expand All @@ -52,3 +70,14 @@ export const WithLabel = {
label: "Loading...",
},
};

export const Variants = {
args: {
...defaultProps,
classNames: {
label: "text-primary-400 mt-4",
},
},

render: VariantsTemplate,
};
7 changes: 6 additions & 1 deletion packages/core/system/src/provider-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {SupportedCalendars} from "./types";
import type {SpinnerVariants, SupportedCalendars} from "./types";
import type {Calendar} from "@internationalized/date";
import type {DateValue} from "@react-types/datepicker";

Expand Down Expand Up @@ -87,6 +87,11 @@ export type ProviderContextProps = {
* @default all calendars
*/
createCalendar?: (calendar: SupportedCalendars) => Calendar | null;
/**
* The default variant of the spinner.
* @default default
*/
spinnerVariant?: SpinnerVariants;
};

export const [ProviderContext, useProviderContext] = createContext<ProviderContextProps>({
Expand Down
3 changes: 3 additions & 0 deletions packages/core/system/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const HeroUIProvider: React.FC<HeroUIProviderProps> = ({
// then they will be set in `use-date-input.ts` or `use-calendar-base.ts`
defaultDates,
createCalendar,
spinnerVariant,
...otherProps
}) => {
let contents = children;
Expand All @@ -87,6 +88,7 @@ export const HeroUIProvider: React.FC<HeroUIProviderProps> = ({
disableRipple,
validationBehavior,
labelPlacement,
spinnerVariant,
};
}, [
createCalendar,
Expand All @@ -96,6 +98,7 @@ export const HeroUIProvider: React.FC<HeroUIProviderProps> = ({
disableRipple,
validationBehavior,
labelPlacement,
spinnerVariant,
]);

return (
Expand Down
5 changes: 5 additions & 0 deletions packages/core/system/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ export type SupportedCalendars =
| "persian"
| "roc"
| "gregory";

/**
* Spinner Variants
*/
export type SpinnerVariants = "default" | "gradient" | "wave" | "dots" | "spinner";
33 changes: 33 additions & 0 deletions packages/core/theme/src/animations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export const animations = {
"drip-expand": "drip-expand 420ms linear",
"spinner-ease-spin": "spinner-spin 0.8s ease infinite",
"spinner-linear-spin": "spinner-spin 0.8s linear infinite",
sway: "sway 750ms ease infinite",
blink: "blink 1.4s infinite both",
"fade-out": "fade-out 1.2s linear 0s infinite normal none running",
"appearance-in": "appearance-in 250ms ease-out normal both",
"appearance-out": "appearance-out 60ms ease-in normal both",
"indeterminate-bar":
Expand Down Expand Up @@ -67,5 +70,35 @@ export const animations = {
transform: "translateX(100%) scaleX(1)",
},
},
sway: {
"0%": {
transform: "translate(0px, 0px)",
},
"50%": {
transform: "translate(0px, -150%)",
},
"100%": {
transform: "translate(0px, 0px)",
},
},
blink: {
"0%": {
opacity: "0.2",
},
"20%": {
opacity: "1",
},
"100%": {
opacity: "0.2",
},
},
"fade-out": {
"0%": {
opacity: "1",
},
"100%": {
opacity: "0.15",
},
},
},
};
Loading