create-cascade 0.1.6 → 0.1.8

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 (2) hide show
  1. package/index.js +272 -71
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -111,11 +111,13 @@ function printHelp() {
111
111
  }
112
112
 
113
113
  function normalizePackageName(name) {
114
- return name
115
- .trim()
116
- .toLowerCase()
117
- .replace(/[^a-z0-9-_]+/g, "-")
118
- .replace(/^-+|-+$/g, "") || "cascade-app"
114
+ return (
115
+ name
116
+ .trim()
117
+ .toLowerCase()
118
+ .replace(/[^a-z0-9-_]+/g, "-")
119
+ .replace(/^-+|-+$/g, "") || "cascade-app"
120
+ )
119
121
  }
120
122
 
121
123
  function ensureDirectoryIsEmpty(targetDir) {
@@ -158,7 +160,9 @@ async function selectOption(rl, label, options) {
158
160
  const prefix = isSelected ? `${ANSI_BOLD}${ANSI_CYAN}>${ANSI_RESET}` : " "
159
161
  const styleStart = isSelected ? `${ANSI_BOLD}${ANSI_CYAN}` : ""
160
162
  const styleEnd = isSelected ? ANSI_RESET : ""
161
- stdout.write(`${prefix} ${styleStart}${option.label}${styleEnd} ${ANSI_DIM}(${option.id}) - ${option.description}${ANSI_RESET}\n`)
163
+ stdout.write(
164
+ `${prefix} ${styleStart}${option.label}${styleEnd} ${ANSI_DIM}(${option.id}) - ${option.description}${ANSI_RESET}\n`
165
+ )
162
166
  }
163
167
 
164
168
  stdout.write(`${ANSI_DIM}Use Up/Down arrows and Enter${ANSI_RESET}\n`)
@@ -367,84 +371,212 @@ function getSource(framework, starter) {
367
371
 
368
372
  const renderer = await createCliRenderer({ exitOnCtrlC: true })
369
373
 
370
- const panel = new BoxRenderable(renderer, {
374
+ const root = new BoxRenderable(renderer, {
375
+ width: "100%",
376
+ height: "100%",
377
+ padding: 1,
378
+ flexDirection: "column",
379
+ })
380
+
381
+ const card = new BoxRenderable(renderer, {
371
382
  border: true,
372
383
  borderStyle: "single",
373
384
  padding: 1,
374
- margin: 1,
375
- width: 56,
376
- height: 8,
385
+ width: 72,
386
+ height: 16,
377
387
  flexDirection: "column",
378
388
  })
379
389
 
390
+ const header = new BoxRenderable(renderer, {
391
+ flexDirection: "column",
392
+ marginBottom: 1,
393
+ })
394
+
380
395
  const title = new TextRenderable(renderer, {
381
- content: "Hello from Cascade Core",
396
+ content: "Cascade Core",
382
397
  fg: "#00ff99",
383
398
  })
384
399
 
385
400
  const subtitle = new TextRenderable(renderer, {
386
- content: "Run bun run dev after edits to refresh your app",
401
+ content: "A tiny terminal UI runtime with clean layout primitives.",
402
+ fg: "#cbd5e1",
403
+ marginTop: 1,
404
+ })
405
+
406
+ header.add(title)
407
+ header.add(subtitle)
408
+
409
+ const steps = new BoxRenderable(renderer, {
410
+ flexDirection: "column",
387
411
  marginTop: 1,
388
- fg: "#cccccc",
412
+ })
413
+
414
+ const s1 = new TextRenderable(renderer, { content: "1) Edit src/index.ts", fg: "#e2e8f0" })
415
+ const s2 = new TextRenderable(renderer, { content: "2) bun run dev", fg: "#e2e8f0", marginTop: 1 })
416
+ const s3 = new TextRenderable(renderer, { content: "3) Compose renderables into screens", fg: "#e2e8f0", marginTop: 1 })
417
+
418
+ steps.add(s1)
419
+ steps.add(s2)
420
+ steps.add(s3)
421
+
422
+ const footer = new BoxRenderable(renderer, {
423
+ marginTop: 2,
424
+ border: true,
425
+ borderStyle: "single",
426
+ padding: 1,
427
+ flexDirection: "column",
428
+ })
429
+
430
+ const keys = new TextRenderable(renderer, {
431
+ content: "Keys: Ctrl+C quit",
432
+ fg: "#94a3b8",
389
433
  })
390
434
 
391
435
  const hint = new TextRenderable(renderer, {
392
- content: "Press Ctrl+C to exit",
436
+ content: "Tip: Keep rendering deterministic. Let state drive UI updates.",
437
+ fg: "#94a3b8",
393
438
  marginTop: 1,
394
- fg: "#999999",
395
439
  })
396
440
 
397
- panel.add(title)
398
- panel.add(subtitle)
399
- panel.add(hint)
400
- renderer.root.add(panel)
441
+ footer.add(keys)
442
+ footer.add(hint)
443
+
444
+ card.add(header)
445
+ card.add(steps)
446
+ card.add(footer)
447
+
448
+ root.add(card)
449
+ renderer.root.add(root)
401
450
  `,
402
- counter: `import { TextRenderable, createCliRenderer } from "@cascadetui/core"
451
+ counter: `import { BoxRenderable, TextRenderable, createCliRenderer } from "@cascadetui/core"
403
452
 
404
453
  const renderer = await createCliRenderer({ exitOnCtrlC: true })
405
454
  let count = 0
406
455
 
407
- const text = new TextRenderable(renderer, {
408
- content: "Count: 0",
409
- margin: 2,
456
+ const root = new BoxRenderable(renderer, {
457
+ width: "100%",
458
+ height: "100%",
459
+ padding: 1,
460
+ flexDirection: "column",
461
+ })
462
+
463
+ const card = new BoxRenderable(renderer, {
464
+ border: true,
465
+ borderStyle: "single",
466
+ padding: 1,
467
+ width: 60,
468
+ height: 10,
469
+ flexDirection: "column",
470
+ })
471
+
472
+ const title = new TextRenderable(renderer, {
473
+ content: "Core Counter",
410
474
  fg: "#00ff99",
411
475
  })
412
476
 
413
- renderer.root.add(text)
477
+ const value = new TextRenderable(renderer, {
478
+ content: "Count: 0",
479
+ fg: "#e2e8f0",
480
+ marginTop: 1,
481
+ })
482
+
483
+ const meta = new TextRenderable(renderer, {
484
+ content: "Updates every second. Ctrl+C to exit.",
485
+ fg: "#94a3b8",
486
+ marginTop: 1,
487
+ })
488
+
489
+ card.add(title)
490
+ card.add(value)
491
+ card.add(meta)
492
+
493
+ root.add(card)
494
+ renderer.root.add(root)
414
495
 
415
496
  setInterval(() => {
416
497
  count += 1
417
- text.content = \`Count: \${count}\`
498
+ value.content = \`Count: \${count}\`
418
499
  }, 1000)
419
500
  `,
420
501
  layout: `import { BoxRenderable, TextRenderable, createCliRenderer } from "@cascadetui/core"
421
502
 
422
503
  const renderer = await createCliRenderer({ exitOnCtrlC: true })
423
504
 
424
- const container = new BoxRenderable(renderer, {
505
+ const root = new BoxRenderable(renderer, {
506
+ width: "100%",
507
+ height: "100%",
508
+ padding: 1,
509
+ flexDirection: "row",
510
+ gap: 2,
511
+ })
512
+
513
+ const sidebar = new BoxRenderable(renderer, {
425
514
  border: true,
426
515
  borderStyle: "single",
427
516
  padding: 1,
428
- margin: 1,
429
- width: 50,
430
- height: 9,
517
+ width: 26,
518
+ height: 18,
431
519
  flexDirection: "column",
432
520
  })
433
521
 
434
- const title = new TextRenderable(renderer, {
435
- content: "Cascade Core Starter",
522
+ const sideTitle = new TextRenderable(renderer, {
523
+ content: "Navigation",
436
524
  fg: "#00ff99",
437
525
  })
438
526
 
439
- const subtitle = new TextRenderable(renderer, {
440
- content: "Press Ctrl+C to exit",
527
+ const nav1 = new TextRenderable(renderer, { content: "• Overview", fg: "#e2e8f0", marginTop: 1 })
528
+ const nav2 = new TextRenderable(renderer, { content: " Components", fg: "#e2e8f0", marginTop: 1 })
529
+ const nav3 = new TextRenderable(renderer, { content: "• Input & Focus", fg: "#e2e8f0", marginTop: 1 })
530
+ const nav4 = new TextRenderable(renderer, { content: "• Performance", fg: "#e2e8f0", marginTop: 1 })
531
+
532
+ sidebar.add(sideTitle)
533
+ sidebar.add(nav1)
534
+ sidebar.add(nav2)
535
+ sidebar.add(nav3)
536
+ sidebar.add(nav4)
537
+
538
+ const main = new BoxRenderable(renderer, {
539
+ border: true,
540
+ borderStyle: "single",
541
+ padding: 1,
542
+ width: 74,
543
+ height: 18,
544
+ flexDirection: "column",
545
+ })
546
+
547
+ const mainTitle = new TextRenderable(renderer, {
548
+ content: "Layout Starter",
549
+ fg: "#00ff99",
550
+ })
551
+
552
+ const mainBody = new TextRenderable(renderer, {
553
+ content: "Compose a screen with a sidebar + content panel. Add your own state and update text/content deterministically.",
554
+ fg: "#cbd5e1",
441
555
  marginTop: 1,
442
- fg: "#cccccc",
443
556
  })
444
557
 
445
- container.add(title)
446
- container.add(subtitle)
447
- renderer.root.add(container)
558
+ const mainFooter = new BoxRenderable(renderer, {
559
+ border: true,
560
+ borderStyle: "single",
561
+ padding: 1,
562
+ marginTop: 2,
563
+ flexDirection: "column",
564
+ })
565
+
566
+ const footerText = new TextRenderable(renderer, {
567
+ content: "Keys: Ctrl+C quit",
568
+ fg: "#94a3b8",
569
+ })
570
+
571
+ mainFooter.add(footerText)
572
+
573
+ main.add(mainTitle)
574
+ main.add(mainBody)
575
+ main.add(mainFooter)
576
+
577
+ root.add(sidebar)
578
+ root.add(main)
579
+ renderer.root.add(root)
448
580
  `,
449
581
  },
450
582
  react: {
@@ -453,10 +585,20 @@ import { createRoot } from "@cascadetui/react"
453
585
 
454
586
  function App() {
455
587
  return (
456
- <box style={{ border: true, borderStyle: "single", padding: 1, margin: 1, width: 56, height: 8, flexDirection: "column" }}>
457
- <text content="Hello from Cascade React" fg="#00ff99" />
458
- <text content="Edit src/index.tsx and run bun run dev" marginTop={1} fg="#cccccc" />
459
- <text content="Press Ctrl+C to exit" marginTop={1} fg="#999999" />
588
+ <box style={{ width: "100%", height: "100%", padding: 1, flexDirection: "column", alignItems: "flex-start" }}>
589
+ <box style={{ border: true, borderStyle: "single", padding: 1, width: 72, height: 16, flexDirection: "column" }}>
590
+ <text content="Cascade React" fg="#00ff99" />
591
+ <text content="Build interactive terminal UIs with components and hooks." fg="#cbd5e1" marginTop={1} />
592
+ <box style={{ flexDirection: "column", marginTop: 2 }}>
593
+ <text content="1) Edit src/index.tsx" fg="#e2e8f0" />
594
+ <text content="2) bun run dev" fg="#e2e8f0" marginTop={1} />
595
+ <text content="3) Add screens, keymaps, and deterministic state" fg="#e2e8f0" marginTop={1} />
596
+ </box>
597
+ <box style={{ border: true, borderStyle: "single", padding: 1, marginTop: 2, flexDirection: "column" }}>
598
+ <text content="Keys: Ctrl+C quit" fg="#94a3b8" />
599
+ <text content="Tip: Keep expensive panels stable. Avoid re-render storms in lists." fg="#94a3b8" marginTop={1} />
600
+ </box>
601
+ </box>
460
602
  </box>
461
603
  )
462
604
  }
@@ -466,19 +608,27 @@ createRoot(renderer).render(<App />)
466
608
  `,
467
609
  counter: `import { createCliRenderer } from "@cascadetui/core"
468
610
  import { createRoot } from "@cascadetui/react"
469
- import { useEffect, useState } from "react"
611
+ import { useEffect, useMemo, useState } from "react"
470
612
 
471
613
  function App() {
472
614
  const [count, setCount] = useState(0)
615
+ const startedAt = useMemo(() => Date.now(), [])
473
616
 
474
617
  useEffect(() => {
475
618
  const timer = setInterval(() => setCount((value) => value + 1), 1000)
476
619
  return () => clearInterval(timer)
477
620
  }, [])
478
621
 
622
+ const seconds = Math.floor((Date.now() - startedAt) / 1000)
623
+
479
624
  return (
480
- <box style={{ border: true, padding: 1, margin: 1 }}>
481
- <text content={\`React counter: \${count}\`} fg="#00ff99" />
625
+ <box style={{ width: "100%", height: "100%", padding: 1, flexDirection: "column" }}>
626
+ <box style={{ border: true, borderStyle: "single", padding: 1, width: 60, height: 10, flexDirection: "column" }}>
627
+ <text content="React Counter" fg="#00ff99" />
628
+ <text content={\`Count: \${count}\`} fg="#e2e8f0" marginTop={1} />
629
+ <text content={\`Uptime: \${seconds}s\`} fg="#cbd5e1" marginTop={1} />
630
+ <text content="Ctrl+C to exit" fg="#94a3b8" marginTop={1} />
631
+ </box>
482
632
  </box>
483
633
  )
484
634
  }
@@ -488,22 +638,36 @@ createRoot(renderer).render(<App />)
488
638
  `,
489
639
  login: `import { createCliRenderer } from "@cascadetui/core"
490
640
  import { createRoot, useKeyboard } from "@cascadetui/react"
491
- import { useState } from "react"
641
+ import { useMemo, useState } from "react"
492
642
 
493
643
  function App() {
494
644
  const [username, setUsername] = useState("")
495
645
  const [password, setPassword] = useState("")
496
- const [focused, setFocused] = useState("username")
497
- const [status, setStatus] = useState("idle")
646
+ const [focused, setFocused] = useState<"username" | "password">("username")
647
+ const [status, setStatus] = useState<"idle" | "invalid" | "success">("idle")
648
+
649
+ const statusText = useMemo(() => {
650
+ if (status === "success") return "Authenticated"
651
+ if (status === "invalid") return "Invalid credentials"
652
+ return "Idle"
653
+ }, [status])
498
654
 
499
655
  useKeyboard((key) => {
500
656
  if (key.name === "tab") {
501
657
  setFocused((value) => (value === "username" ? "password" : "username"))
658
+ return
659
+ }
660
+ if (key.name === "escape") {
661
+ setUsername("")
662
+ setPassword("")
663
+ setStatus("idle")
664
+ setFocused("username")
665
+ return
502
666
  }
503
667
  })
504
668
 
505
669
  const submit = () => {
506
- if (username === "admin" && password === "secret") {
670
+ if (username.trim() === "admin" && password === "secret") {
507
671
  setStatus("success")
508
672
  return
509
673
  }
@@ -511,15 +675,19 @@ function App() {
511
675
  }
512
676
 
513
677
  return (
514
- <box style={{ padding: 2, flexDirection: "column" }}>
678
+ <box style={{ width: "100%", height: "100%", padding: 2, flexDirection: "column" }}>
515
679
  <text content="Cascade Login" fg="#00ff99" />
516
- <box title="Username" style={{ border: true, width: 40, height: 3, marginTop: 1 }}>
517
- <input focused={focused === "username"} placeholder="admin" onInput={setUsername} onSubmit={submit} />
680
+ <text content="Tab switch fields, Enter submit, Esc reset" fg="#94a3b8" marginTop={1} />
681
+ <box title="Username" style={{ border: true, borderStyle: "single", width: 44, height: 3, marginTop: 2 }}>
682
+ <input focused={focused === "username"} placeholder="admin" value={username} onInput={setUsername} onSubmit={submit} />
683
+ </box>
684
+ <box title="Password" style={{ border: true, borderStyle: "single", width: 44, height: 3, marginTop: 1 }}>
685
+ <input focused={focused === "password"} placeholder="secret" value={password} onInput={setPassword} onSubmit={submit} />
518
686
  </box>
519
- <box title="Password" style={{ border: true, width: 40, height: 3, marginTop: 1 }}>
520
- <input focused={focused === "password"} placeholder="secret" onInput={setPassword} onSubmit={submit} />
687
+ <box style={{ border: true, borderStyle: "single", padding: 1, width: 44, marginTop: 2, flexDirection: "column" }}>
688
+ <text content={\`Status: \${statusText}\`} fg={status === "success" ? "green" : status === "invalid" ? "yellow" : "#cbd5e1"} />
689
+ <text content={\`Focused: \${focused}\`} fg="#94a3b8" marginTop={1} />
521
690
  </box>
522
- <text content={\`Status: \${status}\`} marginTop={1} fg={status === "success" ? "green" : "yellow"} />
523
691
  </box>
524
692
  )
525
693
  }
@@ -532,10 +700,20 @@ createRoot(renderer).render(<App />)
532
700
  minimal: `import { render } from "@cascadetui/solid"
533
701
 
534
702
  const App = () => (
535
- <box style={{ border: true, borderStyle: "single", padding: 1, margin: 1, width: 56, height: 8, flexDirection: "column" }}>
536
- <text content="Hello from Cascade Solid" fg="#00ff99" />
537
- <text content="Edit src/index.tsx and run bun run dev" marginTop={1} fg="#cccccc" />
538
- <text content="Press Ctrl+C to exit" marginTop={1} fg="#999999" />
703
+ <box style={{ width: "100%", height: "100%", padding: 1, flexDirection: "column" }}>
704
+ <box style={{ border: true, borderStyle: "single", padding: 1, width: 72, height: 16, flexDirection: "column" }}>
705
+ <text content="Cascade Solid" fg="#00ff99" />
706
+ <text content="Write reactive terminal apps with signals and components." fg="#cbd5e1" marginTop={1} />
707
+ <box style={{ flexDirection: "column", marginTop: 2 }}>
708
+ <text content="1) Edit src/index.tsx" fg="#e2e8f0" />
709
+ <text content="2) bun run dev" fg="#e2e8f0" marginTop={1} />
710
+ <text content="3) Keep state and rendering deterministic" fg="#e2e8f0" marginTop={1} />
711
+ </box>
712
+ <box style={{ border: true, borderStyle: "single", padding: 1, marginTop: 2, flexDirection: "column" }}>
713
+ <text content="Keys: Ctrl+C quit" fg="#94a3b8" />
714
+ <text content="Tip: Keep list rows stable to avoid selection jumps." fg="#94a3b8" marginTop={1} />
715
+ </box>
716
+ </box>
539
717
  </box>
540
718
  )
541
719
 
@@ -546,12 +724,23 @@ import { createSignal, onCleanup } from "solid-js"
546
724
 
547
725
  const App = () => {
548
726
  const [count, setCount] = createSignal(0)
549
- const timer = setInterval(() => setCount((value) => value + 1), 1000)
727
+ const [uptime, setUptime] = createSignal(0)
728
+
729
+ const timer = setInterval(() => {
730
+ setCount((value) => value + 1)
731
+ setUptime((value) => value + 1)
732
+ }, 1000)
733
+
550
734
  onCleanup(() => clearInterval(timer))
551
735
 
552
736
  return (
553
- <box style={{ border: true, padding: 1, margin: 1 }}>
554
- <text content={\`Solid counter: \${count()}\`} fg="#00ff99" />
737
+ <box style={{ width: "100%", height: "100%", padding: 1, flexDirection: "column" }}>
738
+ <box style={{ border: true, borderStyle: "single", padding: 1, width: 60, height: 10, flexDirection: "column" }}>
739
+ <text content="Solid Counter" fg="#00ff99" />
740
+ <text content={\`Count: \${count()}\`} fg="#e2e8f0" marginTop={1} />
741
+ <text content={\`Uptime: \${uptime()}s\`} fg="#cbd5e1" marginTop={1} />
742
+ <text content="Ctrl+C to exit" fg="#94a3b8" marginTop={1} />
743
+ </box>
555
744
  </box>
556
745
  )
557
746
  }
@@ -564,20 +753,32 @@ import { createSignal } from "solid-js"
564
753
  const App = () => {
565
754
  const [value, setValue] = createSignal("")
566
755
  const [submitted, setSubmitted] = createSignal("")
756
+ const [status, setStatus] = createSignal<"idle" | "submitted">("idle")
567
757
 
568
758
  return (
569
- <box style={{ padding: 2, flexDirection: "column" }}>
570
- <text content="Cascade Solid Input" fg="#00ff99" />
571
- <box title="Message" style={{ border: true, width: 40, height: 3, marginTop: 1 }}>
759
+ <box style={{ width: "100%", height: "100%", padding: 2, flexDirection: "column" }}>
760
+ <text content="Solid Input" fg="#00ff99" />
761
+ <text content="Enter submit, Ctrl+C quit" fg="#94a3b8" marginTop={1} />
762
+ <box title="Message" style={{ border: true, borderStyle: "single", width: 52, height: 3, marginTop: 2 }}>
572
763
  <input
573
764
  focused
574
765
  placeholder="Type something..."
575
- onInput={setValue}
576
- onSubmit={(nextValue) => setSubmitted(nextValue)}
766
+ value={value()}
767
+ onInput={(nextValue) => {
768
+ setValue(nextValue)
769
+ setStatus("idle")
770
+ }}
771
+ onSubmit={(nextValue) => {
772
+ setSubmitted(nextValue)
773
+ setStatus("submitted")
774
+ }}
577
775
  />
578
776
  </box>
579
- <text content={\`Current: \${value()}\`} marginTop={1} />
580
- <text content={\`Submitted: \${submitted() || "-"}\`} />
777
+ <box style={{ border: true, borderStyle: "single", padding: 1, width: 52, marginTop: 2, flexDirection: "column" }}>
778
+ <text content={\`Current: \${value() || "-"}\`} fg="#cbd5e1" />
779
+ <text content={\`Last submit: \${submitted() || "-"}\`} fg="#cbd5e1" marginTop={1} />
780
+ <text content={\`Status: \${status()}\`} fg={status() === "submitted" ? "green" : "#94a3b8"} marginTop={1} />
781
+ </box>
581
782
  </box>
582
783
  )
583
784
  }
@@ -689,6 +890,6 @@ async function main() {
689
890
  }
690
891
 
691
892
  main().catch((error) => {
692
- console.error(error instanceof Error ? error.message : String(error))
693
- process.exit(1)
694
- })
893
+ console.error(error instanceof Error ? error.message : String(error))
894
+ process.exit(1)
895
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cascade",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Create a new Cascade TUI project",
5
5
  "type": "module",
6
6
  "license": "MIT",