Skip to content

Commit 0a65bc8

Browse files
committed
back up
1 parent 2d90493 commit 0a65bc8

10 files changed

Lines changed: 112 additions & 99 deletions

File tree

app/components/ui/ArrowRight.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import { ArrowRight as Icon } from "lucide-react"
1+
import { ArrowRight as Icon, type LucideProps } from "lucide-react"
2+
const pascalToKebab = (s: string) =>
3+
s
4+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2") // abcX → abc-x
5+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") // XXx → x-x
6+
.replace(/([a-z])([0-9])/g, "$1-$2") // abc123 → abc-123
7+
.replace(/([0-9])([a-z])/g, "$1-$2") // 123abc → 123-abc
8+
.toLowerCase()
29

3-
export const ArrowRight: React.FC = () => {
4-
if (process.env.NODE_ENV === "development") {
5-
return <Icon />
10+
const DEV = process.env.NODE_ENV === "development"
11+
if (!Icon?.displayName) {
12+
throw new Error("Icon.displayName is not defined")
13+
}
14+
const name = pascalToKebab(Icon.displayName)
15+
16+
export const ArrowRight: React.FC<LucideProps> = (props) => {
17+
if (DEV) {
18+
return <Icon {...props} />
619
}
720
return (
821
<svg>
9-
<use href={`/icons.svg#arrow-right`} />
22+
<use href={`/icons.svg#${name}`} />
1023
</svg>
1124
)
1225
}

app/components/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./ArrowRight"

app/page.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
import { ArrowRight } from "./components/ui/ArrowRight"
2-
import { Icon } from "./components/ui/Icon"
3-
import type { IconName } from "./components/ui/Icon"
1+
import { ArrowRight as Icon } from "@svg-sprite"
42

5-
const iconNames: IconName[] = ["AArrowDown", "AArrowUp", "ALargeSmall", "Accessibility", "Activity"]
3+
// const iconNames: IconName[] = ["AArrowDown", "AArrowUp", "ALargeSmall", "Accessibility", "Activity"]
64

75
const Home: React.FC = () => {
86
return (
97
<div className="flex-center py-40 text-8xl font-black uppercase">
108
NEXT.js Starter
11-
<ArrowRight />
9+
<Icon className="h-20 w-20" />
1210
<button className="flex gap-2 **:rounded-lg **:bg-blue-500 **:text-white">
1311
{/* */}
1412
{/* {iconNames.map((name) => (
1513
<Icon key={name} name={name} className="h-20 w-20" />
1614
))} */}
1715
<hr />
18-
<Icon name="ArrowDown" className="h-20 w-20" />
16+
{/* <Icon name="ArrowDown" className="h-20 w-20" />
1917
<Icon name="ArrowUp" className="h-20 w-20" />
2018
<Icon name="ArrowUpLeft" className="h-20 w-20" />
2119
<Icon name="ArrowUpRight" className="h-20 w-20" />
@@ -28,7 +26,7 @@ const Home: React.FC = () => {
2826
<Icon name="Clock10" className="h-20 w-20" />
2927
<Icon name="Clock11" className="h-20 w-20" />
3028
<Icon name="Mail" className="h-20 w-20" />
31-
<Icon name="Figma" className="h-20 w-20" />
29+
<Icon name="Figma" className="h-20 w-20" /> */}
3230
Contact
3331
</button>
3432
</div>

public/icons.svg

Lines changed: 1 addition & 43 deletions
Loading

scripts/build-sprite.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const needed = new Set(ICONS.map(toKebab))
1717
const found = new Set()
1818

1919
const store = svgstore({
20-
copyAttrs: ["viewBox", "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "class", "style"],
20+
copyAttrs: ["viewBox", "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "class", "style", "width", "height"],
2121
})
2222
const iconsDir = "node_modules/lucide-static/icons"
2323

scripts/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// config.js
2+
export const IMPORT_NAME = "@svg-sprite"

scripts/gen-wrappers.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env node
2+
import fs from "fs"
3+
import path from "path"
4+
5+
// 1️⃣ Where Lucide's raw SVGs live
6+
const ICON_SVG_DIR = path.dirname(require.resolve("../lucide-static/icons/mail.svg"))
7+
8+
// 2️⃣ Where to write your wrappers
9+
const OUT_DIR = path.resolve("app/components/ui")
10+
fs.mkdirSync(OUT_DIR, { recursive: true })
11+
12+
// 3️⃣ Read every SVG filename
13+
const files = fs.readdirSync(ICON_SVG_DIR).filter((f) => f.endsWith(".svg"))
14+
15+
// 4️⃣ Helper: kebab ⇄ Pascal
16+
const toPascal = (s) =>
17+
s
18+
.split("-")
19+
.map((w) => w[0].toUpperCase() + w.slice(1))
20+
.join("")
21+
22+
// 5️⃣ Generate one .tsx per icon
23+
for (const file of files) {
24+
const id = file.replace(".svg", "") // e.g. "arrow-right"
25+
const pascal = toPascal(id) // "ArrowRight"
26+
const wrapperTsx = `\
27+
import { ${pascal} as DevIcon, type LucideProps } from "lucide-react"
28+
import { SPRITE_PATH } from "../lib/sprite-path"
29+
30+
export const ${pascal}: React.FC<LucideProps> = (props) =>
31+
process.env.NODE_ENV === "development" ? (
32+
<DevIcon {...props} />
33+
) : (
34+
<svg {...props}>
35+
<use href={\`\${SPRITE_PATH}#${id}\`} />
36+
</svg>
37+
)
38+
`
39+
fs.writeFileSync(path.join(OUT_DIR, `${pascal}.tsx`), wrapperTsx)
40+
}
41+
42+
// 6️⃣ Generate index.ts
43+
const exports = files
44+
.map((f) => {
45+
const id = f.replace(".svg", "")
46+
const pascal = toPascal(id)
47+
return `export { ${pascal} } from "./${pascal}"`
48+
})
49+
.join("\n")
50+
fs.writeFileSync(path.join(OUT_DIR, "index.ts"), exports + "\n")
51+
52+
console.log(`✅ Generated ${files.length} icon wrappers in ${OUT_DIR}`)

scripts/scan-icons.js

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
1-
// src/scan-icons.js
1+
// scripts/scan-icons.js
22
import fs from "fs"
33
import path from "path"
44
import * as babel from "@babel/core"
55
import traverseImport from "@babel/traverse"
66
import * as t from "@babel/types"
7+
import { IMPORT_NAME } from "./config.js"
8+
79
const traverse = traverseImport.default
8-
const iconNames = new Set()
10+
const ICONS = new Set()
11+
const ROOT = path.join(process.cwd(), "app") // or "src"
912

10-
function walk(dir) {
11-
for (const file of fs.readdirSync(dir)) {
12-
const full = path.join(dir, file)
13+
// walk all TSX/JSX files
14+
function collect(dir) {
15+
for (const f of fs.readdirSync(dir)) {
16+
const full = path.join(dir, f)
1317
if (fs.statSync(full).isDirectory()) {
14-
walk(full)
15-
} else if (full.endsWith(".tsx")) {
16-
const code = fs.readFileSync(full, "utf8")
17-
const ast = babel.parseSync(code, {
18+
collect(full)
19+
} else if (/\.[jt]sx$/.test(full)) {
20+
const src = fs.readFileSync(full, "utf8")
21+
const ast = babel.parseSync(src, {
1822
filename: full,
1923
presets: [
2024
["@babel/preset-typescript", { isTSX: true, allExtensions: true }],
2125
["@babel/preset-react", { runtime: "automatic" }],
2226
],
2327
sourceType: "module",
2428
})
25-
if (!ast) continue
26-
2729
traverse(ast, {
28-
JSXElement(path) {
29-
const opening = path.node.openingElement
30-
const name = opening.name
31-
// here we find all the elements that are named "Icon" or return
32-
if (!t.isJSXIdentifier(name) || name.name !== "Icon") return
33-
34-
for (const attr of opening.attributes) {
35-
// here we find all the attributes that are named "name" and are a string literal
36-
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: "name" })) {
37-
iconNames.add(attr.value.value)
30+
ImportDeclaration(path) {
31+
const from = path.node.source.value
32+
// adjust this to match your icon lib path
33+
if (from === IMPORT_NAME) {
34+
for (const spec of path.node.specifiers) {
35+
if (t.isImportSpecifier(spec)) {
36+
ICONS.add(spec.imported.name)
37+
}
3838
}
3939
}
4040
},
@@ -43,11 +43,9 @@ function walk(dir) {
4343
}
4444
}
4545

46-
const rootDir = "app" // or "src" or whatever your root is
47-
const currentDir = process.cwd()
48-
walk(path.join(currentDir, rootDir))
46+
collect(ROOT)
4947

50-
const output = [...iconNames].sort()
51-
console.log("output: ", output)
52-
fs.writeFileSync("scripts/used-icons.js", `export const ICONS = ${JSON.stringify(output, null, 2)}`)
53-
console.log(`✅ Found ${output.length} used icons`)
48+
// write the result for build-sprite to consume
49+
const list = [...ICONS].sort()
50+
fs.writeFileSync("scripts/used-icons.js", `export const ICONS = ${JSON.stringify(list, null, 2)};`)
51+
console.log(`✅ Found ${list.length} icons:`, list)

scripts/used-icons.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,3 @@
11
export const ICONS = [
2-
"ArrowDown",
3-
"ArrowDownLeft",
4-
"ArrowDownRight",
5-
"ArrowLeft",
6-
"ArrowRight",
7-
"ArrowUp",
8-
"ArrowUpLeft",
9-
"ArrowUpRight",
10-
"Clock",
11-
"Clock1",
12-
"Clock10",
13-
"Clock11",
14-
"Figma",
15-
"Mail"
16-
]
2+
"ArrowRight"
3+
];

tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
"@zero-ui/attributes": [
3535
"./.zero-ui/attributes.js"
3636
],
37+
"@svg-sprite": [
38+
"./app/components/ui/index.ts"
39+
],
3740
},
3841
"baseUrl": "."
3942
},
@@ -44,6 +47,7 @@
4447
".next/types/**/*.ts",
4548
".zero-ui/**/*.d.ts",
4649
"next-env.d.ts",
50+
"scripts/config.js",
4751
],
4852
"exclude": [
4953
"node_modules"

0 commit comments

Comments
 (0)