Skip to content
Open
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
82 changes: 53 additions & 29 deletions apps/www/content/docs/components/confetti.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,44 +79,68 @@ npm install canvas-confetti
### Usage

```tsx showLineNumbers
import { Confetti } from "@/components/ui/confetti"
```

```tsx showLineNumbers
<Confetti />
import { useRef } from "react"

import { Confetti, type ConfettiRef } from "@/components/ui/confetti"

export function ConfettiDemo() {
const confettiRef = useRef<ConfettiRef>(null)

return (
<div>
<Confetti ref={confettiRef} manualstart />
<button onClick={() => confettiRef.current?.fire()}>
Trigger Confetti
</button>
</div>
)
}
```

## Props

### Confetti

| Prop | Type | Default | Description |
| ------------------------- | --------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------ |
| `particleCount` | `Integer` | `50` | The number of confetti particles to launch |
| `angle` | `Number` | `90` | The angle in degrees at which to launch confetti |
| `spread` | `Number` | `45` | The spread in degrees of the confetti |
| `startVelocity` | `Number` | `45` | The initial velocity of the confetti |
| `decay` | `Number` | `0.9` | The rate at which confetti slows down |
| `gravity` | `Number` | `1` | The gravity applied to confetti particles |
| `drift` | `Number` | `0` | The horizontal drift applied to particles |
| `flat` | `Boolean` | `false` | Whether confetti particles are flat |
| `ticks` | `Number` | `200` | The number of frames confetti lasts |
| `origin` | `Object` | `{ x: 0.5, y: 0.5 }` | The origin point of the confetti |
| `colors` | `Array of Strings` | `['#26ccff', '#a25afd', '#ff5e7e', '#88ff5a', '#fcff42', '#ffa62d', '#ff36ff']` | Array of color strings in HEX format |
| `shapes` | `Array of Strings` | `['square', 'circle', 'star']` | Array of shapes for the confetti |
| `zIndex` | `Integer` | `100` | The z-index of the confetti |
| `disableForReducedMotion` | `Boolean` | `false` | Disables confetti for users who prefer no motion |
| `useWorker` | `Boolean` | `true` | Use Web Worker for better performance |
| `resize` | `Boolean` | `true` | Whether to resize the canvas |
| `canvas` | `HTMLCanvasElement or null` | `null` | Custom canvas element to draw confetti |
| `scalar` | `Number` | `1` | Scaling factor for confetti size |
| Prop | Type | Default | Description |
| --------------- | ------------------------ | ----------------------------------- | ---------------------------------------------------------------- |
| `ref` | `React.Ref<ConfettiRef>` | `null` | Ref exposing imperative `fire(options?: ConfettiOptions)` method |
| `options` | `ConfettiOptions` | `{}` | Default options for automatic or imperative confetti triggers |
| `globalOptions` | `ConfettiGlobalOptions` | `{ resize: true, useWorker: true }` | Global options passed to `canvas-confetti.create()` |
| `manualstart` | `Boolean` | `false` | Prevents automatic confetti firing on component mount |
| `className` | `String` | `undefined` | Custom CSS classes applied to the portaled canvas element |

### ConfettiRef

| Method | Parameters | Return Type | Description |
| ------ | --------------------------- | ----------------------- | ------------------------------------------------------------- |
| `fire` | `options?: ConfettiOptions` | `Promise<void> \| void` | Imperatively triggers a confetti burst on the canvas instance |

### ConfettiOptions

| Field | Type | Default | Description |
| --------------- | -------------------------- | ------------------------------ | ------------------------------------------------------- |
| `particleCount` | `Integer` | `50` | Number of confetti particles to launch |
| `angle` | `Number` | `90` | Launch angle in degrees |
| `spread` | `Number` | `45` | Spread angle in degrees |
| `startVelocity` | `Number` | `45` | Initial particle velocity |
| `decay` | `Number` | `0.9` | Velocity decay rate |
| `gravity` | `Number` | `1` | Gravity applied to particles |
| `drift` | `Number` | `0` | Horizontal drift factor |
| `flat` | `Boolean` | `false` | Renders flat 2D shapes |
| `ticks` | `Number` | `200` | Number of animation frames confetti lasts |
| `origin` | `{ x: number, y: number }` | `{ x: 0.5, y: 0.5 }` | Normalized viewport origin coordinates (`0.0` to `1.0`) |
| `colors` | `Array of Strings` | `HEX array` | Custom HEX color palette |
| `shapes` | `Array of Strings` | `['square', 'circle', 'star']` | Particle shapes or custom text shapes |
| `zIndex` | `Integer` | `9999` | Z-index of the confetti burst |
| `scalar` | `Number` | `1` | Scaling factor for particle dimensions |

### ConfettiButton

| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ------------------------------------ |
| `options` | `Object` | `{}` | Options for the confetti |
| `children` | `React.ReactNode` | `null` | Children to render inside the button |
| Prop | Type | Default | Description |
| ---------- | ----------------------------------------- | ------- | ------------------------------------------------- |
| `options` | `ConfettiOptions & ConfettiGlobalOptions` | `{}` | Options for the confetti launched on button click |
| `children` | `React.ReactNode` | `null` | Button label and child elements |
| `ref` | `React.Ref<HTMLButtonElement>` | `null` | Forwarded DOM reference to the underlying button |

