Skip to content

doomsun-dev/shadcn-cljs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shadcn-cljs

Bridge shadcn/ui React components to ClojureScript with compile-time component resolution.

shadcn-cljs is a build-time code generator that reads your components.json and tsconfig.json, scans your compiled shadcn component files, and produces a .cljc namespace with:

  • A compile-time macro ($sui) for keyword→component resolution
  • Runtime resolution for dynamic component lookup
  • Auto-discovered hooks and cn utility

Works with UIx and Helix. Only runs on the JVM during shadow-cljs compilation.

Quick Start

Assumes a working shadcn/ui + TypeScript compilation setup. See Prerequisites if you're starting from scratch.

1. Add dependency

In your deps.edn (:dev alias):

io.github.doomsun-dev/shadcn-cljs {:git/url "https://github.com/doomsun-dev/shadcn-cljs.git"
                                   :sha     "..."}

2. Configure build hook

In your shadow-cljs.edn:

:build-hooks [(shadcn-cljs.hook/shadow-hook
                {:output-ns  "lib.shadcn-ui"
                 :output-dir "src/cljs"})]

That's it. All paths are inferred from components.json and tsconfig.json.

3. Use in ClojureScript

(ns my-app.ui
  (:require [lib.shadcn-ui :refer [$sui cn]]))

;; Compile-time component resolution
($sui :button {:variant "outline"} "Click me")
($sui :card-header {} "Title")

;; Dynamic resolution
($sui some-variable {:class (cn "p-4" "bg-muted")})

Configuration Reference

Key Required Default Description
:output-ns No "shadcn-cljs.ui" Namespace of generated file
:output-dir No "src/main" Directory to write the generated .cljc file (must be on classpath)
:wrapper No :uix React wrapper library (:uix or :helix)
:macro-name No "$sui" Name of the generated macro
:components-json No "components.json" Path to shadcn config file
:tsconfig No "tsconfig.json" Path to TypeScript config

All other paths (components dir, import prefix, utils, hooks) are derived from components.json + tsconfig.json.

Hooks

Hooks are handled automatically:

  • Standalone hooks (in the hooks directory) are auto-discovered by reading JS exports
  • Known component hooks (like useSidebar) are built into the library

Custom Wrapper Library

For libraries beyond UIx/Helix:

{:wrapper        :custom
 :wrapper-config {:dollar-sym   'my.lib/$
                  :require      '[my.lib :refer [$] :rename {$ my-$}]
                  :display-name "MyLib"}}

Icons (Optional)

Separate hook for icon libraries like lucide-react:

:build-hooks
[(shadcn-cljs.hook/shadow-hook
   {:output-ns  "lib.shadcn-ui"
    :output-dir "src/cljs"})

 (shadcn-cljs.icons.hook/shadow-hook
   {:output-ns   "lib.lucide-react"
    :output-dir  "src/cljs"
    :npm-package "lucide-react"})]

Usage:

(ns my-app.ui
  (:require [lib.lucide-react :refer [$lucide]]))

($lucide :panel-left {:class "size-4"})
($lucide :bell)

Prerequisites

shadcn-cljs reads your existing components.json and tsconfig.json to find everything it needs. You need a TypeScript compilation pipeline that gets shadcn components from source to JS output before the hook runs.

Project layout

your-project/
├── components.json                # shadcn CLI config (shadcn-cljs reads this)
├── tsconfig.json                  # TypeScript config (shadcn-cljs reads this)
├── src/main/js/shadcn/            # TypeScript source (shadcn CLI writes here)
│   ├── components/ui/
│   ├── hooks/
│   └── lib/utils.ts
├── src/main/gen/shadcn/           # Compiled JS output (tsc writes here)
│   ├── components/ui/             # shadcn-cljs scans these
│   ├── hooks/
│   └── lib/utils.js
└── src/main/cljs/                 # Generated .cljc files land here

TypeScript compilation

Configure tsconfig.json so TypeScript compiles shadcn source into a gen/ output directory:

{
  "compilerOptions": {
    "target": "ES2017",
    "outDir": "./src/main/gen",
    "rootDir": "./src/main/js",
    "module": "commonjs",
    "moduleResolution": "node",
    "jsx": "react-jsx",
    "baseUrl": "./",
    "paths": {
      "@/shadcn/*": ["./src/main/js/shadcn/*"],
      "@/*": ["./src/main/js/*"]
    }
  },
  "include": ["src/main/js/**/*.tsx", "src/main/js/**/*.ts"],
  "exclude": ["node_modules", "src/main/gen"]
}

Run tsc --build && tsc-alias before starting shadow-cljs.

shadow-cljs JS interop

Tell shadow-cljs where to find your compiled JS:

;; shadow-cljs.edn
:js-options {:js-package-dirs ["node_modules" "src/main/gen"]}

Adding components

npx shadcn add button card dialog

After adding components, recompile TypeScript and restart shadow-cljs. The build hook detects new .js files and regenerates automatically.

clj-kondo

The generated macros need a :lint-as entry in your .clj-kondo/config.edn. Map to your wrapper's $ macro:

UIx:

{:lint-as {lib.shadcn-ui/$sui       uix.core/$
           lib.lucide-react/$lucide uix.core/$}}

Helix:

{:lint-as {lib.shadcn-ui/$sui       helix.core/$
           lib.lucide-react/$lucide helix.core/$}}

Adjust the namespace if you customized :output-ns or :macro-name.

How It Works

  1. Config: Reads components.json + tsconfig.json to derive all paths
  2. Discovery: Scans the components and hooks directories for .js files
  3. Generation: Produces a .cljc file with CLJ macros + CLJS runtime code
  4. Staleness: Only regenerates when the output file is missing or older than the components directory
  5. Macro: $sui resolves component keywords to JS module property access at compile time
  6. Runtime: resolve-component handles dynamic keyword lookup for cases where the tag isn't a literal

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors