+ CommandInputProps
+>(({ className, wrapperClassName, hideIcon, ...props }, ref) => (
+
+ {hideIcon ? null : (
+
+ )}
@@ -33,6 +34,18 @@ Open the newly created file and paste the following code:
+## Usage
+
+The stack has three states:
+
+1. **Deck** — all cards share one origin (transform-only, no absolute fan layout).
+2. **Hover** — a light vertical peek on the outer layer; spread motion is unchanged.
+3. **Click** — cards throw into their spread positions with a custom `--ease-throw` curve; collapse uses a separate ease-out gather.
+
+Toggle with click or
Enter /
Space when the stage is focused. The demo composes Notes, Shopping List, and related widget cards — swap in your own `w-48` tiles as needed.
+
+Motion is CSS-only: an inner wrapper handles spread/collapse; an outer wrapper handles hover peek so the two never fight for `transform`.
+
## Credits
Built by [sudha](https://github.com/sudhashrestha)
diff --git a/content/docs/card/collab-card.mdx b/content/docs/card/collab-card.mdx
new file mode 100644
index 00000000..3828e755
--- /dev/null
+++ b/content/docs/card/collab-card.mdx
@@ -0,0 +1,134 @@
+---
+title: Collab Card
+description: Bento tile for realtime collaboration—not a generic card. Live presence, selection frame, teammate cursors.
+labels: ["bento", "collaboration", "animated"]
+---
+
+
+
+A bento feature tile for realtime collaboration, not a general-purpose card. Put it in a wide cell on your marketing site or product landing when you're selling live editing, shared canvases, or co-authored docs. Skip it for heroes and for slots where collaboration isn't the point.
+
+Out of the box it looks like Figma multiplayer: live status and an avatar stack up top, a blue dashed frame with corner handles around the greeting, and two named pills with cursors that drift and flash a click ring. Change the strings and `collaborators` tuple for Linear, Notion, or whatever tool you're echoing.
+
+## Installation
+
+### CLI
+
+
+
+### Manual
+
+
+
+Run the following command
+
+This creates `collab-card.tsx` in `components/animata/card`.
+
+```bash
+mkdir -p components/animata/card && touch components/animata/card/collab-card.tsx
+```
+
+Paste the code
+
+Open the file and paste:
+
+```jsx file=/animata/card/collab-card.tsx
+
+```
+
+
+
+## When to use
+
+Feature grids and bento rows about multiplayer editing, shared canvases, co-authored docs, or live presence. One host cursor runs the frame edge; two teammate cursors sit on their pills. If your users know Figma, they'll read it instantly.
+
+## When not to use
+
+Solo flows—the card implies other people in the file. Tight grid cells—the greeting is sized in `cqi`, so give the tile at least half a row of height. Pages with no collaboration story—the motion promises co-editing; don't leave that hanging.
+
+## How it works
+
+The root is a container query (`@container`). Spacing, type, frame padding, and cursor offsets use `cqi`, so everything scales with the card width. No breakpoint ladder, no `ResizeObserver`.
+
+Animation is CSS only. The host cursor loops the dashed frame. Collaborator cursors wobble beside their pills; a masked `conic-gradient` draws the click burst on a delay. Put a `text-*` class on the cursor wrapper and both pointer and burst pick it up through `currentColor`.
+
+With `prefers-reduced-motion: reduce`, animations stop. Cursors and bursts stay visible at rest so the card still reads offline.
+
+Defaults borrow Figma's `#0D99FF` frame and the usual violet/coral collaborator colors. Background glows use `color-mix(in oklch, …)` on the dark canvas.
+
+## Reskinning
+
+```tsx
+// Figma — default
+
+
+// Linear — issue assignment
+
+
+// Notion — wiki co-editors
+
+```
+
+## Props
+
+
+
+ Text inside the dashed frame.
+
+
+ Line above the frame.
+
+
+ Copy before the first pill.
+
+
+ Between the two pills.
+
+
+ After the second pill; omitted when empty.
+
+
+ Next to the green live dot.
+
+
+ Hex fills for stack avatars, left to right.
+
+
+ Shown as +N on the avatar stack.
+
+
+ name, pill, optional pillText, and cursor per person.
+
+
+ Cover image; without it, dark canvas and dot grid.
+
+
+ On the outer wrapper (size, radius).
+
+
+
+## Accessibility
+
+Cursors and bursts are `aria-hidden`. Screen readers still get the greeting, eyebrow, and names from visible text.
+
+Reduced motion turns animations off; layout stays the same.
+
+The frame is a dashed `border`, not a background image, so it holds up in forced-colors mode.
diff --git a/content/docs/changelog/2026-06.mdx b/content/docs/changelog/2026-06.mdx
new file mode 100644
index 00000000..dac01abc
--- /dev/null
+++ b/content/docs/changelog/2026-06.mdx
@@ -0,0 +1,72 @@
+---
+title: June 2026
+description: Widget glance polish, Card Spread and Collab Card, and catalog cleanup.
+date: 2026-06-01
+---
+
+Wave 1 widgets got Apple-style type hierarchy and props-driven demos. Card Spread was rebuilt for deck, peek, and throw-on-click motion. Four oversized widgets left the public catalog, and the internal widget standards doc was removed from contributing.
+
+## Improved components
+
+
+
+ Reminder
+ Glance typography, toggleable rows, and theme tokens — use on home-screen–style boards where quick task completion matters.
+
+
+ Weather Card
+ Props for city, conditions, and highs/lows with a compact square layout for dashboards and marketing bentos.
+
+
+ Water Tracker
+ Configurable goal and fill level with clearer hierarchy for health and habit apps.
+
+
+ VPN Widget
+ Sliding connect control with labeled states — fits security or connectivity glance tiles.
+
+
+ Delivery Card
+ Transit progress, arrival time, and location in a dark tile suited to order-tracking UIs.
+
+
+ Alarm Clock
+ Toggleable alarms with schedule labels — for clock or morning-routine widget grids.
+
+
+ Calendar Event
+ Dot-and-chip event rows with default data — drop into calendar or agenda previews.
+
+
+ Music Widget
+ Track list, cover art, and transport controls for media player mockups.
+
+
+ Shopping List
+ Interactive checkboxes and configurable items — grocery or errand list tiles.
+
+
+ Notes
+ Line-based note content with the yellow sticky treatment — quick memo widgets.
+
+
+ Fund Widget
+ Chart scaled into the square `size-52` shell for finance glance layouts.
+
+
+ Calorie Counter
+ Tall donut summary with flex layout instead of absolute positioning — nutrition dashboards.
+
+
+ Card Spread
+ Stacked deck with a subtle hover peek and click-to-spread throw easing. Use when you want several related cards in one compact stack that expands on demand.
+
+
+ Collab Card
+ Bento-sized multiplayer feature card with live presence, a Figma-style selection frame, and animated teammate cursors — reskin with props for Linear, Notion, or your own co-editing story.
+
+
+
+## Documentation
+
+Unpublished Calendar Widget, Music Stack Interaction, Team Clock, and Video Chat from the docs and shadcn registry so the widget overview lists only glance-sized tiles. Removed the internal widget catalog standards page from contributing. Added `useIsMobile` for responsive docs and demo behavior. Widget overview now tracks published entries only and places Fund Widget in the square section.
diff --git a/content/docs/changelog/index.mdx b/content/docs/changelog/index.mdx
index cfbc52d1..d8341b61 100644
--- a/content/docs/changelog/index.mdx
+++ b/content/docs/changelog/index.mdx
@@ -8,13 +8,13 @@ New components, bug fixes, and infrastructure changes, going back to the beta la
## Recent releases
-### [May 2026](/docs/changelog/2026-05)
+### [June 2026](/docs/changelog/2026-06)
-[Boids Ecosystem](/docs/background/boids-ecosystem), [Sibling Focus Nav](/docs/container/sibling-focus-nav), live demos at `/demo`, and Tailwind CSS 4.3 with a shadcn/ui registry refresh.
+Wave 1 widget glance polish, [Card Spread](/docs/card/card-spread) throw motion, and catalog cleanup for published widgets only.
-### [April 2026](/docs/changelog/2026-04)
+### [May 2026](/docs/changelog/2026-05)
-19 new text animation presets built on a shared WAAPI runtime. shadcn registry went live — every component became installable via URLs like `https://animata.design/r/{category}/{name}.json`. Announcement ribbon was added, and the site was revamped for speed and accessibility.
+[Boids Ecosystem](/docs/background/boids-ecosystem), [Sibling Focus Nav](/docs/container/sibling-focus-nav), live demos at `/demo`, and Tailwind CSS 4.3 with a shadcn/ui registry refresh.
---
@@ -22,6 +22,7 @@ New components, bug fixes, and infrastructure changes, going back to the beta la
| Month | Highlights |
|---|---|
+| [June 2026](/docs/changelog/2026-06) | Widget glance polish, Card Spread, unpublished four oversized widgets |
| [May 2026](/docs/changelog/2026-05) | Tailwind 4.3, Boids Ecosystem, Sibling Focus Nav, live demos, docs typography |
| [April 2026](/docs/changelog/2026-04) | 19 text animation presets, shadcn registry, announcement ribbon, site revamp |
| [March 2026](/docs/changelog/2026-03) | Next.js 16, Tailwind v4, Storybook 10, Biome |
diff --git a/content/docs/contributing/components.mdx b/content/docs/contributing/components.mdx
index 5021e5ae..49dcf008 100644
--- a/content/docs/contributing/components.mdx
+++ b/content/docs/contributing/components.mdx
@@ -50,6 +50,42 @@ Simply open the `config/docs.ts` file and paste the new entry in the `docsConfig
The script above generates a placeholder documentation file for the new component. You can find the file in the `content/docs/{type}` directory. Update it with the relevant information about the component you are creating and remove the placeholder content.
+### Props tables
+
+Document the component API with `
` and `` — not a markdown pipe table. See `content/docs/card/collab-card.mdx` for a full example.
+
+```mdx
+## Props
+
+
+
+ Classes on the root element.
+
+
+```
+
+For two exported components in one doc (e.g. `DebugGrid` and `DebugSurface`), use `title` on each table: ``.
+
+### Manual install steps (`` / ``)
+
+`` renders an ``. In MDX, put a **blank line** after ``, after each `
`, and before fenced code blocks. Otherwise MDX wraps steps in `` and you get invalid HTML (`
` cannot contain `
`) and hydration errors.
+
+````mdx
+
+
+Install dependencies
+
+```bash
+npm install lucide-react
+```
+
+Run the following command
+
+Your prose here…
+
+
+````
+
## Proposing pull requests
- **Making changes**: implement your bug fix or feature and write stories to cover different use cases. Then commit your changes, push your bug fix/feature branch to the origin (your forked repo) and open a pull request to the upstream (the repository you originally forked)‘s main branch.
diff --git a/content/docs/widget/alarm-clock.mdx b/content/docs/widget/alarm-clock.mdx
index 71d96111..ca02f999 100644
--- a/content/docs/widget/alarm-clock.mdx
+++ b/content/docs/widget/alarm-clock.mdx
@@ -1,11 +1,15 @@
---
title: Alarm Clock
-description: It is a alarm clock
+description: Toggle one or two alarms on a compact card — built for a morning routine glance.
labels: ["requires interaction", "toggle"]
author: Sumingurung9
---
-
+
+
+## How it works
+
+Each entry in `alarms` needs `time`, `schedule`, and a stable `id` for toggle state. `defaultEnabledIds` sets which alarms start on. The list is capped at two rows so the tile stays readable; add more alarms in your app screen, not on the widget.
## Installation
diff --git a/content/docs/widget/calendar-event.mdx b/content/docs/widget/calendar-event.mdx
index 4f97a524..58349471 100644
--- a/content/docs/widget/calendar-event.mdx
+++ b/content/docs/widget/calendar-event.mdx
@@ -1,10 +1,23 @@
---
title: Calendar event
-description: Calendar event widget
+description: Show today’s date plus the next events — built for a calendar home-screen widget.
author: Sumin Gurung
---
-
+
+
+## How it works
+
+Pass an `events` array with `title`, `time`, and optional `variant` (`violet`, `cyan`, `emerald`, `amber`, `rose`) for chip colors. `maxVisible` controls how many rows fit before a “+N more” footer. Override `date` when you are not showing “today”.
+
+```tsx
+
+```
## Installation
@@ -15,6 +28,7 @@ author: Sumin Gurung
### Manual
+
Run the following command
It will create a new file called `calendar-event.tsx` inside the `components/animata/widget` directory.
diff --git a/content/docs/widget/calendar-widget.mdx b/content/docs/widget/calendar-widget.mdx
index b1e34200..523580b3 100644
--- a/content/docs/widget/calendar-widget.mdx
+++ b/content/docs/widget/calendar-widget.mdx
@@ -2,6 +2,7 @@
title: Calendar Widget
description: calendar widget is calender like widget with smooth animations and cool effects
author: anshu_code
+published: false
---
diff --git a/content/docs/widget/delivery-card.mdx b/content/docs/widget/delivery-card.mdx
index fcf028b0..ba76e5ef 100644
--- a/content/docs/widget/delivery-card.mdx
+++ b/content/docs/widget/delivery-card.mdx
@@ -1,10 +1,14 @@
---
title: Delivery Card
-description: This card helps user to track their orders.
+description: Show delivery ETA, status, and route progress on a single home-screen tile.
author: sanjaya22780
---
-
+
+
+## How it works
+
+Pass `progress` (0–100), `arrivalTime`, `location`, and `timeAgo` from your order API. Progress drives the truck marker along a simple bar. Set `simulateProgress` only in demos — it cycles progress on an interval so Storybook feels alive without surprising production embeds.
## Installation
@@ -15,6 +19,7 @@ author: sanjaya22780
### Manual
+
Install dependencies
```bash
diff --git a/content/docs/widget/index.mdx b/content/docs/widget/index.mdx
index a438b656..b9e7c5e0 100644
--- a/content/docs/widget/index.mdx
+++ b/content/docs/widget/index.mdx
@@ -14,6 +14,7 @@ import DeliveryCard from "@/animata/widget/delivery-card";
import DirectionCard from "@/animata/widget/direction-card";
import ExpenseTracker from "@/animata/widget/expense-tracker";
import FlightWidget from "@/animata/widget/flight-widget";
+import FundWidget from "@/animata/widget/fund-widget";
import LiveScore from "@/animata/widget/live-score";
import MobileDetail from "@/animata/widget/mobile-detail";
import MusicWidget from "@/animata/widget/music-widget";
@@ -100,6 +101,14 @@ import WeeklyProgress from "@/animata/widget/weekly-progress";
+
+
+ ```tsx file=/animata/widget/fund-widget.tsx copyId="fund-widget"
+
+ ````
+
+
+
```tsx file=/animata/widget/mobile-detail.tsx copyId="mobile-detail"
diff --git a/content/docs/widget/music-stack-interaction.mdx b/content/docs/widget/music-stack-interaction.mdx
index 4fbaf5c0..6f474398 100644
--- a/content/docs/widget/music-stack-interaction.mdx
+++ b/content/docs/widget/music-stack-interaction.mdx
@@ -2,6 +2,7 @@
title: Music Stack Interaction
description: widget for Music stack and unstacking
author: Mahlawat2001
+published: false
---
diff --git a/content/docs/widget/reminder.mdx b/content/docs/widget/reminder.mdx
index 90b9127c..af68d39b 100644
--- a/content/docs/widget/reminder.mdx
+++ b/content/docs/widget/reminder.mdx
@@ -1,10 +1,14 @@
---
title: Reminder
-description: A reminder widget shows notifications for important tasks
+description: Glance at open tasks by list and see what is coming up next.
author: ll4xmii
---
-
+
+
+## How it works
+
+The card is a fixed `size-52` tile. Pass `buckets` for the two summary counts (work, home, or any labels you define) and `nextLabel` for the single line people read first — usually the next meeting or errand. Styling stays in your story or app; the primitive only handles layout.
## Installation
@@ -15,6 +19,7 @@ author: ll4xmii
### Manual
+
Run the following command
It will create a new file called `reminder.tsx` inside the `components/animata/widget` directory.
diff --git a/content/docs/widget/team-clock.mdx b/content/docs/widget/team-clock.mdx
index 44f0cdb2..0bd78371 100644
--- a/content/docs/widget/team-clock.mdx
+++ b/content/docs/widget/team-clock.mdx
@@ -2,6 +2,7 @@
title: Team Clock
description: A customizable, animated clock displaying multiple time zones for remote teams.
author: arjuncodess
+published: false
---
diff --git a/content/docs/widget/video-chat.mdx b/content/docs/widget/video-chat.mdx
index 3409013f..a640c438 100644
--- a/content/docs/widget/video-chat.mdx
+++ b/content/docs/widget/video-chat.mdx
@@ -3,6 +3,7 @@ title: Video Chat
description: Video chat in real-time
labels: ["requires interaction", "click"]
author: AashishKatila
+published: false
---
diff --git a/content/docs/widget/vpn-widget.mdx b/content/docs/widget/vpn-widget.mdx
index ef0a6f27..b376c409 100644
--- a/content/docs/widget/vpn-widget.mdx
+++ b/content/docs/widget/vpn-widget.mdx
@@ -1,10 +1,14 @@
---
title: VPN Connection
-description: Shows the Vpn Connectivity
+description: See server, speed, and connection status at a glance — with a slide-to-connect control.
labels: ["requires interaction", "click"]
---
-
+
+
+## How it works
+
+`serverName`, `latency`, and `downloadMbps` are display-only. Connection state is uncontrolled (`defaultConnected`) or controlled (`connected` + `onConnectedChange`). The toggle is a real `role="switch"` button with a 44px hit area, not a hover-only chip.
## Installation
@@ -15,10 +19,13 @@ labels: ["requires interaction", "click"]
### Manual
+
Install dependencies
+
```bash
npm install lucide-react
```
+
Run the following command
It will create a new file called `vpn-widget.tsx` inside the `components/animata/widget` directory.
diff --git a/content/docs/widget/water-tracker.mdx b/content/docs/widget/water-tracker.mdx
index 05f59f17..55d72c6d 100644
--- a/content/docs/widget/water-tracker.mdx
+++ b/content/docs/widget/water-tracker.mdx
@@ -1,11 +1,15 @@
---
title: Water Tracker
-description: Water tracker
+description: Track daily water intake with a glanceable fill bar and one-tap top-ups.
labels: ["requires interaction", "click"]
author: Sumingurung9
---
-
+
+
+## How it works
+
+`dailyGoal` and `sipMl` set the target and increment size. Use `defaultIntake` for an uncontrolled demo, or `intake` with `onIntakeChange` when your app owns hydration state. The right edge is a CSS height transition — no chart library — so the tile stays lightweight for bento grids.
## Installation
@@ -16,6 +20,7 @@ author: Sumingurung9
### Manual
+
Install dependencies
```bash
diff --git a/content/docs/widget/weather-card.mdx b/content/docs/widget/weather-card.mdx
index 1dcf1bff..d1a99837 100644
--- a/content/docs/widget/weather-card.mdx
+++ b/content/docs/widget/weather-card.mdx
@@ -1,10 +1,14 @@
---
title: Weather Card
-description: A cool weather widget
+description: Current temperature, feels-like, and today’s high/low for any city.
author: AdashBaral
---
-
+
+
+## How it works
+
+All content is props — `city`, `temperature`, `feelsLike`, `high`, `low` — wired to your weather API in the parent. Typography uses three levels: city label, hero degree, and footer hi/lo. The card uses theme tokens so light and dark modes stay legible without custom hex per theme.
## Installation
@@ -17,7 +21,10 @@ author: AdashBaral
Install dependencies
-```bash npm install lucide-react ```
+
+```bash
+npm install lucide-react
+```
Run the following command
diff --git a/hooks/use-mobile.tsx b/hooks/use-mobile.tsx
new file mode 100644
index 00000000..502fd323
--- /dev/null
+++ b/hooks/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from "react";
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined);
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener("change", onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
+ return !!isMobile;
+}
diff --git a/lib/inline-code.ts b/lib/inline-code.ts
index 1421bc23..01b775bd 100644
--- a/lib/inline-code.ts
+++ b/lib/inline-code.ts
@@ -17,8 +17,8 @@ const inlineCodeDark = {
boxShadow: "inset 0 1px 0 0 hsl(0 0% 100% / 0.05), 0 1px 2px hsl(0 0% 0% / 0.34)",
} as const;
-/** Keep tokens on one line — asymmetric pad + trailing margin for prose gaps. */
-const inlineCodeWhitespaceClasses = "whitespace-nowrap ms-[0.06em] me-[0.32em]";
+/** Keep inline code on one line. */
+const inlineCodeWhitespaceClasses = "whitespace-nowrap";
/** Rich inline chip — gradient fill, hairline border, inset highlight. */
export const inlineCodeClassName = cn(
diff --git a/package.json b/package.json
index a6ae9b3a..7f2ae44b 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"lint": "biome check .",
"lint:fix": "biome check --fix .",
"validate:demos": "node ./scripts/validate-demo-registry.js",
+ "validate:registry": "node ./scripts/validate-registry-install.js",
+ "validate:registry:build": "node ./scripts/validate-registry-install.js --build",
"format": "biome format --write .",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build -o public/preview",
@@ -53,7 +55,7 @@
"lucide-react": "^0.475.0",
"mdast-util-toc": "^7.1.0",
"motion": "^12.38.0",
- "next": "^16.1.5",
+ "next": "^16.2.6",
"next-mdx-remote": "^6.0.0",
"next-themes": "^0.4.6",
"nuqs": "^2.8.9",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 31a1ea7e..5da95fdd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -111,8 +111,8 @@ importers:
specifier: ^12.38.0
version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
next:
- specifier: ^16.1.5
- version: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ specifier: ^16.2.6
+ version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
next-mdx-remote:
specifier: ^6.0.0
version: 6.0.0(@types/react@19.2.14)(react@19.2.5)
@@ -121,7 +121,7 @@ importers:
version: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
nuqs:
specifier: ^2.8.9
- version: 2.8.9(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)
+ version: 2.8.9(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)
ora:
specifier: ^8.0.1
version: 8.2.0
@@ -209,7 +209,7 @@ importers:
version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))
'@storybook/nextjs':
specifier: ^10.2.15
- version: 10.3.5(esbuild@0.17.19)(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))
+ version: 10.3.5(esbuild@0.17.19)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))
'@storybook/react':
specifier: ^10.2.15
version: 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)
@@ -281,6 +281,10 @@ packages:
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.29.0':
resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
engines: {node: '>=6.9.0'}
@@ -368,6 +372,10 @@ packages:
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
@@ -830,6 +838,10 @@ packages:
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.28.6':
resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'}
@@ -1804,57 +1816,57 @@ packages:
'@types/react': '>=16'
react: '>=16'
- '@next/env@16.2.4':
- resolution: {integrity: sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==}
+ '@next/env@16.2.6':
+ resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==}
- '@next/swc-darwin-arm64@16.2.4':
- resolution: {integrity: sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==}
+ '@next/swc-darwin-arm64@16.2.6':
+ resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@next/swc-darwin-x64@16.2.4':
- resolution: {integrity: sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==}
+ '@next/swc-darwin-x64@16.2.6':
+ resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@next/swc-linux-arm64-gnu@16.2.4':
- resolution: {integrity: sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==}
+ '@next/swc-linux-arm64-gnu@16.2.6':
+ resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@next/swc-linux-arm64-musl@16.2.4':
- resolution: {integrity: sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==}
+ '@next/swc-linux-arm64-musl@16.2.6':
+ resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@next/swc-linux-x64-gnu@16.2.4':
- resolution: {integrity: sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==}
+ '@next/swc-linux-x64-gnu@16.2.6':
+ resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@next/swc-linux-x64-musl@16.2.4':
- resolution: {integrity: sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==}
+ '@next/swc-linux-x64-musl@16.2.6':
+ resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
- '@next/swc-win32-arm64-msvc@16.2.4':
- resolution: {integrity: sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==}
+ '@next/swc-win32-arm64-msvc@16.2.6':
+ resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@next/swc-win32-x64-msvc@16.2.4':
- resolution: {integrity: sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==}
+ '@next/swc-win32-x64-msvc@16.2.6':
+ resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -2693,7 +2705,7 @@ packages:
'@tsparticles/basic@3.9.1':
resolution: {integrity: sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ==}
- deprecated: tsParticles v4 is out, please upgrade for better performances and more features
+ deprecated: Please update to version 4.1.1
'@tsparticles/engine@3.9.1':
resolution: {integrity: sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ==}
@@ -3232,8 +3244,8 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
- baseline-browser-mapping@2.10.21:
- resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==}
+ baseline-browser-mapping@2.10.33:
+ resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -3322,8 +3334,8 @@ packages:
camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
- caniuse-lite@1.0.30001790:
- resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==}
+ caniuse-lite@1.0.30001793:
+ resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
case-sensitive-paths-webpack-plugin@2.4.0:
resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==}
@@ -4862,8 +4874,8 @@ packages:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- next@16.2.4:
- resolution: {integrity: sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==}
+ next@16.2.6:
+ resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
engines: {node: '>=20.9.0'}
hasBin: true
peerDependencies:
@@ -5516,6 +5528,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ semver@7.8.1:
+ resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -6189,6 +6206,12 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
'@babel/compat-data@7.29.0': {}
'@babel/core@7.29.0':
@@ -6322,6 +6345,8 @@ snapshots:
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-identifier@7.29.7': {}
+
'@babel/helper-validator-option@7.27.1': {}
'@babel/helper-wrap-function@7.28.6':
@@ -6909,6 +6934,8 @@ snapshots:
'@babel/runtime@7.29.2': {}
+ '@babel/runtime@7.29.7': {}
+
'@babel/template@7.28.6':
dependencies:
'@babel/code-frame': 7.29.0
@@ -7608,30 +7635,30 @@ snapshots:
'@types/react': 19.2.14
react: 19.2.5
- '@next/env@16.2.4': {}
+ '@next/env@16.2.6': {}
- '@next/swc-darwin-arm64@16.2.4':
+ '@next/swc-darwin-arm64@16.2.6':
optional: true
- '@next/swc-darwin-x64@16.2.4':
+ '@next/swc-darwin-x64@16.2.6':
optional: true
- '@next/swc-linux-arm64-gnu@16.2.4':
+ '@next/swc-linux-arm64-gnu@16.2.6':
optional: true
- '@next/swc-linux-arm64-musl@16.2.4':
+ '@next/swc-linux-arm64-musl@16.2.6':
optional: true
- '@next/swc-linux-x64-gnu@16.2.4':
+ '@next/swc-linux-x64-gnu@16.2.6':
optional: true
- '@next/swc-linux-x64-musl@16.2.4':
+ '@next/swc-linux-x64-musl@16.2.6':
optional: true
- '@next/swc-win32-arm64-msvc@16.2.4':
+ '@next/swc-win32-arm64-msvc@16.2.6':
optional: true
- '@next/swc-win32-x64-msvc@16.2.4':
+ '@next/swc-win32-x64-msvc@16.2.6':
optional: true
'@opentelemetry/api-logs@0.208.0':
@@ -8311,7 +8338,7 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- '@storybook/nextjs@10.3.5(esbuild@0.17.19)(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))':
+ '@storybook/nextjs@10.3.5(esbuild@0.17.19)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
@@ -8335,7 +8362,7 @@ snapshots:
css-loader: 6.11.0(webpack@5.106.2(esbuild@0.17.19))
image-size: 2.0.2
loader-utils: 3.3.1
- next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
node-polyfill-webpack-plugin: 2.0.1(webpack@5.106.2(esbuild@0.17.19))
postcss: 8.5.15
postcss-loader: 8.2.1(postcss@8.5.15)(typescript@5.9.3)(webpack@5.106.2(esbuild@0.17.19))
@@ -8508,8 +8535,8 @@ snapshots:
'@testing-library/dom@10.4.1':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/runtime': 7.29.2
+ '@babel/code-frame': 7.29.7
+ '@babel/runtime': 7.29.7
'@types/aria-query': 5.0.4
aria-query: 5.3.0
dom-accessibility-api: 0.5.16
@@ -9196,7 +9223,7 @@ snapshots:
base64-js@1.5.1: {}
- baseline-browser-mapping@2.10.21: {}
+ baseline-browser-mapping@2.10.33: {}
big.js@5.2.2: {}
@@ -9265,8 +9292,8 @@ snapshots:
browserslist@4.28.2:
dependencies:
- baseline-browser-mapping: 2.10.21
- caniuse-lite: 1.0.30001790
+ baseline-browser-mapping: 2.10.33
+ caniuse-lite: 1.0.30001793
electron-to-chromium: 1.5.344
node-releases: 2.0.38
update-browserslist-db: 1.2.3(browserslist@4.28.2)
@@ -9310,7 +9337,7 @@ snapshots:
pascal-case: 3.1.2
tslib: 2.8.1
- caniuse-lite@1.0.30001790: {}
+ caniuse-lite@1.0.30001793: {}
case-sensitive-paths-webpack-plugin@2.4.0: {}
@@ -11201,25 +11228,25 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+ next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
dependencies:
- '@next/env': 16.2.4
+ '@next/env': 16.2.6
'@swc/helpers': 0.5.15
- baseline-browser-mapping: 2.10.21
- caniuse-lite: 1.0.30001790
+ baseline-browser-mapping: 2.10.33
+ caniuse-lite: 1.0.30001793
postcss: 8.4.31
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5)
optionalDependencies:
- '@next/swc-darwin-arm64': 16.2.4
- '@next/swc-darwin-x64': 16.2.4
- '@next/swc-linux-arm64-gnu': 16.2.4
- '@next/swc-linux-arm64-musl': 16.2.4
- '@next/swc-linux-x64-gnu': 16.2.4
- '@next/swc-linux-x64-musl': 16.2.4
- '@next/swc-win32-arm64-msvc': 16.2.4
- '@next/swc-win32-x64-msvc': 16.2.4
+ '@next/swc-darwin-arm64': 16.2.6
+ '@next/swc-darwin-x64': 16.2.6
+ '@next/swc-linux-arm64-gnu': 16.2.6
+ '@next/swc-linux-arm64-musl': 16.2.6
+ '@next/swc-linux-x64-gnu': 16.2.6
+ '@next/swc-linux-x64-musl': 16.2.6
+ '@next/swc-win32-arm64-msvc': 16.2.6
+ '@next/swc-win32-x64-msvc': 16.2.6
'@opentelemetry/api': 1.9.1
sharp: 0.34.5
transitivePeerDependencies:
@@ -11272,12 +11299,12 @@ snapshots:
dependencies:
boolbase: 1.0.0
- nuqs@2.8.9(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5):
+ nuqs@2.8.9(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5):
dependencies:
'@standard-schema/spec': 1.0.0
react: 19.2.5
optionalDependencies:
- next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
object-inspect@1.13.4: {}
@@ -11985,6 +12012,8 @@ snapshots:
semver@7.7.4: {}
+ semver@7.8.1: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -12033,7 +12062,7 @@ snapshots:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
- semver: 7.7.4
+ semver: 7.8.1
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
diff --git a/scripts/build-docs-markdown.js b/scripts/build-docs-markdown.js
index f91ec5ba..4db61901 100644
--- a/scripts/build-docs-markdown.js
+++ b/scripts/build-docs-markdown.js
@@ -59,8 +59,55 @@ function resolveSourceBlocks(mdx) {
});
}
+function readJsxAttr(attrs, name) {
+ const quoted = attrs.match(new RegExp(`\\b${name}=(?:"([^"]*)"|'([^']*)')`));
+ if (quoted) return quoted[1] ?? quoted[2] ?? "";
+ if (new RegExp(`\\b${name}\\b`).test(attrs) && !attrs.includes(`${name}=`)) return true;
+ return undefined;
+}
+
+function escapeTableCell(value) {
+ return String(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n+/g, " ").trim();
+}
+
+function serializePropsTableBlock(_match, title, body) {
+ const rows = [];
+ const rowRe = /]+)>\s*([\s\S]*?)\s*<\/PropsTable\.Row>/g;
+ for (const rowMatch of body.matchAll(rowRe)) {
+ const attrs = rowMatch[1];
+ const description = rowMatch[2].trim();
+ const prop = readJsxAttr(attrs, "prop");
+ const type = readJsxAttr(attrs, "type");
+ const required = readJsxAttr(attrs, "required") === true;
+ const defaultValue = readJsxAttr(attrs, "default");
+
+ let defaultCell = "—";
+ if (required) defaultCell = "Required";
+ else if (defaultValue !== undefined && defaultValue !== "")
+ defaultCell = `\`${escapeTableCell(defaultValue)}\``;
+
+ rows.push(
+ `| \`${escapeTableCell(prop)}\` | \`${escapeTableCell(type)}\` | ${defaultCell} | ${escapeTableCell(description)} |`,
+ );
+ }
+
+ if (rows.length === 0) return "";
+
+ const header = title ? `### ${title}\n\n` : "";
+ return `${header}| Prop | Type | Default | Description |
+|---|---|---|---|
+${rows.join("\n")}`;
+}
+
+function serializePropsTables(mdx) {
+ return mdx.replace(
+ /([\s\S]*?)<\/PropsTable>/g,
+ serializePropsTableBlock,
+ );
+}
+
function stripJsx(mdx, { siteUrl }) {
- return mdx
+ const stripped = mdx
.replace(/]*\/>/g, "")
.replace(
//g,
@@ -69,7 +116,9 @@ function stripJsx(mdx, { siteUrl }) {
.replace(/([\s\S]*?)<\/Step>/g, "**$1**")
.replace(/\s*/g, "")
.replace(/\s*<\/Steps>/g, "")
- .replace(/\{" "\}/g, " ")
+ .replace(/\{" "\}/g, " ");
+
+ return serializePropsTables(stripped)
.replace(/\n{3,}/g, "\n\n")
.trim();
}
diff --git a/scripts/validate-registry-install.js b/scripts/validate-registry-install.js
new file mode 100644
index 00000000..115a9aae
--- /dev/null
+++ b/scripts/validate-registry-install.js
@@ -0,0 +1,355 @@
+import { execSync } from "node:child_process";
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
+const REGISTRY_DIR = path.join(ROOT, "public", "r");
+const SITE_URL = (process.env.NEXT_PUBLIC_APP_URL || "https://animata.design").replace(/\/$/, "");
+
+/** Representative installs — one dry-run per bundle shape. */
+const INSTALL_FIXTURES = [
+ {
+ id: "simple",
+ label: "single file, no npm deps",
+ item: "preloader/split-reveal.json",
+ expectFiles: ["components/animata/preloader/split-reveal.tsx"],
+ expectDeps: [],
+ },
+ {
+ id: "doc-bundled-shapes",
+ label: "mdx bundled file refs + shape imports",
+ item: "card/card-stack.json",
+ expectFiles: [
+ "components/animata/card/card-stack.tsx",
+ "components/shapes/card-stack-mask-defs.tsx",
+ ],
+ expectDeps: ["lucide-react", "motion"],
+ },
+ {
+ id: "co-located-shared",
+ label: "co-located helper rewritten to ./shared",
+ item: "tabs/fluid-tabs.json",
+ expectFiles: ["components/animata/tabs/fluid-tabs.tsx", "components/animata/tabs/shared.ts"],
+ expectDeps: ["lucide-react", "motion"],
+ },
+ {
+ id: "bundled-hook",
+ label: "bundled hook from @/hooks import",
+ item: "image/trailing-image.json",
+ expectFiles: ["components/animata/image/trailing-image.tsx", "hooks/use-mouse-position.ts"],
+ expectDeps: ["motion"],
+ },
+ {
+ id: "registry-dependencies",
+ label: "registryDependencies resolve to sibling items",
+ item: "card/card-spread.json",
+ localizeDeps: true,
+ expectFiles: [
+ "components/animata/card/card-spread.tsx",
+ "components/animata/widget/notes.tsx",
+ "components/animata/widget/shopping-list.tsx",
+ ],
+ expectDeps: [],
+ },
+];
+
+let failed = false;
+
+function error(message) {
+ console.error(`[registry-install] ${message}`);
+ failed = true;
+}
+
+function info(message) {
+ console.log(`[registry-install] ${message}`);
+}
+
+function walkRegistryJson(dir, acc = []) {
+ if (!fs.existsSync(dir)) return acc;
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ walkRegistryJson(full, acc);
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
+ acc.push(full);
+ }
+ }
+ return acc;
+}
+
+function registryUrlToLocalPath(url) {
+ const prefix = `${SITE_URL}/r/`;
+ if (!url.startsWith(prefix)) return null;
+ return path.join(REGISTRY_DIR, url.slice(prefix.length));
+}
+
+function localizeRegistryItem(itemPath) {
+ const raw = JSON.parse(fs.readFileSync(itemPath, "utf8"));
+ if (!raw.registryDependencies?.length) return itemPath;
+
+ const localized = structuredClone(raw);
+ localized.registryDependencies = raw.registryDependencies.map((dep) => {
+ const local = registryUrlToLocalPath(dep);
+ if (!local) return dep;
+ if (!fs.existsSync(local)) {
+ error(
+ `missing local registry dependency for ${path.relative(REGISTRY_DIR, itemPath)}: ${dep}`,
+ );
+ return dep;
+ }
+ return local;
+ });
+
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "animata-registry-"));
+ const outPath = path.join(tmpDir, path.basename(itemPath));
+ fs.writeFileSync(outPath, `${JSON.stringify(localized, null, 2)}\n`);
+ return outPath;
+}
+
+function validateRegistryJsonStatic(jsonPath) {
+ const rel = path.relative(REGISTRY_DIR, jsonPath);
+ let item;
+
+ try {
+ item = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
+ } catch {
+ error(`${rel}: invalid JSON`);
+ return;
+ }
+
+ if (!item.name || !item.type) {
+ error(`${rel}: missing name or type`);
+ }
+
+ if (!Array.isArray(item.files) || item.files.length === 0) {
+ error(`${rel}: files array is empty`);
+ return;
+ }
+
+ const paths = new Set();
+ for (const file of item.files) {
+ if (
+ !file.path ||
+ !file.type ||
+ typeof file.content !== "string" ||
+ file.content.trim() === ""
+ ) {
+ error(`${rel}: file entry missing path, type, or content`);
+ continue;
+ }
+ if (paths.has(file.path)) {
+ error(`${rel}: duplicate file path ${file.path}`);
+ }
+ paths.add(file.path);
+ }
+
+ for (const dep of item.registryDependencies ?? []) {
+ const local = registryUrlToLocalPath(dep);
+ if (local && !fs.existsSync(local)) {
+ error(`${rel}: registryDependency not found locally — ${dep}`);
+ }
+ }
+}
+
+function createShadcnWorkspace() {
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "animata-shadcn-"));
+
+ fs.writeFileSync(
+ path.join(dir, "package.json"),
+ `${JSON.stringify({ name: "animata-registry-install-test", private: true, type: "module", version: "0.0.0" }, null, 2)}\n`,
+ );
+
+ fs.writeFileSync(
+ path.join(dir, "tsconfig.json"),
+ `${JSON.stringify(
+ {
+ compilerOptions: {
+ target: "ES2017",
+ lib: ["dom", "dom.iterable", "esnext"],
+ allowJs: true,
+ skipLibCheck: true,
+ strict: true,
+ noEmit: true,
+ esModuleInterop: true,
+ module: "esnext",
+ moduleResolution: "bundler",
+ jsx: "preserve",
+ paths: { "@/*": ["./*"] },
+ },
+ include: ["**/*.ts", "**/*.tsx"],
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+
+ fs.mkdirSync(path.join(dir, "app"), { recursive: true });
+ fs.mkdirSync(path.join(dir, "lib"), { recursive: true });
+ fs.writeFileSync(path.join(dir, "app", "globals.css"), '@import "tailwindcss";\n');
+ fs.writeFileSync(
+ path.join(dir, "lib", "utils.ts"),
+ 'export function cn(...inputs: Array) {\n return inputs.filter(Boolean).join(" ");\n}\n',
+ );
+
+ fs.writeFileSync(
+ path.join(dir, "components.json"),
+ `${JSON.stringify(
+ {
+ $schema: "https://ui.shadcn.com/schema.json",
+ style: "new-york",
+ rsc: true,
+ tsx: true,
+ tailwind: {
+ css: "app/globals.css",
+ baseColor: "stone",
+ cssVariables: true,
+ prefix: "",
+ },
+ aliases: {
+ components: "@/components",
+ utils: "@/lib/utils",
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+
+ return dir;
+}
+
+function parseDryRunOutput(output) {
+ const files = [];
+ const deps = [];
+ let inDeps = false;
+
+ for (const line of output.split("\n")) {
+ const fileMatch = line.match(/^│ \+ (.+?) {2}create$/);
+ if (fileMatch) {
+ files.push(fileMatch[1].trim());
+ continue;
+ }
+
+ if (line.includes("├ Dependencies")) {
+ inDeps = true;
+ continue;
+ }
+
+ if (inDeps) {
+ const depMatch = line.match(/^│ \+ (.+)$/);
+ if (depMatch) {
+ deps.push(depMatch[1].trim().split("@")[0]);
+ continue;
+ }
+ if (line.includes("├ Files") || line.includes("│ ") || line.startsWith("└")) {
+ inDeps = false;
+ }
+ }
+ }
+
+ return { files, deps };
+}
+
+function runShadcnDryRun(workspaceDir, registryItemPath) {
+ const command = `npx shadcn@latest add -y --dry-run ${JSON.stringify(registryItemPath)}`;
+
+ try {
+ const output = execSync(command, {
+ cwd: workspaceDir,
+ encoding: "utf8",
+ stdio: ["ignore", "pipe", "pipe"],
+ timeout: 120_000,
+ env: { ...process.env, CI: "true" },
+ });
+ return { ok: true, output };
+ } catch (err) {
+ const stdout = err.stdout?.toString?.() ?? "";
+ const stderr = err.stderr?.toString?.() ?? "";
+ return { ok: false, output: `${stdout}\n${stderr}`.trim() };
+ }
+}
+
+function assertFixtureInstall(fixture, workspaceDir) {
+ const itemPath = path.join(REGISTRY_DIR, fixture.item);
+ if (!fs.existsSync(itemPath)) {
+ error(`fixture ${fixture.id}: missing ${fixture.item}`);
+ return;
+ }
+
+ const installPath = fixture.localizeDeps ? localizeRegistryItem(itemPath) : itemPath;
+ const result = runShadcnDryRun(workspaceDir, installPath);
+
+ if (!result.ok) {
+ error(`fixture ${fixture.id} (${fixture.label}): shadcn dry-run failed\n${result.output}`);
+ return;
+ }
+
+ const { files, deps } = parseDryRunOutput(result.output);
+
+ for (const expected of fixture.expectFiles) {
+ if (!files.includes(expected)) {
+ error(
+ `fixture ${fixture.id}: expected file "${expected}" in dry-run output. Got: ${files.join(", ") || "(none)"}`,
+ );
+ }
+ }
+
+ for (const expectedDep of fixture.expectDeps) {
+ if (!deps.includes(expectedDep)) {
+ error(
+ `fixture ${fixture.id}: expected npm dep "${expectedDep}". Got: ${deps.join(", ") || "(none)"}`,
+ );
+ }
+ }
+
+ info(`fixture ${fixture.id} OK — ${files.length} file(s), ${deps.length} npm dep(s)`);
+}
+
+function main() {
+ if (process.argv.includes("--build")) {
+ execSync("node ./scripts/build-registry.js", { cwd: ROOT, stdio: "inherit" });
+ }
+
+ if (!fs.existsSync(REGISTRY_DIR)) {
+ error(`registry output missing at ${REGISTRY_DIR} — run node scripts/build-registry.js first`);
+ process.exit(1);
+ }
+
+ const registryFiles = walkRegistryJson(REGISTRY_DIR);
+ if (registryFiles.length === 0) {
+ error("no registry JSON files found");
+ process.exit(1);
+ }
+
+ info(`static validation — ${registryFiles.length} item(s)`);
+ for (const jsonPath of registryFiles) {
+ validateRegistryJsonStatic(jsonPath);
+ }
+
+ if (failed) {
+ process.exit(1);
+ }
+
+ info(`shadcn dry-run — ${INSTALL_FIXTURES.length} fixture(s)`);
+ const workspaceDir = createShadcnWorkspace();
+
+ try {
+ for (const fixture of INSTALL_FIXTURES) {
+ assertFixtureInstall(fixture, workspaceDir);
+ }
+ } finally {
+ fs.rmSync(workspaceDir, { recursive: true, force: true });
+ }
+
+ if (failed) {
+ process.exit(1);
+ }
+
+ info(
+ `OK — ${registryFiles.length} registry item(s), ${INSTALL_FIXTURES.length} install fixture(s)`,
+ );
+}
+
+main();