## Credits

Expand Down
143 changes: 72 additions & 71 deletions apps/www/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5160,8 +5160,8 @@ import confetti from "canvas-confetti"

import { Button } from "@/components/ui/button"

type Api = {
fire: (options?: ConfettiOptions) => void
export type ConfettiRef = {
fire: (options?: ConfettiOptions) => Promise<void> | void
}

type Props = React.ComponentPropsWithRef<"canvas"> & {
Expand All @@ -5171,122 +5171,123 @@ type Props = React.ComponentPropsWithRef<"canvas"> & {
children?: ReactNode
}

export type ConfettiRef = Api | null
const ConfettiContext = createContext<ConfettiRef | null>(null)

const ConfettiContext = createContext<Api>({} as Api)

// Define component first
const ConfettiComponent = forwardRef<ConfettiRef, Props>((props, ref) => {
const {
options,
globalOptions = { resize: true, useWorker: true },
manualstart = false,
children,
className,
...rest
} = props

const canvasNodeRef = useRef<HTMLCanvasElement | null>(null)
const instanceRef = useRef<ConfettiInstance | null>(null)
const optionsRef = useRef(options)
const globalOptionsRef = useRef(globalOptions)

const canvasRef = useCallback(
(node: HTMLCanvasElement) => {
if (node !== null) {
if (instanceRef.current) return
instanceRef.current = confetti.create(node, {
...globalOptions,
resize: true,
})
} else {
if (instanceRef.current) {
instanceRef.current.reset()
instanceRef.current = null
}
}
},
[globalOptions]
)
useEffect(() => {
optionsRef.current = options
}, [options])

const fire = useCallback(
async (opts = {}) => {
try {
await instanceRef.current?.({ ...options, ...opts })
} catch (error) {
console.error("Confetti error:", error)
}
},
[options]
)
useEffect(() => {
globalOptionsRef.current = globalOptions
}, [globalOptions])

const api = useMemo(
() => ({
fire,
}),
[fire]
)
useEffect(() => {
if (canvasNodeRef.current && !instanceRef.current) {
instanceRef.current = confetti.create(canvasNodeRef.current, {
resize: true,
useWorker: true,
...globalOptionsRef.current,
})
}

return () => {
instanceRef.current?.reset()
instanceRef.current = null
}
}, [])

const fire = useCallback(async (opts: ConfettiOptions = {}) => {
try {
await instanceRef.current?.({
zIndex: 9999,
...optionsRef.current,
...opts,
})
} catch (error) {
console.error("Confetti error:", error)
}
}, [])

const api = useMemo<ConfettiRef>(() => ({ fire }), [fire])

useImperativeHandle(ref, () => api, [api])

useEffect(() => {
if (!manualstart) {
;(async () => {
try {
await fire()
} catch (error) {
console.error("Confetti effect error:", error)
}
})()
void fire()
}
}, [manualstart, fire])

return (
<ConfettiContext.Provider value={api}>
<canvas ref={canvasRef} {...rest} />
<canvas ref={canvasNodeRef} className={className} {...rest} />
{children}
</ConfettiContext.Provider>
)
})

// Set display name immediately
ConfettiComponent.displayName = "Confetti"

// Export as Confetti
export const Confetti = ConfettiComponent

interface ConfettiButtonProps extends React.ComponentProps<"button"> {
export interface ConfettiButtonProps extends React.ComponentPropsWithoutRef<
typeof Button
> {
options?: ConfettiOptions &
ConfettiGlobalOptions & { canvas?: HTMLCanvasElement }
}

const ConfettiButtonComponent = ({
options,
children,
...props
}: ConfettiButtonProps) => {
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
export const ConfettiButton = forwardRef<
HTMLButtonElement,
ConfettiButtonProps
>(({ options, children, onClick, ...props }, ref) => {
const handleClick: ConfettiButtonProps["onClick"] = async (event) => {
try {
const rect = event.currentTarget.getBoundingClientRect()
const x = rect.left + rect.width / 2
const y = rect.top + rect.height / 2
await confetti({
...options,
origin: {
x: x / window.innerWidth,
y: y / window.innerHeight,
},
})
onClick?.(event)
if (event?.defaultPrevented) return

const target = event?.currentTarget
if (target && "getBoundingClientRect" in target) {
const rect = target.getBoundingClientRect()
const origin = {
x: (rect.left + rect.width / 2) / window.innerWidth,
y: (rect.top + rect.height / 2) / window.innerHeight,
}

await confetti({
zIndex: 9999,
...options,
origin,
})
}
} catch (error) {
console.error("Confetti button error:", error)
}
}

return (
<Button onClick={handleClick} {...props}>
<Button ref={ref} type="button" onClick={handleClick} {...props}>
{children}
</Button>
)
}

ConfettiButtonComponent.displayName = "ConfettiButton"
})

export const ConfettiButton = ConfettiButtonComponent
ConfettiButton.displayName = "ConfettiButton"


===== EXAMPLE: confetti-demo =====
Expand Down
Loading
Loading