From b73ac1d4bc6d89cf654f909d8a2a101d4e2e6c1b Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Wed, 10 Jun 2026 11:44:29 +0200 Subject: [PATCH 001/124] fix(registry): rewrite workspace imports in served registry content Served registry JSON leaked unresolvable @repo/*, @smoothui/data, and deep-relative import specifiers into user projects. Rewrite them to the paths where the shadcn CLI installs the files, emit registry:block for blocks, registry:lib for data and animation constants, human-readable titles, and deduplicated registry dependencies detected from rewritten content. Ship lib/animation.ts as a new installable lib item. --- apps/docs/lib/package.ts | 108 +++++++++++++++++++++++++---- packages/smoothui/lib/package.json | 10 +++ 2 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 packages/smoothui/lib/package.json diff --git a/apps/docs/lib/package.ts b/apps/docs/lib/package.ts index 1e1edd79..3bf1a631 100644 --- a/apps/docs/lib/package.ts +++ b/apps/docs/lib/package.ts @@ -8,6 +8,44 @@ import type { RegistryItem } from "shadcn/schema"; // Regex patterns for detecting imports (hoisted for performance) const SHADCN_IMPORT_REGEX = /@\/components\/ui\/([a-z-]+)/g; const RELATIVE_IMPORT_REGEX = /from\s+["']\.\.\/([a-z-]+)["']/g; +const SMOOTHUI_IMPORT_REGEX = /@\/components\/smoothui\/([a-z0-9-]+)/g; +const SMOOTHUI_DATA_IMPORT_REGEX = /@\/lib\/smoothui-data/; + +const REGISTRY_URL = "https://smoothui.dev/r"; + +// Workspace import specifiers must be rewritten to the paths where the +// registry installs files in user projects — raw @repo/* or @smoothui/* +// specifiers don't resolve outside this monorepo. +const WORKSPACE_IMPORT_REWRITES: ReadonlyArray = [ + [/@repo\/shadcn-ui\/lib\/utils/g, "@/lib/utils"], + [/@repo\/shadcn-ui\/components\/ui\//g, "@/components/ui/"], + [/@repo\/smoothui\/components\//g, "@/components/smoothui/"], + [/@smoothui\/data/g, "@/lib/smoothui-data"], + // Components import shared animation constants via "../../lib/animation"; + // the registry installs that file at components/smoothui/lib/animation.ts. + [/(?:\.\.\/)+lib\/animation/g, "@/components/smoothui/lib/animation"], + // Blocks import the shared helpers barrel via "../../shared", which would + // point outside the install dir (components/smoothui//) in user + // projects. + [/from\s+["'](?:\.\.\/)+shared["']/g, 'from "@/components/smoothui/shared"'], +]; + +const rewriteWorkspaceImports = (content: string): string => { + let rewritten = content; + for (const [pattern, replacement] of WORKSPACE_IMPORT_REWRITES) { + rewritten = rewritten.replace(pattern, replacement); + } + return rewritten; +}; + +const toTitleCase = (name: string): string => + name + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + +// Workspace package name → registry item short name, when they differ +const WORKSPACE_DEP_ALIASES = new Map([["blocks-shared", "shared"]]); // Cache filtered package names for repeated lookups const FILTERED_PACKAGES = new Set([ @@ -15,7 +53,12 @@ const FILTERED_PACKAGES = new Set([ "typescript-config", "patterns", ]); -const FILTERED_DEPS = new Set(["react", "react-dom", "@repo/shadcn-ui"]); +const FILTERED_DEPS = new Set([ + "react", + "react-dom", + "@repo/shadcn-ui", + "@smoothui/data", +]); const FILTERED_DEV_DEPS = new Set([ "@repo/typescript-config", "@types/react", @@ -124,26 +167,45 @@ export const getPackage = cache(async (packageName: string) => { (dep) => !FILTERED_DEV_DEPS.has(dep) ); + const isData = packageName === "data"; + const isSharedLib = packageName === "smoothui/blocks/shared"; + const isBlock = packageName.startsWith("smoothui/blocks/") && !isSharedLib; + const packageFiles = await readdir(packageDir, { withFileTypes: true }); - const tsxFiles = packageFiles.filter( - (file) => file.isFile() && file.name.endsWith(".tsx") + const sourceFiles = packageFiles.filter( + (file) => + file.isFile() && + !file.name.includes(".config.") && + (file.name.endsWith(".tsx") || + (file.name.endsWith(".ts") && !file.name.endsWith(".d.ts"))) ); const cssFiles = packageFiles.filter( (file) => file.isFile() && file.name.endsWith(".css") ); + let fileType: RegistryItem["type"] = "registry:ui"; + if (isData || packageName === "smoothui/lib") { + fileType = "registry:lib"; + } else if (isBlock) { + fileType = "registry:block"; + } else if (isSharedLib) { + fileType = "registry:component"; + } + const files: RegistryItem["files"] = []; - for (const file of tsxFiles) { + for (const file of sourceFiles) { const filePath = join(packageDir, file.name); const content = await readFile(filePath, "utf-8"); files.push({ - type: "registry:ui", + type: fileType, path: file.name, - content, - target: `components/smoothui/${actualPackageName}/${file.name}`, + content: rewriteWorkspaceImports(content), + target: isData + ? `lib/smoothui-data/${file.name}` + : `components/smoothui/${actualPackageName}/${file.name}`, }); } @@ -165,18 +227,34 @@ export const getPackage = cache(async (packageName: string) => { .map((match) => match[1]) .filter((name): name is string => !!name); - const registryDependencies = [...shadcnDependencies]; + const registryDependencies = new Set(shadcnDependencies); // Add smoothui dependencies from package.json for (const dep of smoothuiDependencies) { - const pkg = dep.replace("@repo/", ""); + const raw = dep.replace("@repo/", ""); + const pkg = WORKSPACE_DEP_ALIASES.get(raw) ?? raw; - registryDependencies.push(`https://smoothui.dev/r/${pkg}.json`); + if (pkg !== actualPackageName) { + registryDependencies.add(`${REGISTRY_URL}/${pkg}.json`); + } } // Add relative imports as registry dependencies for (const relativeImport of relativeImports) { - registryDependencies.push(`https://smoothui.dev/r/${relativeImport}.json`); + registryDependencies.add(`${REGISTRY_URL}/${relativeImport}.json`); + } + + // Add cross-item imports detected in the rewritten content + // (@/components/smoothui/ covers components and the shared barrel) + for (const match of allContent.matchAll(SMOOTHUI_IMPORT_REGEX)) { + const name = match[1]; + if (name && name !== actualPackageName) { + registryDependencies.add(`${REGISTRY_URL}/${name}.json`); + } + } + + if (!isData && SMOOTHUI_DATA_IMPORT_REGEX.test(allContent)) { + registryDependencies.add(`${REGISTRY_URL}/data.json`); } const css: RegistryItem["css"] = {}; @@ -246,9 +324,9 @@ export const getPackage = cache(async (packageName: string) => { }); } - let type: RegistryItem["type"] = "registry:ui"; + let type: RegistryItem["type"] = fileType; - if (!Object.keys(files).length && Object.keys(css).length) { + if (!files.length && Object.keys(css).length) { type = "registry:style"; } @@ -256,12 +334,12 @@ export const getPackage = cache(async (packageName: string) => { $schema: "https://ui.shadcn.com/schema/registry-item.json", name: actualPackageName, type, - title: actualPackageName, + title: toTitleCase(actualPackageName), description: packageJson.description, author: "Eduardo Calvo ", dependencies, devDependencies, - registryDependencies, + registryDependencies: Array.from(registryDependencies), files, css, }; diff --git a/packages/smoothui/lib/package.json b/packages/smoothui/lib/package.json new file mode 100644 index 00000000..941269bd --- /dev/null +++ b/packages/smoothui/lib/package.json @@ -0,0 +1,10 @@ +{ + "name": "@repo/smoothui-lib", + "description": "Shared animation constants (springs, easing curves, durations) for SmoothUI components", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "typescript": "^5.9.3" + } +} From 246e0a203300cf1287cd6fe5c3aff43255b901eb Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Wed, 10 Jun 2026 11:44:43 +0200 Subject: [PATCH 002/124] fix(components): add missing "use client" directive to scramble-hover --- packages/smoothui/components/scramble-hover/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/smoothui/components/scramble-hover/index.tsx b/packages/smoothui/components/scramble-hover/index.tsx index da7de655..7405745c 100644 --- a/packages/smoothui/components/scramble-hover/index.tsx +++ b/packages/smoothui/components/scramble-hover/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import type React from "react"; import { useEffect, useRef, useState } from "react"; From f065d774c8bb5997d191d9cd0daba3922449e290 Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Wed, 10 Jun 2026 11:44:43 +0200 Subject: [PATCH 003/124] feat(registry): installable themes, AI skill, and theme install button Add six registry:theme items (candy, indigo, blue, red, orange, green) exposing the SmoothUI neutral scale and brand accents as standard shadcn cssVars with light and dark modes, plus the Inter font token. Add a registry:item skill that installs SKILL.md with SmoothUI usage and animation conventions for AI assistants. The docs color picker now offers a copy-to-clipboard install command for the selected theme. A CI verification script renders every registry item, validates it against the shadcn registry-item schema, and fails on workspace import leaks. --- apps/docs/app/r/[component]/route.ts | 22 ++- apps/docs/app/r/registry.json/route.ts | 12 ++ .../components/color-picker-float-nav.tsx | 44 ++++- apps/docs/lib/registry-skill.ts | 31 ++++ apps/docs/lib/registry-themes.ts | 170 ++++++++++++++++++ apps/docs/registry-assets/SKILL.md | 64 +++++++ apps/docs/scripts/verify-registry.mts | 59 ++++++ 7 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 apps/docs/lib/registry-skill.ts create mode 100644 apps/docs/lib/registry-themes.ts create mode 100644 apps/docs/registry-assets/SKILL.md create mode 100644 apps/docs/scripts/verify-registry.mts diff --git a/apps/docs/app/r/[component]/route.ts b/apps/docs/app/r/[component]/route.ts index 1963101e..d5b07e76 100644 --- a/apps/docs/app/r/[component]/route.ts +++ b/apps/docs/app/r/[component]/route.ts @@ -3,6 +3,8 @@ import { getAllPackageNames, getPackage, } from "@docs/lib/package"; +import { getSkill, SKILL_ITEM_NAME } from "@docs/lib/registry-skill"; +import { getAllThemeNames, getTheme } from "@docs/lib/registry-themes"; import { notFound } from "next/navigation"; import { type NextRequest, NextResponse } from "next/server"; @@ -28,6 +30,16 @@ export const GET = async (_: NextRequest, { params }: RegistryParams) => { notFound(); } + const theme = getTheme(shortName); + + if (theme) { + return NextResponse.json(theme); + } + + if (shortName === SKILL_ITEM_NAME) { + return NextResponse.json(await getSkill()); + } + try { // Get the mapping and find the full path for this short name const mapping = await getAllPackageNameMapping(); @@ -52,7 +64,11 @@ export const generateStaticParams = async () => { const allPackageNames = await getAllPackageNames(); // Return only the short names for the URL - return allPackageNames.map((name) => ({ - component: name.split("/").at(-1) || name, - })); + return [ + ...allPackageNames.map((name) => ({ + component: name.split("/").at(-1) || name, + })), + ...getAllThemeNames().map((name) => ({ component: name })), + { component: SKILL_ITEM_NAME }, + ]; }; diff --git a/apps/docs/app/r/registry.json/route.ts b/apps/docs/app/r/registry.json/route.ts index 8c058c97..6c6e32d7 100644 --- a/apps/docs/app/r/registry.json/route.ts +++ b/apps/docs/app/r/registry.json/route.ts @@ -1,4 +1,6 @@ import { getAllPackageNames, getPackage } from "@docs/lib/package"; +import { getSkill } from "@docs/lib/registry-skill"; +import { getAllThemeNames, getTheme } from "@docs/lib/registry-themes"; import { NextResponse } from "next/server"; import type { Registry } from "shadcn/schema"; @@ -33,5 +35,15 @@ export const GET = async () => { } } + for (const themeName of getAllThemeNames()) { + const theme = getTheme(themeName); + + if (theme) { + response.items.push(theme); + } + } + + response.items.push(await getSkill()); + return NextResponse.json(response); }; diff --git a/apps/docs/components/color-picker-float-nav.tsx b/apps/docs/components/color-picker-float-nav.tsx index 6629d65d..12d4bd57 100644 --- a/apps/docs/components/color-picker-float-nav.tsx +++ b/apps/docs/components/color-picker-float-nav.tsx @@ -7,12 +7,16 @@ import { resetColorPalette, } from "@docs/app/lib/color-palette"; import { Button } from "@repo/shadcn-ui/components/ui/button"; -import { Check, CheckCheck, RotateCcw, Save } from "lucide-react"; +import { Check, CheckCheck, Copy, RotateCcw, Save } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useEffect, useRef, useState } from "react"; const CLOSE_DELAY = 200; const SAVE_MESSAGE_DURATION = 1200; +const COPY_MESSAGE_DURATION = 1200; + +const themeInstallCommand = (paletteName: string) => + `npx shadcn@latest add https://smoothui.dev/r/theme-${paletteName.toLowerCase()}.json`; const PALETTES = [ { @@ -54,8 +58,25 @@ export function ColorPickerFloatNav() { const pickerRef = useRef(null); const [show, setShow] = useState(false); const [saved, setSaved] = useState(false); + const [copied, setCopied] = useState(false); const shouldReduceMotion = useReducedMotion(); + const selectedPalette = PALETTES.find( + (palette) => + palette.candy === candy && palette.candySecondary === candySecondary + ); + + async function handleCopyInstall() { + if (!selectedPalette) { + return; + } + await navigator.clipboard.writeText( + themeInstallCommand(selectedPalette.name) + ); + setCopied(true); + setTimeout(() => setCopied(false), COPY_MESSAGE_DURATION); + } + useEffect(() => { const savedColors = localStorage.getItem(COLOR_STORAGE_KEY); if (savedColors) { @@ -246,6 +267,27 @@ export function ColorPickerFloatNav() { ))} + {selectedPalette && ( + + )}
From 74d0ce107c6beead5be92ed9b8b281c8c47a3ff3 Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Wed, 10 Jun 2026 12:24:16 +0200 Subject: [PATCH 006/124] fix(blocks): respect prefers-reduced-motion across all animated blocks Every animated block (headers, pricing, stats, testimonials, footers, faqs, team, logo clouds, shared helpers) now gates its motion props behind useReducedMotion, per WCAG 2.1 SC 2.3.3: entrance and in-view animations render in their final state instantly, gesture animations are disabled, and transitions drop to zero duration. The non-reduced animation design is unchanged. Also removes two dead constants in footer-2. --- packages/smoothui/blocks/faqs/faq-1/index.tsx | 69 +++++-- packages/smoothui/blocks/faqs/faq-2/index.tsx | 122 ++++++++---- .../blocks/footers/footer-1/index.tsx | 145 +++++++++----- .../blocks/footers/footer-2/index.tsx | 146 +++++++++----- .../blocks/headers/header-2/index.tsx | 121 ++++++++---- .../blocks/headers/header-3/index.tsx | 23 ++- .../blocks/logos/logo-cloud-2/index.tsx | 142 ++++++++----- .../blocks/pricing/pricing-1/index.tsx | 17 +- .../blocks/pricing/pricing-2/index.tsx | 61 ++++-- .../blocks/pricing/pricing-3/index.tsx | 39 ++-- .../smoothui/blocks/shared/animated-group.tsx | 22 ++- .../smoothui/blocks/shared/animated-text.tsx | 32 ++- .../smoothui/blocks/shared/hero-header.tsx | 179 ++++++++++++----- .../smoothui/blocks/stats/stats-1/index.tsx | 53 +++-- .../smoothui/blocks/stats/stats-2/index.tsx | 125 ++++++++---- .../smoothui/blocks/team/team-1/index.tsx | 58 ++++-- .../smoothui/blocks/team/team-2/index.tsx | 54 ++--- .../testimonials/testimonials-1/index.tsx | 105 +++++++--- .../testimonials/testimonials-2/index.tsx | 187 +++++++++++++----- .../testimonials/testimonials-3/index.tsx | 155 ++++++++++----- 20 files changed, 1287 insertions(+), 568 deletions(-) diff --git a/packages/smoothui/blocks/faqs/faq-1/index.tsx b/packages/smoothui/blocks/faqs/faq-1/index.tsx index c549a4af..23d73775 100644 --- a/packages/smoothui/blocks/faqs/faq-1/index.tsx +++ b/packages/smoothui/blocks/faqs/faq-1/index.tsx @@ -17,7 +17,7 @@ import { WalletCards, Zap, } from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; +import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useState } from "react"; const ANIMATION_DURATION = 0.3; @@ -139,6 +139,7 @@ export function FaqsGrid({ }, ], }: FaqsGridProps) { + const shouldReduceMotion = useReducedMotion(); const [activeTab, setActiveTab] = useState(0); return ( @@ -164,20 +165,24 @@ export function FaqsGrid({ key={category.id} onClick={() => setActiveTab(index)} type="button" - whileHover={{ scale: HOVER_SCALE }} - whileTap={{ scale: TAP_SCALE }} + whileHover={shouldReduceMotion ? {} : { scale: HOVER_SCALE }} + whileTap={shouldReduceMotion ? {} : { scale: TAP_SCALE }} > {category.name} {activeTab === index && ( )} @@ -189,11 +194,25 @@ export function FaqsGrid({
@@ -203,14 +222,26 @@ export function FaqsGrid({ return (
diff --git a/packages/smoothui/blocks/faqs/faq-2/index.tsx b/packages/smoothui/blocks/faqs/faq-2/index.tsx index 417b7c11..68699c35 100644 --- a/packages/smoothui/blocks/faqs/faq-2/index.tsx +++ b/packages/smoothui/blocks/faqs/faq-2/index.tsx @@ -1,6 +1,6 @@ "use client"; -import { AnimatePresence, motion } from "motion/react"; +import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useState } from "react"; const ANIMATION_DURATION = 0.6; @@ -58,6 +58,7 @@ export function FaqsAccordion({ }, ], }: FaqsAccordionProps) { + const shouldReduceMotion = useReducedMotion(); const [openIndex, setOpenIndex] = useState(0); const toggleAccordion = (index: number) => { @@ -68,12 +69,22 @@ export function FaqsAccordion({

{title} @@ -86,37 +97,61 @@ export function FaqsAccordion({
{faqs.map((faq, index) => ( toggleAccordion(index)} type="button" - whileHover={{ scale: HOVER_SCALE }} - whileTap={{ scale: TAP_SCALE }} + whileHover={shouldReduceMotion ? {} : { scale: HOVER_SCALE }} + whileTap={shouldReduceMotion ? {} : { scale: TAP_SCALE }} >

{faq.question}

{faq.answer} diff --git a/packages/smoothui/blocks/footers/footer-1/index.tsx b/packages/smoothui/blocks/footers/footer-1/index.tsx index 0e277225..f903bc9d 100644 --- a/packages/smoothui/blocks/footers/footer-1/index.tsx +++ b/packages/smoothui/blocks/footers/footer-1/index.tsx @@ -1,6 +1,6 @@ "use client"; -import { motion } from "motion/react"; +import { motion, useReducedMotion } from "motion/react"; const ANIMATION_DURATION = 0.6; const DELAY_INCREMENT = 0.1; @@ -59,28 +59,45 @@ export function FooterSimple({ }, copyright = "© 2024 Smoothui. All rights reserved.", }: FooterSimpleProps) { + const shouldReduceMotion = useReducedMotion(); return (

diff --git a/apps/docs/components/blog-float-nav.tsx b/apps/docs/components/blog-float-nav.tsx index 2a903e10..40e4c123 100644 --- a/apps/docs/components/blog-float-nav.tsx +++ b/apps/docs/components/blog-float-nav.tsx @@ -1,8 +1,12 @@ "use client"; import { useSearchContext } from "fumadocs-ui/contexts/search"; -import { Moon, Search, Sun } from "lucide-react"; import { useTheme } from "next-themes"; +import { + IconMagnifierFill24, + IconMoonFill24, + IconSunFill24, +} from "nucleo-core-fill-24"; import { ColorPickerFloatNav } from "./color-picker-float-nav"; function ThemeSwitch() { @@ -18,7 +22,11 @@ function ThemeSwitch() { onClick={toggleTheme} type="button" > - {resolvedTheme === "dark" ? : } + {resolvedTheme === "dark" ? ( + + ) : ( + + )} ); } @@ -33,7 +41,7 @@ function SearchButton() { onClick={() => setOpenSearch(true)} type="button" > - + ); } diff --git a/apps/docs/components/blog/interactive-avatar-tutorial.tsx b/apps/docs/components/blog/interactive-avatar-tutorial.tsx index 980ffd9e..a1edd1e3 100644 --- a/apps/docs/components/blog/interactive-avatar-tutorial.tsx +++ b/apps/docs/components/blog/interactive-avatar-tutorial.tsx @@ -7,8 +7,12 @@ import { } from "@repo/shadcn-ui/components/ui/popover"; import { cn } from "@repo/shadcn-ui/lib/utils"; import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; -import { Eye, Package, User } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; +import { + IconBoxFill24, + IconEyeFill24, + IconUserFill24, +} from "nucleo-core-fill-24"; import { useEffect, useRef, useState } from "react"; // SmoothUI Logo Isotype @@ -393,7 +397,7 @@ export function InteractiveAvatarTutorial({ onClick={() => handleSectionClick("profile")} type="button" > - + Edit Profile @@ -487,7 +491,7 @@ export function InteractiveAvatarTutorial({ onClick={() => handleSectionClick("orders")} type="button" > - + Last Orders @@ -605,7 +609,7 @@ export function InteractiveAvatarTutorial({ className="flex shrink-0 items-center justify-center rounded-md border bg-background p-2" type="button" > - diff --git a/apps/docs/components/blog/interactive-rich-popover-tutorial.tsx b/apps/docs/components/blog/interactive-rich-popover-tutorial.tsx index 7ac6d754..489337cd 100644 --- a/apps/docs/components/blog/interactive-rich-popover-tutorial.tsx +++ b/apps/docs/components/blog/interactive-rich-popover-tutorial.tsx @@ -7,8 +7,12 @@ import { } from "@repo/shadcn-ui/components/ui/popover"; import { cn } from "@repo/shadcn-ui/lib/utils"; import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; -import { Clock, ExternalLink, Play } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; +import { + IconClockFill24, + IconExternalLinkFill24, + IconMediaPlayFill24, +} from "nucleo-core-fill-24"; import { useEffect, useRef, useState } from "react"; // SmoothUI Logo Isotype @@ -87,7 +91,7 @@ const CODE_SNIPPETS: Record = {

Video Title - +

Description text here... @@ -368,7 +372,7 @@ export function InteractiveRichPopoverTutorial({ target="_blank" > Introducing GPT-5 - +

OpenAI announces their most capable model yet @@ -379,14 +383,14 @@ export function InteractiveRichPopoverTutorial({ {/* Meta & Action */}

