juo 0.2.1 → 0.3.0-alpha.1

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 (33) hide show
  1. package/README.md +5 -5
  2. package/bin/dev.js +17 -0
  3. package/dist/{analytics-BMClHCkY.js → analytics-CuhiZN94.js} +5 -5
  4. package/dist/commands/blocks/dev.d.ts +3 -3
  5. package/dist/commands/blocks/dev.js +20 -40
  6. package/dist/commands/create.d.ts +4 -4
  7. package/dist/commands/create.js +16 -3
  8. package/dist/commands/generate.d.ts +5 -5
  9. package/dist/commands/generate.js +2 -2
  10. package/dist/commands/publish/index.d.ts +2 -2
  11. package/dist/{generate-CUst3S4-.js → generate-BiHo2p9_.js} +1 -1
  12. package/dist/hooks/postrun.js +1 -1
  13. package/dist/hooks/prerun.js +1 -1
  14. package/dist/{juoTemplateGenerator-Bsxy3cfd.js → juoTemplateGenerator-RkinYN2l.js} +1 -0
  15. package/dist/lib/index.d.ts +3 -0
  16. package/dist/lib/index.js +2 -2
  17. package/dist/{packageGenerator-BiEzLxCa.js → packageGenerator-D81lWXTA.js} +5 -6
  18. package/oclif.manifest.json +4 -4
  19. package/package.json +1 -1
  20. package/templates/block/preact/{{block.clsName}}.tsx.liquid +30 -65
  21. package/templates/block/react/{{block.clsName}}.tsx.liquid +6 -22
  22. package/templates/configFiles/src/main.ts.liquid +5 -0
  23. package/templates/configFiles/vite-env.d.ts.liquid +17 -0
  24. package/templates/configFiles/vite.config.ts.liquid +4 -3
  25. package/templates/css/styles.css +2 -14
  26. package/templates/dev/index.html.liquid +21 -0
  27. package/templates/dev/vite/blocks-shim.ts.liquid +6 -0
  28. package/templates/dev/vite/juo-editor-plugin.ts.liquid +43 -0
  29. package/templates/dev/vite/theme-shell.ts.liquid +146 -0
  30. package/templates/tailwind/postcss.config.js.liquid +6 -0
  31. package/templates/tailwind/src/tailwind.css.liquid +6 -1
  32. package/templates/tailwind/tailwind.config.cjs.liquid +15 -0
  33. package/templates/tailwind/vite.config.ts.liquid +0 -34
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g juo
20
20
  $ juo COMMAND
21
21
  running command...
22
22
  $ juo (--version)
23
- juo/0.2.1 linux-x64 node-v22.21.0
23
+ juo/0.3.0-alpha.1 linux-x64 node-v22.21.0
24
24
  $ juo --help [COMMAND]
25
25
  USAGE
26
26
  $ juo COMMAND
@@ -39,24 +39,24 @@ USAGE
39
39
 
40
40
  ## `juo blocks dev`
41
41
 
42
- Start local block dev server for use with the Juo editor
42
+ Start local block dev server with embedded editor
43
43
 
44
44
  ```
45
45
  USAGE
46
46
  $ juo blocks dev [--reconfigure] [-p <value>]
47
47
 
48
48
  FLAGS
49
- -p, --port=<value> [default: 5174] Port for the local block dev server
49
+ -p, --port=<value> [default: 5174] Port for the local dev server
50
50
 
51
51
  GLOBAL FLAGS
52
52
  --reconfigure Reconfigure settings
53
53
 
54
54
  DESCRIPTION
55
- Start local block dev server for use with the Juo editor
55
+ Start local block dev server with embedded editor
56
56
 
57
57
  EXAMPLES
58
58
  $ juo blocks dev
59
- Start dev server on default port (5174).
59
+ Start dev server on default port (5174). Opens editor at http://localhost:5174/editor.
60
60
 
61
61
  $ juo blocks dev --port 3000
62
62
  Start dev server on port 3000.
package/bin/dev.js CHANGED
@@ -1,5 +1,22 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
 
3
3
  import {execute} from '@oclif/core'
4
+ import {readFileSync} from 'node:fs'
5
+ import {fileURLToPath} from 'node:url'
6
+
7
+ // Dev mode runs the TypeScript source through tsx, so tsdown's build-time
8
+ // `define` substitutions (see tsdown.config.ts) never happen. Reproduce them
9
+ // here from the workspace package.json files so `juo create` behaves like the
10
+ // built CLI.
11
+ const readVersion = (relPath) =>
12
+ JSON.parse(readFileSync(fileURLToPath(new URL(relPath, import.meta.url)), 'utf8')).version
13
+
14
+ process.env.BLOCKS_EDITOR_VERSION = readVersion('../../editor/package.json')
15
+ process.env.BLOCKS_EXTENSIONS_VERSION = readVersion('../../extensions/package.json')
16
+ // `__CUSTOMER_UI_VERSION__` / `__BLOCKS_VERSION__` are bare globals in
17
+ // packageGenerator.ts (tsdown defines in prod); set them on globalThis so the
18
+ // free identifiers resolve in dev.
19
+ globalThis.__CUSTOMER_UI_VERSION__ = `^${readVersion('../../../components/customer-ui/package.json')}`
20
+ globalThis.__BLOCKS_VERSION__ = `^${readVersion('../../core/package.json')}`
4
21
 
5
22
  await execute({development: true, dir: import.meta.url})
@@ -1,11 +1,11 @@
1
1
  import debug from "debug";
2
2
 
3
3
  //#region src/utils/config.ts
4
- const MIXPANEL_TOKEN = "919dd811a5e2137983cc8fc304b8584b";
5
- const MIXPANEL_API_URL = "https://api-eu.mixpanel.com";
6
- const CUSTOMERIO_BETA_FORM_URL = "https://track-cio.juo.io/api/v1/forms/3c02106b-9746-4ebe-a116-553e47494eb4/submit";
7
- const CUSTOMERIO_SITE_ID = "60da89062ce61084fc27";
8
- const CUSTOMERIO_API_KEY = "1d54688e66cddda9ea72";
4
+ const MIXPANEL_TOKEN = "";
5
+ const MIXPANEL_API_URL = "https://api.mixpanel.com";
6
+ const CUSTOMERIO_BETA_FORM_URL = "";
7
+ const CUSTOMERIO_SITE_ID = "";
8
+ const CUSTOMERIO_API_KEY = "";
9
9
 
10
10
  //#endregion
11
11
  //#region src/utils/analytics.ts
@@ -1,16 +1,16 @@
1
1
  import { t as BaseCommand } from "../../base-command-BQVnm_gK.js";
2
- import * as _oclif_core_interfaces10 from "@oclif/core/interfaces";
2
+ import * as _oclif_core_interfaces0 from "@oclif/core/interfaces";
3
3
 
4
4
  //#region src/commands/blocks/dev.d.ts
5
5
  declare class Dev extends BaseCommand<typeof Dev> {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
- port: _oclif_core_interfaces10.OptionFlag<number, _oclif_core_interfaces10.CustomOptions>;
9
+ port: _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
10
10
  };
11
11
  run(): Promise<void>;
12
12
  private resolvePackageJsonPath;
13
- private waitForFile;
13
+ private tryLoadTenantId;
14
14
  }
15
15
  //#endregion
16
16
  export { Dev as default };
@@ -6,45 +6,29 @@ import { spawn } from "node:child_process";
6
6
 
7
7
  //#region src/commands/blocks/dev.ts
8
8
  var Dev = class Dev extends BaseCommand {
9
- static description = "Start local block dev server for use with the Juo editor";
9
+ static description = "Start local block dev server with embedded editor";
10
10
  static examples = [`<%= config.bin %> <%= command.id %>
