velloo 0.0.0 → 0.2.0

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.
Files changed (267) hide show
  1. package/BUN-LICENSE.md +91 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +9 -0
  4. package/README.md +288 -2
  5. package/THIRD-PARTY-NOTICES.md +467 -0
  6. package/assets/elsewhere-alps-9fqhv029.jpg +0 -0
  7. package/assets/elsewhere-bali-vp60g115.jpg +0 -0
  8. package/assets/elsewhere-forest-xgn23zrj.jpg +0 -0
  9. package/assets/elsewhere-japan-qs7qkbr2.jpg +0 -0
  10. package/assets/elsewhere-kyoto-map-n2bvkrya.svg +1 -0
  11. package/assets/elsewhere-kyoto-qemtn6nk.jpg +0 -0
  12. package/assets/elsewhere-room-g1gaw7cs.jpg +0 -0
  13. package/canvas/apple-touch-icon.png +0 -0
  14. package/canvas/assets/index-BCvDZiBo.css +2 -0
  15. package/canvas/assets/index-ClaBqcaS.js +28 -0
  16. package/canvas/assets/lucide-all-iIF-pmzN.js +1 -0
  17. package/canvas/assets/radix-C3HaOB-d.js +41 -0
  18. package/canvas/assets/react-D0Ec-185.js +9 -0
  19. package/canvas/assets/rolldown-runtime-hePW80VL.js +1 -0
  20. package/canvas/assets/vendor-CKmQ-Zst.js +53 -0
  21. package/canvas/favicon.ico +0 -0
  22. package/canvas/favicon.svg +12 -0
  23. package/canvas/fonts/OFL-1.1.txt +93 -0
  24. package/canvas/fonts/poppins-700.woff2 +0 -0
  25. package/canvas/icon-192.png +0 -0
  26. package/canvas/icon-512.png +0 -0
  27. package/canvas/index.html +22 -0
  28. package/canvas/site.webmanifest +11 -0
  29. package/chunk-08sv3k41.js +4 -0
  30. package/chunk-0fe83yzt.js +2 -0
  31. package/chunk-0t68szgr.js +3 -0
  32. package/chunk-1h55s5he.js +2 -0
  33. package/chunk-2c7jbx46.js +2 -0
  34. package/chunk-2smsg2ct.js +7 -0
  35. package/chunk-308ynsxd.js +2 -0
  36. package/chunk-3e8vez4d.js +1229 -0
  37. package/chunk-40xgcth8.js +63 -0
  38. package/chunk-4dn75m6t.js +3 -0
  39. package/chunk-4fj5tahv.js +3 -0
  40. package/chunk-6nqghjda.js +3 -0
  41. package/chunk-6tx68d90.js +3 -0
  42. package/chunk-6w6ch4ce.js +2 -0
  43. package/chunk-735ezz23.js +86 -0
  44. package/chunk-7eq7nx37.js +7 -0
  45. package/chunk-7eyb8yrf.js +6 -0
  46. package/chunk-80fjfyha.js +86 -0
  47. package/chunk-80r93t2e.js +4 -0
  48. package/chunk-94vx1yb0.js +3 -0
  49. package/chunk-9vg3yafz.js +3 -0
  50. package/chunk-a84n72m2.js +4 -0
  51. package/chunk-ampmdzz4.js +10 -0
  52. package/chunk-avns4dbf.js +2 -0
  53. package/chunk-ayrb3qnm.js +3 -0
  54. package/chunk-bwnvcncx.js +3 -0
  55. package/chunk-ccszg02q.js +5 -0
  56. package/chunk-cvn689d5.js +3 -0
  57. package/chunk-cxe6jjwj.js +3 -0
  58. package/chunk-dg6z4vdd.js +3 -0
  59. package/chunk-dgwjpbp9.js +3 -0
  60. package/chunk-dy41n60j.js +52 -0
  61. package/chunk-ek71z2fv.js +226 -0
  62. package/chunk-er1fxb7d.js +2 -0
  63. package/chunk-eyr8hrzf.js +2 -0
  64. package/chunk-fjbzk4sw.js +3 -0
  65. package/chunk-fsbsksga.js +3 -0
  66. package/chunk-h6gchare.js +3 -0
  67. package/chunk-hcb68zt7.js +85 -0
  68. package/chunk-hggap8fq.js +3 -0
  69. package/chunk-j11fyeqh.js +2 -0
  70. package/chunk-jb21jkp1.js +8 -0
  71. package/chunk-jfx9xjmr.js +18 -0
  72. package/chunk-jr0ss0mm.js +303 -0
  73. package/chunk-jrsknzjt.js +3 -0
  74. package/chunk-jv0xggh0.js +2 -0
  75. package/chunk-k17gfwq3.js +2 -0
  76. package/chunk-mh2pr7cq.js +5 -0
  77. package/chunk-mnapdp1y.js +2 -0
  78. package/chunk-mzhdrawd.js +3 -0
  79. package/chunk-n9gs6yx9.js +2 -0
  80. package/chunk-ngnymg2q.js +3 -0
  81. package/chunk-nwx5700b.js +3 -0
  82. package/chunk-pjtg3wnm.js +4 -0
  83. package/chunk-pp86v6m6.js +3 -0
  84. package/chunk-r22epxz2.js +3 -0
  85. package/chunk-rgasy862.js +5 -0
  86. package/chunk-rpwpeph5.js +3 -0
  87. package/chunk-s23gtejf.js +3 -0
  88. package/chunk-sjgqnykd.js +6 -0
  89. package/chunk-sn3x2m0m.js +6 -0
  90. package/chunk-t448rrfr.js +2 -0
  91. package/chunk-tdsk5wxa.js +451 -0
  92. package/chunk-v40fn08f.js +442 -0
  93. package/chunk-vbk1qhdz.js +3 -0
  94. package/chunk-vdkrbzh1.js +2 -0
  95. package/chunk-vgmr98cv.js +40 -0
  96. package/chunk-vsv8z729.js +6 -0
  97. package/chunk-x4f4k9pp.js +26 -0
  98. package/chunk-xt5ayg7g.js +4 -0
  99. package/chunk-ydhwv29b.js +244 -0
  100. package/chunk-zb6hc1y0.js +2 -0
  101. package/cli.js +3 -0
  102. package/launcher.cjs +97 -0
  103. package/package.json +58 -5
  104. package/pkgs/helpers/src/box.tsx +21 -0
  105. package/pkgs/helpers/src/cn.ts +32 -0
  106. package/pkgs/helpers/src/descriptors.ts +270 -0
  107. package/pkgs/helpers/src/divider.tsx +123 -0
  108. package/pkgs/helpers/src/gradient.tsx +90 -0
  109. package/pkgs/helpers/src/heading.tsx +20 -0
  110. package/pkgs/helpers/src/icon-data.ts +8009 -0
  111. package/pkgs/helpers/src/icon.tsx +53 -0
  112. package/pkgs/helpers/src/image.tsx +111 -0
  113. package/pkgs/helpers/src/index.ts +29 -0
  114. package/pkgs/helpers/src/layer.tsx +61 -0
  115. package/pkgs/helpers/src/lowering.ts +53 -0
  116. package/pkgs/helpers/src/paths.ts +47 -0
  117. package/pkgs/helpers/src/placeholder.tsx +88 -0
  118. package/pkgs/helpers/src/prose.tsx +42 -0
  119. package/pkgs/helpers/src/registry.ts +41 -0
  120. package/pkgs/helpers/src/svg.tsx +62 -0
  121. package/pkgs/helpers/src/text.tsx +16 -0
  122. package/pkgs/provider-antd/src/index.ts +78 -0
  123. package/pkgs/provider-antd/src/intro.ts +14 -0
  124. package/pkgs/provider-antd/src/manifest.ts +439 -0
  125. package/pkgs/provider-antd/src/overlays.ts +176 -0
  126. package/pkgs/provider-antd/src/registry.ts +104 -0
  127. package/pkgs/provider-antd/src/render-pass.ts +43 -0
  128. package/pkgs/provider-antd/src/tailwind-entry.css +21 -0
  129. package/pkgs/provider-antd/src/theme.ts +132 -0
  130. package/pkgs/provider-antd/src/version.ts +8 -0
  131. package/pkgs/provider-chakra/src/index.ts +80 -0
  132. package/pkgs/provider-chakra/src/intro.ts +14 -0
  133. package/pkgs/provider-chakra/src/manifest.ts +421 -0
  134. package/pkgs/provider-chakra/src/overlays.ts +246 -0
  135. package/pkgs/provider-chakra/src/registry.ts +227 -0
  136. package/pkgs/provider-chakra/src/render-pass.ts +55 -0
  137. package/pkgs/provider-chakra/src/tailwind-entry.css +21 -0
  138. package/pkgs/provider-chakra/src/theme.ts +180 -0
  139. package/pkgs/provider-chakra/src/version.ts +8 -0
  140. package/pkgs/provider-mui/src/index.ts +117 -0
  141. package/pkgs/provider-mui/src/intro.ts +12 -0
  142. package/pkgs/provider-mui/src/manifest.ts +263 -0
  143. package/pkgs/provider-mui/src/overlays.ts +124 -0
  144. package/pkgs/provider-mui/src/registry.ts +120 -0
  145. package/pkgs/provider-mui/src/render-pass.ts +36 -0
  146. package/pkgs/provider-mui/src/tailwind-entry.css +21 -0
  147. package/pkgs/provider-mui/src/theme.ts +129 -0
  148. package/pkgs/provider-mui/src/version.ts +8 -0
  149. package/pkgs/provider-none/src/components-inline.tsx +204 -0
  150. package/pkgs/provider-none/src/components.tsx +155 -0
  151. package/pkgs/provider-none/src/index.ts +57 -0
  152. package/pkgs/provider-none/src/intro.ts +18 -0
  153. package/pkgs/provider-none/src/manifest.ts +163 -0
  154. package/pkgs/provider-none/src/registry-inline.ts +36 -0
  155. package/pkgs/provider-none/src/registry.ts +39 -0
  156. package/pkgs/provider-none/src/tailwind-entry.css +57 -0
  157. package/pkgs/provider-none/src/version.ts +10 -0
  158. package/pkgs/schema/src/annotation.ts +120 -0
  159. package/pkgs/schema/src/asset.ts +66 -0
  160. package/pkgs/schema/src/board.ts +71 -0
  161. package/pkgs/schema/src/comment.ts +107 -0
  162. package/pkgs/schema/src/config.ts +185 -0
  163. package/pkgs/schema/src/css-sanitize.ts +56 -0
  164. package/pkgs/schema/src/extension.ts +105 -0
  165. package/pkgs/schema/src/fonts.ts +690 -0
  166. package/pkgs/schema/src/frame.ts +40 -0
  167. package/pkgs/schema/src/icon-name.ts +21 -0
  168. package/pkgs/schema/src/ids.ts +14 -0
  169. package/pkgs/schema/src/index.ts +169 -0
  170. package/pkgs/schema/src/migrate.ts +297 -0
  171. package/pkgs/schema/src/node.ts +229 -0
  172. package/pkgs/schema/src/paths.ts +28 -0
  173. package/pkgs/schema/src/repo.ts +134 -0
  174. package/pkgs/schema/src/screen.ts +30 -0
  175. package/pkgs/schema/src/snippet-resolve.ts +193 -0
  176. package/pkgs/schema/src/snippet.ts +104 -0
  177. package/pkgs/schema/src/svg-sanitize.ts +487 -0
  178. package/pkgs/schema/src/theme.ts +218 -0
  179. package/pkgs/schema/src/typeset.ts +693 -0
  180. package/pkgs/schema/src/validate-ids.ts +46 -0
  181. package/pkgs/schema/src/viewport.ts +8 -0
  182. package/pkgs/shadcn-snapshot/dist/manifest.json +8109 -0
  183. package/pkgs/shadcn-snapshot/src/components/canvas-portal.tsx +55 -0
  184. package/pkgs/shadcn-snapshot/src/components/ui/accordion.tsx +122 -0
  185. package/pkgs/shadcn-snapshot/src/components/ui/alert-dialog.tsx +164 -0
  186. package/pkgs/shadcn-snapshot/src/components/ui/alert.tsx +74 -0
  187. package/pkgs/shadcn-snapshot/src/components/ui/aspect-ratio.tsx +13 -0
  188. package/pkgs/shadcn-snapshot/src/components/ui/attachment.tsx +197 -0
  189. package/pkgs/shadcn-snapshot/src/components/ui/avatar.tsx +100 -0
  190. package/pkgs/shadcn-snapshot/src/components/ui/badge.tsx +50 -0
  191. package/pkgs/shadcn-snapshot/src/components/ui/breadcrumb.tsx +109 -0
  192. package/pkgs/shadcn-snapshot/src/components/ui/bubble.tsx +130 -0
  193. package/pkgs/shadcn-snapshot/src/components/ui/button-group.tsx +82 -0
  194. package/pkgs/shadcn-snapshot/src/components/ui/button.tsx +72 -0
  195. package/pkgs/shadcn-snapshot/src/components/ui/calendar.tsx +229 -0
  196. package/pkgs/shadcn-snapshot/src/components/ui/card.tsx +92 -0
  197. package/pkgs/shadcn-snapshot/src/components/ui/carousel.tsx +235 -0
  198. package/pkgs/shadcn-snapshot/src/components/ui/chart-option.ts +250 -0
  199. package/pkgs/shadcn-snapshot/src/components/ui/chart.tsx +177 -0
  200. package/pkgs/shadcn-snapshot/src/components/ui/checkbox.tsx +41 -0
  201. package/pkgs/shadcn-snapshot/src/components/ui/collapsible.tsx +32 -0
  202. package/pkgs/shadcn-snapshot/src/components/ui/combobox.tsx +196 -0
  203. package/pkgs/shadcn-snapshot/src/components/ui/context-menu.tsx +227 -0
  204. package/pkgs/shadcn-snapshot/src/components/ui/dialog.tsx +131 -0
  205. package/pkgs/shadcn-snapshot/src/components/ui/direction.tsx +24 -0
  206. package/pkgs/shadcn-snapshot/src/components/ui/drawer.tsx +112 -0
  207. package/pkgs/shadcn-snapshot/src/components/ui/dropdown-menu.tsx +205 -0
  208. package/pkgs/shadcn-snapshot/src/components/ui/empty.tsx +98 -0
  209. package/pkgs/shadcn-snapshot/src/components/ui/field.tsx +228 -0
  210. package/pkgs/shadcn-snapshot/src/components/ui/hover-card.tsx +51 -0
  211. package/pkgs/shadcn-snapshot/src/components/ui/input-group.tsx +148 -0
  212. package/pkgs/shadcn-snapshot/src/components/ui/input.tsx +43 -0
  213. package/pkgs/shadcn-snapshot/src/components/ui/item.tsx +187 -0
  214. package/pkgs/shadcn-snapshot/src/components/ui/kbd.tsx +30 -0
  215. package/pkgs/shadcn-snapshot/src/components/ui/label.tsx +25 -0
  216. package/pkgs/shadcn-snapshot/src/components/ui/marker.tsx +71 -0
  217. package/pkgs/shadcn-snapshot/src/components/ui/menubar.tsx +243 -0
  218. package/pkgs/shadcn-snapshot/src/components/ui/message.tsx +89 -0
  219. package/pkgs/shadcn-snapshot/src/components/ui/native-select.tsx +60 -0
  220. package/pkgs/shadcn-snapshot/src/components/ui/navigation-menu.tsx +165 -0
  221. package/pkgs/shadcn-snapshot/src/components/ui/pagination.tsx +117 -0
  222. package/pkgs/shadcn-snapshot/src/components/ui/popover.tsx +66 -0
  223. package/pkgs/shadcn-snapshot/src/components/ui/progress.tsx +35 -0
  224. package/pkgs/shadcn-snapshot/src/components/ui/radio-group.tsx +48 -0
  225. package/pkgs/shadcn-snapshot/src/components/ui/scroll-area.tsx +59 -0
  226. package/pkgs/shadcn-snapshot/src/components/ui/select.tsx +180 -0
  227. package/pkgs/shadcn-snapshot/src/components/ui/separator.tsx +32 -0
  228. package/pkgs/shadcn-snapshot/src/components/ui/sheet.tsx +107 -0
  229. package/pkgs/shadcn-snapshot/src/components/ui/skeleton.tsx +17 -0
  230. package/pkgs/shadcn-snapshot/src/components/ui/slider.tsx +68 -0
  231. package/pkgs/shadcn-snapshot/src/components/ui/sonner.tsx +91 -0
  232. package/pkgs/shadcn-snapshot/src/components/ui/spinner.tsx +21 -0
  233. package/pkgs/shadcn-snapshot/src/components/ui/switch.tsx +41 -0
  234. package/pkgs/shadcn-snapshot/src/components/ui/table.tsx +93 -0
  235. package/pkgs/shadcn-snapshot/src/components/ui/tabs.tsx +84 -0
  236. package/pkgs/shadcn-snapshot/src/components/ui/textarea.tsx +24 -0
  237. package/pkgs/shadcn-snapshot/src/components/ui/toggle-group.tsx +94 -0
  238. package/pkgs/shadcn-snapshot/src/components/ui/toggle.tsx +50 -0
  239. package/pkgs/shadcn-snapshot/src/components/ui/tooltip.tsx +47 -0
  240. package/pkgs/shadcn-snapshot/src/examples.ts +36 -0
  241. package/pkgs/shadcn-snapshot/src/groups.ts +87 -0
  242. package/pkgs/shadcn-snapshot/src/index.ts +43 -0
  243. package/pkgs/shadcn-snapshot/src/lib/utils.ts +4 -0
  244. package/pkgs/shadcn-snapshot/src/manifest.ts +12 -0
  245. package/pkgs/shadcn-snapshot/src/notes.ts +97 -0
  246. package/pkgs/shadcn-snapshot/src/paths.ts +47 -0
  247. package/pkgs/shadcn-snapshot/src/registry-files.ts +293 -0
  248. package/pkgs/shadcn-snapshot/src/registry.ts +631 -0
  249. package/pkgs/shadcn-snapshot/src/shadcn-tailwind.css +632 -0
  250. package/pkgs/shadcn-snapshot/src/tailwind-entry.css +159 -0
  251. package/pkgs/shadcn-snapshot/src/version.ts +8 -0
  252. package/plugins/claude/velloo/.claude-plugin/plugin.json +6 -0
  253. package/plugins/claude/velloo/agents/velloo-design-reviewer.md +33 -0
  254. package/plugins/claude/velloo/agents/velloo-designer.md +39 -0
  255. package/plugins/claude/velloo/commands/design.md +11 -0
  256. package/plugins/claude/velloo/commands/implement.md +10 -0
  257. package/plugins/claude/velloo/commands/review.md +8 -0
  258. package/plugins/gemini/GEMINI.md +31 -0
  259. package/plugins/gemini/commands/velloo/design.toml +10 -0
  260. package/plugins/gemini/commands/velloo/implement.toml +9 -0
  261. package/plugins/gemini/gemini-extension.json +9 -0
  262. package/skills/velloo-brand/SKILL.md +124 -0
  263. package/skills/velloo-design/SKILL.md +144 -0
  264. package/skills/velloo-design-system/SKILL.md +90 -0
  265. package/skills/velloo-implement/SKILL.md +101 -0
  266. package/skills/velloo-logo/SKILL.md +93 -0
  267. package/skills/velloo-setup/SKILL.md +156 -0
