@create-ui/cli 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EWAP55CF.js → chunk-5YX4Z2U3.js} +3 -3
- package/dist/{chunk-EWAP55CF.js.map → chunk-5YX4Z2U3.js.map} +1 -1
- package/dist/{chunk-UVIUVCLG.js → chunk-EF7MDUJN.js} +3 -3
- package/dist/{chunk-UVIUVCLG.js.map → chunk-EF7MDUJN.js.map} +1 -1
- package/dist/{chunk-MK3CCMH4.js → chunk-Z73MEB7I.js} +3 -3
- package/dist/{chunk-MK3CCMH4.js.map → chunk-Z73MEB7I.js.map} +1 -1
- package/dist/icons/index.d.ts +3 -3
- package/dist/icons/index.js +1 -1
- package/dist/icons/index.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/skills/createui/SKILL.md +54 -53
- package/dist/skills/createui/agents/openai.yml +1 -1
- package/dist/skills/createui/assets/createui-small.png +0 -0
- package/dist/skills/createui/assets/createui.png +0 -0
- package/dist/skills/createui/cli.md +25 -93
- package/dist/skills/createui/customization.md +48 -31
- package/dist/skills/createui/evals/evals.json +12 -11
- package/dist/skills/createui/mcp.md +6 -32
- package/dist/skills/createui/rules/composition.md +112 -101
- package/dist/skills/createui/rules/forms.md +31 -24
- package/dist/skills/createui/rules/icons.md +41 -25
- package/dist/skills/createui/rules/styling.md +5 -9
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/dist/skills/createui/contributing.md +0 -213
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Contents
|
|
4
4
|
|
|
5
|
-
- [
|
|
5
|
+
- [Icons and assets come from @create-ui/assets](#icons-and-assets-come-from-create-uiassets)
|
|
6
6
|
- [Icons in Button use the leadingIcon / trailingIcon props](#icons-in-button-use-the-leadingicon--trailingicon-props)
|
|
7
7
|
- [Icon-only buttons](#icon-only-buttons)
|
|
8
8
|
- [No sizing classes on icons inside components](#no-sizing-classes-on-icons-inside-components)
|
|
@@ -10,11 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Icons and assets come from @create-ui/assets
|
|
14
14
|
|
|
15
|
-
**
|
|
15
|
+
**All icons and marks come from `@create-ui/assets`** — Create UI's own asset library. Registry components already import from it, and the CLI ships those imports as-is. Never reach for `lucide-react`, `@tabler/icons-react`, or any other icon package.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
**General-purpose UI icons** → `@create-ui/assets/icons` (Remix Icon, `Ri*` names). This subpath re-exports [`@remixicon/react`](https://remixicon.com), so the full Remix set is available:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { RiArrowDownSLine, RiSearchLine, RiCheckLine } from "@create-ui/assets/icons"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Brand, flag, payment, social, and badge marks** → category subpaths (PascalCase, 900+ typed SVG components):
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Github } from "@create-ui/assets/social"
|
|
27
|
+
import { Turkey } from "@create-ui/assets/flags"
|
|
28
|
+
import { VisaColor } from "@create-ui/assets/payments"
|
|
29
|
+
import { Docker } from "@create-ui/assets/brands"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Subpaths: `icons` (Remix UI icons), `social`, `flags`, `payments`, `brands`, `banks`, `badges`, `crypto` (Web3 Icons). Marks that ship multiple treatments carry a suffix — pick the variant: `VisaColor` / `VisaBlack` / `VisaWhite`.
|
|
33
|
+
|
|
34
|
+
**Sizing & color differ from UI icons.** Standalone marks have their source dimensions stripped, so set a size explicitly (`className="size-6"` or `width`/`height`), and their brand colors are baked in — don't recolor; use the `Black` / `White` variant for contrast. This is the opposite of `Ri*` icons rendered *inside* Create UI components (`Button`, `DropdownMenuItem`, …), which are auto-sized via CVA — see [No sizing classes on icons inside components](#no-sizing-classes-on-icons-inside-components).
|
|
18
35
|
|
|
19
36
|
---
|
|
20
37
|
|
|
@@ -26,22 +43,22 @@ Inside this monorepo, components import their icons from `@create-ui/assets/icon
|
|
|
26
43
|
|
|
27
44
|
```tsx
|
|
28
45
|
<Button>
|
|
29
|
-
<
|
|
46
|
+
<RiSearchLine className="mr-2 size-4" />
|
|
30
47
|
Search
|
|
31
48
|
</Button>
|
|
32
49
|
|
|
33
50
|
<Button>
|
|
34
51
|
Next
|
|
35
|
-
<
|
|
52
|
+
<RiArrowRightLine className="ml-2 size-4" />
|
|
36
53
|
</Button>
|
|
37
54
|
```
|
|
38
55
|
|
|
39
56
|
**Correct:**
|
|
40
57
|
|
|
41
58
|
```tsx
|
|
42
|
-
<Button leadingIcon={<
|
|
59
|
+
<Button leadingIcon={<RiSearchLine />}>Search</Button>
|
|
43
60
|
|
|
44
|
-
<Button trailingIcon={<
|
|
61
|
+
<Button trailingIcon={<RiArrowRightLine />}>Next</Button>
|
|
45
62
|
```
|
|
46
63
|
|
|
47
64
|
---
|
|
@@ -54,31 +71,31 @@ For a button that shows only an icon, set `iconOnly`, pass the icon via `leading
|
|
|
54
71
|
|
|
55
72
|
```tsx
|
|
56
73
|
<Button>
|
|
57
|
-
<
|
|
74
|
+
<RiSearchLine className="size-4" />
|
|
58
75
|
</Button>
|
|
59
76
|
```
|
|
60
77
|
|
|
61
78
|
**Correct:**
|
|
62
79
|
|
|
63
80
|
```tsx
|
|
64
|
-
<Button iconOnly aria-label="Search" leadingIcon={<
|
|
81
|
+
<Button iconOnly aria-label="Search" leadingIcon={<RiSearchLine />} />
|
|
65
82
|
|
|
66
|
-
<Button iconOnly aria-label="More options" appearance="ghost" leadingIcon={<
|
|
83
|
+
<Button iconOnly aria-label="More options" appearance="ghost" leadingIcon={<RiMore2Line />} />
|
|
67
84
|
```
|
|
68
85
|
|
|
69
86
|
---
|
|
70
87
|
|
|
71
88
|
## No sizing classes on icons inside components
|
|
72
89
|
|
|
73
|
-
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons rendered inside `Button`, `DropdownMenuItem`, `
|
|
90
|
+
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons rendered inside `Button`, `DropdownMenuItem`, `InlineAlert`, `Toast`, or other Create UI components — unless the user explicitly asks for a custom icon size.
|
|
74
91
|
|
|
75
92
|
**Incorrect:**
|
|
76
93
|
|
|
77
94
|
```tsx
|
|
78
|
-
<Button leadingIcon={<
|
|
95
|
+
<Button leadingIcon={<RiSearchLine className="size-4" />}>Search</Button>
|
|
79
96
|
|
|
80
97
|
<DropdownMenuItem>
|
|
81
|
-
<
|
|
98
|
+
<RiSettings3Line className="mr-2 size-4" />
|
|
82
99
|
Settings
|
|
83
100
|
</DropdownMenuItem>
|
|
84
101
|
```
|
|
@@ -86,10 +103,10 @@ Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other s
|
|
|
86
103
|
**Correct:**
|
|
87
104
|
|
|
88
105
|
```tsx
|
|
89
|
-
<Button leadingIcon={<
|
|
106
|
+
<Button leadingIcon={<RiSearchLine />}>Search</Button>
|
|
90
107
|
|
|
91
108
|
<DropdownMenuItem>
|
|
92
|
-
<
|
|
109
|
+
<RiSettings3Line />
|
|
93
110
|
Settings
|
|
94
111
|
</DropdownMenuItem>
|
|
95
112
|
```
|
|
@@ -98,33 +115,32 @@ Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other s
|
|
|
98
115
|
|
|
99
116
|
## Pass icons as component values, not string keys
|
|
100
117
|
|
|
101
|
-
Use `icon={
|
|
118
|
+
Use `icon={RiCheckLine}`, not a string key into a lookup map.
|
|
102
119
|
|
|
103
120
|
**Incorrect:**
|
|
104
121
|
|
|
105
122
|
```tsx
|
|
106
123
|
const iconMap = {
|
|
107
|
-
check:
|
|
108
|
-
alert:
|
|
124
|
+
check: RiCheckLine,
|
|
125
|
+
alert: RiAlertLine,
|
|
109
126
|
}
|
|
110
127
|
|
|
111
|
-
function
|
|
128
|
+
function StatusIcon({ icon }: { icon: string }) {
|
|
112
129
|
const Icon = iconMap[icon]
|
|
113
130
|
return <Icon />
|
|
114
131
|
}
|
|
115
132
|
|
|
116
|
-
<
|
|
133
|
+
<StatusIcon icon="check" />
|
|
117
134
|
```
|
|
118
135
|
|
|
119
136
|
**Correct:**
|
|
120
137
|
|
|
121
138
|
```tsx
|
|
122
|
-
|
|
123
|
-
import { CheckIcon } from "lucide-react"
|
|
139
|
+
import { RiCheckLine } from "@create-ui/assets/icons"
|
|
124
140
|
|
|
125
|
-
function
|
|
141
|
+
function StatusIcon({ icon: Icon }: { icon: React.ComponentType }) {
|
|
126
142
|
return <Icon />
|
|
127
143
|
}
|
|
128
144
|
|
|
129
|
-
<
|
|
145
|
+
<StatusIcon icon={RiCheckLine} />
|
|
130
146
|
```
|
|
@@ -87,7 +87,7 @@ Create UI's own `Button` implements its `danger` / `success` variants with the r
|
|
|
87
87
|
|
|
88
88
|
Every primitive exposes its full visual range through typed props. Drive appearance through those props, not through manual `border` / `bg` / `hover:` class overrides.
|
|
89
89
|
|
|
90
|
-
`Button` has four styling axes (verify any other component
|
|
90
|
+
`Button` has four styling axes (verify any other component's props from its installed source under the project's `ui` alias — e.g. `components/ui/<name>.tsx` — or with `npx @create-ui/cli view <name>`):
|
|
91
91
|
|
|
92
92
|
- `variant`: `primary` (default), `neutral-solid`, `neutral-light`, `danger`, `success`, `inverse-solid`, `inverse-light`
|
|
93
93
|
- `appearance`: `solid` (default), `outline`, `ghost`, `soft`
|
|
@@ -139,17 +139,13 @@ Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for ove
|
|
|
139
139
|
**Incorrect:**
|
|
140
140
|
|
|
141
141
|
```tsx
|
|
142
|
-
<
|
|
143
|
-
<CardContent>Dashboard</CardContent>
|
|
144
|
-
</Card>
|
|
142
|
+
<Input className="bg-sky-100 text-sky-900 font-bold" placeholder="Search…" />
|
|
145
143
|
```
|
|
146
144
|
|
|
147
145
|
**Correct:**
|
|
148
146
|
|
|
149
147
|
```tsx
|
|
150
|
-
<
|
|
151
|
-
<CardContent>Dashboard</CardContent>
|
|
152
|
-
</Card>
|
|
148
|
+
<Input className="mx-auto max-w-md" placeholder="Search…" />
|
|
153
149
|
```
|
|
154
150
|
|
|
155
151
|
---
|
|
@@ -210,7 +206,7 @@ Semantic tokens already swap with the `.dark` class — don't write parallel `da
|
|
|
210
206
|
|
|
211
207
|
## Use cn() for conditional classes
|
|
212
208
|
|
|
213
|
-
Use `cn()` for conditional or merged class names instead of template-string ternaries. Import it from `@/lib/utils`
|
|
209
|
+
Use `cn()` for conditional or merged class names instead of template-string ternaries. Import it from `@/lib/utils` (the project's `utils` alias).
|
|
214
210
|
|
|
215
211
|
**Incorrect:**
|
|
216
212
|
|
|
@@ -250,4 +246,4 @@ Use `outline-primary-700` (or `outline-primary-500`) for primary controls and `o
|
|
|
250
246
|
|
|
251
247
|
## No manual z-index on overlay components
|
|
252
248
|
|
|
253
|
-
`
|
|
249
|
+
`DropdownMenu`, `Tooltip`, `InfoTooltip`, and `CommandDialog` manage their own stacking. Never add `z-50` or `z-[999]`.
|
package/dist/utils/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {a}from'../chunk-VQCXAURP.js';import {W as W$1}from'../chunk-
|
|
1
|
+
import {a}from'../chunk-VQCXAURP.js';import {W as W$1}from'../chunk-5YX4Z2U3.js';export{B as transformMenu}from'../chunk-5YX4Z2U3.js';import'../chunk-Y7WZRQWW.js';import J from'postcss';import M from'postcss-selector-parser';import {z as z$1}from'zod';import {Project,ScriptKind,SyntaxKind,Node}from'ts-morph';var A="cn-",V=z$1.record(z$1.string().startsWith(A),z$1.string());function v(t){let e=J.parse(t),r={};return e.walkRules(n=>{let s=n.selectors??[];if(s.length===0)return;let i=F(n);if(i)for(let g of s){let l=R(g);M(o=>{o.each(a=>{let f=$(a);if(!f)return;let u=f.value;u.startsWith(A)&&(r[u]=r[u]?`${i} ${r[u]}`:i);});}).processSync(l);}}),V.parse(r)}function R(t){return t.replace(/\s*&\s*/g,"").trim()}function F(t){let e=[];for(let r of t.nodes||[])if(r.type==="atrule"&&r.name==="apply"){let n=r.params.trim();n&&e.push(n);}return e.length===0?null:e.join(" ")}function $(t){let e=[];return t.walkClasses(r=>{r.value.startsWith(A)&&e.push(r);}),e.length===0?null:e[e.length-1]}var z=new Set(["cn-menu-target"]);function p(t){return Node.isStringLiteral(t)||Node.isNoSubstitutionTemplateLiteral(t)}var O=async({sourceFile:t,styleMap:e})=>{let r=new Set;return W(t,e,r),w(t,e,r),_(t,e,r),t};function E(t,e,r){let n=t.getLiteralText(),s=y(n);if(s.length===0)return;let i=s.filter(l=>!r.has(l));if(i.length===0){let l=m(n);t.setLiteralValue(l);return}let g=i.map(l=>e[l]).filter(l=>!!l);if(g.length>0){let l=g.join(" "),o=m(d(l,n));t.setLiteralValue(o),i.forEach(a=>r.add(a));}else {let l=m(n);t.setLiteralValue(l);}}function W(t,e,r){t.forEachDescendant(n=>{if(!Node.isCallExpression(n))return;let s=n.getExpression();if(!Node.isIdentifier(s)||s.getText()!=="cva")return;let i=n.getArguments()[0];Node.isStringLiteral(i)&&E(i,e,r);let g=n.getArguments()[1];if(!g||!Node.isObjectLiteralExpression(g))return;let l=g.getProperties().find(a=>Node.isPropertyAssignment(a)&&Node.isIdentifier(a.getNameNode())&&a.getNameNode().getText()==="variants");if(!l||!Node.isPropertyAssignment(l))return;let o=l.getInitializer();!o||!Node.isObjectLiteralExpression(o)||o.getProperties().forEach(a=>{if(!Node.isPropertyAssignment(a))return;let f=a.getInitializer();!f||!Node.isObjectLiteralExpression(f)||f.getProperties().forEach(u=>{if(!Node.isPropertyAssignment(u))return;let x=u.getInitializer();x&&Node.isStringLiteral(x)&&E(x,e,r);});});});}function w(t,e,r){t.forEachDescendant(n=>{if(!Node.isJsxAttribute(n)||n.getNameNode().getText()!=="className")return;let s=n.getInitializer();if(!s)return;let i=D(s);if(i.length===0)return;let g=n.getParent()?.getParent();if(!g||!Node.isJsxOpeningElement(g)&&!Node.isJsxSelfClosingElement(g))return;let l=i.filter(a=>!r.has(a));if(l.length===0){I(s);return}let o=l.map(a=>e[a]).filter(a=>!!a);if(o.length>0){let a=o.join(" ");B(g,a);}else I(s);});}function D(t){let e=[];if(p(t))return y(t.getLiteralText());if(!Node.isJsxExpression(t))return e;let r=t.getExpression();if(!r)return e;if(p(r))return y(r.getLiteralText());if(Node.isCallExpression(r)&&T(r))for(let n of r.getArguments())p(n)&&e.push(...y(n.getLiteralText()));return e}function I(t){if(p(t)){let r=m(t.getLiteralText());t.setLiteralValue(r);return}if(!Node.isJsxExpression(t))return;let e=t.getExpression();if(e){if(p(e)){let r=m(e.getLiteralText());e.setLiteralValue(r);return}if(Node.isCallExpression(e)&&T(e)){for(let r of e.getArguments())if(p(r)){let n=m(r.getLiteralText());r.setLiteralValue(n);}C(e);}}}function y(t){let e=t.matchAll(/\bcn-[\w-]+\b/g);return Array.from(e,r=>r[0])}function m(t){return t.replace(/\bcn-[\w-]+\b/g,e=>z.has(e)?e:"").replace(/\s+/g," ").trim()}function C(t){if(!T(t))return;let e=t.getArguments(),r=e.filter(n=>p(n)?n.getLiteralText().trim()!=="":true);if(r.length!==e.length){let n=r.map(i=>i.getText()),s=t.getParent();s&&Node.isJsxExpression(s)?s.replaceWithText(`{cn(${n.join(", ")})}`):t.replaceWithText(`cn(${n.join(", ")})`);}}function B(t,e){if(!Node.isJsxOpeningElement(t)&&!Node.isJsxSelfClosingElement(t))return;let r=t.getAttributes().find(i=>Node.isJsxAttribute(i)&&i.getNameNode().getText()==="className");if(!r||!Node.isJsxAttribute(r)){t.addAttribute({name:"className",initializer:`{cn(${JSON.stringify(e)})}`});return}let n=r.getInitializer();if(!n){r.setInitializer(`{cn(${JSON.stringify(e)})}`);return}if(p(n)){let i=n.getLiteralText(),g=m(d(e,i));n.setLiteralValue(g);return}if(!Node.isJsxExpression(n))return;let s=n.getExpression();if(!s){r.setInitializer(`{cn(${JSON.stringify(e)})}`);return}if(p(s)){let i=s.getLiteralText(),g=m(d(e,i));s.setLiteralValue(g);return}if(Node.isCallExpression(s)&&T(s)){let i=s.getArguments()[0];if(p(i)){let o=i.getLiteralText(),a=m(d(e,o));i.setLiteralValue(a);for(let f=1;f<s.getArguments().length;f++){let u=s.getArguments()[f];if(p(u)){let x=u.getLiteralText(),S=m(x);S!==x&&u.setLiteralValue(S);}}C(s);return}let g=s.getArguments().map(o=>{if(p(o)){let a=m(o.getLiteralText());return a?JSON.stringify(a):null}return o.getText()}).filter(o=>o!==null),l=[JSON.stringify(e),...g];r.setInitializer(`{cn(${l.join(", ")})}`);return}r.setInitializer(`{cn(${JSON.stringify(e)}, ${s.getText()})}`);}function d(t,e){let r=e.split(/\s+/).filter(Boolean);return [...t.split(/\s+/).filter(Boolean),...r].join(" ").trim()}function T(t){let e=t.getExpression();return Node.isIdentifier(e)&&e.getText()==="cn"}function _(t,e,r){t.forEachDescendant(n=>{if(!Node.isCallExpression(n))return;let s=n.getExpression();if(!(!Node.isIdentifier(s)||s.getText()!=="mergeProps"))for(let i of n.getArguments()){if(!Node.isObjectLiteralExpression(i))continue;let g=i.getProperties().find(o=>Node.isPropertyAssignment(o)&&Node.isIdentifier(o.getNameNode())&&o.getNameNode().getText()==="className");if(!g||!Node.isPropertyAssignment(g))continue;let l=g.getInitializer();if(l&&Node.isCallExpression(l)&&T(l)){let o=k(l);if(o.length===0)continue;let a=o.filter(u=>!r.has(u));if(a.length===0){b(l);continue}let f=a.map(u=>e[u]).filter(u=>!!u);if(f.length>0){let u=f.join(" ");K(l,u,r,a);}else b(l);}}});}function k(t){let e=[];for(let r of t.getArguments())p(r)&&e.push(...y(r.getLiteralText()));return e}function b(t){for(let e of t.getArguments())if(p(e)){let r=m(e.getLiteralText());e.setLiteralValue(r);}C(t);}function K(t,e,r,n){let s=t.getArguments()[0];if(p(s)){let o=s.getLiteralText(),a=m(d(e,o));s.setLiteralValue(a),n.forEach(f=>r.add(f));for(let f=1;f<t.getArguments().length;f++){let u=t.getArguments()[f];if(p(u)){let x=u.getLiteralText(),S=m(x);S!==x&&u.setLiteralValue(S);}}C(t);return}let i=t.getArguments().map(o=>{if(p(o)){let a=m(o.getLiteralText());return a?JSON.stringify(a):null}return o.getText()}).filter(o=>o!==null),g=[JSON.stringify(e),...i];n.forEach(o=>r.add(o)),t.getParent()&&t.replaceWithText(`cn(${g.join(", ")})`);}async function X(t,{styleMap:e,transformers:r=[O]}){let s=new Project({useInMemoryFileSystem:true}).createSourceFile("component.tsx",t,{scriptKind:ScriptKind.TSX,overwrite:true});for(let i of r)await i({sourceFile:s,styleMap:e});return s.getText()}var P="lucide",q=async({sourceFile:t,config:e})=>{if(!e.iconLibrary||!(e.iconLibrary in a))return t;let r=P,n=e.iconLibrary;if(r===n)return t;let s=await W$1(),i=[];for(let g of t.getImportDeclarations()??[])if(g.getModuleSpecifier()?.getText()===`"${a[P].import}"`){for(let l of g.getNamedImports()??[]){let o=l.getName(),a=s[o]?.[n];!a||i.includes(a)||(i.push(a),l.remove(),t.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement).filter(f=>f.getTagNameNode()?.getText()===o).forEach(f=>f.getTagNameNode()?.replaceWithText(a)));}g.getNamedImports()?.length===0&&g.remove();}if(i.length>0){let g=t.addImportDeclaration({moduleSpecifier:a[n]?.import,namedImports:i.map(l=>({name:l}))});H(t)||g.replaceWithText(g.getText().replace(";",""));}return t};function H(t){return t.getImportDeclarations()?.[0]?.getText().endsWith(";")??false}export{v as createStyleMap,q as transformIcons,X as transformStyle};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# Contributing to the Create UI Monorepo
|
|
2
|
-
|
|
3
|
-
This guide is for contributors working **inside** the Create UI monorepo — adding or editing
|
|
4
|
-
registry components, the CLI, or the documentation site. If you only want to consume Create UI in
|
|
5
|
-
your own app, see [`cli.md`](./cli.md) instead.
|
|
6
|
-
|
|
7
|
-
## Contents
|
|
8
|
-
|
|
9
|
-
- [Monorepo layout](#monorepo-layout)
|
|
10
|
-
- [Dev, build & quality commands](#dev-build--quality-commands)
|
|
11
|
-
- [Tests](#tests)
|
|
12
|
-
- [The registry](#the-registry)
|
|
13
|
-
- [Authoring a component](#authoring-a-component)
|
|
14
|
-
- [Scaffolding & validation](#scaffolding--validation)
|
|
15
|
-
- [Testing the CLI locally](#testing-the-cli-locally)
|
|
16
|
-
- [Website: three-layer architecture](#website-three-layer-architecture)
|
|
17
|
-
- [Component rules recap](#component-rules-recap)
|
|
18
|
-
- [Commit convention](#commit-convention)
|
|
19
|
-
|
|
20
|
-
## Monorepo layout
|
|
21
|
-
|
|
22
|
-
Create UI is a **pnpm 9.0.6 + Turbo** monorepo with two workspaces:
|
|
23
|
-
|
|
24
|
-
| Workspace | What it is | Stack |
|
|
25
|
-
|---|---|---|
|
|
26
|
-
| `apps/v4` | Documentation site & component showcase | Next.js 16 (App Router), React 19, Tailwind CSS v4 |
|
|
27
|
-
| `packages/createui` | The `createui` CLI published to npm | Commander.js, tsup (ESM-only) |
|
|
28
|
-
|
|
29
|
-
The site is also where the **registry** lives (`apps/v4/registry/`). The CLI reads that registry —
|
|
30
|
-
in the monorepo it points at the local dev server (`http://localhost:4000/r`).
|
|
31
|
-
|
|
32
|
-
## Dev, build & quality commands
|
|
33
|
-
|
|
34
|
-
Run all commands from the repo root. Package manager is **pnpm** — do not use npm or yarn.
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
# Develop
|
|
38
|
-
pnpm dev # everything, in parallel via Turbo
|
|
39
|
-
pnpm v4:dev # docs/showcase site only (port 4000)
|
|
40
|
-
pnpm createui:dev # CLI only, in watch mode
|
|
41
|
-
|
|
42
|
-
# Build
|
|
43
|
-
pnpm build # all workspaces
|
|
44
|
-
pnpm v4:build # site only
|
|
45
|
-
pnpm createui:build # CLI only
|
|
46
|
-
|
|
47
|
-
# Quality
|
|
48
|
-
pnpm check # lint + typecheck + format:check (run this before opening a PR)
|
|
49
|
-
pnpm lint # ESLint
|
|
50
|
-
pnpm lint:fix # ESLint with --fix
|
|
51
|
-
pnpm typecheck # TypeScript
|
|
52
|
-
pnpm format:write # Prettier write
|
|
53
|
-
pnpm format:check # Prettier check
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Tests
|
|
57
|
-
|
|
58
|
-
The test suite needs the v4 dev server running; `pnpm test` handles that for you.
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
pnpm test # starts the v4 dev server, then runs Vitest
|
|
62
|
-
pnpm test:dev # runs Vitest against an already-running server (no startup)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## The registry
|
|
66
|
-
|
|
67
|
-
Every UI component is defined under `apps/v4/registry/` and served to users through the CLI.
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
apps/v4/registry/
|
|
71
|
-
ui/ core UI components (button, select, dialog, …)
|
|
72
|
-
examples/ demos shown in the docs
|
|
73
|
-
blocks/ larger composed blocks
|
|
74
|
-
hooks/ custom React hooks
|
|
75
|
-
lib/ utilities (e.g. cn())
|
|
76
|
-
internal/ internal-only components (not exposed to users)
|
|
77
|
-
components/ shared registry components
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Each subdirectory has a `_registry.ts` that exports a metadata array, and every array is aggregated
|
|
81
|
-
in `apps/v4/registry/registry.ts`.
|
|
82
|
-
|
|
83
|
-
In-repo registry entries **omit `content`** — `pnpm registry:build` injects each file's source when
|
|
84
|
-
it generates `apps/v4/public/r/<name>.json`. Real entry shapes:
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
// apps/v4/registry/ui/_registry.ts
|
|
88
|
-
{
|
|
89
|
-
name: "button",
|
|
90
|
-
type: "registry:ui",
|
|
91
|
-
dependencies: ["class-variance-authority", "radix-ui"],
|
|
92
|
-
files: [{ path: "ui/button.tsx", type: "registry:ui" }],
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
name: "select",
|
|
97
|
-
type: "registry:ui",
|
|
98
|
-
registryDependencies: ["field", "dropdown-menu"],
|
|
99
|
-
files: [{ path: "ui/select.tsx", type: "registry:ui" }],
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
The `type` values, file objects (`{ path, type, content?, target? }`), and item fields are defined by
|
|
104
|
-
the registry-item schema (`$schema: https://createui.co/schema/registry-item.json`). Use
|
|
105
|
-
`dependencies` for npm packages and `registryDependencies` for other registry items the component
|
|
106
|
-
needs.
|
|
107
|
-
|
|
108
|
-
## Authoring a component
|
|
109
|
-
|
|
110
|
-
1. Create `apps/v4/registry/ui/<name>.tsx`:
|
|
111
|
-
- use **`cva`** for variants and **`cn()`** (from `@/registry/lib/utils`) to merge classes
|
|
112
|
-
- put `data-slot="<name>"` on the root element
|
|
113
|
-
- support polymorphism with **`asChild`** via the Radix `Slot`
|
|
114
|
-
- type props as `React.ComponentProps<"el"> & VariantProps<typeof variants> & { … }`
|
|
115
|
-
- use a **named export** (`export { Name }`)
|
|
116
|
-
2. Add an entry to `apps/v4/registry/ui/_registry.ts` (shape above).
|
|
117
|
-
3. Add a demo under `apps/v4/registry/examples/<name>-demo.tsx` and register it in
|
|
118
|
-
`apps/v4/registry/examples/_registry.ts`.
|
|
119
|
-
4. Run **`pnpm registry:build`** to rebuild the registry (it also runs `lint:fix` + format and
|
|
120
|
-
regenerates the `public/r/*.json` files).
|
|
121
|
-
|
|
122
|
-
> Filenames are **kebab-case**; exports are PascalCase named exports. There is **no** `pnpm tokens:build` —
|
|
123
|
-
> tokens live in `apps/v4/styles/globals.css` and are edited directly.
|
|
124
|
-
|
|
125
|
-
## Scaffolding & validation
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
pnpm scaffold <name> # create a component + example + registry entry
|
|
129
|
-
pnpm scaffold <name> --existing # generate an example from an existing component (parses CVA)
|
|
130
|
-
pnpm scaffold <name> --dry-run # preview output without writing files
|
|
131
|
-
pnpm scaffold <name> --force # overwrite existing files
|
|
132
|
-
|
|
133
|
-
pnpm validate:registries # validate every _registry.ts against the schema
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Testing the CLI locally
|
|
137
|
-
|
|
138
|
-
The root `pnpm createui` script runs the **local CLI build** against `http://localhost:4000/r`, so
|
|
139
|
-
start the site first:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
pnpm v4:dev # serves the local registry at http://localhost:4000/r
|
|
143
|
-
pnpm createui add -c ~/path/to/app # run the local CLI against a target app
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
`-c` (`--cwd`) points at the project you want to add components to.
|
|
147
|
-
|
|
148
|
-
## Website: three-layer architecture
|
|
149
|
-
|
|
150
|
-
Site work follows a strict three-layer model:
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
registry/ui/ primitives — SACROSANCT, never edited by site work
|
|
154
|
-
↓ composed by
|
|
155
|
-
components/site/ shared composites (reused across ≥2 route groups)
|
|
156
|
-
↓ composed by
|
|
157
|
-
app/(group)/_components/ route-local composites (used by one route group)
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
A new composite belongs in `components/site/` only if it is reused across **two or more** route
|
|
161
|
-
groups; otherwise it lives in the route group's `_components/`. If a primitive is missing, **ask
|
|
162
|
-
first** — never edit `registry/ui/` to make a page work.
|
|
163
|
-
|
|
164
|
-
The authoritative website guides live in **`mds/website/`** — start at
|
|
165
|
-
[`mds/website/BRIEF.md`](../../mds/website/BRIEF.md) (it is written in Turkish). Consult those guides
|
|
166
|
-
for any website task; do not duplicate them here.
|
|
167
|
-
|
|
168
|
-
## Component rules recap
|
|
169
|
-
|
|
170
|
-
These conventions keep every component consistent (see [`rules/styling.md`](./rules/styling.md),
|
|
171
|
-
[`rules/composition.md`](./rules/composition.md), and [`rules/icons.md`](./rules/icons.md) for the
|
|
172
|
-
full rules):
|
|
173
|
-
|
|
174
|
-
- **`data-slot`** on the root element (and on meaningful sub-parts) for semantic styling hooks.
|
|
175
|
-
- **`cn()`** for all class merging — never string-concatenate classes.
|
|
176
|
-
- **`cva`** for variant-based styling; hoist the variants object out of the component.
|
|
177
|
-
- **Semantic tokens** for color: `bg-primary-base`, `text-error-base`, `bg-weak`, `text-body` —
|
|
178
|
-
never `bg-background` / `text-foreground`-style names.
|
|
179
|
-
- **Radix UI** primitives for accessibility; polymorphism via **`asChild`** + Radix `Slot` only.
|
|
180
|
-
- **Focus is `outline-*`**, never `ring-*` (e.g. `focus-visible:outline-primary-700`).
|
|
181
|
-
- Intersection prop types: `React.ComponentProps<"button"> & VariantProps<…> & { … }`.
|
|
182
|
-
- **kebab-case** filenames, **named** exports.
|
|
183
|
-
|
|
184
|
-
A correct root element looks like:
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
// Incorrect — ring focus, non-semantic token, anonymous default export
|
|
188
|
-
<button className={`${base} focus-visible:ring-2 bg-primary`}>{children}</button>
|
|
189
|
-
|
|
190
|
-
// Correct — data-slot, cn(), outline focus, semantic token
|
|
191
|
-
<Comp
|
|
192
|
-
data-slot="button"
|
|
193
|
-
className={cn(buttonVariants({ variant, appearance, size, className }))}
|
|
194
|
-
{...props}
|
|
195
|
-
/>
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
When you reference a component in an example, **read its source** at
|
|
199
|
-
`apps/v4/registry/ui/<name>.tsx` first — never guess props. For instance, `Button` takes
|
|
200
|
-
`variant` (`primary` | `neutral-solid` | `neutral-light` | `danger` | `success` | `inverse-solid` |
|
|
201
|
-
`inverse-light`), `appearance` (`solid` | `outline` | `ghost` | `soft`), `size`, `shape`, `loading`,
|
|
202
|
-
`iconOnly`, `leadingIcon`, `trailingIcon`, and `asChild` — there is no `variant="outline"` or
|
|
203
|
-
`variant="destructive"`.
|
|
204
|
-
|
|
205
|
-
## Commit convention
|
|
206
|
-
|
|
207
|
-
Conventional commits: `category(scope): message`.
|
|
208
|
-
|
|
209
|
-
Categories: `feat`, `fix`, `refactor`, `docs`, `build`, `test`, `ci`, `chore`.
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
feat(components): add loading prop to the button component
|
|
213
|
-
```
|