Skip to content

Commit 25f6094

Browse files
committed
feat: auto-sync registry
1 parent 20bc86e commit 25f6094

44 files changed

Lines changed: 2649 additions & 598 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ARCHITECTURE.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,20 @@ Icons are distributed via shadcn CLI:
142142
npx shadcn@latest add https://itshover.com/r/[icon-name].json
143143
```
144144

145-
The `registry.json` file is auto-generated by `npm run registry:build`.
145+
### Registry Generation
146+
147+
The registry is auto-generated in two steps:
148+
149+
1. **`scripts/generate-registry.ts`** - Reads `icons/index.ts` and generates `registry.json`
150+
2. **`shadcn build`** - Reads `registry.json` and generates individual JSON files in `public/r/`
151+
152+
Run both with:
153+
154+
```bash
155+
npm run registry:sync
156+
```
157+
158+
This ensures the registry stays in sync with `icons/index.ts` and prevents missing icons.
146159

147160
## Code of Conduct
148161

CONTRIBUTING.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,19 @@ import YourIcon from "./your-icon";
241241

242242
**c) `registry.json`:**
243243

244-
Auto-generated when you run `npm run registry:build` in the next step.
244+
Auto-generated when you run `npm run registry:sync` in the next step.
245245

246246
### Step 3: Build Registry
247247

248248
```bash
249-
npm run registry:build
249+
npm run registry:sync
250250
```
251251

252+
This command does two things:
253+
254+
1. **Generates `registry.json`** from `icons/index.ts` (ensures all icons are included)
255+
2. **Builds individual JSON files** in `public/r/` for the shadcn CLI
256+
252257
Wait for it to succeed before proceeding.
253258

254259
### Step 4: Run All Checks
@@ -350,8 +355,8 @@ To verify icons work when installed via the shadcn CLI:
350355
# Run all checks before submitting
351356
npm run check
352357

353-
# Verify registry builds successfully
354-
npm run registry:build
358+
# Verify registry syncs successfully (generates + builds)
359+
npm run registry:sync
355360
```
356361

357362
## Making Changes
@@ -426,7 +431,7 @@ docs: update contributing guidelines
426431