11
- Start dev server on default port (5174).
11
+ Start dev server on default port (5174). Opens editor at http://localhost:5174/editor.
12
12
  `, `<%= config.bin %> <%= command.id %> --port 3000
13
13
  Start dev server on port 3000.
14
14
  `];
15
15
  static flags = { port: Flags.integer({
16
16
  char: "p",
17
17
  default: 5174,
18
- description: "Port for the local block dev server"
18
+ description: "Port for the local dev server"
19
19
  }) };
20
20
  async run() {
21
21
  const { flags } = await this.parse(Dev);
22
22
  const { port } = flags;
23
23
  const rootDir = this.resolvePackageJsonPath();
24
- const tenantId = await this.ensureTenantId();
25
- const devBlocksUrl = `http://localhost:${port}/index.js`;
26
- const editorUrl = `https://admin.juo.io/store/${encodeURIComponent(tenantId)}/editor?dev-blocks-url=${encodeURIComponent(devBlocksUrl)}`;
27
- this.log(`\nStarting Juo Blocks dev server...`);
28
- this.log(`\n Open the editor to preview your blocks:`);
29
- this.log(` ${editorUrl}`);
30
- this.log(`\n Dev server running on http://localhost:${port}\n`);
31
- const viteBuild = spawn("npx", [
32
- "vite",
33
- "build",
34
- "--watch"
35
- ], {
36
- cwd: rootDir,
37
- shell: true,
38
- stdio: "inherit"
39
- });
40
- viteBuild.on("error", (err) => {
41
- this.error(`Failed to start vite build: ${err.message}`);
42
- });
43
- const distFile = path.join(rootDir, "dist", "index.js");
44
- await this.waitForFile(distFile);
45
- const vitePreview = spawn("npx", [
24
+ const tenantId = this.tryLoadTenantId();
25
+ if (tenantId) {
26
+ const devBlocksUrl = `http://localhost:${port}/src/blocks/register.ts`;
27
+ const remoteUrl = `https://admin.juo.io/store/${encodeURIComponent(tenantId)}/editor?dev-blocks-url=${encodeURIComponent(devBlocksUrl)}`;
28
+ this.log(`\n remote editor ${remoteUrl}`);
29
+ }
30
+ const vite = spawn("npx", [
46
31
  "vite",
47
- "preview",
48
32
  "--port",
49
33
  String(port)
50
34
  ], {
@@ -52,13 +36,11 @@ Start dev server on port 3000.
52
36
  shell: true,
53
37
  stdio: "inherit"
54
38
  });
55
- vitePreview.on("error", (err) => {
56
- this.error(`Failed to start vite preview: ${err.message}`);
39
+ vite.on("error", (err) => {
40
+ this.error(`Failed to start vite: ${err.message}`);
57
41
  });
58
- this.log(`\nDev server ready. Press Ctrl+C to stop.\n`);
59
42
  const cleanup = () => {
60
- viteBuild.kill();
61
- vitePreview.kill();
43
+ vite.kill();
62
44
  process.exit(0);
63
45
  };
64
46
  process.on("SIGINT", cleanup);
@@ -74,16 +56,14 @@ Start dev server on port 3000.
74
56
  }
75
57
  return currentPath;
76
58
  }
77
- async waitForFile(filePath, timeout = 3e4) {
78
- const interval = 200;
79
- let waited = 0;
80
- while (!existsSync(filePath) && waited < timeout) {
81
- await new Promise((resolve) => {
82
- setTimeout(resolve, interval);
83
- });
84
- waited += interval;
59
+ tryLoadTenantId() {
60
+ if (!this.configExists()) return null;
61
+ try {
62
+ return this.loadConfig().JUO_TENANT_ID?.trim() || null;
63
+ } catch (error) {
64
+ this.warn(`Could not read config file: ${error instanceof Error ? error.message : String(error)}`);
65
+ return null;
85
66
  }
86
- if (!existsSync(filePath)) this.error(`Timed out waiting for ${filePath}`);
87
67
  }
88
68
  };
89
69
 
@@ -1,13 +1,13 @@
1
1
  import { Command } from "@oclif/core";
2
- import * as _oclif_core_interfaces5 from "@oclif/core/interfaces";
2
+ import * as _oclif_core_interfaces1 from "@oclif/core/interfaces";
3
3
 
4
4
  //#region src/commands/create.d.ts
5
5
  declare class Create extends Command {
6
6
  static description: string;
7
7
  static flags: {
8
- quickstart: _oclif_core_interfaces5.BooleanFlag<boolean>;
9
- storybook: _oclif_core_interfaces5.BooleanFlag<boolean>;
10
- verbose: _oclif_core_interfaces5.BooleanFlag<boolean>;
8
+ quickstart: _oclif_core_interfaces1.BooleanFlag<boolean>;
9
+ storybook: _oclif_core_interfaces1.BooleanFlag<boolean>;
10
+ verbose: _oclif_core_interfaces1.BooleanFlag<boolean>;
11
11
  };
12
12
  run(): Promise<void>;
13
13
  private getProjectConfig;
@@ -1,6 +1,6 @@
1
- import { r as BaseTemplateGenerator } from "../juoTemplateGenerator-Bsxy3cfd.js";
2
- import { t as PackageGenerator } from "../packageGenerator-BiEzLxCa.js";
3
- import { t as Generate } from "../generate-CUst3S4-.js";
1
+ import { r as BaseTemplateGenerator } from "../juoTemplateGenerator-RkinYN2l.js";
2
+ import { t as PackageGenerator } from "../packageGenerator-D81lWXTA.js";
3
+ import { t as Generate } from "../generate-BiHo2p9_.js";
4
4
  import { Command, Flags } from "@oclif/core";
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import path from "node:path";
@@ -16,6 +16,18 @@ var ConfigFilesGenerator = class extends BaseTemplateGenerator {
16
16
  }
17
17
  };
18
18
 
19
+ //#endregion
20
+ //#region src/utils/devHarnessGenerator.ts
21
+ var DevHarnessGenerator = class extends BaseTemplateGenerator {
22
+ generate(destDir, options) {
23
+ this.renderTemplate("dev", destDir, {
24
+ editorVersion: "0.25.0-alpha.2",
25
+ extensionsVersion: "1.12.0-alpha.1",
26
+ tailwind: options.tailwind
27
+ });
28
+ }
29
+ };
30
+
19
31
  //#endregion
20
32
  //#region src/utils/storybookGenerator.ts
21
33
  const COMMAND_CONFIG = {
@@ -262,6 +274,7 @@ var Create = class Create extends Command {
262
274
  });
263
275
  tailwindSpinner.stop("Generated Tailwind CSS configuration");
264
276
  }
277
+ new DevHarnessGenerator().generate(projectDir, { tailwind });
265
278
  const originalCwd = process.cwd();
266
279
  try {
267
280
  process.chdir(projectDir);
@@ -1,15 +1,15 @@
1
1
  import { Command } from "@oclif/core";
2
- import * as _oclif_core_interfaces0 from "@oclif/core/interfaces";
2
+ import * as _oclif_core_interfaces6 from "@oclif/core/interfaces";
3
3
 
4
4
  //#region src/commands/generate.d.ts
5
5
  declare class Generate extends Command {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
- name: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
10
- tailwind: _oclif_core_interfaces0.BooleanFlag<boolean>;
11
- type: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
12
- verbose: _oclif_core_interfaces0.BooleanFlag<boolean>;
9
+ name: _oclif_core_interfaces6.OptionFlag<string | undefined, _oclif_core_interfaces6.CustomOptions>;
10
+ tailwind: _oclif_core_interfaces6.BooleanFlag<boolean>;
11
+ type: _oclif_core_interfaces6.OptionFlag<string, _oclif_core_interfaces6.CustomOptions>;
12
+ verbose: _oclif_core_interfaces6.BooleanFlag<boolean>;
13
13
  };
14
14
  static id: string;
15
15
  run(): Promise<void>;
@@ -1,4 +1,4 @@
1
- import "../juoTemplateGenerator-Bsxy3cfd.js";
2
- import { t as Generate } from "../generate-CUst3S4-.js";
1
+ import "../juoTemplateGenerator-RkinYN2l.js";
2
+ import { t as Generate } from "../generate-BiHo2p9_.js";
3
3
 
4
4
  export { Generate as default };
@@ -1,5 +1,5 @@
1
1
  import { t as BaseCommand } from "../../base-command-BQVnm_gK.js";
2
- import * as _oclif_core_interfaces8 from "@oclif/core/interfaces";
2
+ import * as _oclif_core_interfaces4 from "@oclif/core/interfaces";
3
3
 
4
4
  //#region src/commands/publish/index.d.ts
5
5
  declare class Publish extends BaseCommand<typeof Publish> {
@@ -7,7 +7,7 @@ declare class Publish extends BaseCommand<typeof Publish> {
7
7
  static description: string;
8
8
  static examples: string[];
9
9
  static flags: {
10
- theme: _oclif_core_interfaces8.OptionFlag<string, _oclif_core_interfaces8.CustomOptions>;
10
+ theme: _oclif_core_interfaces4.OptionFlag<string, _oclif_core_interfaces4.CustomOptions>;
11
11
  };
12
12
  getAllFiles(dirPath: string, basePath: string): string[];
13
13
  run(): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { t as JuoTemplateGenerator } from "./juoTemplateGenerator-Bsxy3cfd.js";
1
+ import { t as JuoTemplateGenerator } from "./juoTemplateGenerator-RkinYN2l.js";
2
2
  import { Command, Flags } from "@oclif/core";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
@@ -1,4 +1,4 @@
1
- import { a as CUSTOMERIO_BETA_FORM_URL, i as CUSTOMERIO_API_KEY, n as trackCommandCompleted, o as CUSTOMERIO_SITE_ID, t as trackBetaSignup } from "../analytics-BMClHCkY.js";
1
+ import { a as CUSTOMERIO_BETA_FORM_URL, i as CUSTOMERIO_API_KEY, n as trackCommandCompleted, o as CUSTOMERIO_SITE_ID, t as trackBetaSignup } from "../analytics-CuhiZN94.js";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { confirm, isCancel, text } from "@clack/prompts";
@@ -1,4 +1,4 @@
1
- import { r as trackCommandStarted } from "../analytics-BMClHCkY.js";
1
+ import { r as trackCommandStarted } from "../analytics-CuhiZN94.js";
2
2
 
3
3
  //#region src/hooks/prerun.ts
4
4
  const hook = async function(options) {
@@ -143,6 +143,7 @@ var JuoTemplateGenerator = class extends BaseTemplateGenerator {
143
143
  });
144
144
  if (exports.length === 0) return;
145
145
  const registerContent = [
146
+ `import "@juo/customer-ui/web-components";`,
146
147
  `import { registerBlock } from "@juo/blocks";`,
147
148
  exports.map(({ name, slug }) => `import { ${name} } from "./${slug}";`).join("\n"),
148
149
  "",
@@ -7,6 +7,9 @@ interface TemplateScope {
7
7
  slug: string;
8
8
  tailwind?: boolean;
9
9
  };
10
+ editorVersion?: string;
11
+ extensionsVersion?: string;
12
+ tailwind?: boolean;
10
13
  }
11
14
  declare abstract class BaseTemplateGenerator {
12
15
  protected getTemplateDir(name: string): string;
package/dist/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as StoryGenerator, r as BaseTemplateGenerator, t as JuoTemplateGenerator } from "../juoTemplateGenerator-Bsxy3cfd.js";
2
- import { t as PackageGenerator } from "../packageGenerator-BiEzLxCa.js";
1
+ import { n as StoryGenerator, r as BaseTemplateGenerator, t as JuoTemplateGenerator } from "../juoTemplateGenerator-RkinYN2l.js";
2
+ import { t as PackageGenerator } from "../packageGenerator-D81lWXTA.js";
3
3
 
4
4
  export { BaseTemplateGenerator, JuoTemplateGenerator, PackageGenerator, StoryGenerator };
@@ -16,9 +16,6 @@ const FRAMEWORK_CONFIGS = {
16
16
  },
17
17
  react: {
18
18
  dependencies: {
19
- "@juo/blocks": "^1.0.0",
20
- "@mantine/core": "7.5.3",
21
- "@mantine/hooks": "7.5.3",
22
19
  react: "^18.2.0",
23
20
  "react-dom": "^18.2.0"
24
21
  },
@@ -53,8 +50,8 @@ var PackageGenerator = class {
53
50
  "postcss-prefix-selector": "^2.1.1",
54
51
  prettier: "^3.5.3",
55
52
  ...options.tailwind ? {
56
- "@tailwindcss/vite": "^4.1.17",
57
- tailwindcss: "^4.1.17"
53
+ autoprefixer: "^10.4.19",
54
+ tailwindcss: "^3.3.5"
58
55
  } : {},
59
56
  ...options.storybook ? { storybook: "^10.0.7" } : {},
60
57
  typebox: "^1.0.51",
@@ -75,7 +72,7 @@ var PackageGenerator = class {
75
72
  "build-storybook": "storybook build",
76
73
  storybook: "storybook dev -p 6006"
77
74
  } : {},
78
- dev: "vite build -w & vite preview --port 5174",
75
+ dev: "vite --port 5174",
79
76
  preview: "vite preview"
80
77
  },
81
78
  type: "module",
@@ -83,6 +80,8 @@ var PackageGenerator = class {
83
80
  });
84
81
  const frameworkConfig = FRAMEWORK_CONFIGS[options.framework];
85
82
  baseConfig.dependencies = {
83
+ "@juo/blocks": "^1.12.0-alpha.2",
84
+ "@juo/customer-ui": "^0.5.0-alpha.4",
86
85
  ...baseConfig.dependencies,
87
86
  ...frameworkConfig.dependencies
88
87
  };
@@ -96,9 +96,9 @@
96
96
  "blocks:dev": {
97
97
  "aliases": [],
98
98
  "args": {},
99
- "description": "Start local block dev server for use with the Juo editor",
99
+ "description": "Start local block dev server with embedded editor",
100
100
  "examples": [
101
- "<%= config.bin %> <%= command.id %>\nStart dev server on default port (5174).\n",
101
+ "<%= config.bin %> <%= command.id %>\nStart dev server on default port (5174). Opens editor at http://localhost:5174/editor.\n",
102
102
  "<%= config.bin %> <%= command.id %> --port 3000\nStart dev server on port 3000.\n"
103
103
  ],
104
104
  "flags": {
@@ -111,7 +111,7 @@
111
111
  },
112
112
  "port": {
113
113
  "char": "p",
114
- "description": "Port for the local block dev server",
114
+ "description": "Port for the local dev server",
115
115
  "name": "port",
116
116
  "default": 5174,
117
117
  "hasDynamicHelp": false,
@@ -179,5 +179,5 @@
179
179
  ]
180
180
  }
181
181
  },
182
- "version": "0.2.1"
182
+ "version": "0.3.0-alpha.1"
183
183
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "juo",
3
3
  "description": "A CLI tool for the Juo platform",
4
- "version": "0.2.1",
4
+ "version": "0.3.0-alpha.1",
5
5
  "author": "Juo (https://juo.io)",
6
6
  "bin": {
7
7
  "juo": "./bin/run.js"
@@ -1,24 +1,6 @@
1
1
  import { useState } from "preact/hooks";
2
2
  import type { Props } from "./index.ts";{% if block.tailwind %}
3
3
 
4
- const Counter = ({
5
- className,
6
- initialCount,
7
- }: {
8
- className: string;
9
- initialCount: number;
10
- }) => {
11
- const [count, setCount] = useState(initialCount);
12
- return (
13
- <button
14
- className={`px-4 py-2 rounded-sm cursor-pointer ${className}`}
15
- onClick={() => setCount(count + 1)}
16
- >
17
- count: is {count}
18
- </button>
19
- );
20
- };
21
-
22
4
  const FrameworkIcon = ({ className }: { className: string }) => {
23
5
  return (
24
6
  <svg
@@ -77,47 +59,30 @@ const TypeScriptIcon = ({ className }: { className: string }) => {
77
59
  };
78
60
 
79
61
  export function {{ block.clsName }}(props: Props) {
62
+ const [count, setCount] = useState(props.initialCount);
80
63
  return (
81
- <div className="font-[540] flex gap-8 justify-between items-center py-10 px-10 rounded-lg border border-gray-400 bg-[#fafafa]">
82
- <div className="flex items-center gap-[14px]">
83
- <div className="flex items-center gap-2">
84
- <FrameworkIcon className="w-9 h-9 fill-current text-black" />
85
- <TypeScriptIcon className="w-10 h-10 fill-current text-black" />
86
- </div>
87
- <div className="flex flex-col self-center">
88
- <p className="text-[12px] leading-4 text-[#696969]">
89
- {props.subtitle}
90
- </p>
91
- <p className="text-[15px] leading-none text-[#111]">{props.title}</p>
64
+ <juo-card>
65
+ <div className="font-[540] flex gap-8 justify-between items-center py-10 px-10">
66
+ <div className="flex items-center gap-[14px]">
67
+ <div className="flex items-center gap-2">
68
+ <FrameworkIcon className="w-9 h-9 fill-current text-black" />
69
+ <TypeScriptIcon className="w-10 h-10 fill-current text-black" />
70
+ </div>
71
+ <div className="flex flex-col self-center">
72
+ <p className="text-[12px] leading-4 text-[#696969]">
73
+ {props.subtitle}
74
+ </p>
75
+ <p className="text-[15px] leading-none text-[#111]">{props.title}</p>
76
+ </div>
92
77
  </div>
78
+ <juo-button onClick={() => setCount(count + 1)}>count: is {count}</juo-button>
93
79
  </div>
94
- <Counter
95
- className="text-xs leading-3 text-white bg-[#111] rounded-md px-2 py-1.5 min-w-22"
96
- initialCount={props.initialCount}
97
- />
98
- </div>
80
+ </juo-card>
99
81
  );
100
82
  }
101
83
 
102
84
  {% else %}
103
85
  import "../../styles.css";
104
- const Counter = ({
105
- className,
106
- initialCount,
107
- }: {
108
- className: string;
109
- initialCount: number;
110
- }) => {
111
- const [count, setCount] = useState(initialCount);
112
- return (
113
- <button
114
- className={`counter-button ${className}`}
115
- onClick={() => setCount(count + 1)}
116
- >
117
- count: is {count}
118
- </button>
119
- );
120
- };
121
86
 
122
87
  const FrameworkIcon = ({ className }: { className: string }) => {
123
88
  return (
@@ -177,23 +142,23 @@ const TypeScriptIcon = ({ className }: { className: string }) => {
177
142
  };
178
143
 
179
144
  export function {{ block.clsName }}(props: Props) {
145
+ const [count, setCount] = useState(props.initialCount);
180
146
  return (
181
- <div className="container">
182
- <div className="content">
183
- <div className="icon-container">
184
- <FrameworkIcon className="framework-icon" />
185
- <TypeScriptIcon className="typescript-icon" />
186
- </div>
187
- <div className="text-container">
188
- <p className="subtitle">{props.subtitle}</p>
189
- <p className="title">{props.title}</p>
147
+ <juo-card>
148
+ <div className="container">
149
+ <div className="content">
150
+ <div className="icon-container">
151
+ <FrameworkIcon className="framework-icon" />
152
+ <TypeScriptIcon className="typescript-icon" />
153
+ </div>
154
+ <div className="text-container">
155
+ <p className="subtitle">{props.subtitle}</p>
156
+ <p className="title">{props.title}</p>
157
+ </div>
190
158
  </div>
159
+ <juo-button onClick={() => setCount(count + 1)}>count: is {count}</juo-button>
191
160
  </div>
192
- <Counter
193
- className="counter-button-custom"
194
- initialCount={props.initialCount}
195
- />
196
- </div>
161
+ </juo-card>
197
162
  );
198
163
  }
199
164
  {% endif %}
@@ -1,19 +1,6 @@
1
- import { useRef, useState } from "react";
2
- import { MantineProvider } from "@mantine/core";
1
+ import { useState } from "react";
3
2
  import type { Props } from ".";
4
3
 
5
- const Counter = ({ className, initialCount = 0 }: { className: string; initialCount?: number }) => {
6
- const [count, setCount] = useState(initialCount);
7
- return (
8
- <button
9
- className={`px-4 py-2 rounded-md cursor-pointer ${className}`}
10
- onClick={() => setCount(count + 1)}
11
- >
12
- count: is {count}
13
- </button>
14
- );
15
- };
16
-
17
4
  const FrameworkIcon = ({ className }: { className: string }) => {
18
5
  return (
19
6
  <svg
@@ -63,11 +50,11 @@ const TypeScriptIcon = ({ className }: { className: string }) => {
63
50
  };
64
51
 
65
52
  export function {{ block.clsName }}(props: Props) {
66
- const root = useRef<HTMLDivElement>(null);
53
+ const [count, setCount] = useState(props.initialCount ?? 0);
67
54
 
68
55
  return (
69
- <MantineProvider>
70
- <div ref={root} className="font-[540] flex gap-8 justify-between items-center py-10 px-10 rounded-lg border border-gray-400 bg-[#fafafa]">
56
+ <juo-card>
57
+ <div className="font-[540] flex gap-8 justify-between items-center py-10 px-10">
71
58
  <div className="flex items-center gap-[14px]">
72
59
  <div className="flex items-center gap-2">
73
60
  <FrameworkIcon className="w-9 h-9 fill-current text-black" />
@@ -80,11 +67,8 @@ export function {{ block.clsName }}(props: Props) {
80
67
  <p className="text-[15px] leading-none text-[#111]">{props.title}</p>
81
68
  </div>
82
69
  </div>
83
- <Counter
84
- className="text-xs leading-3 text-white bg-[#111] rounded-md px-2 py-1.5 min-w-22"
85
- initialCount={props.initialCount}
86
- />
70
+ <juo-button onClick={() => setCount(count + 1)}>count: is {count}</juo-button>
87
71
  </div>
88
- </MantineProvider>
72
+ </juo-card>
89
73
  );
90
74
  }
@@ -0,0 +1,5 @@
1
+ // Your blocks theme entry. `vite build` bundles this file in library mode into
2
+ // the artifact the Juo runtime loads in production, so it must export
3
+ // `registerBlocks`. Only your project's blocks belong here — the dev preview
4
+ // (theme shell + native blocks) lives in `vite/` and never ships in this bundle.
5
+ export * from "./blocks/register";
@@ -1,2 +1,19 @@
1
1
  /// <reference types="vite/client" />
2
2
  /// <reference types="vite-plugin-svgr/client" />
3
+ {% if framework == "react" %}
4
+
5
+ declare namespace React {
6
+ namespace JSX {
7
+ // Bridge HTMLElementTagNameMap (augmented by @juo/customer-ui/web-components) into
8
+ // React's IntrinsicElements so that juo-* custom elements typecheck without a
9
+ // hardcoded list. Omit<…, keyof HTMLElement> strips DOM methods/properties,
10
+ // leaving only component-specific props.
11
+ type _JuoElements = {
12
+ [K in Extract<keyof HTMLElementTagNameMap, `juo-${string}`>]:
13
+ React.HTMLAttributes<HTMLElementTagNameMap[K]> &
14
+ Partial<Omit<HTMLElementTagNameMap[K], keyof HTMLElement>>;
15
+ };
16
+ interface IntrinsicElements extends _JuoElements {}
17
+ }
18
+ }
19
+ {% endif %}
@@ -1,5 +1,6 @@
1
1
  import { defineConfig } from "vite";
2
2
  import svgr from "vite-plugin-svgr";
3
+ import juo from "./vite/juo-editor-plugin";
3
4
  {% if framework == "react" %}
4
5
  import react from "@vitejs/plugin-react";
5
6
  {% elsif framework == "preact" %}
@@ -10,7 +11,7 @@ export default defineConfig({
10
11
  define: {
11
12
  "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
12
13
  },
13
- plugins: [{% if framework == "react" %}react(), {% endif %}{% if framework == "preact" %}preact(), {% endif %}
14
+ plugins: [juo(), {% if framework == "react" %}react(), {% endif %}{% if framework == "preact" %}preact(), {% endif %}
14
15
  svgr({
15
16
  svgrOptions: {
16
17
  icon: true,
@@ -22,12 +23,12 @@ export default defineConfig({
22
23
  },
23
24
  build: {
24
25
  lib: {
25
- entry: "src/blocks/register.ts",
26
+ entry: "src/main.ts",
26
27
  formats: ["es"],
27
28
  fileName: "index",
28
29
  },
29
30
  rollupOptions: {
30
- external: ["@juo/blocks"],
31
+ external: [/^@juo\/blocks/],
31
32
  },
32
33
  },
33
34
  });
@@ -1,8 +1,5 @@
1
- .counter-button {
2
- padding: 0.5rem 1rem;
3
- border-radius: 0.125rem;
4
- cursor: pointer;
5
- }
1
+ @import "@juo/customer-ui/design-tokens.css";
2
+ @import "@juo/customer-ui/theme-modes.css";
6
3
 
7
4
  .container {
8
5
  font-weight: 540;
@@ -60,12 +57,3 @@
60
57
  color: #111;
61
58
  }
62
59
 
63
- .counter-button-custom {
64
- font-size: 0.75rem;
65
- line-height: 0.75rem;
66
- color: white;
67
- background-color: #111;
68
- border-radius: 0.375rem;
69
- padding: 0.375rem 0.5rem;
70
- min-width: 5.5rem;
71
- }
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Juo Blocks Dev</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@juo/blocks": "/vite/blocks-shim.ts"
11
+ }
12
+ }
13
+ </script>
14
+ </head>
15
+ <body>
16
+ <juo-context-root>
17
+ <div id="app"></div>
18
+ </juo-context-root>
19
+ <script type="module" src="/vite/theme-shell.ts"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,6 @@
1
+ // Re-exports the locally installed @juo/blocks. The CDN-hosted
2
+ // @juo/blocks-extensions bundle externalizes "@juo/blocks", so the index.html
3
+ // import map points that bare specifier at this shim — ensuring the native
4
+ // blocks register into the SAME @juo/blocks instance the theme shell uses
5
+ // (Vite dedupes both to one pre-bundled module). Dev-harness only.
6
+ export * from "@juo/blocks";
@@ -0,0 +1,43 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ const EDITOR_EMBED_URL =
4
+ "https://cdn.juo.io/web/blocks/juo/editor/{{ editorVersion }}/juo-editor.embed.js";
5
+
6
+ export default function juo(): Plugin {
7
+ return {
8
+ name: "juo-editor",
9
+ configureServer(server) {
10
+ server.middlewares.use("/editor", (_req, res) => {
11
+ res.writeHead(200, { "Content-Type": "text/html" });
12
+ res.end(`<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8" />
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
+ <title>Juo Blocks Editor</title>
18
+ <style>
19
+ html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; }
20
+ juo-editor { display: block; height: 100vh; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <script type="module" src="${EDITOR_EMBED_URL}"></script>
25
+ <juo-editor url="/?editor=true"></juo-editor>
26
+ </body>
27
+ </html>`);
28
+ });
29
+
30
+ server.httpServer?.once("listening", () => {
31
+ const address = server.httpServer?.address();
32
+ const port =
33
+ address && typeof address === "object" ? address.port : 5174;
34
+ const base = `http://localhost:${port}`;
35
+ server.config.logger.info(
36
+ `\n juo editor ${base}/editor` +
37
+ `\n editor view mode ${base}/` +
38
+ `\n blocks bundle ${base}/src/main.ts\n`,
39
+ );
40
+ });
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,146 @@
1
+ // Dev-only theme shell. Lives outside the project's `src/` so the author's
2
+ // source tree contains only their blocks. Vite serves this file directly (see
3
+ // the <script> + import map injected in index.html); it never enters the built
4
+ // blocks bundle (whose entry is `src/blocks/register.ts`).
5
+ import "@juo/blocks/web-components/editor";
6
+ {% if tailwind %}import "../src/tailwind.css";
7
+ {% else %}import "../src/styles.css";
8
+ {% endif %}
9
+ import {
10
+ createBlockInstance,
11
+ createThemeState,
12
+ defineBlock,
13
+ effect,
14
+ injectContext,
15
+ isEditorPreview,
16
+ provideContext,
17
+ registerBlock,
18
+ serializeBlocks,
19
+ ThemeStateContext,
20
+ TranslationContext,
21
+ createTranslationService,
22
+ untracked,
23
+ } from "@juo/blocks";
24
+ import { registerBlocks as registerProjectBlocks } from "../src/main";
25
+ // Native blocks shipped by @juo/blocks-extensions, loaded from CDN. Its bare
26
+ // "@juo/blocks" import is bridged to the local instance via the index.html
27
+ // import map (see vite/blocks-shim.ts).
28
+ // @ts-expect-error Remote, untyped module
29
+ import { blocks as nativeBlocks } from "https://cdn.juo.io/web/blocks/juo/{{ extensionsVersion }}/extensions.js";
30
+
31
+ const homePageBlock = defineBlock("HomePage", {
32
+ displayName: "Home page",
33
+ group: "page",
34
+ schema: {
35
+ type: "object",
36
+ properties: {
37
+ props: {
38
+ type: "object",
39
+ properties: {},
40
+ additionalProperties: false,
41
+ },
42
+ slots: {
43
+ type: "object",
44
+ properties: { main: {} },
45
+ additionalProperties: false,
46
+ },
47
+ },
48
+ required: ["props", "slots"],
49
+ additionalProperties: false,
50
+ },
51
+ initialValue: () => ({
52
+ props: {},
53
+ slots: {
54
+ main: [createBlockInstance("Starter Block")],
55
+ },
56
+ }),
57
+ renderer() {
58
+ const page = document.createElement("juo-page");
59
+ page.setAttribute("name", "HomePage");
60
+ const extensionRoot = document.createElement("juo-extension-root");
61
+ extensionRoot.setAttribute("name", "main");
62
+ page.appendChild(extensionRoot);
63
+ return page;
64
+ },
65
+ });
66
+
67
+ function registerBlocks() {
68
+ Object.values(nativeBlocks).forEach((block) => registerBlock(block));
69
+ registerProjectBlocks();
70
+ registerBlock(homePageBlock);
71
+ }
72
+
73
+ async function setupEditorMode(root: HTMLElement) {
74
+ const editorModule = await import("@juo/blocks/editor");
75
+ const editor = editorModule.createEditorBridge();
76
+ const routerService = {
77
+ push: () => {},
78
+ subscribe: () => () => {},
79
+ getPages: () => [{ path: "/", block: "HomePage", title: "Home", routeNames: [] }],
80
+ };
81
+ const themeState = createThemeState({
82
+ resolve: (surface, path) => editor.requestThemeState(surface, path),
83
+ upsertState: (surface, path, blocks) =>
84
+ editor.upsertThemeState(surface, path, blocks),
85
+ translations: {
86
+ resolveTranslations: (locale) =>
87
+ editor.requestTranslationOverrides(locale),
88
+ },
89
+ routerService,
90
+ });
91
+ provideContext(root, ThemeStateContext, themeState);
92
+ await editorModule.setupEditorMode({ routerService, themeState });
93
+ }
94
+
95
+ function setupViewMode(root: HTMLElement) {
96
+ const themeState = createThemeState({
97
+ resolve: async () => ({
98
+ blocks: serializeBlocks([createBlockInstance("HomePage")]),
99
+ }),
100
+ });
101
+ provideContext(root, ThemeStateContext, themeState);
102
+ }
103
+
104
+ async function initializeTheme() {
105
+ const root = document.querySelector("juo-context-root") as HTMLElement;
106
+
107
+ registerBlocks();
108
+
109
+ if (isEditorPreview()) {
110
+ await setupEditorMode(root);
111
+ } else {
112
+ setupViewMode(root);
113
+ }
114
+
115
+ const themeState = injectContext(root, ThemeStateContext);
116
+
117
+ await themeState.locales.setLocale("en");
118
+ provideContext(
119
+ root,
120
+ TranslationContext,
121
+ createTranslationService(themeState.locales, {
122
+ async loadDefaultTranslations() {
123
+ return {};
124
+ },
125
+ }),
126
+ );
127
+
128
+ void themeState.resolve("page", "/");
129
+
130
+ effect(() => {
131
+ const block = themeState.page.blocks.value.at(0);
132
+ untracked(() => {
133
+ if (block == null) {
134
+ document.querySelector("#app")?.replaceChildren();
135
+ } else {
136
+ document.querySelector("#app")?.replaceChildren(block.definition.render(block));
137
+ }
138
+ });
139
+ });
140
+ }
141
+
142
+ if (document.readyState === "loading") {
143
+ document.addEventListener("DOMContentLoaded", () => void initializeTheme());
144
+ } else {
145
+ void initializeTheme();
146
+ }
@@ -0,0 +1,6 @@
1
+ import autoprefixer from "autoprefixer";
2
+ import tailwindcss from "tailwindcss";
3
+
4
+ export default {
5
+ plugins: [tailwindcss(), autoprefixer()],
6
+ };
@@ -1 +1,6 @@
1
- @import "tailwindcss";
1
+ @import "@juo/customer-ui/design-tokens.css";
2
+ @import "@juo/customer-ui/theme-modes.css";
3
+
4
+ @tailwind base;
5
+ @tailwind components;
6
+ @tailwind utilities;
@@ -0,0 +1,15 @@
1
+ /* eslint-disable @typescript-eslint/no-require-imports */
2
+ /** @type {import('tailwindcss').Config} */
3
+ module.exports = {
4
+ presets: [{ theme: { extend: require("@juo/customer-ui/tailwind-v3").theme } }],
5
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,vue}"],
6
+ theme: {
7
+ extend: {
8
+ colors: {
9
+ primary: "var(--primary)",
10
+ muted: "var(--muted)",
11
+ },
12
+ },
13
+ },
14
+ plugins: [],
15
+ };
@@ -1,34 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import tailwindcss from "@tailwindcss/vite";
3
- import svgr from "vite-plugin-svgr";
4
- {% if framework == "react" %}
5
- import react from "@vitejs/plugin-react";
6
- {% elsif framework == "preact" %}
7
- import preact from "@preact/preset-vite";
8
- {% endif %}
9
-
10
- export default defineConfig({
11
- define: {
12
- "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
13
- },
14
- plugins: [tailwindcss(){% if framework == "react" %}, react(){% endif %}{% if framework == "preact" %}, preact(){% endif %},
15
- svgr({
16
- svgrOptions: {
17
- icon: true,
18
- },
19
- }),
20
- ],
21
- preview: {
22
- cors: true,
23
- },
24
- build: {
25
- lib: {
26
- entry: "src/blocks/register.ts",
27
- formats: ["es"],
28
- fileName: "index",
29
- },
30
- rollupOptions: {
31
- external: ["@juo/blocks"],
32
- },
33
- },
34
- });