@@ -0,0 +1,53 @@
1
+ // Velloo-owned lucide icon rendered as an inline <svg> from build-time-extracted
2
+ // node data (icon-data.ts, see scripts/generate-icon-data.ts) — no lucide-react
3
+ // at runtime, so bundles carry path data instead of ~2k component exports.
4
+ // The Velloo manifest still carries an "icon" control with the full lucide name
5
+ // set, and codegen lowers <Icon name="X"/> to `<X />` from "lucide-react" so the
6
+ // emitted code has zero Velloo runtime dependencies.
7
+
8
+ import { pascalizeIconName } from "@velloo/schema/icon-name";
9
+ import * as React from "react";
10
+ import { cn } from "./cn.ts";
11
+ import { ICON_ALIASES, ICON_NODES, type IconNode, LUCIDE_SVG_ATTRIBUTES } from "./icon-data.ts";
12
+
13
+ export interface IconProps extends React.SVGAttributes<SVGSVGElement> {
14
+ /** Lucide icon name — PascalCase ("ChevronRight") or kebab-case ("chevron-right"). */
15
+ name: string;
16
+ /** Pixel size — the svg's width/height. */
17
+ size?: number;
18
+ /** Stroke width. */
19
+ strokeWidth?: number;
20
+ }
21
+
22
+ function lookup(name: string): IconNode | undefined {
23
+ const trimmed = name.trim();
24
+ const direct = ICON_NODES[trimmed] ?? ICON_NODES[ICON_ALIASES[trimmed] ?? ""];
25
+ if (direct) return direct;
26
+ const viaPascal = ICON_ALIASES[pascalizeIconName(trimmed)];
27
+ return viaPascal === undefined ? undefined : ICON_NODES[viaPascal];
28
+ }
29
+
30
+ const FALLBACK = lookup("HelpCircle");
31
+
32
+ export function Icon({ name, className, size = 16, strokeWidth = 2, ...props }: IconProps) {
33
+ const hit = lookup(name);
34
+ const node = hit ?? FALLBACK;
35
+ if (!node) return null;
36
+ const tint = hit ? className : cn("text-[var(--color-fg-muted)]", className);
37
+ return (
38
+ // biome-ignore lint/a11y/noSvgWithoutTitle: decorative by default, like lucide's own components — callers pass aria-* through props.
39
+ <svg
40
+ {...LUCIDE_SVG_ATTRIBUTES}
41
+ width={size}
42
+ height={size}
43
+ strokeWidth={strokeWidth}
44
+ className={tint}
45
+ {...props}
46
+ >
47
+ {node.map(([tag, attrs], i) =>
48
+ // Node data is static per icon name, so the index key is stable.
49
+ React.createElement(tag, { ...attrs, key: i }),
50
+ )}
51
+ </svg>
52
+ );
53
+ }
@@ -0,0 +1,111 @@
1
+ // Velloo-owned image helper. Renders a real `<img>` in design mode (so
2
+ // designs look like the user's final page, not like Placeholder
3
+ // abstractions) but carries Velloo-shaped metadata the agent reads when
4
+ // emitting code: focal point for object-position, aspect for sizing,
5
+ // treatment for overlays.
6
+ //
7
+ // The `src` is preserved verbatim in codegen — point at `assets/`,
8
+ // arbitrary URLs, or whatever the host app convention is.
9
+ import type * as React from "react";
10
+ import { cn } from "./cn.ts";
11
+
12
+ export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "loading"> {
13
+ /**
14
+ * Asset path. Relative paths resolve against the design folder's
15
+ * `assets/` directory in design mode; the agent emits them verbatim.
16
+ */
17
+ src: string;
18
+ /**
19
+ * Aspect ratio shorthand. Matches Placeholder's set so designs stay
20
+ * consistent when swapping placeholder → real image.
21
+ */
22
+ aspect?: "1/1" | "4/3" | "3/4" | "16/9" | "21/9";
23
+ /**
24
+ * Focal point (0-1 coords). When the image is cropped via aspect,
25
+ * the cropping window centers on this point. Defaults to center.
26
+ */
27
+ focal?: { x: number; y: number };
28
+ /**
29
+ * Optional visual treatment layered on top.
30
+ * `"overlay-dark"` darkens for hero text legibility;
31
+ * `"overlay-light"` lightens for light-text-on-bright-image cases;
32
+ * `"blend-multiply"` blends with the theme's primary;
33
+ * `"grayscale"` desaturates.
34
+ */
35
+ treatment?: "overlay-dark" | "overlay-light" | "blend-multiply" | "grayscale";
36
+ /**
37
+ * Whether the image should fill its parent (`true`, default — uses
38
+ * absolute-positioning inside a relative wrapper) or render at intrinsic
39
+ * size (`false`).
40
+ */
41
+ fill?: boolean;
42
+ }
43
+
44
+ const ASPECT_CLASS: Record<NonNullable<ImageProps["aspect"]>, string> = {
45
+ "1/1": "aspect-square",
46
+ "4/3": "aspect-[4/3]",
47
+ "3/4": "aspect-[3/4]",
48
+ "16/9": "aspect-video",
49
+ "21/9": "aspect-[21/9]",
50
+ };
51
+
52
+ const TREATMENT_CLASS: Record<NonNullable<ImageProps["treatment"]>, string> = {
53
+ "overlay-dark":
54
+ "after:absolute after:inset-0 after:bg-black/40 after:pointer-events-none after:rounded-[inherit]",
55
+ "overlay-light":
56
+ "after:absolute after:inset-0 after:bg-white/40 after:pointer-events-none after:rounded-[inherit]",
57
+ "blend-multiply": "[&>img]:mix-blend-multiply",
58
+ grayscale: "[&>img]:grayscale",
59
+ };
60
+
61
+ function focalToObjectPosition(focal: { x: number; y: number } | undefined): string | undefined {
62
+ if (!focal) return undefined;
63
+ const clamp = (n: number) => Math.max(0, Math.min(1, n));
64
+ return `${(clamp(focal.x) * 100).toFixed(1)}% ${(clamp(focal.y) * 100).toFixed(1)}%`;
65
+ }
66
+
67
+ export function Image({
68
+ src,
69
+ alt = "",
70
+ aspect,
71
+ focal,
72
+ treatment,
73
+ fill = true,
74
+ className,
75
+ style,
76
+ ...rest
77
+ }: ImageProps) {
78
+ const objectPosition = focalToObjectPosition(focal);
79
+ const aspectClass = aspect ? ASPECT_CLASS[aspect] : "";
80
+ const treatmentClass = treatment ? TREATMENT_CLASS[treatment] : "";
81
+
82
+ if (!fill) {
83
+ return (
84
+ <img
85
+ src={src}
86
+ alt={alt}
87
+ data-slot="image"
88
+ className={cn("inline-block", className)}
89
+ style={objectPosition ? { ...style, objectPosition } : style}
90
+ {...rest}
91
+ />
92
+ );
93
+ }
94
+
95
+ return (
96
+ <div
97
+ data-slot="image-wrapper"
98
+ className={cn("relative overflow-hidden", aspectClass, treatmentClass, className)}
99
+ style={style}
100
+ >
101
+ <img
102
+ src={src}
103
+ alt={alt}
104
+ data-slot="image"
105
+ className="absolute inset-0 h-full w-full object-cover"
106
+ style={objectPosition ? { objectPosition } : undefined}
107
+ {...rest}
108
+ />
109
+ </div>
110
+ );
111
+ }
@@ -0,0 +1,29 @@
1
+ export { Box } from "./box.tsx";
2
+ export { cn, mergeTailwind } from "./cn.ts";
3
+ export { HELPER_DESCRIPTORS, helperDescriptors } from "./descriptors.ts";
4
+ export { Divider, type DividerProps } from "./divider.tsx";
5
+ export { Gradient, type GradientProps } from "./gradient.tsx";
6
+ export { Heading, type HeadingProps } from "./heading.tsx";
7
+ export { Icon, type IconProps } from "./icon.tsx";
8
+ export {
9
+ ICON_ALIASES,
10
+ ICON_NODES,
11
+ type IconNode,
12
+ type IconNodeChild,
13
+ LUCIDE_SVG_ATTRIBUTES,
14
+ LUCIDE_VERSION,
15
+ } from "./icon-data.ts";
16
+ export { Image, type ImageProps } from "./image.tsx";
17
+ export { Layer, type LayerProps } from "./layer.tsx";
18
+ export {
19
+ CONTAINER_WIDTH_CLASS,
20
+ PLACEHOLDER_ASPECT_CLASS,
21
+ PLACEHOLDER_AVATAR_SIZE_CLASS,
22
+ STACK_ALIGN_CLASS,
23
+ STACK_JUSTIFY_CLASS,
24
+ } from "./lowering.ts";
25
+ export { Placeholder, type PlaceholderProps } from "./placeholder.tsx";
26
+ export { Prose, type ProseProps } from "./prose.tsx";
27
+ export { helpersRegistry } from "./registry.ts";
28
+ export { SVG, type SVGProps } from "./svg.tsx";
29
+ export { Text, type TextProps } from "./text.tsx";
@@ -0,0 +1,61 @@
1
+ // Velloo-owned absolute-positioning escape hatch. The default mental
2
+ // model is flex / grid; Layer is for hero compositions where neither
3
+ // makes sense (overlapping decoration, parallax-feel stacks, deliberate
4
+ // off-grid placement).
5
+ //
6
+ // Marked with data-slot="layer" so the canvas can flag heavy usage in
7
+ // inspect_design_quality later — Layer is a "use sparingly" tool.
8
+ import type * as React from "react";
9
+ import { cn } from "./cn.ts";
10
+
11
+ export interface LayerProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ /**
13
+ * Pixel offset from the parent's top edge. Accepts numbers (treated
14
+ * as px) or any valid CSS length string ("8rem", "12%").
15
+ */
16
+ top?: number | string;
17
+ right?: number | string;
18
+ bottom?: number | string;
19
+ left?: number | string;
20
+ /** Stacking order. Default 1. */
21
+ z?: number;
22
+ /**
23
+ * Whether the layer captures pointer events. Default `true`. Set to
24
+ * `false` for decorative-only layers that shouldn't block clicks.
25
+ */
26
+ pointerEvents?: boolean;
27
+ }
28
+
29
+ function asLength(v: number | string | undefined): string | undefined {
30
+ if (v === undefined) return undefined;
31
+ return typeof v === "number" ? `${v}px` : v;
32
+ }
33
+
34
+ export function Layer({
35
+ top,
36
+ right,
37
+ bottom,
38
+ left,
39
+ z = 1,
40
+ pointerEvents = true,
41
+ className,
42
+ style,
43
+ ...rest
44
+ }: LayerProps) {
45
+ return (
46
+ <div
47
+ data-slot="layer"
48
+ className={cn("absolute", className)}
49
+ style={{
50
+ top: asLength(top),
51
+ right: asLength(right),
52
+ bottom: asLength(bottom),
53
+ left: asLength(left),
54
+ zIndex: z,
55
+ pointerEvents: pointerEvents ? undefined : "none",
56
+ ...style,
57
+ }}
58
+ {...rest}
59
+ />
60
+ );
61
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Codegen lowering tables — the Tailwind class strings each lowered helper
3
+ * emits, mirroring what the component applies at runtime. Data-only mirrors:
4
+ * the literals stay duplicated in the co-located `.tsx` components because
5
+ * the server's Tailwind JIT scans only `.tsx` files for class candidates and
6
+ * would not see strings that live only in this `.ts` module.
7
+ *
8
+ * The typography ladder is the exception, and the model for retiring the rest:
9
+ * `headingClasses` / `textClasses` in `@velloo/schema/typeset` are called by the
10
+ * component AND by codegen, because the generated `@source inline(...)` safelist
11
+ * makes the JIT see those utilities without scanning a literal.
12
+ */
13
+
14
+ // Keep in sync with ./placeholder.tsx (`ASPECT_CLASS`).
15
+ export const PLACEHOLDER_ASPECT_CLASS: Record<string, string> = {
16
+ "1/1": "aspect-square",
17
+ "4/3": "aspect-[4/3]",
18
+ "3/4": "aspect-[3/4]",
19
+ "16/9": "aspect-video",
20
+ "21/9": "aspect-[21/9]",
21
+ };
22
+
23
+ // Avatar size ladder — keep in sync with ./placeholder.tsx (`AVATAR_SIZE_CLASS`).
24
+ export const PLACEHOLDER_AVATAR_SIZE_CLASS: Record<string, string> = {
25
+ sm: "size-8 text-xs",
26
+ md: "size-10 text-sm",
27
+ lg: "size-14 text-base",
28
+ xl: "size-20 text-lg",
29
+ };
30
+
31
+ // Keep in sync with packages/provider-none/src/components.tsx (Stack/Container
32
+ // live there, not here — these tables ride along so codegen has one home for
33
+ // every lowered-primitive class map).
34
+ export const STACK_ALIGN_CLASS: Record<string, string> = {
35
+ start: "items-start",
36
+ center: "items-center",
37
+ end: "items-end",
38
+ stretch: "items-stretch",
39
+ };
40
+ export const STACK_JUSTIFY_CLASS: Record<string, string> = {
41
+ start: "justify-start",
42
+ center: "justify-center",
43
+ end: "justify-end",
44
+ between: "justify-between",
45
+ around: "justify-around",
46
+ };
47
+ export const CONTAINER_WIDTH_CLASS: Record<string, string> = {
48
+ sm: "max-w-screen-sm",
49
+ md: "max-w-screen-md",
50
+ lg: "max-w-screen-lg",
51
+ xl: "max-w-screen-xl",
52
+ full: "max-w-full",
53
+ };
@@ -0,0 +1,47 @@
1
+ // Node-only module — exported as `@velloo/helpers/paths`, deliberately NOT from
2
+ // the package index: the index is compiled into browser bundles (velloo-cloud's
3
+ // share viewer imports the registries from source) where node:* imports fail.
4
+ import { existsSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+
10
+ /**
11
+ * Absolute path to the helper component sources. The server's Tailwind JIT
12
+ * adds this directory to its class-candidate scan so the helpers' structural
13
+ * default classes (Heading's size ladder, Placeholder's aspect classes, …)
14
+ * compile no matter which provider's `componentsDir` is active. Resolves from
15
+ * source (`here` = this `src/` dir) and from the bundled CLI, where the CLI's
16
+ * `build.ts` copies these sources to `<dist>/pkgs/helpers/src`.
17
+ */
18
+ function resolveDir(): string {
19
+ const candidates = [
20
+ process.env.VELLOO_HELPERS_SRC,
21
+ join(here, "pkgs", "helpers", "src"),
22
+ here,
23
+ ].filter((p): p is string => Boolean(p));
24
+ return candidates.find((d) => existsSync(join(d, "heading.tsx"))) ?? here;
25
+ }
26
+
27
+ export const helpersComponentsDir: string = resolveDir();
28
+
29
+ const HELPER_SOURCE_FILES: Record<string, string> = {
30
+ Box: "box.tsx",
31
+ Divider: "divider.tsx",
32
+ Gradient: "gradient.tsx",
33
+ Heading: "heading.tsx",
34
+ Icon: "icon.tsx",
35
+ Image: "image.tsx",
36
+ Layer: "layer.tsx",
37
+ Placeholder: "placeholder.tsx",
38
+ Prose: "prose.tsx",
39
+ SVG: "svg.tsx",
40
+ Text: "text.tsx",
41
+ };
42
+
43
+ /** Absolute source module for a framework-neutral helper, or undefined. */
44
+ export function helperSourcePath(id: string): string | undefined {
45
+ const file = HELPER_SOURCE_FILES[id];
46
+ return file ? join(helpersComponentsDir, file) : undefined;
47
+ }
@@ -0,0 +1,88 @@
1
+ // Velloo-owned placeholder component. Designs often need a "picture goes
2
+ // here" slot — agent-authored marketing pages especially. This stands in
3
+ // for real imagery during design time and emits as a `<div role="img">` in
4
+ // codegen so the user can swap in a real `<img>` / `<Image>` / `<Avatar>`
5
+ // after export.
6
+ import type * as React from "react";
7
+ import { cn } from "./cn.ts";
8
+
9
+ export interface PlaceholderProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ /**
11
+ * Visual shape. `"image"` is a rounded rectangle with an optional label;
12
+ * `"avatar"` is a circle with a fallback letter — both honor a custom
13
+ * className for sizing.
14
+ */
15
+ kind?: "image" | "avatar" | undefined;
16
+ /**
17
+ * Fallback letter or short label inside the placeholder. For avatars
18
+ * this is typically a single initial; for images it's a hint about
19
+ * the content ("Hero", "Logo", "Product").
20
+ */
21
+ label?: string | undefined;
22
+ /**
23
+ * Aspect ratio — `"16/9"`, `"4/3"`, `"1/1"`, `"3/4"`. Only used when
24
+ * `kind: "image"`. Defaults to `"16/9"`.
25
+ */
26
+ aspect?: "1/1" | "4/3" | "3/4" | "16/9" | "21/9" | undefined;
27
+ /**
28
+ * Size ladder for `kind: "avatar"`. Mirrors shadcn Avatar conventions:
29
+ * `sm` (size-8) / `md` (size-10, default) / `lg` (size-14) / `xl` (size-20).
30
+ * Ignored when `kind: "image"` — sizing there is driven by aspect + width.
31
+ */
32
+ size?: "sm" | "md" | "lg" | "xl" | undefined;
33
+ }
34
+
35
+ const ASPECT_CLASS: Record<NonNullable<PlaceholderProps["aspect"]>, string> = {
36
+ "1/1": "aspect-square",
37
+ "4/3": "aspect-[4/3]",
38
+ "3/4": "aspect-[3/4]",
39
+ "16/9": "aspect-video",
40
+ "21/9": "aspect-[21/9]",
41
+ };
42
+
43
+ const AVATAR_SIZE_CLASS: Record<NonNullable<PlaceholderProps["size"]>, string> = {
44
+ sm: "size-8 text-xs",
45
+ md: "size-10 text-sm",
46
+ lg: "size-14 text-base",
47
+ xl: "size-20 text-lg",
48
+ };
49
+
50
+ export function Placeholder({
51
+ kind = "image",
52
+ label,
53
+ aspect = "16/9",
54
+ size = "md",
55
+ className,
56
+ ...rest
57
+ }: PlaceholderProps) {
58
+ if (kind === "avatar") {
59
+ return (
60
+ <div
61
+ role="img"
62
+ aria-label={label ? `placeholder: ${label}` : "placeholder avatar"}
63
+ className={cn(
64
+ "inline-flex items-center justify-center rounded-full bg-muted text-muted-foreground font-medium",
65
+ AVATAR_SIZE_CLASS[size],
66
+ className,
67
+ )}
68
+ {...rest}
69
+ >
70
+ {label?.slice(0, 2).toUpperCase() ?? ""}
71
+ </div>
72
+ );
73
+ }
74
+ return (
75
+ <div
76
+ role="img"
77
+ aria-label={label ? `placeholder: ${label}` : "placeholder image"}
78
+ className={cn(
79
+ "flex w-full items-center justify-center bg-muted text-muted-foreground text-xs uppercase tracking-wider rounded-md border border-dashed border-border",
80
+ ASPECT_CLASS[aspect],
81
+ className,
82
+ )}
83
+ {...rest}
84
+ >
85
+ {label ?? "image"}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,42 @@
1
+ // Velloo-owned long-form content region.
2
+ //
3
+ // `typeset` is velloo CSS, not a Tailwind utility — `themeToCss` ships it on
4
+ // every channel — so Prose lives here in the framework-neutral helpers rather
5
+ // than in the shadcn provider. A MUI, Ant Design, Chakra, or no-framework
6
+ // folder gets the same behavior from the same class.
7
+ //
8
+ // It exists to make the common case declarative: long-form content is a
9
+ // *region* with a rhythm, not a stack of individually-classed blocks. Inside
10
+ // one, headings, paragraphs, lists, quotes, code, and tables all pick up the
11
+ // typeset's proportions and vertical spacing automatically.
12
+
13
+ import type * as React from "react";
14
+ import { createElement } from "react";
15
+ import { cn } from "./cn.ts";
16
+
17
+ export interface ProseProps extends React.ComponentProps<"div"> {
18
+ /**
19
+ * A named typeset from the theme (`set_typeset`). Omitted uses the folder
20
+ * baseline. The class is emitted unconditionally so a preset added to the
21
+ * theme later starts applying without touching the screen.
22
+ */
23
+ preset?: string;
24
+ /** HTML tag to render. Defaults to `div`; `article` / `section` are common. */
25
+ as?: string;
26
+ }
27
+
28
+ const TAG = /^[a-z][a-z0-9]*$/;
29
+ const PRESET = /^[A-Za-z0-9_-]+$/;
30
+
31
+ export function Prose({ preset, as, className, ...props }: ProseProps) {
32
+ const tag = typeof as === "string" && TAG.test(as) ? as : "div";
33
+ // A preset name reaches CSS as a class, so reject anything not ident-shaped
34
+ // rather than emitting it.
35
+ const presetClass =
36
+ typeof preset === "string" && PRESET.test(preset) ? `typeset-${preset}` : undefined;
37
+ return createElement(tag, {
38
+ "data-slot": "prose",
39
+ className: cn("typeset", presetClass, className),
40
+ ...props,
41
+ });
42
+ }
@@ -0,0 +1,41 @@
1
+ import type { ComponentRegistry } from "@velloo/provider";
2
+ import { Box } from "./box.tsx";
3
+ import { Divider } from "./divider.tsx";
4
+ import { Gradient } from "./gradient.tsx";
5
+ import { Heading } from "./heading.tsx";
6
+ import { Icon } from "./icon.tsx";
7
+ import { Image } from "./image.tsx";
8
+ import { Layer } from "./layer.tsx";
9
+ import { Placeholder } from "./placeholder.tsx";
10
+ import { Prose } from "./prose.tsx";
11
+ import { SVG } from "./svg.tsx";
12
+ import { Text } from "./text.tsx";
13
+
14
+ const ALL: ComponentRegistry = {
15
+ Box,
16
+ Divider,
17
+ Gradient,
18
+ Heading,
19
+ Icon,
20
+ Image,
21
+ Layer,
22
+ Placeholder,
23
+ Prose,
24
+ SVG,
25
+ Text,
26
+ };
27
+
28
+ /**
29
+ * Build a registry subset of the velloo helpers. Each provider passes its own
30
+ * deliberate id list (provider-none reuses nine, its inline channel seven,
31
+ * provider-mui six; the shadcn snapshot takes all ten) — the *mechanism* is
32
+ * shared, the lists are not. Unknown ids are skipped.
33
+ */
34
+ export function helpersRegistry(ids: readonly string[]): ComponentRegistry {
35
+ const out: ComponentRegistry = {};
36
+ for (const id of ids) {
37
+ const component = ALL[id];
38
+ if (component) out[id] = component;
39
+ }
40
+ return out;
41
+ }
@@ -0,0 +1,62 @@
1
+ // Velloo-owned helper. Inline SVG that takes its content via the `content`
2
+ // prop (markup string) or via children (full JSX). Used for hero
3
+ // illustrations, decorative shapes, brand marks — anywhere the static
4
+ // design needs custom vector art without re-creating a real `<svg>` tree
5
+ // node-by-node.
6
+ //
7
+ // Codegen emits this as `<svg dangerouslySetInnerHTML={{ __html: ... }}>`
8
+ // when `content` is set, or a regular `<svg>{children}</svg>` otherwise.
9
+
10
+ import { sanitizeSvgMarkup } from "@velloo/schema/svg-sanitize";
11
+ import type * as React from "react";
12
+ import { cn } from "./cn.ts";
13
+
14
+ export interface SVGProps extends Omit<React.SVGAttributes<SVGSVGElement>, "content"> {
15
+ /**
16
+ * Inline SVG markup. Pass the contents *inside* the outer `<svg>` —
17
+ * the wrapper element handles viewBox / xmlns. Wins over `children`.
18
+ */
19
+ content?: string;
20
+ /**
21
+ * viewBox for the wrapper svg. Defaults to "0 0 24 24" so single-glyph
22
+ * paths line up; override for full-bleed illustrations.
23
+ */
24
+ viewBox?: string;
25
+ /**
26
+ * The fill / currentColor source. Defaults to "currentColor" so the
27
+ * SVG inherits the parent's text color and theme-flips correctly.
28
+ */
29
+ color?: "currentColor" | "primary" | "accent" | "muted" | string;
30
+ }
31
+
32
+ const COLOR_CLASS: Record<string, string> = {
33
+ primary: "text-primary",
34
+ accent: "text-accent",
35
+ muted: "text-muted-foreground",
36
+ };
37
+
38
+ export function SVG({
39
+ content,
40
+ viewBox = "0 0 24 24",
41
+ color = "currentColor",
42
+ className,
43
+ children,
44
+ ...rest
45
+ }: SVGProps) {
46
+ const colorClass = COLOR_CLASS[color] ?? "";
47
+ const props = {
48
+ xmlns: "http://www.w3.org/2000/svg",
49
+ viewBox,
50
+ fill: color === "currentColor" ? "currentColor" : undefined,
51
+ "data-slot": "svg",
52
+ className: cn("inline-block", colorClass, className),
53
+ ...rest,
54
+ };
55
+
56
+ if (content) {
57
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: this is the whole point of the SVG helper
58
+ return <svg {...props} dangerouslySetInnerHTML={{ __html: sanitizeSvgMarkup(content) }} />;
59
+ }
60
+ // biome-ignore lint/a11y/noSvgWithoutTitle: consumer supplies title via children when needed
61
+ return <svg {...props}>{children}</svg>;
62
+ }
@@ -0,0 +1,16 @@
1
+ // Velloo-owned typography primitive — no shadcn equivalent exists.
2
+ import { textClasses } from "@velloo/schema/typeset";
3
+ import * as React from "react";
4
+ import { cn } from "./cn.ts";
5
+
6
+ export interface TextProps extends React.HTMLAttributes<HTMLParagraphElement> {
7
+ /** Visual weight. Selects a rung of the theme's type ladder plus a tone. */
8
+ variant?: "default" | "muted" | "small" | "lead";
9
+ }
10
+
11
+ export const Text = React.forwardRef<HTMLParagraphElement, TextProps>(
12
+ ({ variant = "default", className, ...props }, ref) => (
13
+ <p ref={ref} className={cn(textClasses(variant), className)} {...props} />
14
+ ),
15
+ );
16
+ Text.displayName = "Text";