Skip to content

Commit d571fff

Browse files
authored
Merge pull request #28 from luth-v/feat/auto-registry-generator
Feat: Generate-Registry Script
2 parents 6471a18 + 28dbd4e commit d571fff

Some content is hidden

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

45 files changed

+2823
-595
lines changed

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:build
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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ Auto-generated when you run `npm run registry:build` in the next step.
249249
npm run registry:build
250250
```
251251

252+
This command does three 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+
3. **Formats** `registry.json` and `public/r/*.json` files
257+
252258
Wait for it to succeed before proceeding.
253259

254260
### Step 4: Run All Checks
@@ -350,7 +356,7 @@ To verify icons work when installed via the shadcn CLI:
350356
# Run all checks before submitting
351357
npm run check
352358

353-
# Verify registry builds successfully
359+
# Verify registry syncs successfully (generates + builds)
354360
npm run registry:build
355361
```
356362

icons/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import AlarmClockPlusIcon from "./alarm-clock-plus-icon";
44
import AlignCenterIcon from "./align-center-icon";
55
import AlignVerticalSpaceAroundIcon from "./align-vertical-space-around-icon";
66
import AmbulanceIcon from "./ambulance-icon";
7+
import AmpersandIcon from "./ampersand-icon";
78
import AngryIcon from "./angry-icon";
89
import AnnoyedIcon from "./annoyed-icon";
910
import ArrowBackIcon from "./arrow-back-icon";
@@ -14,7 +15,10 @@ import ArrowBigRightIcon from "./arrow-big-right-icon";
1415
import ArrowBigRightDashIcon from "./arrow-big-right-dash-icon";
1516
import ArrowBigUpIcon from "./arrow-big-up-icon";
1617
import ArrowBigUpDashIcon from "./arrow-big-up-dash-icon";
18+
import ArrowBigDownIcon from "./arrow-big-down-icon";
19+
import ArrowBigDownDashIcon from "./arrow-big-down-dash-icon";
1720
import ArrowDown10Icon from "./arrow-down-1-0-icon";
21+
import ArrowDown01Icon from "./arrow-down-0-1-icon";
1822
import ArrowNarrowDownDashedIcon from "./arrow-narrow-down-dashed-icon";
1923
import ArrowNarrowDownIcon from "./arrow-narrow-down-icon";
2024
import ArrowNarrowLeftDashedIcon from "./arrow-narrow-left-dashed-icon";
@@ -34,6 +38,7 @@ import BluetoothConnectedIcon from "./bluetooth-connected-icon";
3438
import BookIcon from "./book-icon";
3539
import BookmarkIcon from "./bookmark-icon";
3640
import BrandGoogleIcon from "./brand-google-icon";
41+
import BrandNextjsIcon from "./brand-nextjs-icon";
3742
import BrightnessDownIcon from "./brightness-down-icon";
3843
import BulbSvg from "./bulb-svg";
3944
import CameraIcon from "./camera-icon";
@@ -50,6 +55,7 @@ import ClockIcon from "./clock-icon";
5055
import CodeIcon from "./code-icon";
5156
import CodeXmlIcon from "./code-xml-icon";
5257
import CoffeeIcon from "./coffee-icon";
58+
import CoinBitcoinIcon from "./coin-bitcoin-icon";
5359
import CreditCard from "./credit-card";
5460
import CopyIcon from "./copy-icon";
5561
import DiscordIcon from "./discord-icon";
@@ -229,6 +235,11 @@ const ICON_LIST: IconType[] = [
229235
icon: AngryIcon,
230236
keywords: ["angry", "mad", "emotion", "face", "emoji", "upset"],
231237
},
238+
{
239+
name: "ampersand-icon",
240+
icon: AmpersandIcon,
241+
keywords: ["ampersand", "and", "symbol", "typography", "character"],
242+
},
232243
{
233244
name: "annoyed-icon",
234245
icon: AnnoyedIcon,
@@ -286,11 +297,26 @@ const ICON_LIST: IconType[] = [
286297
icon: ArrowBigUpDashIcon,
287298
keywords: ["arrow", "big", "up", "dash", "upload", "top"],
288299
},
300+
{
301+
name: "arrow-big-down-icon",
302+
icon: ArrowBigDownIcon,
303+
keywords: ["arrow", "big", "down", "download", "bottom", "navigation"],
304+
},
305+
{
306+
name: "arrow-big-down-dash-icon",
307+
icon: ArrowBigDownDashIcon,
308+
keywords: ["arrow", "big", "down", "dash", "download", "bottom"],
309+
},
289310
{
290311
name: "arrow-down-1-0-icon",
291312
icon: ArrowDown10Icon,
292313
keywords: ["arrow", "down", "sort", "descending", "number", "order"],
293314
},
315+
{
316+
name: "arrow-down-0-1-icon",
317+
icon: ArrowDown01Icon,
318+
keywords: ["arrow", "down", "sort", "ascending", "number", "order"],
319+
},
294320
{
295321
name: "arrow-narrow-down-dashed-icon",
296322
icon: ArrowNarrowDownDashedIcon,
@@ -388,6 +414,19 @@ const ICON_LIST: IconType[] = [
388414
icon: BrandGoogleIcon,
389415
keywords: ["google", "brand", "logo", "search", "social"],
390416
},
417+
{
418+
name: "brand-nextjs-icon",
419+
icon: BrandNextjsIcon,
420+
keywords: [
421+
"nextjs",
422+
"next",
423+
"brand",
424+
"logo",
425+
"react",
426+
"framework",
427+
"vercel",
428+
],
429+
},
391430
{
392431
name: "brightness-down-icon",
393432
icon: BrightnessDownIcon,
@@ -475,6 +514,19 @@ const ICON_LIST: IconType[] = [
475514
icon: CoffeeIcon,
476515
keywords: ["coffee", "cup", "drink", "cafe", "steam", "hot", "beverage"],
477516
},
517+
{
518+
name: "coin-bitcoin-icon",
519+
icon: CoinBitcoinIcon,
520+
keywords: [
521+
"coin",
522+
"bitcoin",
523+
"crypto",
524+
"btc",
525+
"cryptocurrency",
526+
"money",
527+
"digital",
528+
],
529+
},
478530
{
479531
name: "credit-card",
480532
icon: CreditCard,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"dev": "next dev",
1010
"build": "next build",
1111
"start": "next start",
12-
"registry:build": "shadcn build",
12+
"registry:build": "npx tsx scripts/generate-registry.ts && shadcn build && prettier --write registry.json public/r",
1313
"lint": "eslint",
1414
"lint:fix": "eslint --fix",
1515
"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+
}

0 commit comments

Comments
 (0)