- + 12:34
diff --git a/apps/docs/components/bundle-size-badge.tsx b/apps/docs/components/bundle-size-badge.tsx index f525efc4..84c8a0e1 100644 --- a/apps/docs/components/bundle-size-badge.tsx +++ b/apps/docs/components/bundle-size-badge.tsx @@ -1,6 +1,6 @@ import { formatSize, getBundleSize } from "@docs/lib/bundle-size"; import { cn } from "@repo/shadcn-ui/lib/utils"; -import { Package } from "lucide-react"; +import { IconBoxFill24 } from "nucleo-core-fill-24"; export type BundleSizeBadgeProps = { slug: string; @@ -26,7 +26,7 @@ export const BundleSizeBadge = ({ slug, className }: BundleSizeBadgeProps) => { )} title={`Minified: ${formatSize(size.minified)} / Gzipped: ${formatSize(size.gzipped)}`} > -
)} diff --git a/apps/docs/components/contributor.tsx b/apps/docs/components/contributor.tsx index 444974c3..68f0faf8 100644 --- a/apps/docs/components/contributor.tsx +++ b/apps/docs/components/contributor.tsx @@ -4,8 +4,8 @@ import { TooltipContent, TooltipTrigger, } from "@repo/shadcn-ui/components/ui/tooltip"; -import { GitBranch, UserIcon } from "lucide-react"; import Image from "next/image"; +import { IconBranchOutFill24, IconUserFill24 } from "nucleo-core-fill-24"; interface ContributorProps { contributors?: ContributorInfo[]; @@ -27,7 +27,7 @@ export const Contributor = ({ {/* Created by section */}
- +

Created by

@@ -51,7 +51,7 @@ export const Contributor = ({
) : (
- +
)} @@ -72,7 +72,7 @@ export const Contributor = ({
) : (
- +
)} @@ -87,7 +87,7 @@ export const Contributor = ({ {otherContributors.length > 0 && (
- +

Improved by

@@ -115,7 +115,7 @@ export const Contributor = ({ /> ) : (
- +
)}
@@ -137,7 +137,7 @@ export const Contributor = ({ /> ) : (
- +
)}
diff --git a/apps/docs/components/float-nav.tsx b/apps/docs/components/float-nav.tsx index e407baa6..baed596a 100644 --- a/apps/docs/components/float-nav.tsx +++ b/apps/docs/components/float-nav.tsx @@ -1,7 +1,7 @@ "use client"; -import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; +import { IconMoonFill24, IconSunFill24 } from "nucleo-core-fill-24"; import { ColorPickerFloatNav } from "./color-picker-float-nav"; function ThemeSwitch() { @@ -17,7 +17,11 @@ function ThemeSwitch() { onClick={toggleTheme} type="button" > - {resolvedTheme === "dark" ? : } + {resolvedTheme === "dark" ? ( + + ) : ( + + )} ); } diff --git a/apps/docs/components/gallery/component-card.tsx b/apps/docs/components/gallery/component-card.tsx index 0fdffc72..c9908165 100644 --- a/apps/docs/components/gallery/component-card.tsx +++ b/apps/docs/components/gallery/component-card.tsx @@ -3,9 +3,13 @@ import type { GalleryComponentMeta } from "@docs/lib/gallery"; import { cn } from "@repo/shadcn-ui/lib/utils"; import SmoothButton from "@repo/smoothui/components/smooth-button"; -import { Check, Copy, Package } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import Link from "next/link"; +import { + IconBoxFill24, + IconCheckFill24, + IconCopyFill24, +} from "nucleo-core-fill-24"; import { useCallback, useEffect, useState } from "react"; import { GalleryPreview } from "./gallery-preview"; @@ -114,7 +118,7 @@ export const ComponentCard = ({ component }: ComponentCardProps) => { className="inline-flex items-center gap-0.5 text-[10px] text-muted-foreground/70" title={`Gzipped: ${formatSize(component.bundleSize.gzipped)}`} > -
{/* Search input */}
- + - + )}
diff --git a/apps/docs/components/landing/ai-section.tsx b/apps/docs/components/landing/ai-section.tsx index efbce3e5..92b9fdbf 100644 --- a/apps/docs/components/landing/ai-section.tsx +++ b/apps/docs/components/landing/ai-section.tsx @@ -4,10 +4,10 @@ import Divider from "@docs/components/landing/divider"; import { SectionHeader } from "@docs/components/landing/section-header"; import { cn } from "@repo/shadcn-ui/lib/utils"; import AgentAvatar from "@repo/smoothui/components/agent-avatar"; -import { ArrowRight, Sparkles } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import Image from "next/image"; import Link from "next/link"; +import { IconArrowRightFill24, IconSparkleFill24 } from "nucleo-core-fill-24"; function McpIllustration() { const shouldReduceMotion = useReducedMotion(); @@ -50,7 +50,7 @@ function McpIllustration() { >
- +
@@ -379,7 +379,7 @@ export function AISection() { href="/docs/guides/ai-integration" > Learn more about AI integration - diff --git a/apps/docs/components/landing/block-categories.tsx b/apps/docs/components/landing/block-categories.tsx index c995d2fa..c61e9a55 100644 --- a/apps/docs/components/landing/block-categories.tsx +++ b/apps/docs/components/landing/block-categories.tsx @@ -21,19 +21,18 @@ import { import InfiniteSlider from "@repo/smoothui/components/infinite-slider"; import PriceFlow from "@repo/smoothui/components/price-flow"; import { getAllPeople, getAvatarUrl, getImageKitUrl } from "@smoothui/data"; -import { - ChevronDown, - ChevronLeft, - ChevronRight, - Github, - HelpCircle, - Link as LinkIcon, - Star, - Twitter, -} from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Image from "next/image"; import Link from "next/link"; +import { + IconChevronDownFill24, + IconChevronLeftFill24, + IconChevronRightFill24, + IconCircleQuestionFill24, + IconExternalLinkFill24, + IconStarFill24, +} from "nucleo-core-fill-24"; +import { IconGithub, IconXTwitter } from "nucleo-social-media"; import { useState } from "react"; const EASE_OUT_QUAD = [0.25, 0.46, 0.45, 0.94] as const; @@ -92,7 +91,6 @@ const blockCategories = [ function HeroPreview() { return (
- {/* Left — real mini hero copy */}
New @@ -112,7 +110,6 @@ function HeroPreview() {
- {/* Right — the SmoothUI orb */}
setCurrentIndex((prev) => (prev + 1) % people.length)} >
- - + +
{[...new Array(5)].map((_, i) => ( -

- “{quote}” + "{quote}"

{currentPerson?.name} @@ -261,25 +258,13 @@ function TestimonialPreview() { function FAQPreview() { const chevronVariants = { - open: { - rotate: 180, - }, - closed: { - rotate: 0, - }, + open: { rotate: 180 }, + closed: { rotate: 0 }, }; const contentVariants = { - open: { - opacity: 1, - height: "auto", - marginTop: "0.25rem", - }, - closed: { - opacity: 0, - height: 0, - marginTop: 0, - }, + open: { opacity: 1, height: "auto", marginTop: "0.25rem" }, + closed: { opacity: 0, height: 0, marginTop: 0 }, }; return ( @@ -288,7 +273,6 @@ function FAQPreview() { initial="open" whileHover="closed" > - {/* First FAQ — expanded by default, closes on hover */}
- + Is it free to use? @@ -305,7 +289,7 @@ function FAQPreview() { transition={{ duration: 0.2, ease: EASE_OUT_QUAD }} variants={chevronVariants} > - +
- + {question}
- +
) )} @@ -368,10 +352,16 @@ function FooterPreview() { © 2026 SmoothUI
- {[Twitter, Github, LinkIcon].map((Icon) => ( + {( + [ + { Icon: IconXTwitter, key: "twitter" }, + { Icon: IconGithub, key: "github" }, + { Icon: IconExternalLinkFill24, key: "link" }, + ] as const + ).map(({ Icon, key }) => ( @@ -384,7 +374,6 @@ function FooterPreview() { } function LogoCloudPreview() { - // Use more logos for a smoother infinite loop const logos = [ Canpoy, Canva, @@ -401,17 +390,10 @@ function LogoCloudPreview() { ]; const logoVariants = { - rest: { - scale: 1, - opacity: 0.6, - }, - hover: { - scale: 1.08, - opacity: 1, - }, + rest: { scale: 1, opacity: 0.6 }, + hover: { scale: 1.08, opacity: 1 }, }; - // Create blur mask layers - simplified for preview const createBlurMask = ( direction: "left" | "right", blur: number, @@ -439,30 +421,21 @@ function LogoCloudPreview() { return (
- {/* Base gradient backgrounds */}
- - {/* Left blur mask layers */}
{Array.from({ length: 4 }, (_, i) => createBlurMask("left", i, i))}
- - {/* Right blur mask layers */}
{Array.from({ length: 4 }, (_, i) => createBlurMask("right", i, i))}
- {logos.map((LogoComponent) => ( @@ -569,7 +542,6 @@ export function BlockCategories() { title="Elevate your design with premium blocks" /> - {/* Grid Container with overflow */}
{blockCategories.map((category, index) => ( @@ -592,14 +564,11 @@ export function BlockCategories() { } > - {/* Preview Area - Simple rounded container with frame-box */}
- - {/* Content - Centered with pill tag */}
@@ -614,7 +583,6 @@ export function BlockCategories() { ))}
- {/* Bottom Button Overlay */} - {/* BlurMagic Fade Effect */}
diff --git a/apps/docs/components/landing/footer.tsx b/apps/docs/components/landing/footer.tsx index 774d3222..088a9ea3 100644 --- a/apps/docs/components/landing/footer.tsx +++ b/apps/docs/components/landing/footer.tsx @@ -9,9 +9,9 @@ import { TailwindLogo } from "@docs/components/landing/logos/tailwind-logo"; import { SponsorLogo } from "@docs/components/sponsor-logo"; import { getExternalSponsors } from "@docs/lib/sponsors"; import { cn } from "@repo/shadcn-ui/lib/utils"; -import { ArrowUpRight, Heart } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import Link from "next/link"; +import { IconArrowUpRightFill24, IconHeartFill24 } from "nucleo-core-fill-24"; // --------------------------------------------------------------------------- // Types @@ -160,7 +160,10 @@ function CtaCard() { {hasSponsors ? ( <>
- + Thank you to our sponsors

@@ -208,7 +211,7 @@ function CtaCard() { href="/docs/guides/sponsors" > Become a sponsor - + {link.label} - diff --git a/apps/docs/components/landing/hero.tsx b/apps/docs/components/landing/hero.tsx index 1edb08cc..0b7c082b 100644 --- a/apps/docs/components/landing/hero.tsx +++ b/apps/docs/components/landing/hero.tsx @@ -18,9 +18,13 @@ import ButtonCopy from "@repo/smoothui/components/button-copy"; import ClipCornersButton from "@repo/smoothui/components/clip-corners-button"; import ScrambleHover from "@repo/smoothui/components/scramble-hover"; import SiriOrb from "@repo/smoothui/components/siri-orb"; -import { ArrowUpRight, User, Wand2 } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import Link from "next/link"; +import { + IconArrowUpRightFill24, + IconUserFill24, + IconWandSparkleFill24, +} from "nucleo-core-fill-24"; function AnnouncementBadge() { const shouldReduceMotion = useReducedMotion(); @@ -45,12 +49,12 @@ function AnnouncementBadge() { }} /> )} - + New

UI Craft — Design taste for AI agents - diff --git a/apps/docs/components/landing/navbar/navbar.tsx b/apps/docs/components/landing/navbar/navbar.tsx index bf388411..4f9c3586 100644 --- a/apps/docs/components/landing/navbar/navbar.tsx +++ b/apps/docs/components/landing/navbar/navbar.tsx @@ -10,21 +10,20 @@ import { Viewport as NavigationMenuViewport, } from "@radix-ui/react-navigation-menu"; import { - ArrowRight, - Book, - Compass, - FileText, - Heart, - Layers3, - LayoutDashboard, - PackagePlus, - Palette, - Sparkles, - Type, - User, - Wand2, - Zap, -} from "lucide-react"; + IconArrowRightFill24, + IconBoltFill24, + IconBookFill24, + IconBoxFill24, + IconColorPaletteFill24, + IconCompassFill24, + IconGrid2Fill24, + IconHeartFill24, + IconLayersFill24, + IconSparkleFill24, + IconTextFill24, + IconUserFill24, + IconWandSparkleFill24, +} from "nucleo-core-fill-24"; import type React from "react"; import { useState } from "react"; @@ -46,17 +45,17 @@ import { MobileNavbar } from "./mobile-navbar"; const componentPreviews = { text: { title: "Text Components", - icon: , + icon: , section: "text", }, basic: { title: "Basic Components", - icon: , + icon: , section: "basic", }, components: { title: "UI Components", - icon: , + icon: , section: "components", }, }; @@ -64,17 +63,17 @@ const componentPreviews = { const blockPreviews = { hero: { title: "Hero Blocks", - icon: , + icon: , section: "hero", }, pricing: { title: "Pricing Blocks", - icon: , + icon: , section: "pricing", }, testimonial: { title: "Testimonial Blocks", - icon: , + icon: , section: "testimonial", }, }; @@ -104,7 +103,7 @@ export default function Navbar({ className }: NavbarProps) { - + Components @@ -144,7 +143,7 @@ export default function Navbar({ className }: NavbarProps) { href="/docs/components" > Explore all components - +
@@ -168,7 +167,7 @@ export default function Navbar({ className }: NavbarProps) { - + Blocks @@ -208,7 +207,7 @@ export default function Navbar({ className }: NavbarProps) { href="/docs/blocks" > Explore all blocks - +
@@ -232,17 +231,17 @@ export default function Navbar({ className }: NavbarProps) { - Themes + Themes - Docs + Docs - + Resources @@ -251,7 +250,7 @@ export default function Navbar({ className }: NavbarProps) {
} + icon={} onHover={() => setHoveredResource("blog")} onLeave={() => setHoveredResource(null)} title="Blog" @@ -260,7 +259,7 @@ export default function Navbar({ className }: NavbarProps) { } + icon={} onHover={() => setHoveredResource("sponsors")} onLeave={() => setHoveredResource(null)} title="Sponsors" @@ -270,7 +269,7 @@ export default function Navbar({ className }: NavbarProps) { } + icon={} onHover={() => setHoveredResource("skills")} onLeave={() => setHoveredResource(null)} title="Skills" @@ -283,7 +282,7 @@ export default function Navbar({ className }: NavbarProps) { href="/blog" > Read the blog - +
diff --git a/apps/docs/components/landing/skills-section.tsx b/apps/docs/components/landing/skills-section.tsx index adc258cf..98e3308b 100644 --- a/apps/docs/components/landing/skills-section.tsx +++ b/apps/docs/components/landing/skills-section.tsx @@ -2,16 +2,34 @@ import Divider from "@docs/components/landing/divider"; import { SectionHeader } from "@docs/components/landing/section-header"; -import { ArrowUpRight, Eye, Search, Sparkles, Wand2 } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import Image from "next/image"; import Link from "next/link"; +import { + IconArrowUpRightFill24, + IconEyeFill24, + IconMagnifierFill24, + IconSparkleFill24, + IconWandSparkleFill24, +} from "nucleo-core-fill-24"; const modes = [ - { icon: Wand2, label: "Build", description: "Scaffold with taste" }, - { icon: Sparkles, label: "Animate", description: "Motion that feels right" }, - { icon: Eye, label: "Review", description: "Catch what AI misses" }, - { icon: Search, label: "Polish", description: "Pixel-perfect details" }, + { + icon: IconWandSparkleFill24, + label: "Build", + description: "Scaffold with taste", + }, + { + icon: IconSparkleFill24, + label: "Animate", + description: "Motion that feels right", + }, + { icon: IconEyeFill24, label: "Review", description: "Catch what AI misses" }, + { + icon: IconMagnifierFill24, + label: "Polish", + description: "Pixel-perfect details", + }, ]; export function SkillsSection() { @@ -157,7 +175,7 @@ export function SkillsSection() { target="_blank" > Explore UI Craft skill - diff --git a/apps/docs/components/landing/sponsors.tsx b/apps/docs/components/landing/sponsors.tsx index 68fb2565..c1456bc6 100644 --- a/apps/docs/components/landing/sponsors.tsx +++ b/apps/docs/components/landing/sponsors.tsx @@ -2,8 +2,8 @@ import { SponsorLogo } from "@docs/components/sponsor-logo"; import { getExternalSponsors } from "@docs/lib/sponsors"; -import { Heart } from "lucide-react"; import Link from "next/link"; +import { IconHeartFill24 } from "nucleo-core-fill-24"; import Divider from "./divider"; export function Sponsors() { @@ -17,7 +17,7 @@ export function Sponsors() { {hasSponsors && (
- + Thank You to Our Sponsors diff --git a/apps/docs/components/landing/what-they-say.tsx b/apps/docs/components/landing/what-they-say.tsx index ada6c0a7..4ee6fe88 100644 --- a/apps/docs/components/landing/what-they-say.tsx +++ b/apps/docs/components/landing/what-they-say.tsx @@ -1,9 +1,12 @@ "use client"; import { cn } from "@repo/shadcn-ui/lib/utils"; -import { ChevronLeft, ChevronRight } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Image from "next/image"; +import { + IconChevronLeftFill24, + IconChevronRightFill24, +} from "nucleo-core-fill-24"; import { useState } from "react"; import Divider from "./divider"; @@ -306,7 +309,7 @@ export function WhatTheySay() { onClick={() => goTo(-1)} type="button" > - +
diff --git a/apps/docs/components/logo-context-menu.tsx b/apps/docs/components/logo-context-menu.tsx index 88f6694c..901adc4e 100644 --- a/apps/docs/components/logo-context-menu.tsx +++ b/apps/docs/components/logo-context-menu.tsx @@ -1,8 +1,8 @@ "use client"; import { cn } from "@repo/shadcn-ui/lib/utils"; -import { Volume2 } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; +import { IconVolume2Fill24 } from "nucleo-core-fill-24"; import { createContext, type ReactNode, @@ -209,7 +209,7 @@ function LogoContextMenuPopup() { type="button" > /smooth-yoo-eye/ -
))} diff --git a/apps/docs/components/powered-by.tsx b/apps/docs/components/powered-by.tsx index 42322082..15cc5412 100644 --- a/apps/docs/components/powered-by.tsx +++ b/apps/docs/components/powered-by.tsx @@ -1,5 +1,5 @@ -import { LibraryIcon } from "lucide-react"; import Image from "next/image"; +import { IconBooksFill24 } from "nucleo-core-fill-24"; interface PoweredByProps { packages: string[]; @@ -36,7 +36,7 @@ const getPackageName = (url: string) => { export const PoweredBy = ({ packages }: PoweredByProps) => (
- +

Powered by

diff --git a/apps/docs/components/preview/shell.tsx b/apps/docs/components/preview/shell.tsx index 5dd42615..d38caa04 100644 --- a/apps/docs/components/preview/shell.tsx +++ b/apps/docs/components/preview/shell.tsx @@ -13,16 +13,16 @@ import { ToggleGroupItem, } from "@repo/shadcn-ui/components/ui/toggle-group"; import { cn } from "@repo/shadcn-ui/lib/utils"; -import { - BoxIcon, - CodeIcon, - EyeIcon, - Fullscreen, - Monitor, - Smartphone, - Tablet, -} from "lucide-react"; import Link from "next/link"; +import { + IconBoxFill24, + IconCodeFill24, + IconExpandFill24, + IconEyeFill24, + IconMobile2Fill24, + IconMonitorFill24, + IconTabletFill24, +} from "nucleo-core-fill-24"; import type { ReactNode } from "react"; import { useState } from "react"; @@ -43,17 +43,17 @@ const previewSizes: { label: string; value: PreviewSize; icon: ReactNode }[] = [ { label: "Desktop preview", value: "desktop", - icon: , + icon: , }, { label: "Tablet preview", value: "tablet", - icon: , + icon: , }, { label: "Mobile preview", value: "mobile", - icon: , + icon: , }, ]; @@ -89,16 +89,16 @@ export const PreviewShell = ({
- + Preview - + Example {sourceComponents.length > 0 && ( - + Code )} @@ -145,7 +145,7 @@ export const PreviewShell = ({ target="_blank" > Open preview in new tab - + )} diff --git a/apps/docs/components/preview/source.tsx b/apps/docs/components/preview/source.tsx index e4c3369b..f1402373 100644 --- a/apps/docs/components/preview/source.tsx +++ b/apps/docs/components/preview/source.tsx @@ -1,7 +1,7 @@ "use client"; import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; -import { Hand } from "lucide-react"; +import { IconPointerFill24 } from "nucleo-core-fill-24"; import { Accordion, AccordionContent, @@ -24,7 +24,7 @@ export const PreviewSource = ({ source }: PreviewSourceProps) => (
- + {name}
diff --git a/apps/docs/components/reference.tsx b/apps/docs/components/reference.tsx index e81063dd..da156eb3 100644 --- a/apps/docs/components/reference.tsx +++ b/apps/docs/components/reference.tsx @@ -1,5 +1,5 @@ -import { LibraryIcon, SparklesIcon } from "lucide-react"; import Image from "next/image"; +import { IconBooksFill24, IconSparkleFill24 } from "nucleo-core-fill-24"; interface ReferenceProps { sources: string[]; @@ -35,7 +35,7 @@ const getSourceName = (url: string) => { export const Reference = ({ sources }: ReferenceProps) => (
- +

Inspired by

@@ -58,7 +58,7 @@ export const Reference = ({ sources }: ReferenceProps) => ( width={14} /> ) : ( - + )} {isValidUrl ? ( - + - +
); diff --git a/apps/docs/components/sponsor-card.tsx b/apps/docs/components/sponsor-card.tsx index 007c3d75..6fc34552 100644 --- a/apps/docs/components/sponsor-card.tsx +++ b/apps/docs/components/sponsor-card.tsx @@ -2,8 +2,8 @@ import { SponsorLogo } from "@docs/components/sponsor-logo"; import type { Sponsor, SponsorTier } from "@docs/lib/sponsors"; -import { Zap } from "lucide-react"; import Link from "next/link"; +import { IconBoltFill24 } from "nucleo-core-fill-24"; interface SponsorCardProps { sponsor: Sponsor; @@ -29,7 +29,7 @@ export function SponsorCard({ sponsor, tier }: SponsorCardProps) { {/* Velocity Badge */} {isVelocity && (
- + Velocity
)} diff --git a/apps/docs/components/sponsors-empty-state.tsx b/apps/docs/components/sponsors-empty-state.tsx index 914d96de..264cc963 100644 --- a/apps/docs/components/sponsors-empty-state.tsx +++ b/apps/docs/components/sponsors-empty-state.tsx @@ -1,13 +1,13 @@ -import { Heart } from "lucide-react"; import Link from "next/link"; +import { IconHeartFill24 } from "nucleo-core-fill-24"; import { Button } from "./smoothbutton"; export function SponsorsEmptyState() { return (
- - + +

No Active Sponsors Yet diff --git a/apps/docs/components/themes/theme-studio.tsx b/apps/docs/components/themes/theme-studio.tsx index 663a7a66..a83fc85d 100644 --- a/apps/docs/components/themes/theme-studio.tsx +++ b/apps/docs/components/themes/theme-studio.tsx @@ -24,18 +24,18 @@ import { import { cn } from "@repo/shadcn-ui/lib/utils"; import Scrubber from "@repo/smoothui/components/scrubber"; import SmoothButton from "@repo/smoothui/components/smooth-button"; -import { - Check, - Copy, - Crosshair, - ExternalLink, - Moon, - Search, - Shuffle, - Sun, - Terminal, -} from "lucide-react"; import dynamic from "next/dynamic"; +import { + IconCheckFill24, + IconConsoleFill24, + IconCopyFill24, + IconDiceFill24, + IconExternalLinkFill24, + IconMagnifierFill24, + IconMoonFill24, + IconSunFill24, + IconTargetFill24, +} from "nucleo-core-fill-24"; import { type ComponentType, useEffect, @@ -202,7 +202,7 @@ function StudioSidebar({ title="Center board" type="button" > - +

- + {entry === "light" ? ( - + ) : ( - + )} {entry} @@ -345,9 +345,9 @@ function StudioSidebar({ > {presetCode} {presetCopy.copied ? ( - + ) : ( - + )} shadcn/create (approx.) - +
@@ -386,9 +386,9 @@ function StudioSidebar({ variant="candy" > {commandCopy.copied ? ( - + ) : ( - + )} {commandCopy.copied ? "Copied!" : `Install ${palette.label}`} @@ -398,9 +398,9 @@ function StudioSidebar({ variant="outline" > {cssCopy.copied ? ( - + ) : ( - + )} {cssCopy.copied ? "Copied!" : "Copy CSS variables"} @@ -765,9 +765,9 @@ function CopyInstallButton({ slug }: { slug: string }) { type="button" > {copied ? ( - + ) : ( - + )} ); @@ -918,7 +918,7 @@ function PreviewCanvas({ className="text-muted-foreground transition-colors hover:text-foreground" href={`/docs/components/${slug}`} > - +
diff --git a/apps/docs/lib/nucleo-icons-plugin.ts b/apps/docs/lib/nucleo-icons-plugin.ts new file mode 100644 index 00000000..552044e6 --- /dev/null +++ b/apps/docs/lib/nucleo-icons-plugin.ts @@ -0,0 +1,160 @@ +import { + IconAccessibilityFill24, + IconAiFill24, + IconAlbumFill24, + IconBag24Fill24, + IconBellFill24, + IconBoltFill24, + IconBookBookmarkFill24, + IconBrainFill24, + IconCalendarFill24, + IconChatBubblePlusFill24, + IconCheckboxChecked2Fill24, + IconChevronDownFill24, + IconClock2Fill24, + IconCodeFill24, + IconCompass2Fill24, + IconCopyFill24, + IconCursor2Fill24, + IconDotsLoaderFill24, + IconDownloadFill24, + IconDropletFill24, + IconExpandFill24, + IconFileFill24, + IconGlobe2Fill24, + IconGrid3Fill24, + IconHashtagFill24, + IconHeartFill24, + IconImageFill24, + IconImagesFill24, + IconKeyFill24, + IconLayersFill24, + IconLetterAFill24, + IconLightbulbFill24, + IconListCheckFill24, + IconMagnetFill24, + IconMagnifierFill24, + IconMessage2Fill24, + IconPaletteFill24, + IconPeople3Fill24, + IconPopcornFill24, + IconPowerLevelFill24, + IconRefresh2Fill24, + IconRocketFill24, + IconShareLeft3Fill24, + IconSidebarRightFill24, + IconSliders2Fill24, + IconSparkleFill24, + IconStarFill24, + IconSunFill24, + IconSwapNodesFill24, + IconTagFill24, + IconToggleFill24, + IconTriangleWarningFill24, + IconTrophyFill24, + IconUploadFill24, + IconUserFill24, + IconWaveformLinesFill24, + IconWrenchFill24, +} from "nucleo-core-fill-24"; +import { createElement } from "react"; + +type NucleoIcon = React.ComponentType<{ className?: string; size?: number }>; + +const LUCIDE_TO_NUCLEO: Record = { + Accessibility: IconAccessibilityFill24, + Album: IconAlbumFill24, + ArrowRightLeft: IconSwapNodesFill24, + BadgeDollarSign: IconBoltFill24, + Bell: IconBellFill24, + BookOpen: IconBookBookmarkFill24, + Bot: IconAiFill24, + Brain: IconBrainFill24, + Briefcase: IconBag24Fill24, + Calendar: IconCalendarFill24, + ChevronDown: IconChevronDownFill24, + CircleDot: IconDotsLoaderFill24, + Code: IconCodeFill24, + Copy: IconCopyFill24, + Download: IconDownloadFill24, + Droplets: IconDropletFill24, + FileText: IconFileFill24, + Globe: IconGlobe2Fill24, + Grid3x3: IconGrid3Fill24, + Hash: IconHashtagFill24, + Heart: IconHeartFill24, + History: IconClock2Fill24, + Image: IconImageFill24, + Images: IconImagesFill24, + KeyRound: IconKeyFill24, + Lightbulb: IconLightbulbFill24, + ListOrdered: IconListCheckFill24, + Loader: IconDotsLoaderFill24, + Magnet: IconMagnetFill24, + MessageSquare: IconMessage2Fill24, + MessageSquarePlus: IconChatBubblePlusFill24, + MousePointer: IconCursor2Fill24, + MousePointerClick: IconCursor2Fill24, + MoveHorizontal: IconExpandFill24, + Navigation: IconCompass2Fill24, + Palette: IconPaletteFill24, + PanelLeft: IconSidebarRightFill24, + Popcorn: IconPopcornFill24, + Power: IconPowerLevelFill24, + RectangleEllipsis: IconDotsLoaderFill24, + RefreshCw: IconRefresh2Fill24, + Rocket: IconRocketFill24, + Search: IconMagnifierFill24, + Share2: IconShareLeft3Fill24, + ShoppingCart: IconBag24Fill24, + SlidersHorizontal: IconSliders2Fill24, + Sparkles: IconSparkleFill24, + Square: IconExpandFill24, + SquareCheck: IconCheckboxChecked2Fill24, + SquareStack: IconLayersFill24, + Star: IconStarFill24, + SunMedium: IconSunFill24, + Tag: IconTagFill24, + TextSearch: IconMagnifierFill24, + ToggleLeft: IconToggleFill24, + Triangle: IconTriangleWarningFill24, + Trophy: IconTrophyFill24, + Type: IconLetterAFill24, + Upload: IconUploadFill24, + User: IconUserFill24, + Users: IconPeople3Fill24, + Waves: IconWaveformLinesFill24, + Wrench: IconWrenchFill24, + Zap: IconBoltFill24, +}; + +function resolveIcon(name: string | undefined): React.ReactElement | undefined { + if (!name) { + return; + } + const Icon = LUCIDE_TO_NUCLEO[name]; + if (!Icon) { + console.warn(`[nucleo-icons-plugin] Unknown icon: ${name}`); + return; + } + return createElement(Icon, { className: "size-4" }); +} + +function replaceIcon(node: Record): Record { + if (node.icon === undefined || typeof node.icon === "string") { + node.icon = resolveIcon(node.icon as string | undefined); + } + return node; +} + +export function nucleoIconsPlugin() { + return { + name: "smoothui:nucleo-icons", + // biome-ignore lint/suspicious/noExplicitAny: matches fumadocs internal LoaderPlugin shape + transformPageTree: { + file: replaceIcon, + folder: replaceIcon, + separator: replaceIcon, + }, + } as unknown as import("fumadocs-core/source").LoaderPlugin; +} diff --git a/apps/docs/lib/source.ts b/apps/docs/lib/source.ts index 26769b31..2f91ad21 100644 --- a/apps/docs/lib/source.ts +++ b/apps/docs/lib/source.ts @@ -1,12 +1,12 @@ +import { nucleoIconsPlugin } from "@docs/lib/nucleo-icons-plugin"; import { type InferPageType, loader } from "fumadocs-core/source"; -import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons"; import { blog, docs } from "@/.source/server"; // See https://fumadocs.dev/docs/headless/source-api for more info export const source = loader({ baseUrl: "/docs", source: docs.toFumadocsSource(), - plugins: [lucideIconsPlugin()], + plugins: [nucleoIconsPlugin()], }); export const blogSource = loader({ diff --git a/apps/docs/package.json b/apps/docs/package.json index 5fc00a20..b5e22bed 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -34,6 +34,8 @@ "motion": "^12.23.25", "next": "16.2.6", "next-themes": "^0.4.6", + "nucleo-core-fill-24": "^1.1.3", + "nucleo-social-media": "^1.0.2", "oxc-transform": "^0.126.0", "popmotion": "^11.0.3", "postcss-nested": "^7.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58a724ff..994f66db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,12 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + nucleo-core-fill-24: + specifier: ^1.1.3 + version: 1.1.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + nucleo-social-media: + specifier: ^1.0.2 + version: 1.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) oxc-transform: specifier: ^0.126.0 version: 0.126.0 @@ -8364,6 +8370,18 @@ packages: resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + nucleo-core-fill-24@1.1.3: + resolution: {integrity: sha512-06Y3AGaO8dzkgSc/39sSm1TShhVDhnXiRwE/mqZcqwtEAF4N/Sk3hWP45Wcat1PGtagtPVYhD1ZktC7aOQla/g==} + peerDependencies: + react: ^19.2.1 + react-dom: ^19.2.1 + + nucleo-social-media@1.0.2: + resolution: {integrity: sha512-lKke7vUABXW65pJWBwhZ/wzcLVqPDbjh37KfxjWDhfvtEJLOcCTLl/N1C2K8D6B6m+G33mRmcN8jTxJqPJXEtw==} + peerDependencies: + react: ^19.2.1 + react-dom: ^19.2.1 + nypm@0.6.6: resolution: {integrity: sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==} engines: {node: '>=18'} @@ -10148,7 +10166,7 @@ snapshots: lodash.get: 4.4.2 make-error: 1.3.6 ts-node: 9.1.1(typescript@5.9.3) - tslib: 2.1.0 + tslib: 2.8.1 transitivePeerDependencies: - typescript @@ -14569,6 +14587,18 @@ snapshots: npm-to-yarn@3.0.1: {} + nucleo-core-fill-24@1.1.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + tslib: 2.8.1 + + nucleo-social-media@1.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + tslib: 2.8.1 + nypm@0.6.6: dependencies: citty: 0.2.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index aa8e23e4..5d735aa3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,7 @@ packages: allowBuilds: esbuild: true msw: true + nucleo-core-fill-24: true sharp: true overrides: From 9e37c1fad1ae3fc78de3ac6b8f0714fd93663492 Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Sun, 14 Jun 2026 08:18:11 +0200 Subject: [PATCH 068/124] style(docs): add thin scrollbars and antialiased text rendering --- apps/docs/app/global.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 9e8ecf06..a0738192 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -63,10 +63,12 @@ border-color: var(--color-gray-200, currentColor); } * { + scrollbar-color: gray transparent; + scrollbar-width: thin; @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground antialiased; } .dark { From 15b137f409c3cdccec1b3497f2926a21bdb52961 Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Sun, 14 Jun 2026 08:31:39 +0200 Subject: [PATCH 069/124] fix(docs): center blog post layout and fix AnimatedInput hydration mismatch Replace Math.random() ID with useId() in AnimatedInput to fix SSR/client mismatch. Center blog post with mx-auto. Add key props to DocsLayout children. --- apps/docs/app/blog/[slug]/page.tsx | 2 +- apps/docs/app/docs/layout.tsx | 6 +++--- packages/smoothui/components/animated-input/index.tsx | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/docs/app/blog/[slug]/page.tsx b/apps/docs/app/blog/[slug]/page.tsx index 4dc4fed0..f0513d05 100644 --- a/apps/docs/app/blog/[slug]/page.tsx +++ b/apps/docs/app/blog/[slug]/page.tsx @@ -43,7 +43,7 @@ export default async function BlogPostPage({ params }: PageProps) { title={post.data.title} url={post.url} /> -
+
) { nav={{ ...options.nav, mode: "top" }} sidebar={{ collapsible: false, - footer: , + footer: , }} tabMode="navbar" > {children} - + {/* SEO footer inside the scroll flow, spanning the full grid width so it lands at the bottom without breaking the 100dvh layout. DocsFooter constrains its content to the main column band. */} -
+
diff --git a/packages/smoothui/components/animated-input/index.tsx b/packages/smoothui/components/animated-input/index.tsx index ab74886b..830b86f8 100644 --- a/packages/smoothui/components/animated-input/index.tsx +++ b/packages/smoothui/components/animated-input/index.tsx @@ -1,13 +1,10 @@ import { motion, useReducedMotion } from "motion/react"; -import { useRef, useState } from "react"; +import { useId, useRef, useState } from "react"; const EASE_IN_OUT_CUBIC_X1 = 0.4; const EASE_IN_OUT_CUBIC_Y1 = 0; const EASE_IN_OUT_CUBIC_X2 = 0.2; const EASE_IN_OUT_CUBIC_Y2 = 1; -const RADIX_BASE_36 = 36; -const RANDOM_ID_START_INDEX = 2; -const RANDOM_ID_LENGTH = 9; const LABEL_TRANSITION = { duration: 0.28, @@ -51,7 +48,8 @@ export default function AnimatedInput({ const [isFocused, setIsFocused] = useState(false); const isFloating = !!val || isFocused; const shouldReduceMotion = useReducedMotion(); - const inputId = `animated-input-${Math.random().toString(RADIX_BASE_36).substring(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH)}`; + const reactId = useId(); + const inputId = `animated-input-${reactId.replace(/:/g, "")}`; const getLabelAnimation = () => { if (shouldReduceMotion) { From 8dd3dc2e6a10f6da6ca94f5496b936bfe1914e94 Mon Sep 17 00:00:00 2001 From: Eduardo Calvo Date: Sun, 14 Jun 2026 10:19:22 +0200 Subject: [PATCH 070/124] feat(docs): add changelog popover and theme switch to docs nav Bell icon opens popover with real SmoothUI changelog entries (from changelog.mdx). Theme switch added alongside. JSON-LD moved from next/script to native