427432
- [ ] Code follows project style guidelines
428433
- [ ] `npm run check` passes
429-
- [ ] `npm run registry:build` passes (for new icons)
434+
- [ ] `npm run registry:sync` passes (for new icons)
430435
- [ ] Icons work on all screen sizes
431436
- [ ] Self-review completed
432437
```

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"dev": "next dev",
1010
"build": "next build",
1111
"start": "next start",
12+
"registry:generate": "npm run scripts/generate-registry.ts",
1213
"registry:build": "shadcn build",
14+
"registry:sync": "npm run scripts/generate-registry.ts && shadcn build",
1315
"lint": "eslint",
1416
"lint:fix": "eslint --fix",
1517
"format": "prettier --write .",

public/r/accessibility-icon.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "accessibility-icon",
4+
"dependencies": ["motion"],
5+
"devDependencies": [],
6+
"registryDependencies": [],
7+
"files": [
8+
{
9+
"path": "icons/accessibility-icon.tsx",
10+
"content": "import { forwardRef, useImperativeHandle } from \"react\";\nimport { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst AccessibilityIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n (\n { size = 24, color = \"currentColor\", strokeWidth = 2, className = \"\" },\n ref,\n ) => {\n const [scope, animate] = useAnimate();\n\n const start = async () => {\n animate(\n \".wheel\",\n { rotate: [0, 360] },\n { duration: 1, ease: \"easeInOut\", repeat: Infinity },\n );\n animate(\n \".person\",\n { y: [0, -2, 0] },\n { duration: 0.6, ease: \"easeInOut\" },\n );\n };\n\n const stop = () => {\n animate(\".wheel\", { rotate: 0 }, { duration: 0.3 });\n animate(\".person\", { y: 0 }, { duration: 0.2 });\n };\n\n useImperativeHandle(ref, () => {\n return {\n startAnimation: start,\n stopAnimation: stop,\n };\n });\n\n const handleHoverStart = () => {\n start();\n };\n\n const handleHoverEnd = () => {\n stop();\n };\n\n return (\n <motion.svg\n ref={scope}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`cursor - pointer ${className} `}\n >\n <motion.circle className=\"person\" cx=\"16\" cy=\"4\" r=\"1\" />\n <motion.path className=\"person\" d=\"m18 19 1-7-6 1\" />\n <motion.path className=\"person\" d=\"m5 8 3-3 5.5 3-2.36 3.5\" />\n <motion.g className=\"wheel\" style={{ transformOrigin: \"8.5px 17.5px\" }}>\n <path d=\"M4.24 14.5a5 5 0 0 0 6.88 6\" />\n <path d=\"M13.76 17.5a5 5 0 0 0-6.88-6\" />\n </motion.g>\n </motion.svg>\n );\n },\n);\n\nAccessibilityIcon.displayName = \"AccessibilityIcon\";\n\nexport default AccessibilityIcon;\n",
11+
"type": "registry:ui"
12+
},
13+
{
14+
"path": "icons/types.ts",
15+
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
16+
"type": "registry:ui"
17+
}
18+
],
19+
"type": "registry:ui"
20+
}

public/r/ampersand-icon.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "ampersand-icon",
4+
"dependencies": ["motion"],
5+
"devDependencies": [],
6+
"registryDependencies": [],
7+
"files": [
8+
{
9+
"path": "icons/ampersand-icon.tsx",
10+
"content": "import { forwardRef, useImperativeHandle } from \"react\";\nimport { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst AmpersandIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 40, className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = async () => {\n animate(\"path\", { pathLength: [0, 1] }, { duration: 0.6 });\n };\n\n const stop = async () => {\n animate(\"path\", { pathLength: 1 }, { duration: 0.3 });\n };\n\n useImperativeHandle(ref, () => {\n return {\n startAnimation: start,\n stopAnimation: stop,\n };\n });\n\n const handleHoverStart = () => {\n start();\n };\n\n const handleHoverEnd = () => {\n stop();\n };\n\n return (\n <motion.svg\n ref={scope}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`cursor-pointer ${className}`}\n >\n <path d=\"M16 12h3\" />\n <path d=\"M17.5 12a8 8 0 0 1-8 8A4.5 4.5 0 0 1 5 15.5c0-6 8-4 8-8.5a3 3 0 1 0-6 0c0 3 2.5 8.5 12 13\" />\n </motion.svg>\n );\n },\n);\n\nAmpersandIcon.displayName = \"AmpersandIcon\";\n\nexport default AmpersandIcon;\n",
11+
"type": "registry:ui"
12+
},
13+
{
14+
"path": "icons/types.ts",
15+
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
16+
"type": "registry:ui"
17+
}
18+
],
19+
"type": "registry:ui"
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "arrow-big-down-dash-icon",
4+
"dependencies": ["motion"],
5+
"devDependencies": [],
6+
"registryDependencies": [],
7+
"files": [
8+
{
9+
"path": "icons/arrow-big-down-dash-icon.tsx",
10+
"content": "import { forwardRef, useImperativeHandle } from \"react\";\nimport { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst ArrowBigDownDashIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 40, className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = async () => {\n animate(\".arrow\", { y: [0, 4, 0] }, { duration: 0.5 });\n animate(\".dash\", { opacity: [1, 0.5, 1] }, { duration: 0.5 });\n };\n\n const stop = async () => {\n animate(\".arrow\", { y: 0 }, { duration: 0.3 });\n animate(\".dash\", { opacity: 1 }, { duration: 0.3 });\n };\n\n useImperativeHandle(ref, () => {\n return {\n startAnimation: start,\n stopAnimation: stop,\n };\n });\n\n const handleHoverStart = () => {\n start();\n };\n\n const handleHoverEnd = () => {\n stop();\n };\n\n return (\n <motion.svg\n ref={scope}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`cursor-pointer ${className}`}\n >\n <motion.path\n className=\"arrow\"\n d=\"M15 11a1 1 0 0 0 1 1h2.939a1 1 0 0 1 .75 1.811l-6.835 6.836a1.207 1.207 0 0 1-1.707 0L4.31 13.81a1 1 0 0 1 .75-1.811H8a1 1 0 0 0 1-1V9a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1z\"\n />\n <motion.path className=\"dash\" d=\"M9 4h6\" />\n </motion.svg>\n );\n },\n);\n\nArrowBigDownDashIcon.displayName = \"ArrowBigDownDashIcon\";\n\nexport default ArrowBigDownDashIcon;\n",
11+
"type": "registry:ui"
12+
},
13+
{
14+
"path": "icons/types.ts",
15+
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
16+
"type": "registry:ui"
17+
}
18+
],
19+
"type": "registry:ui"
20+
}

public/r/arrow-big-down-icon.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "arrow-big-down-icon",
4+
"dependencies": ["motion"],
5+
"devDependencies": [],
6+
"registryDependencies": [],
7+
"files": [
8+
{
9+
"path": "icons/arrow-big-down-icon.tsx",
10+
"content": "import { forwardRef, useImperativeHandle } from \"react\";\nimport { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst ArrowBigDownIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 40, className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = async () => {\n animate(scope.current, { y: [0, 4, 0] }, { duration: 0.5 });\n };\n\n const stop = async () => {\n animate(scope.current, { y: 0 }, { duration: 0.3 });\n };\n\n useImperativeHandle(ref, () => {\n return {\n startAnimation: start,\n stopAnimation: stop,\n };\n });\n\n const handleHoverStart = () => {\n start();\n };\n\n const handleHoverEnd = () => {\n stop();\n };\n\n return (\n <motion.svg\n ref={scope}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`cursor-pointer ${className}`}\n >\n <path d=\"M15 11a1 1 0 0 0 1 1h2.939a1 1 0 0 1 .75 1.811l-6.835 6.836a1.207 1.207 0 0 1-1.707 0L4.31 13.81a1 1 0 0 1 .75-1.811H8a1 1 0 0 0 1-1V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1z\" />\n </motion.svg>\n );\n },\n);\n\nArrowBigDownIcon.displayName = \"ArrowBigDownIcon\";\n\nexport default ArrowBigDownIcon;\n",
11+
"type": "registry:ui"
12+
},
13+
{
14+
"path": "icons/types.ts",
15+
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
16+
"type": "registry:ui"
17+
}
18+
],
19+
"type": "registry:ui"
20+
}

public/r/arrow-down-0-1-icon.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "arrow-down-0-1-icon",
4+
"dependencies": ["motion"],
5+
"devDependencies": [],
6+
"registryDependencies": [],
7+
"files": [
8+
{
9+
"path": "icons/arrow-down-0-1-icon.tsx",
10+
"content": "import { forwardRef, useImperativeHandle } from \"react\";\nimport { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst ArrowDown01Icon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 40, className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const swapDistance = 10;\n\n const start = async () => {\n animate(\n \".zero\",\n { y: swapDistance },\n { duration: 0.3, ease: \"easeInOut\" },\n );\n animate(\n \".one\",\n { y: -swapDistance },\n { duration: 0.3, ease: \"easeInOut\" },\n );\n };\n\n const stop = async () => {\n animate(\".zero, .one\", { y: 0 }, { duration: 0.3, ease: \"easeInOut\" });\n };\n\n useImperativeHandle(ref, () => {\n return {\n startAnimation: start,\n stopAnimation: stop,\n };\n });\n\n const handleHoverStart = () => {\n start();\n };\n\n const handleHoverEnd = () => {\n stop();\n };\n\n return (\n <motion.svg\n ref={scope}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`cursor-pointer ${className}`}\n >\n <path d=\"m3 16 4 4 4-4\" />\n <path d=\"M7 20V4\" />\n <motion.rect\n className=\"zero\"\n x=\"15\"\n y=\"4\"\n width=\"4\"\n height=\"6\"\n ry=\"2\"\n />\n <motion.g className=\"one\">\n <path d=\"M17 20v-6h-2\" />\n <path d=\"M15 20h4\" />\n </motion.g>\n </motion.svg>\n );\n },\n);\n\nArrowDown01Icon.displayName = \"ArrowDown01Icon\";\n\nexport default ArrowDown01Icon;\n",
11+
"type": "registry:ui"
12+
},
13+
{
14+
"path": "icons/types.ts",
15+
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
16+
"type": "registry:ui"
17+
}
18+
],
19+
"type": "registry:ui"
20+
}

public/r/brand-nextjs-icon.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "brand-nextjs-icon",
4+
"dependencies": ["motion"],
5+
"devDependencies": [],
6+
"registryDependencies": [],
7+
"files": [
8+
{
9+
"path": "icons/brand-nextjs-icon.tsx",
10+
"content": "import { forwardRef, useImperativeHandle } from \"react\";\nimport { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst BrandNextjsIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n (\n { size = 24, color = \"currentColor\", strokeWidth = 2, className = \"\" },\n ref,\n ) => {\n const [scope, animate] = useAnimate();\n\n const start = async () => {\n await animate(\".circle\", { pathLength: 0, opacity: 0 }, { duration: 0 });\n await animate(\".line\", { pathLength: 0, opacity: 0 }, { duration: 0 });\n\n await animate(\n \".circle\",\n { pathLength: [0, 1], opacity: [0, 1] },\n { duration: 1.5 },\n );\n\n await animate(\n \".line\",\n { pathLength: [0, 1], opacity: [0, 1] },\n { duration: 0.4 },\n );\n };\n\n const stop = () => {\n animate(\".circle\", { pathLength: 1, opacity: 1 }, { duration: 0.2 });\n animate(\".line\", { pathLength: 1, opacity: 1 }, { duration: 0.2 });\n };\n\n useImperativeHandle(ref, () => {\n return {\n startAnimation: start,\n stopAnimation: stop,\n };\n });\n\n const handleHoverStart = () => {\n start();\n };\n\n const handleHoverEnd = () => {\n stop();\n };\n\n return (\n <svg\n ref={scope}\n onMouseEnter={handleHoverStart}\n onMouseLeave={handleHoverEnd}\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`icon icon-tabler icons-tabler-outline icon-tabler-brand-nextjs cursor-pointer ${className}`}\n >\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <motion.path\n className=\"circle\"\n d=\"M9 15v-6l7.745 10.65a9 9 0 1 1 2.255 -1.993\"\n initial={{ pathLength: 1, opacity: 1 }}\n />\n <motion.path\n className=\"line\"\n d=\"M15 12v-3\"\n initial={{ pathLength: 1, opacity: 1 }}\n />\n </svg>\n );\n },\n);\n\nBrandNextjsIcon.displayName = \"BrandNextjsIcon\";\n\nexport default BrandNextjsIcon;\n",
11+
"type": "registry:ui"
12+
},
13+
{
14+
"path": "icons/types.ts",
15+
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
16+
"type": "registry:ui"
17+
}
18+
],
19+
"type": "registry:ui"
20+
}

0 commit comments

Comments
 (0)